windows-nt/Source/XPSP1/NT/inetsrv/iis/svcs/cmp/asp51/aspdmon.cpp
2020-09-26 16:20:57 +08:00

632 lines
18 KiB
C++

/*++
Copyright (c) 1997 Microsoft Corporation
Module Name :
aspdirmon.cpp
Abstract:
This module includes derivation of class supporting change
notification for ASP template cache, from abstract class DIR_MON_ENTRY
Author:
Charles Grant ( cgrant ) June-1997
Revision History:
--*/
/************************************************************
* Include Headers
************************************************************/
#include "denpre.h"
#pragma hdrstop
#include "aspdmon.h"
#include "ie449.h"
#include "memchk.h"
/************************************************************
* Inlined Documentation on change notification
*
* Change Notification:
* This module is to used to monitor the file system for changes
* to scripts. We need to know about changes to scripts for two
* reasons:
* 1) To keep the template cache current
* 2) To manage applications lifetimes. If the GLOBAL.ASA
* for an application, or a file included in the GLOBAL.ASA
* changes, that application should be restarted.
*
*
* Outline of Change Notification System
*
* To obtain change notification we use the ReadDirectoryChangesW
* API as wrapped by the CDirMonitor and CDirMonitorEntry classes.
* Three hash tables are used by the change notifcation system:
*
* CTemplateCacheManager g_TemplateCache
* CDirMonitor g_DirMonitor
* CFileApplicationMap g_FileAppMap
*
* When a template is compiled and inserted into the g_TemplateCache
* the template is provided with a list of files included in that
* template. For each file included in the template, we search the
* g_DirMonitor table to see if see if we are already monitoring the
* files parent directory for changes. If so we simply addref the
* CDirMonitorEntry instance we obtain, and save a pointer to the
* monitor entry in an array in the corresponding file map. If the
* directory is not being monitored we create a new CDirMonitorEntry'
* instance and add it to g_DirMonitor. When we add the monitor entry
* to the g_DirMonitor we launch an asynchronous request to ReadDirectoryChangesW
* for that directory.
*
* Managing the template cache and application life times are logically
* independent activities. We must monitor GLOBAL.ASA for changes even if
* the GLOBAL.ASA template is not currently in the template cache.
* So, if the template is a GLOBAL.ASA for an application, additional work
* must be done. For each file included in the GLOBAL.ASA we add an entry
* to g_FileAppMap relating that file to the applications that depend on it.
* We store a back pointer to the file/application mappping in the application
* instance, so that the application can remove the mapping when it shuts down.
* In the application we store a pointer to the GLOBAL.ASA template. For
* each file in the GLOBAL.ASA, We check g_DirMonitor to find the monitor entry
* for the parent directory for that file, AddRef the monitor entry we find, and
* add it to a list of monitor entries in the application.
*
* When a change occurs to a directory we are monitoring, the callback function
* DirMontiorCompletionFunction will be invoked, and in turn will invoke the
* the ActOnNotification method of the monitor entry for that directory. If a file
* has changed we use g_FileAppMap to shut down those applications that depend on
* that file and flush the file from the template cache.
*
************************************************************/
#define MAX_BUFFER_SIZE 8192
PTRACE_LOG CASPDirMonitorEntry::gm_pTraceLog = NULL;
CDirMonitor *g_pDirMonitor=NULL;
CASPDirMonitorEntry::CASPDirMonitorEntry() : m_cNotificationFailures(0)
/*++
Routine Description:
Constructor
Arguments:
None
Return Value:
None
--*/
{
}
CASPDirMonitorEntry::~CASPDirMonitorEntry()
/*++
Routine Description:
Destructor
Arguments:
None
Return Value:
None
--*/
{
}
/*++
increment refcount for an entry -- writes to reftrace log if it is defined
--*/
VOID CASPDirMonitorEntry::AddRef(VOID)
{
CDirMonitorEntry::AddRef();
IF_DEBUG(FCN)
WriteRefTraceLogEx(gm_pTraceLog, m_cDirRefCount, this, PVOID(UIntToPtr(m_cIORefCount)), m_pszPath, 0);
}
BOOL CASPDirMonitorEntry::Release(VOID)
/*++
Routine Description:
Decrement refcount to an entry, we override the base class because
otherwise Denali's memory manager can't track when we free the object
and reports it as a memory leak
Arguments:
None
Return Value:
TRUE if object still alive, FALSE if was last release and object
destroyed
--*/
{
BOOL fAlive = CDirMonitorEntry::Release();
IF_DEBUG(FCN)
WriteRefTraceLogEx(gm_pTraceLog, m_cDirRefCount, this, PVOID(UIntToPtr(m_cIORefCount)), m_pszPath, 0);
return fAlive;
}
BOOL
CASPDirMonitorEntry::ActOnNotification(
DWORD dwStatus,
DWORD dwBytesWritten)
/*++
Routine Description:
Do any work associated with a change notification, i.e.
Arguments:
None
Return Value:
TRUE if application should continue to be monitored, otherwise FALSE
--*/
{
FILE_NOTIFY_INFORMATION *pNotify = NULL;
FILE_NOTIFY_INFORMATION *pNextNotify = NULL;
LPSTR pszScriptName = NULL; // Name of script
WCHAR *pwstrFileName = NULL; // Wide file name
DWORD cch = 0;
CWCharToMBCS convFileName;
pNextNotify = (FILE_NOTIFY_INFORMATION *) m_pbBuffer;
// If the status word is not NOERROR, then the ReadDirectoryChangesW failed
if (dwStatus)
{
// If the status is ERROR_ACCESS_DENIED the directory may be deleted
// or secured so we want to stop watching it for changes. The changes to the
// individual scripts will flush the template cache, but we may also be watching
// the directory for the addition of a GLOBAL.ASA. By calling FileChanged on
// global.asa we will force that handle on the directory to close.
if (dwStatus == ERROR_ACCESS_DENIED)
{
FileChanged(SZ_GLOBAL_ASA, false);
// No further notificaitons desired
// so return false
return FALSE;
}
// If we return TRUE, we'll try change notification again
// If we return FALSE, we give up on any further change notifcation
// We'll try a MAX_NOTIFICATION_FAILURES times and give up.
if (m_cNotificationFailures < MAX_NOTIFICATION_FAILURES)
{
IF_DEBUG(FCN)
DBGPRINTF((DBG_CONTEXT, "[CASPDirMonitorEntry] ReadDirectoryChange failed. Status = %d\n", dwStatus));
m_cNotificationFailures++;
return TRUE; // Try to get change notification again
}
else
{
IF_DEBUG(FCN)
DBGPRINTF((DBG_CONTEXT, "[CASPDirMonitorEntry] ReadDirectoryChange failed too many times. Giving up.\n"));
return FALSE; // Give up trying to get change notification
}
}
else
{
// Reset the number of notification failure
m_cNotificationFailures = 0;
}
// If dwBytesWritten is 0, then there were more changes then could be
// recorded in the buffer we provided. Flush the whole cache just in case
// CONSIDER: is this the best course of action, or should iterate through the
// cache and test which files are expired
if (dwBytesWritten == 0)
{
IF_DEBUG(FCN)
DBGPRINTF((DBG_CONTEXT, "[CASPDirMonitorEntry] ReadDirectoryChange failed, too many changes for buffer\n"));
// Flush the 449 response file cache
Do449ChangeNotification();
// Flush everything in the cache as a precaution
g_TemplateCache.FlushAll();
// Check all applications to see if they need to be restarted
g_ApplnMgr.RestartApplications();
// Flush the script engine cache as a precaution (should be flushed via TemplateCache, but just in case.)
g_ScriptManager.FlushAll();
// Try to increase the buffer size so this doesn't happen again
// Unfortunately the first call to ReadDirectoryChangesW on this
// file handle establishes the buffer size. We must close and re-open
// the file handle to change the buffer size
if (ResetDirectoryHandle() && (GetBufferSize() < MAX_BUFFER_SIZE))
{
SetBufferSize(2 * GetBufferSize());
}
return TRUE;
}
while ( pNextNotify != NULL )
{
pNotify = pNextNotify;
pNextNotify = (FILE_NOTIFY_INFORMATION *) ((PCHAR) pNotify + pNotify->NextEntryOffset);
// Get the unicode file name from the notification struct
// pNotify->FileNameLength returns the wstr's length in **bytes** not wchars
cch = pNotify->FileNameLength / 2;
// Convert to ANSI with uniform case and directory delimiters
if (convFileName.Init(pNotify->FileName, CP_ACP, cch))
continue;
pszScriptName = convFileName.GetString();
// Take the appropriate action for the directory change
switch (pNotify->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
// 'File Added' only matters for GLOBAL.ASA
IF_DEBUG(FCN)
DBGPRINTF((DBG_CONTEXT, "Change Notification: New file added: %s\n", pszScriptName));
if (cch != CCH_GLOBAL_ASA ||
lstrcmpi(pszScriptName, SZ_GLOBAL_ASA) != 0)
{
break;
}
case FILE_ACTION_REMOVED:
case FILE_ACTION_MODIFIED:
case FILE_ACTION_RENAMED_OLD_NAME:
IF_DEBUG(FCN)
DBGPRINTF((DBG_CONTEXT, "Change Notification: File %s: %s\n", pNotify->Action == FILE_ACTION_MODIFIED? "changed" : "removed", pszScriptName));
FileChanged(pszScriptName, pNotify->Action != FILE_ACTION_MODIFIED);
break;
default:
break;
}
if(pNotify == pNextNotify)
{
break;
}
}
// We should sign up for further change notification
return TRUE;
}
void
CASPDirMonitorEntry::FileChanged(const char *pszScriptName, bool fFileWasRemoved)
/*++
Routine Description:
An existing file has been modified or deleted
Flush scripts from cache or mark application as expired
Arguments:
pszScriptName Name of file that changed
Return Value:
None Fail silently
--*/
{
// The file name is set by the application that
// modified the file, so old applications like EDIT
// may hand us a munged 8.3 file name which we should
// convert to a long name. All munged 8.3 file names contain '~'
// We assume the path does not contain any munged names.
WIN32_FIND_DATA wfd;
STACK_BUFFER( tempScriptName, MAX_PATH );
STACK_BUFFER( tempScriptPath, MAX_PATH );
bool fRemoveMultiple = false;
char *pT = strchr(pszScriptName, '~');
if (pT)
{
if (ConvertToLongFileName(m_pszPath, pszScriptName, &wfd))
{
pszScriptName = (char *) &wfd.cFileName;
}
else
{
// It could be a long filename that was deleted, so remove everything in cache past the '~'.
if (fFileWasRemoved)
{
fRemoveMultiple = true;
DWORD cchToCopy = (DWORD)(pT - pszScriptName);
if (!tempScriptName.Resize(cchToCopy + 1)) {
return;
}
char *szScriptNameCopy = static_cast<char *>(tempScriptName.QueryPtr());
// copy prefix to delete into local buffer.
strncpy(szScriptNameCopy, pszScriptName, cchToCopy);
szScriptNameCopy[cchToCopy] = '\0';
pszScriptName = szScriptNameCopy;
}
else
return;
}
}
// Allocate enough memory to concatentate the
// application path and script name
DWORD cch = m_cPathLength + strlen(pszScriptName);
if (!tempScriptPath.Resize(cch + 1))
return;
LPSTR pszScriptPath = (LPSTR) tempScriptPath.QueryPtr(); // CONSIDER using malloc
Assert(pszScriptPath != NULL);
// Copy the application path into the script path
// pT will point to the terminator of the application path
pT = strcpyEx(pszScriptPath, m_pszPath);
// Now append the script name. Note that the script name is
// relative to the directory that we received the notification for
lstrcpy(pT, pszScriptName);
Normalize(pszScriptPath);
// It is important that we flush the cache and then shutdown applications
// The call to shut down applications is asynch, and could result in the
// template being delted while we are in the process of flushing it.
// CONSIDER: Is this really indicative of a ref-counting problem?
if (fRemoveMultiple)
{
IF_DEBUG(FCN)
DBGPRINTF((DBG_CONTEXT, "ChangeNotification: Flushing \"%s*\" from cache.\n", pszScriptPath));
g_IncFileMap.FlushFiles(pszScriptPath);
g_TemplateCache.FlushFiles(pszScriptPath);
Do449ChangeNotification(NULL); // not used often, no selective delete
}
else
{
g_IncFileMap.Flush(pszScriptPath);
g_TemplateCache.Flush(pszScriptPath, MATCH_ALL_INSTANCE_IDS);
Do449ChangeNotification( pszScriptPath );
}
// g_FileAppMap will shutdown any applications
// that depend on this file.
g_FileAppMap.ShutdownApplications( pszScriptPath );
}
BOOL CASPDirMonitorEntry::FPathMonitored(LPCSTR pszPath)
{
if (m_fWatchSubdirectories && (strncmp(m_pszPath,pszPath, m_cPathLength) == 0)) {
return TRUE;
}
return FALSE;
}
BOOL
RegisterASPDirMonitorEntry(
LPCSTR pszDirectory,
CASPDirMonitorEntry **ppDME,
BOOL fWatchSubDirs /* = FALSE */
)
/*++
Routine Description:
Find entry and create a new one and start monitoring
if not found.
Arguments:
pszDirectory - directory to monitor
ppDNE - Found (or newly created) entry (optional)
Return Value:
TRUE if success, otherwise FALSE
Remarks:
Not compatible with WIN95
--*/
{
// Keep other threads from add or removing entries while we register this one
g_pDirMonitor->Lock();
LPSTR pszDirCopy = NULL;
DWORD len = strlen(pszDirectory);
// see if the directory string has a trailing back-slash. The trailing
// slash is a requirement as the ActOnNotification code simply appends the
// filename to this directory path. If there isn't a trailing slash, the
// filename and directory are munged together.
if ( *CharPrev(pszDirectory, pszDirectory+len) != '\\')
{
// if not, allocate memory to make a copy of the string that
// does include the trailing back slash
char *pT;
// allocate memory to hold the string plus the trailing
// slash and NULL char
pszDirCopy = (LPSTR)malloc(len+2);
// return failure if unable to allocate the memory
if (pszDirCopy == NULL) {
return FALSE;
}
// copy the directory string to the copy, append the trailing
// backslash and NULL char
pT = strcpyEx(pszDirCopy, pszDirectory);
pT[0] = '\\';
pT[1] = '\0';
// re-assign pszDirectory to this copy
pszDirectory = pszDirCopy;
}
// Check Existing first
CDirMonitorEntry *pDME = g_pDirMonitor->FindEntry(pszDirectory);
if ( pDME == NULL )
{
// Not found - create new entry
pDME = new CASPDirMonitorEntry;
if ( pDME )
{
pDME->AddRef();
pDME->Init(NULL);
// Start monitoring
if ( !g_pDirMonitor->Monitor(pDME, pszDirectory, fWatchSubDirs, FILE_NOTIFY_FILTER) )
{
// Cleanup if failed
pDME->Release();
pDME = NULL;
}
}
}
// delete the temp copy of the directory string, if allocated
if (pszDirCopy)
free(pszDirCopy);
g_pDirMonitor->Unlock();
// Return entry if found
if ( pDME != NULL )
{
*ppDME = static_cast<CASPDirMonitorEntry *>(pDME);
return TRUE;
}
else
{
*ppDME = NULL;
return FALSE;
}
}
BOOL
ConvertToLongFileName(
const char *pszPath,
const char *pszName,
WIN32_FIND_DATA *pwfd)
/*++
Routine Description:
Finds the long filename corresponding to a munged 8.3 filename.
Arguments:
pszPath The path to the file
pszName The 8.3 munged version of the file name
pwfd Find data structure used to contain the long
version of the file name.
Return Value:
TRUE if the file is found,
FALSE otherwise
--*/
{
// Allocate enough memory to concatentate the file path and name
STACK_BUFFER( tempName, MAX_PATH );
DWORD cch = strlen(pszPath) + strlen(pszName);
if (!tempName.Resize(cch + 1))
return FALSE;
char *pszFullName = (char *) tempName.QueryPtr();
Assert(pszFullName != NULL);
// Copy the path into the working string
// pT will point to the terminator of the application path
char* pT = strcpyEx(pszFullName,
pszPath);
// Now append the file name. Note that the script name is
// relative to the directory that we received the notification for
lstrcpy(pT, pszName);
// FindFirstFile will find using the short name
// We can then find the long name from the WIN32_FIND_DATA
HANDLE hFindFile = FindFirstFile(pszFullName, pwfd);
if (hFindFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}
// Now that we have the find data we don't need the handle
FindClose(hFindFile);
return TRUE;
}