Source code of Windows XP (NT5)
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.

645 lines
18 KiB

  1. /*++
  2. Copyright (c) 1997 Microsoft Corporation
  3. Module Name :
  4. aspdirmon.cpp
  5. Abstract:
  6. This module includes derivation of class supporting change
  7. notification for ASP template cache, from abstract class DIR_MON_ENTRY
  8. Author:
  9. Charles Grant ( cgrant ) June-1997
  10. Revision History:
  11. --*/
  12. /************************************************************
  13. * Include Headers
  14. ************************************************************/
  15. #include "denpre.h"
  16. #pragma hdrstop
  17. #include "aspdmon.h"
  18. #include "ie449.h"
  19. #include "memchk.h"
  20. #ifndef UNICODE
  21. #error "ASPDMON.CPP must be compiled with UNICODE defined"
  22. #endif
  23. /************************************************************
  24. * Inlined Documentation on change notification
  25. *
  26. * Change Notification:
  27. * This module is to used to monitor the file system for changes
  28. * to scripts. We need to know about changes to scripts for two
  29. * reasons:
  30. * 1) To keep the template cache current
  31. * 2) To manage applications lifetimes. If the GLOBAL.ASA
  32. * for an application, or a file included in the GLOBAL.ASA
  33. * changes, that application should be restarted.
  34. *
  35. *
  36. * Outline of Change Notification System
  37. *
  38. * To obtain change notification we use the ReadDirectoryChangesW
  39. * API as wrapped by the CDirMonitor and CDirMonitorEntry classes.
  40. * Three hash tables are used by the change notifcation system:
  41. *
  42. * CTemplateCacheManager g_TemplateCache
  43. * CDirMonitor g_DirMonitor
  44. * CFileApplicationMap g_FileAppMap
  45. *
  46. * When a template is compiled and inserted into the g_TemplateCache
  47. * the template is provided with a list of files included in that
  48. * template. For each file included in the template, we search the
  49. * g_DirMonitor table to see if see if we are already monitoring the
  50. * files parent directory for changes. If so we simply addref the
  51. * CDirMonitorEntry instance we obtain, and save a pointer to the
  52. * monitor entry in an array in the corresponding file map. If the
  53. * directory is not being monitored we create a new CDirMonitorEntry'
  54. * instance and add it to g_DirMonitor. When we add the monitor entry
  55. * to the g_DirMonitor we launch an asynchronous request to ReadDirectoryChangesW
  56. * for that directory.
  57. *
  58. * Managing the template cache and application life times are logically
  59. * independent activities. We must monitor GLOBAL.ASA for changes even if
  60. * the GLOBAL.ASA template is not currently in the template cache.
  61. * So, if the template is a GLOBAL.ASA for an application, additional work
  62. * must be done. For each file included in the GLOBAL.ASA we add an entry
  63. * to g_FileAppMap relating that file to the applications that depend on it.
  64. * We store a back pointer to the file/application mappping in the application
  65. * instance, so that the application can remove the mapping when it shuts down.
  66. * In the application we store a pointer to the GLOBAL.ASA template. For
  67. * each file in the GLOBAL.ASA, We check g_DirMonitor to find the monitor entry
  68. * for the parent directory for that file, AddRef the monitor entry we find, and
  69. * add it to a list of monitor entries in the application.
  70. *
  71. * When a change occurs to a directory we are monitoring, the callback function
  72. * DirMontiorCompletionFunction will be invoked, and in turn will invoke the
  73. * the ActOnNotification method of the monitor entry for that directory. If a file
  74. * has changed we use g_FileAppMap to shut down those applications that depend on
  75. * that file and flush the file from the template cache.
  76. *
  77. ************************************************************/
  78. #define MAX_BUFFER_SIZE 8192
  79. PTRACE_LOG CASPDirMonitorEntry::gm_pTraceLog = NULL;
  80. CDirMonitor *g_pDirMonitor = NULL;
  81. CASPDirMonitorEntry::CASPDirMonitorEntry() :
  82. m_cNotificationFailures(0)
  83. /*++
  84. Routine Description:
  85. Constructor
  86. Arguments:
  87. None
  88. Return Value:
  89. None
  90. --*/
  91. {
  92. }
  93. CASPDirMonitorEntry::~CASPDirMonitorEntry()
  94. /*++
  95. Routine Description:
  96. Destructor
  97. Arguments:
  98. None
  99. Return Value:
  100. None
  101. --*/
  102. {
  103. }
  104. /*++
  105. increment refcount for an entry -- writes to reftrace log if it is defined
  106. --*/
  107. VOID CASPDirMonitorEntry::AddRef(VOID)
  108. {
  109. CDirMonitorEntry::AddRef();
  110. IF_DEBUG(FCN)
  111. WriteRefTraceLogEx(gm_pTraceLog, m_cDirRefCount, this, PVOID(UIntToPtr(m_cIORefCount)), m_pszPath, 0);
  112. }
  113. BOOL CASPDirMonitorEntry::Release(VOID)
  114. /*++
  115. Routine Description:
  116. Decrement refcount to an entry, we override the base class because
  117. otherwise Denali's memory manager can't track when we free the object
  118. and reports it as a memory leak
  119. Arguments:
  120. None
  121. Return Value:
  122. TRUE if object still alive, FALSE if was last release and object
  123. destroyed
  124. --*/
  125. {
  126. BOOL fAlive = CDirMonitorEntry::Release();
  127. IF_DEBUG(FCN)
  128. WriteRefTraceLogEx(gm_pTraceLog, m_cDirRefCount, this, PVOID(UIntToPtr(m_cIORefCount)), m_pszPath, 0);
  129. return fAlive;
  130. }
  131. BOOL
  132. CASPDirMonitorEntry::ActOnNotification(
  133. DWORD dwStatus,
  134. DWORD dwBytesWritten)
  135. /*++
  136. Routine Description:
  137. Do any work associated with a change notification, i.e.
  138. Arguments:
  139. None
  140. Return Value:
  141. TRUE if application should continue to be monitored, otherwise FALSE
  142. --*/
  143. {
  144. FILE_NOTIFY_INFORMATION *pNotify = NULL;
  145. FILE_NOTIFY_INFORMATION *pNextNotify = NULL;
  146. WCHAR *pwstrFileName = NULL; // Wide file name
  147. pNextNotify = (FILE_NOTIFY_INFORMATION *) m_pbBuffer;
  148. // If the status word is not S_OK, then the ReadDirectoryChangesW failed
  149. if (dwStatus)
  150. {
  151. // If the status is ERROR_ACCESS_DENIED the directory may be deleted
  152. // or secured so we want to stop watching it for changes. The changes to the
  153. // individual scripts will flush the template cache, but we may also be watching
  154. // the directory for the addition of a GLOBAL.ASA. By calling FileChanged on
  155. // global.asa we will force that handle on the directory to close.
  156. if (dwStatus == ERROR_ACCESS_DENIED)
  157. {
  158. FileChanged(SZ_GLOBAL_ASA, false);
  159. // No further notificaitons desired
  160. // so return false
  161. return FALSE;
  162. }
  163. // If we return TRUE, we'll try change notification again
  164. // If we return FALSE, we give up on any further change notifcation
  165. // We'll try a MAX_NOTIFICATION_FAILURES times and give up.
  166. if (m_cNotificationFailures < MAX_NOTIFICATION_FAILURES)
  167. {
  168. IF_DEBUG(FCN)
  169. DBGPRINTF((DBG_CONTEXT, "[CASPDirMonitorEntry] ReadDirectoryChange failed. Status = %d\n", dwStatus));
  170. m_cNotificationFailures++;
  171. return TRUE; // Try to get change notification again
  172. }
  173. else
  174. {
  175. IF_DEBUG(FCN)
  176. DBGPRINTF((DBG_CONTEXT, "[CASPDirMonitorEntry] ReadDirectoryChange failed too many times. Giving up.\n"));
  177. return FALSE; // Give up trying to get change notification
  178. }
  179. }
  180. else
  181. {
  182. // Reset the number of notification failure
  183. m_cNotificationFailures = 0;
  184. }
  185. // If dwBytesWritten is 0, then there were more changes then could be
  186. // recorded in the buffer we provided. Flush the whole cache just in case
  187. // CONSIDER: is this the best course of action, or should iterate through the
  188. // cache and test which files are expired
  189. if (dwBytesWritten == 0)
  190. {
  191. IF_DEBUG(FCN)
  192. DBGPRINTF((DBG_CONTEXT, "[CASPDirMonitorEntry] ReadDirectoryChange failed, too many changes for buffer\n"));
  193. FlushAll:
  194. // Flush the 449 response file cache
  195. Do449ChangeNotification();
  196. // Flush everything in the cache as a precaution
  197. g_TemplateCache.FlushAll();
  198. // Check all applications to see if they need to be restarted
  199. g_ApplnMgr.RestartApplications();
  200. // Flush the script engine cache as a precaution (should be flushed via TemplateCache, but just in case.)
  201. g_ScriptManager.FlushAll();
  202. // Try to increase the buffer size so this doesn't happen again
  203. // Unfortunately the first call to ReadDirectoryChangesW on this
  204. // file handle establishes the buffer size. We must close and re-open
  205. // the file handle to change the buffer size
  206. if (ResetDirectoryHandle() && (GetBufferSize() < MAX_BUFFER_SIZE))
  207. {
  208. SetBufferSize(2 * GetBufferSize());
  209. }
  210. return TRUE;
  211. }
  212. STACK_BUFFER(filename, MAX_PATH * sizeof(WCHAR));
  213. while ( pNextNotify != NULL )
  214. {
  215. DWORD cch;
  216. pNotify = pNextNotify;
  217. pNextNotify = (FILE_NOTIFY_INFORMATION *) ((PCHAR) pNotify + pNotify->NextEntryOffset);
  218. // Resize the stack buffer to the size of the filename. I know it's
  219. // ugly, but if it fails, jump back up to the flush all logic.
  220. // NOTE that the FileNameLength in the NOTIFY structure is in Bytes, not chars
  221. if (!filename.Resize(pNotify->FileNameLength+2)) {
  222. goto FlushAll;
  223. }
  224. pwstrFileName = (WCHAR *)filename.QueryPtr();
  225. memcpy(pwstrFileName, pNotify->FileName, pNotify->FileNameLength);
  226. cch = pNotify->FileNameLength/sizeof(WCHAR);
  227. pwstrFileName[cch] = L'\0';
  228. // Take the appropriate action for the directory change
  229. switch (pNotify->Action)
  230. {
  231. case FILE_ACTION_ADDED:
  232. case FILE_ACTION_RENAMED_NEW_NAME:
  233. // 'File Added' only matters for GLOBAL.ASA
  234. IF_DEBUG(FCN)
  235. DBGPRINTF((DBG_CONTEXT, "Change Notification: New file added: %S\n", pwstrFileName));
  236. if (cch != CCH_GLOBAL_ASA ||
  237. wcsicmp(pwstrFileName, SZ_GLOBAL_ASA) != 0)
  238. {
  239. break;
  240. }
  241. case FILE_ACTION_REMOVED:
  242. case FILE_ACTION_MODIFIED:
  243. case FILE_ACTION_RENAMED_OLD_NAME:
  244. IF_DEBUG(FCN)
  245. DBGPRINTF((DBG_CONTEXT, "Change Notification: File %s: %S\n", pNotify->Action == FILE_ACTION_MODIFIED? "changed" : "removed", pwstrFileName));
  246. FileChanged(pwstrFileName, pNotify->Action != FILE_ACTION_MODIFIED);
  247. break;
  248. default:
  249. break;
  250. }
  251. if(pNotify == pNextNotify)
  252. {
  253. break;
  254. }
  255. }
  256. // We should sign up for further change notification
  257. return TRUE;
  258. }
  259. void
  260. CASPDirMonitorEntry::FileChanged(const WCHAR *pszScriptName, bool fFileWasRemoved)
  261. /*++
  262. Routine Description:
  263. An existing file has been modified or deleted
  264. Flush scripts from cache or mark application as expired
  265. Arguments:
  266. pszScriptName Name of file that changed
  267. Return Value:
  268. None Fail silently
  269. --*/
  270. {
  271. // The file name is set by the application that
  272. // modified the file, so old applications like EDIT
  273. // may hand us a munged 8.3 file name which we should
  274. // convert to a long name. All munged 8.3 file names contain '~'
  275. // We assume the path does not contain any munged names.
  276. WIN32_FIND_DATA wfd;
  277. STACK_BUFFER( tempScriptName, MAX_PATH );
  278. STACK_BUFFER( tempScriptPath, MAX_PATH );
  279. bool fRemoveMultiple = false;
  280. WCHAR *pT = wcschr(pszScriptName, '~');
  281. if (pT)
  282. {
  283. if (ConvertToLongFileName(m_pszPath, pszScriptName, &wfd))
  284. {
  285. pszScriptName = (WCHAR *) &wfd.cFileName;
  286. }
  287. else
  288. {
  289. // It could be a long filename that was deleted, so remove everything in cache past the '~'.
  290. if (fFileWasRemoved)
  291. {
  292. fRemoveMultiple = true;
  293. DWORD cchToCopy = (DWORD)(pT - pszScriptName)/sizeof(WCHAR);
  294. if (tempScriptName.Resize((cchToCopy+1)*sizeof(WCHAR)) == FALSE) {
  295. return;
  296. }
  297. WCHAR *szScriptNameCopy = (WCHAR *)tempScriptName.QueryPtr();
  298. // copy prefix to delete into local buffer.
  299. wcsncpy(szScriptNameCopy, pszScriptName, cchToCopy);
  300. szScriptNameCopy[cchToCopy] = '\0';
  301. pszScriptName = szScriptNameCopy;
  302. }
  303. else
  304. return;
  305. }
  306. }
  307. // Allocate enough memory to concatentate the
  308. // application path and script name
  309. DWORD cch = m_cPathLength + wcslen(pszScriptName);
  310. if (tempScriptPath.Resize((cch + 1)*sizeof(WCHAR)) == FALSE) {
  311. return;
  312. }
  313. LPWSTR pszScriptPath = (LPWSTR) tempScriptPath.QueryPtr();
  314. Assert(pszScriptPath != NULL);
  315. // Copy the application path into the script path
  316. // pT will point to the terminator of the application path
  317. pT = strcpyEx(pszScriptPath, m_pszPath);
  318. // Now append the script name. Note that the script name is
  319. // relative to the directory that we received the notification for
  320. wcscpy(pT, pszScriptName);
  321. Normalize(pszScriptPath);
  322. // It is important that we flush the cache and then shutdown applications
  323. // The call to shut down applications is asynch, and could result in the
  324. // template being delted while we are in the process of flushing it.
  325. // CONSIDER: Is this really indicative of a ref-counting problem?
  326. if (fRemoveMultiple)
  327. {
  328. IF_DEBUG(FCN)
  329. DBGPRINTF((DBG_CONTEXT, "ChangeNotification: Flushing \"%S*\" from cache.\n", pszScriptPath));
  330. g_IncFileMap.FlushFiles(pszScriptPath);
  331. g_TemplateCache.FlushFiles(pszScriptPath);
  332. Do449ChangeNotification(NULL); // not used often, no selective delete
  333. }
  334. else
  335. {
  336. g_IncFileMap.Flush(pszScriptPath);
  337. g_TemplateCache.Flush(pszScriptPath, MATCH_ALL_INSTANCE_IDS);
  338. Do449ChangeNotification( pszScriptPath );
  339. }
  340. // g_FileAppMap will shutdown any applications
  341. // that depend on this file.
  342. g_FileAppMap.ShutdownApplications( pszScriptPath );
  343. }
  344. BOOL CASPDirMonitorEntry::FPathMonitored(LPCWSTR pszPath)
  345. {
  346. if (m_fWatchSubdirectories && (wcsncmp(m_pszPath,pszPath, m_cPathLength) == 0)) {
  347. return TRUE;
  348. }
  349. return FALSE;
  350. }
  351. BOOL
  352. RegisterASPDirMonitorEntry(
  353. LPCWSTR pcwszDirectoryName,
  354. CASPDirMonitorEntry **ppDME,
  355. BOOL fWatchSubDirs /* = FALSE */
  356. )
  357. /*++
  358. Routine Description:
  359. Find entry and create a new one and start monitoring
  360. if not found.
  361. Arguments:
  362. pszDirectory - directory to monitor
  363. ppDNE - Found (or newly created) entry (optional)
  364. Return Value:
  365. TRUE if success, otherwise FALSE
  366. Remarks:
  367. Not compatible with WIN95
  368. --*/
  369. {
  370. DWORD cchDirectory;
  371. WCHAR *pwszDirectory = (WCHAR *)pcwszDirectoryName;
  372. STACK_BUFFER(tempDirectory, 256);
  373. cchDirectory = wcslen(pcwszDirectoryName);
  374. // The directory monitor code requires, or possibly ASP's use of the directory
  375. // monitor, that the directory contain a trailing backslash
  376. if( cchDirectory
  377. && (pcwszDirectoryName[cchDirectory - 1] != L'\\') ) {
  378. // we need to add the backslash. To do this, we'll need to allocate
  379. // memory from somewhere to make a copy of the converted string with
  380. // the trailing backslash.
  381. if (tempDirectory.Resize((cchDirectory + 2) * sizeof(WCHAR)) == FALSE) {
  382. return FALSE;
  383. }
  384. // copy the converted string to the just allocated buffer and add
  385. // the trailing backslash and NULL terminator
  386. wcscpy((WCHAR *)tempDirectory.QueryPtr(), pcwszDirectoryName);
  387. pwszDirectory = (WCHAR *)tempDirectory.QueryPtr();
  388. pwszDirectory[cchDirectory] = L'\\';
  389. cchDirectory++;
  390. pwszDirectory[cchDirectory] = '\0';
  391. }
  392. // Don't loop forever
  393. BOOL fTriedTwice = FALSE;
  394. TryAgain:
  395. // Check Existing first
  396. CASPDirMonitorEntry *pDME = (CASPDirMonitorEntry *)g_pDirMonitor->FindEntry( pwszDirectory );
  397. if ( pDME == NULL )
  398. {
  399. // Not found - create new entry
  400. pDME = new CASPDirMonitorEntry;
  401. if ( pDME )
  402. {
  403. pDME->AddRef();
  404. pDME->Init(NULL);
  405. // Start monitoring
  406. if ( !g_pDirMonitor->Monitor(pDME, pwszDirectory, fWatchSubDirs, FILE_NOTIFY_FILTER) )
  407. {
  408. // Cleanup if failed
  409. pDME->Release();
  410. pDME = NULL;
  411. //
  412. // We might still be successful if the monitor failed because
  413. // someone slipped it into the hash table before we had
  414. // a chance
  415. //
  416. if ( GetLastError() == ERROR_ALREADY_EXISTS &&
  417. !fTriedTwice )
  418. {
  419. fTriedTwice = TRUE;
  420. goto TryAgain;
  421. }
  422. }
  423. }
  424. }
  425. // Return entry if found
  426. if ( pDME != NULL )
  427. {
  428. *ppDME = static_cast<CASPDirMonitorEntry *>(pDME);
  429. return TRUE;
  430. }
  431. else
  432. {
  433. *ppDME = NULL;
  434. return FALSE;
  435. }
  436. }
  437. BOOL
  438. ConvertToLongFileName(
  439. const WCHAR *pszPath,
  440. const WCHAR *pszName,
  441. WIN32_FIND_DATA *pwfd)
  442. /*++
  443. Routine Description:
  444. Finds the long filename corresponding to a munged 8.3 filename.
  445. Arguments:
  446. pszPath The path to the file
  447. pszName The 8.3 munged version of the file name
  448. pwfd Find data structure used to contain the long
  449. version of the file name.
  450. Return Value:
  451. TRUE if the file is found,
  452. FALSE otherwise
  453. --*/
  454. {
  455. STACK_BUFFER( tempName, MAX_PATH*sizeof(WCHAR) );
  456. // Allocate enough memory to concatentate the file path and name
  457. DWORD cb = (wcslen(pszPath) + wcslen(pszName)) * sizeof(WCHAR);
  458. if (tempName.Resize(cb + sizeof(WCHAR)) == FALSE) {
  459. return FALSE;
  460. }
  461. WCHAR *pszFullName = (WCHAR *) tempName.QueryPtr();
  462. Assert(pszFullName != NULL);
  463. // Copy the path into the working string
  464. // pT will point to the terminator of the application path
  465. WCHAR* pT = strcpyEx(pszFullName,
  466. pszPath);
  467. // Now append the file name. Note that the script name is
  468. // relative to the directory that we received the notification for
  469. wcscpy(pT, pszName);
  470. // FindFirstFile will find using the short name
  471. // We can then find the long name from the WIN32_FIND_DATA
  472. HANDLE hFindFile = FindFirstFile(pszFullName, pwfd);
  473. if (hFindFile == INVALID_HANDLE_VALUE)
  474. {
  475. return FALSE;
  476. }
  477. // Now that we have the find data we don't need the handle
  478. FindClose(hFindFile);
  479. return TRUE;
  480. }