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.

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