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.

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