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.

728 lines
18 KiB

  1. /*++
  2. Copyright (c) 1998 Microsoft Corporation
  3. Module Name :
  4. dirchang.cxx
  5. Abstract:
  6. This module contains the directory change manager routines
  7. Author:
  8. MuraliK
  9. Revision History:
  10. MCourage 24-Mar-1998 Rewrote to use CDirMonitor
  11. --*/
  12. #include "tsunamip.Hxx"
  13. #pragma hdrstop
  14. #include "dbgutil.h"
  15. #include <mbstring.h>
  16. extern "C" {
  17. #include <lmuse.h>
  18. }
  19. #if ENABLE_DIR_MONITOR
  20. #include <malloc.h>
  21. #include "filecach.hxx"
  22. #include "filehash.hxx"
  23. //#define DIR_CHANGE_FILTER (FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES)
  24. #define DIR_CHANGE_FILTER (FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS)
  25. #define DIRMON_BUFFER_SIZE 4096
  26. BOOL ConvertToLongFileName(
  27. const char *pszPath,
  28. const char *pszName,
  29. WIN32_FIND_DATA *pwfd);
  30. CDirMonitor * g_pVRootDirMonitor;
  31. #endif // ENABLE_DIR_MONITOR
  32. BOOL
  33. DcmInitialize(
  34. VOID
  35. )
  36. {
  37. #if ENABLE_DIR_MONITOR
  38. g_pVRootDirMonitor = new CDirMonitor;
  39. return (g_pVRootDirMonitor != NULL);
  40. #else
  41. return TRUE;
  42. #endif // ENABLE_DIR_MONITOR
  43. }
  44. VOID
  45. DcmTerminate(
  46. VOID
  47. )
  48. {
  49. #if ENABLE_DIR_MONITOR
  50. if (g_pVRootDirMonitor) {
  51. g_pVRootDirMonitor->Cleanup();
  52. delete g_pVRootDirMonitor;
  53. g_pVRootDirMonitor = NULL;
  54. }
  55. #endif // ENABLE_DIR_MONITOR
  56. }
  57. BOOL
  58. DcmAddRoot(
  59. PVIRTUAL_ROOT_MAPPING pVrm
  60. )
  61. {
  62. #if ENABLE_DIR_MONITOR
  63. IF_DEBUG( DIRECTORY_CHANGE ) {
  64. DBGPRINTF(( DBG_CONTEXT,
  65. "DCM: Adding root \"%s\" to \"%s\"\n",
  66. pVrm->pszRootA, pVrm->pszDirectoryA ));
  67. }
  68. ASSERT(NULL != g_pVRootDirMonitor);
  69. CVRootDirMonitorEntry * pDME;
  70. //
  71. // See if we're already watching this directory
  72. //
  73. pDME = (CVRootDirMonitorEntry *) g_pVRootDirMonitor->FindEntry(pVrm->pszDirectoryA);
  74. if ( pDME == NULL )
  75. {
  76. // Not found - create new entry
  77. pDME = new CVRootDirMonitorEntry;
  78. if ( pDME )
  79. {
  80. pDME->AddRef();
  81. // Start monitoring
  82. if ( !g_pVRootDirMonitor->Monitor(pDME, pVrm->pszDirectoryA, TRUE, DIR_CHANGE_FILTER) )
  83. {
  84. // Cleanup if failed
  85. pDME->Release();
  86. pDME = NULL;
  87. }
  88. }
  89. }
  90. // Return entry if found
  91. if ( pDME != NULL )
  92. {
  93. pVrm->pDME = static_cast<CVRootDirMonitorEntry *>(pDME);
  94. return TRUE;
  95. }
  96. else
  97. {
  98. pVrm->pDME = NULL;
  99. return FALSE;
  100. }
  101. #else // !ENABLE_DIR_MONITOR
  102. //
  103. // Doesn't do anything. Ha!
  104. //
  105. return TRUE;
  106. #endif // ENABLE_DIR_MONITOR
  107. }
  108. VOID
  109. DcmRemoveRoot(
  110. PVIRTUAL_ROOT_MAPPING pVrm
  111. )
  112. {
  113. #if ENABLE_DIR_MONITOR
  114. IF_DEBUG( DIRECTORY_CHANGE ) {
  115. DBGPRINTF(( DBG_CONTEXT,
  116. "DCM: Removing root \"%s\" to \"%s\"\n",
  117. pVrm->pszRootA, pVrm->pszDirectoryA ));
  118. }
  119. CVRootDirMonitorEntry * pDME = pVrm->pDME;
  120. pVrm->pDME = NULL;
  121. if (pDME) {
  122. pDME->Release();
  123. }
  124. #else // !ENABLE_DIR_MONITOR
  125. //
  126. // Doesn't do anything. Ha!
  127. //
  128. #endif // ENABLE_DIR_MONITOR
  129. }
  130. #if ENABLE_DIR_MONITOR
  131. typedef struct _FLUSH_PREFIX_PARAM {
  132. PCSTR pszPrefix;
  133. DWORD cbPrefix;
  134. } FLUSH_PREFIX_PARAM;
  135. BOOL
  136. FlushFilterPrefix(
  137. TS_OPEN_FILE_INFO * pOpenFile,
  138. PVOID pv
  139. )
  140. {
  141. DBG_ASSERT( pOpenFile );
  142. DBG_ASSERT( pOpenFile->GetKey() );
  143. FLUSH_PREFIX_PARAM * fpp = (FLUSH_PREFIX_PARAM *)pv;
  144. const CFileKey * pfk = pOpenFile->GetKey();
  145. //
  146. // If the prefix matches then we flush.
  147. //
  148. //
  149. // The key stored in TS_OPEN_FILE_INFO is uppercased, so we will do a
  150. // case insensitive memcmp here.
  151. // The alternative is to create a temporary and uppercase all instances
  152. // when the directory is dumped or to have CDirMonitorEntry store its
  153. // name uppercased.
  154. //
  155. return ((pfk->m_cbFileName >= fpp->cbPrefix)
  156. && (_memicmp(pfk->m_pszFileName, fpp->pszPrefix, fpp->cbPrefix) == 0));
  157. }
  158. /*===================================================================
  159. strcpyEx
  160. Copy one string to another, returning a pointer to the NUL character
  161. in the destination
  162. Parameters:
  163. szDest - pointer to the destination string
  164. szSrc - pointer to the source string
  165. Returns:
  166. A pointer to the NUL terminator is returned.
  167. ===================================================================*/
  168. char *strcpyEx(char *szDest, const char *szSrc)
  169. {
  170. while (*szDest++ = *szSrc++)
  171. ;
  172. return szDest - 1;
  173. }
  174. CVRootDirMonitorEntry::CVRootDirMonitorEntry() : m_cNotificationFailures(0)
  175. /*++
  176. Routine Description:
  177. Constructor
  178. Arguments:
  179. None
  180. Return Value:
  181. None
  182. --*/
  183. {
  184. }
  185. CVRootDirMonitorEntry::~CVRootDirMonitorEntry()
  186. /*++
  187. Routine Description:
  188. Destructor
  189. Arguments:
  190. None
  191. Return Value:
  192. None
  193. --*/
  194. {
  195. }
  196. BOOL
  197. CVRootDirMonitorEntry::Init(
  198. VOID
  199. )
  200. /*++
  201. Routine Description:
  202. Initialize monitor entry
  203. Arguments:
  204. pvData - passed to base Init member
  205. Return Value:
  206. TRUE if success, otherwise FALSE
  207. --*/
  208. {
  209. return CDirMonitorEntry::Init(DIRMON_BUFFER_SIZE);
  210. }
  211. #if 0
  212. BOOL CVRootDirMonitorEntry::Release(VOID)
  213. /*++
  214. Routine Description:
  215. Decrement refcount to an entry, we override the base class because
  216. otherwise Denali's memory manager can't track when we free the object
  217. and reports it as a memory leak
  218. Arguments:
  219. None
  220. Return Value:
  221. TRUE if object still alive, FALSE if was last release and object
  222. destroyed
  223. --*/
  224. {
  225. IF_DEBUG( DIRECTORY_CHANGE ) {
  226. DBGPRINTF((DBG_CONTEXT, "[CVRootDirMonitorEntry] Before Release Ref count %d on directory %s\n", m_cDirRefCount, m_pszPath));
  227. }
  228. if ( !InterlockedDecrement( &m_cDirRefCount ) )
  229. {
  230. BOOL fDeleteNeeded = Cleanup();
  231. if (fDeleteNeeded)
  232. {
  233. delete this;
  234. }
  235. return FALSE;
  236. }
  237. return TRUE;
  238. }
  239. #endif
  240. BOOL
  241. CVRootDirMonitorEntry::ActOnNotification(
  242. DWORD dwStatus,
  243. DWORD dwBytesWritten)
  244. /*++
  245. Routine Description:
  246. Do any work associated with a change notification, i.e.
  247. Arguments:
  248. None
  249. Return Value:
  250. TRUE if directory should continue to be monitored, otherwise FALSE
  251. --*/
  252. {
  253. FILE_NOTIFY_INFORMATION *pNotify = NULL;
  254. FILE_NOTIFY_INFORMATION *pNextNotify = NULL;
  255. LPSTR pszScriptName = NULL; // Name of script
  256. WCHAR *pwstrFileName = NULL; // Wide file name
  257. DWORD cch = 0;
  258. pNextNotify = (FILE_NOTIFY_INFORMATION *) m_pbBuffer;
  259. // If the status word is not NOERROR, then the ReadDirectoryChangesW failed
  260. if (dwStatus)
  261. {
  262. // If the status is ERROR_ACCESS_DENIED the directory may be deleted
  263. // or secured so we want to stop watching it for changes.
  264. // we should flush the dir and everything in it.
  265. if (dwStatus == ERROR_ACCESS_DENIED)
  266. {
  267. //
  268. // Flush the dir here
  269. //
  270. IF_DEBUG( DIRECTORY_CHANGE ) {
  271. DBGPRINTF(( DBG_CONTEXT,
  272. "DCM: Flushing directory \"%s\" because we got ACCESS_DENIED\n",
  273. m_pszPath ));
  274. }
  275. FLUSH_PREFIX_PARAM param;
  276. param.pszPrefix = m_pszPath;
  277. param.cbPrefix = strlen(m_pszPath);
  278. FilteredFlushFileCache(FlushFilterPrefix, &param);
  279. //
  280. // no point in having the handle open anymore
  281. //
  282. m_hDir = INVALID_HANDLE_VALUE;
  283. AtqCloseFileHandle( m_pAtqCtxt );
  284. // No further notifications desired
  285. // so return false
  286. return FALSE;
  287. }
  288. // If we return TRUE, we'll try change notification again
  289. // If we return FALSE, we give up on any further change notifcation
  290. // We'll try a MAX_NOTIFICATION_FAILURES times and give up.
  291. if (m_cNotificationFailures < MAX_NOTIFICATION_FAILURES)
  292. {
  293. IF_DEBUG ( DIRECTORY_CHANGE ) {
  294. DBGPRINTF((DBG_CONTEXT, "[CVRootDirMonitorEntry] ReadDirectoryChange failed. Status = %d\n", dwStatus));
  295. }
  296. m_cNotificationFailures++;
  297. return TRUE; // Try to get change notification again
  298. }
  299. else
  300. {
  301. // CONSIDER: Should we log this?
  302. DBGPRINTF((DBG_CONTEXT, "[CVRootDirMonitorEntry] ReadDirectoryChange failed too many times. Giving up.\n"));
  303. return FALSE; // Give up trying to get change notification
  304. }
  305. }
  306. else
  307. {
  308. // Reset the number of notification failure
  309. m_cNotificationFailures = 0;
  310. }
  311. // If dwBytesWritten is 0, then there were more changes then could be
  312. // recorded in the buffer we provided. Expire the application just in case
  313. // CONSIDER: is this the best course of action, or should iterate through the
  314. // cache and test which files are expired
  315. if (dwBytesWritten == 0)
  316. {
  317. IF_DEBUG( DIRECTORY_CHANGE ) {
  318. DBGPRINTF((DBG_CONTEXT, "[CVRootDirMonitorEntry] ReadDirectoryChange failed, too many changes for buffer\n"));
  319. }
  320. // Flush everything in the dir as a precaution
  321. FLUSH_PREFIX_PARAM param;
  322. param.pszPrefix = m_pszPath;
  323. param.cbPrefix = strlen(m_pszPath);
  324. FilteredFlushFileCache(FlushFilterPrefix, &param);
  325. return TRUE;
  326. }
  327. while ( pNextNotify != NULL )
  328. {
  329. BOOL bDoFlush = TRUE;
  330. pNotify = pNextNotify;
  331. pNextNotify = (FILE_NOTIFY_INFORMATION *) ((PCHAR) pNotify + pNotify->NextEntryOffset);
  332. // Get the unicode file name from the notification struct
  333. // pNotify->FileNameLength returns the wstr's length in **bytes** not wchars
  334. cch = pNotify->FileNameLength / 2;
  335. // Convert to ANSI with uniform case and directory delimiters
  336. pszScriptName = (LPSTR) _alloca(pNotify->FileNameLength + 1);
  337. DBG_ASSERT(pszScriptName != NULL);
  338. pszScriptName[ 0 ] = '\0';
  339. cch = WideCharToMultiByte(CP_ACP, 0, pNotify->FileName, cch, pszScriptName, pNotify->FileNameLength + 1, NULL, NULL);
  340. pszScriptName[cch] = '\0';
  341. // Take the appropriate action for the directory change
  342. switch (pNotify->Action)
  343. {
  344. case FILE_ACTION_MODIFIED:
  345. //
  346. // Since this change won't change the pathname of
  347. // any files, we don't have to do a flush.
  348. //
  349. bDoFlush = FALSE;
  350. case FILE_ACTION_REMOVED:
  351. case FILE_ACTION_RENAMED_OLD_NAME:
  352. FileChanged(pszScriptName, bDoFlush);
  353. break;
  354. case FILE_ACTION_ADDED:
  355. case FILE_ACTION_RENAMED_NEW_NAME:
  356. default:
  357. break;
  358. }
  359. if(pNotify == pNextNotify)
  360. {
  361. break;
  362. }
  363. }
  364. // We should sign up for further change notification
  365. return TRUE;
  366. }
  367. void
  368. CVRootDirMonitorEntry::FileChanged(const char *pszScriptName, BOOL bDoFlush)
  369. /*++
  370. Routine Description:
  371. An existing file has been modified or deleted
  372. Flush scripts from cache or mark application as expired
  373. Arguments:
  374. pszScriptName Name of file that changed
  375. Return Value:
  376. None Fail silently
  377. --*/
  378. {
  379. // The file name is set by the application that
  380. // modified the file, so old applications like EDIT
  381. // may hand us a munged 8.3 file name which we should
  382. // convert to a long name. All munged 8.3 file names contain '~'
  383. // We assume the path does not contain any munged names.
  384. WIN32_FIND_DATA wfd;
  385. CHAR achFullScriptName[ MAX_PATH + 1 ];
  386. IF_DEBUG( DIRECTORY_CHANGE ) {
  387. DBGPRINTF(( DBG_CONTEXT,
  388. "DCM: Notification on \"%s\\%s\"\n",
  389. m_pszPath, pszScriptName ));
  390. }
  391. if (strchr(pszScriptName, '~'))
  392. {
  393. if (ConvertToLongFileName(m_pszPath, pszScriptName, &wfd))
  394. {
  395. CHAR * pszEnd;
  396. //
  397. // The filename in wfd.cFileName is the path-less. We need to
  398. // append it to another other sub dirs in pszScriptName (if any)
  399. //
  400. pszEnd = strrchr( pszScriptName, '\\' );
  401. if ( pszEnd )
  402. {
  403. memcpy( achFullScriptName,
  404. pszScriptName,
  405. DIFF( pszEnd - pszScriptName ) + 1 );
  406. memcpy( achFullScriptName + ( pszEnd - pszScriptName ) + 1,
  407. wfd.cFileName,
  408. strlen( wfd.cFileName ) + 1 );
  409. pszScriptName = achFullScriptName;
  410. }
  411. else
  412. {
  413. pszScriptName = wfd.cFileName;
  414. }
  415. IF_DEBUG( DIRECTORY_CHANGE ) {
  416. DBGPRINTF(( DBG_CONTEXT,
  417. "DCM: Converted name to \"%s\"\n",
  418. pszScriptName ));
  419. }
  420. }
  421. else
  422. {
  423. // Fail silently
  424. return;
  425. }
  426. }
  427. // Allocate enough memory to concatentate the
  428. // application path and script name
  429. DWORD cch = m_cPathLength + strlen(pszScriptName) + 1;
  430. LPSTR pszScriptPath = (LPSTR) _alloca(cch + 1); // CONSIDER using malloc
  431. DBG_ASSERT(pszScriptPath != NULL);
  432. // Copy the application path into the script path
  433. // pT will point to the terminator of the application path
  434. char* pT = strcpyEx(pszScriptPath, m_pszPath);
  435. // append a backslash
  436. *pT++ = '\\';
  437. // Now append the script name. Note that the script name is
  438. // relative to the directory that we received the notification for
  439. lstrcpy(pT, pszScriptName);
  440. _mbsupr((PUCHAR)pszScriptPath);
  441. // Get rid of this file, or dir tree
  442. TS_OPEN_FILE_INFO * pOpenFile;
  443. if (bDoFlush) {
  444. //
  445. // This path is a directory that got removed or renamed
  446. // so we have to flush everything below it.
  447. //
  448. IF_DEBUG( DIRECTORY_CHANGE ) {
  449. DBGPRINTF(( DBG_CONTEXT,
  450. "DCM: Flushing directory \"%s\"\n",
  451. pszScriptPath ));
  452. }
  453. FLUSH_PREFIX_PARAM param;
  454. param.pszPrefix = pszScriptPath;
  455. param.cbPrefix = strlen(pszScriptPath);
  456. FilteredFlushFileCache(FlushFilterPrefix, &param);
  457. } else if (CheckoutFile(pszScriptPath, 0, &pOpenFile)) {
  458. //
  459. // this is just one file, or a directory whose
  460. // name didn't change. We only have to decache it.
  461. //
  462. IF_DEBUG( DIRECTORY_CHANGE ) {
  463. DBGPRINTF(( DBG_CONTEXT,
  464. "DCM: decaching file \"%s\"\n",
  465. pszScriptPath ));
  466. }
  467. DecacheFile(pOpenFile, 0);
  468. }
  469. }
  470. BOOL
  471. ConvertToLongFileName(
  472. const char *pszPath,
  473. const char *pszName,
  474. WIN32_FIND_DATA *pwfd)
  475. /*++
  476. Routine Description:
  477. Finds the long filename corresponding to a munged 8.3 filename.
  478. Arguments:
  479. pszPath The path to the file
  480. pszName The 8.3 munged version of the file name
  481. pwfd Find data structure used to contain the long
  482. version of the file name.
  483. Return Value:
  484. TRUE if the file is found,
  485. FALSE otherwise
  486. --*/
  487. {
  488. // Allocate enough memory to concatentate the file path and name
  489. DWORD cch = strlen(pszPath) + strlen(pszName) + 1;
  490. char *pszFullName = (char *) _alloca(cch + 1);
  491. DBG_ASSERT(pszFullName != NULL);
  492. // Copy the path into the working string
  493. // pT will point to the terminator of the application path
  494. char* pT = strcpyEx(pszFullName,
  495. pszPath);
  496. // append a backslash
  497. *pT++ = '\\';
  498. // Now append the file name. Note that the script name is
  499. // relative to the directory that we received the notification for
  500. lstrcpy(pT, pszName);
  501. // FindFirstFile will find using the short name
  502. // We can then find the long name from the WIN32_FIND_DATA
  503. HANDLE hFindFile = FindFirstFile(pszFullName, pwfd);
  504. if (hFindFile == INVALID_HANDLE_VALUE)
  505. {
  506. return FALSE;
  507. }
  508. // Now that we have the find data we don't need the handle
  509. FindClose(hFindFile);
  510. return TRUE;
  511. }
  512. #endif // ENABLE_DIR_MONITOR
  513. /*******************************************************************
  514. NAME: IsCharTermA (DBCS enabled)
  515. SYNOPSIS: check the character in string is terminator or not.
  516. terminator is '/', '\0' or '\\'
  517. ENTRY: lpszString - string
  518. cch - offset for char to check
  519. RETURNS: BOOL - TRUE if it is a terminator
  520. HISTORY:
  521. v-ChiKos 15-May-1997 Created.
  522. ********************************************************************/
  523. BOOL
  524. IsCharTermA(
  525. IN LPCSTR lpszString,
  526. IN INT cch
  527. )
  528. {
  529. CHAR achLast;
  530. achLast = *(lpszString + cch);
  531. if ( achLast == '/' || achLast == '\0' )
  532. {
  533. return TRUE;
  534. }
  535. achLast = *CharPrev(lpszString, lpszString + cch + 1);
  536. return (achLast == '\\');
  537. }