windows-nt/Source/XPSP1/NT/inetsrv/iis/iisrearc/iisplus/ulw3/filecache.cxx

1810 lines
38 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1999 Microsoft Corporation
Module Name :
filecache.cxx
Abstract:
A file cache (filename->W3_FILE_INFO cache)
Author:
Bilal Alam (balam) 11-Nov-2000
Environment:
Win32 - User Mode
Project:
ULW3.DLL
--*/
#include "precomp.hxx"
#define STRONG_ETAG_DELTA 30000000
#define SIZE_PRIVILEGE_SET 128
ALLOC_CACHE_HANDLER * W3_FILE_INFO::sm_pachW3FileInfo;
GENERIC_MAPPING g_gmFile = {
FILE_GENERIC_READ,
FILE_GENERIC_WRITE,
FILE_GENERIC_EXECUTE,
FILE_ALL_ACCESS
};
HRESULT
W3_FILE_INFO_KEY::CreateCacheKey(
WCHAR * pszFileKey,
DWORD cchFileKey,
BOOL fCopy
)
/*++
Routine Description:
Initialize a file cache key
Arguments:
pszFileKey - filename
cchFileKey - size of filename
fCopy - TRUE if we should copy into key buffer, otherwise just keep ref
Return Value:
HRESULT
--*/
{
HRESULT hr;
if ( fCopy )
{
hr = _strFileKey.Copy( pszFileKey );
if ( FAILED( hr ) )
{
return hr;
}
_pszFileKey = _strFileKey.QueryStr();
_cchFileKey = _strFileKey.QueryCCH();
}
else
{
_pszFileKey = pszFileKey;
_cchFileKey = cchFileKey;
}
return NO_ERROR;
}
W3_FILE_INFO::~W3_FILE_INFO(
VOID
)
{
HRESULT hr;
W3_FILE_INFO_CACHE* pFileCache;
DBG_ASSERT( CheckSignature() );
_dwSignature = W3_FILE_INFO_SIGNATURE_FREE;
//
// Clear any associated object
//
LockCacheEntry();
if ( _pAssociatedObject != NULL )
{
_pAssociatedObject->Cleanup();
_pAssociatedObject = NULL;
}
UnlockCacheEntry();
//
// Release the contents buffer if it exists
//
if ( _pFileBuffer != NULL )
{
pFileCache = (W3_FILE_INFO_CACHE*) QueryCache();
hr = pFileCache->ReleaseFromMemoryCache( _pFileBuffer,
_nFileSizeLow );
DBG_ASSERT( SUCCEEDED( hr ) );
_pFileBuffer = NULL;
}
//
// Close the file handle if it still around
//
if ( _hFile != INVALID_HANDLE_VALUE )
{
CloseHandle( _hFile );
_hFile = NULL;
}
}
BOOL
W3_FILE_INFO::SetAssociatedObject(
ASSOCIATED_FILE_OBJECT * pObject
)
/*++
Routine Description:
Associate object with this cache entry
Arguments:
pObject - Object to associate
Return Value:
BOOL
--*/
{
BOOL fRet = FALSE;
LockCacheEntry();
if ( _pAssociatedObject == NULL )
{
_pAssociatedObject = pObject;
fRet = TRUE;
}
UnlockCacheEntry();
return fRet;
}
PSECURITY_DESCRIPTOR
W3_FILE_INFO::QuerySecDesc(
VOID
)
/*++
Routine Description:
Return security descriptor
Arguments:
None
Return Value:
pointer to security descriptor
--*/
{
if ( _pFileBuffer != NULL )
{
//
// The file is cached, therefore we must have security already
//
return _bufSecDesc.QueryPtr();
}
else
{
DBG_ASSERT( _hFile != NULL );
if ( FAILED( ReadSecurityDescriptor() ) )
{
return NULL;
}
return _bufSecDesc.QueryPtr();
}
}
HRESULT
W3_FILE_INFO::GenerateETag(
VOID
)
/*++
Routine Description:
Generate ETag string
Arguments:
None
Return Value:
HRESULT
--*/
{
CHAR * psz = _achETag;
PBYTE pbTime = (PBYTE) &_ftLastWriteTime;
DWORD dwChangeNumber;
const CHAR szHex[] = "0123456789abcdef";
FILETIME ftNow;
__int64 iNow;
__int64 iFileTime;
//
// Is this ETag weak? If so put the preceding W/
//
GetSystemTimeAsFileTime(&ftNow);
iNow = (__int64)*(__int64 *)&ftNow;
iFileTime = (__int64)*(__int64 *)&_ftLastWriteTime;
if ( ( iNow - iFileTime ) <= STRONG_ETAG_DELTA )
{
//
// This is a weak ETag
//
*psz++ = 'W';
*psz++ = '/';
}
//
// System change number is from the metabase
//
dwChangeNumber = g_pW3Server->QuerySystemChangeNumber();
//
// Generate the meat of the ETag
//
*psz++ = '\"';
for (int i = 0; i < 8; i++)
{
BYTE b = *pbTime++;
BYTE bH = b >> 4;
if (bH != 0)
*psz++ = szHex[bH];
*psz++ = szHex[b & 0xF];
}
*psz++ = ':';
psz += strlen(_itoa((DWORD) dwChangeNumber, psz, 16));
*psz++ = '\"';
*psz = '\0';
_cchETag = DIFF(psz - _achETag);
return NO_ERROR;
}
HRESULT
W3_FILE_INFO::GenerateLastModifiedTimeString(
VOID
)
/*++
Routine Description:
Generate the Last-Modified-Time header string
Arguments:
None
Return Value:
HRESULT
--*/
{
SYSTEMTIME st;
FileTimeToSystemTime( &_ftLastWriteTime, &st );
if ( !SystemTimeToGMT( st,
_achLastModified,
sizeof(_achLastModified) ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
else
{
return NO_ERROR;
}
}
HRESULT
W3_FILE_INFO::DoAccessCheck(
FILE_CACHE_USER * pFileCacheUser
)
/*++
Routine Description:
Check whether given token has access to this file
Arguments:
pFileCacheUser - User to access cache with
Return Value:
HRESULT
--*/
{
BYTE psFile[SIZE_PRIVILEGE_SET];
DWORD dwPS;
DWORD dwGrantedAccess;
BOOL fAccess;
if ( pFileCacheUser == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
//
// If we don't have a security descriptor, then local system must have
// accessed the file originally. Just return success
//
if ( pFileCacheUser->_hToken == NULL )
{
return NO_ERROR;
}
//
// If we have a last-user-sid, and the caller provided a sid, then do a
// quick check of sid equality
//
if ( QueryLastSid() != NULL &&
pFileCacheUser->_pSid != NULL )
{
if ( EqualSid( QueryLastSid(), pFileCacheUser->_pSid ) )
{
return NO_ERROR;
}
}
//
// Ok. Just use the token and cached security descriptor
//
dwPS = sizeof(psFile);
((PRIVILEGE_SET*)&psFile)->PrivilegeCount = 0;
//
// We must have a security descriptor if we've cached the file
//
DBG_ASSERT( QuerySecDesc() );
if ( !AccessCheck( QuerySecDesc(),
pFileCacheUser->_hToken,
FILE_GENERIC_READ,
&g_gmFile,
(PRIVILEGE_SET*)psFile,
&dwPS,
&dwGrantedAccess,
&fAccess ) || !fAccess )
{
return HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
}
return NO_ERROR;
}
HRESULT
W3_FILE_INFO::OpenFile(
STRU & strFileName,
FILE_CACHE_USER * pOpeningUser
)
/*++
Routine Description:
Open the given file (but don't read in the file contents). This method
does the minimum needed to allow the caller to make a reasonable
decision about whether this file should be cached here or in UL
Arguments:
strFileName - file name to open
pOpeningUser - User to open file under
Return Value:
HRESULT
--*/
{
HANDLE hFile = INVALID_HANDLE_VALUE;
STACK_STRU( strFilePath, MAX_PATH + 1 );
HRESULT hr = NO_ERROR;
BOOL fImpersonated = FALSE;
BY_HANDLE_FILE_INFORMATION FileInfo;
if ( pOpeningUser == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
//
// First make a cache key
//
hr = _cacheKey.CreateCacheKey( strFileName.QueryStr(),
strFileName.QueryCCH(),
TRUE );
if ( FAILED( hr ) )
{
goto Finished;
}
//
// Avoid the infamous ::$DATA bug
//
if ( wcschr( strFileName.QueryStr() + 2, L':' ) != NULL )
{
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
goto Finished;
}
//
// Turn off NT file canonicalization
//
hr = MakePathCanonicalizationProof( strFileName.QueryStr(),
&strFilePath );
if ( FAILED( hr ) )
{
goto Finished;
}
//
// We may need to impersonate some other user to open the file
//
if ( pOpeningUser->_hToken != NULL )
{
if ( !SetThreadToken( NULL, pOpeningUser->_hToken ) )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto Finished;
}
fImpersonated = TRUE;
}
//
// Open the file
//
hFile = CreateFileW( strFilePath.QueryStr(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_DIRECTORY_FILE | FILE_FLAG_BACKUP_SEMANTICS,
NULL );
if ( hFile == INVALID_HANDLE_VALUE )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto Finished;
}
//
// Stop impersonating
//
if ( fImpersonated )
{
RevertToSelf();
fImpersonated = FALSE;
}
//
// We shouldn't be opening byte streams (like COM, LPT)
//
if ( GetFileType( hFile ) != FILE_TYPE_DISK )
{
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
CloseHandle( hFile );
hFile = INVALID_HANDLE_VALUE;
goto Finished;
}
//
// Get file attributes
//
if ( !GetFileInformationByHandle( hFile,
&FileInfo ) )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
CloseHandle( hFile );
hFile = INVALID_HANDLE_VALUE;
goto Finished;
}
//
// Set the minimum properties now
//
_hFile = hFile;
_ftLastWriteTime = FileInfo.ftLastWriteTime;
_dwFileAttributes = FileInfo.dwFileAttributes;
_nFileSizeLow = FileInfo.nFileSizeLow;
_nFileSizeHigh = FileInfo.nFileSizeHigh;
*((__int64 *)&_CastratedLastWriteTime)
= (*((__int64 *)&_ftLastWriteTime) / 10000000) * 10000000;
//
// Create the ETag and LastModified strings
//
hr = GenerateETag();
if ( FAILED( hr ) )
{
goto Finished;
}
hr = GenerateLastModifiedTimeString();
if ( FAILED( hr ) )
{
goto Finished;
}
//
// Turn off the hidden attribute if this is a root directory listing
// (root some times has the bit set for no apparent reason)
//
if ( _dwFileAttributes & FILE_ATTRIBUTE_HIDDEN )
{
if ( strFileName.QueryCCH() >= 2 )
{
if ( strFileName.QueryStr()[ 1 ] == L':' )
{
if ( ( strFileName.QueryStr()[ 2 ] == L'\0' ) ||
( strFileName.QueryStr()[ 2 ] == L'\\' &&
strFileName.QueryStr()[ 3 ] == L'\0' ) )
{
//
// This looks like a local root. Mask out the bit
//
_dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
}
}
}
}
Finished:
if ( FAILED( hr ) )
{
if ( fImpersonated )
{
RevertToSelf();
fImpersonated = FALSE;
}
if ( hFile != INVALID_HANDLE_VALUE )
{
CloseHandle( hFile );
hFile = INVALID_HANDLE_VALUE;
}
}
return hr;
}
HRESULT
W3_FILE_INFO::ReadSecurityDescriptor(
VOID
)
/*++
Routine Description:
Read security descriptor for current file
Arguments:
None
Return Value:
HRESULT
--*/
{
DWORD cbRequired;
//
// Cache the security descriptor
//
if ( !GetKernelObjectSecurity( _hFile,
OWNER_SECURITY_INFORMATION
| GROUP_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION,
(PSECURITY_DESCRIPTOR) _bufSecDesc.QueryPtr(),
_bufSecDesc.QuerySize(),
&cbRequired ) )
{
if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
DBG_ASSERT( cbRequired > _bufSecDesc.QuerySize() );
if ( !_bufSecDesc.Resize( cbRequired ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
//
// Try again with the bigger buffer. No more excuses
//
if ( !GetKernelObjectSecurity( _hFile,
OWNER_SECURITY_INFORMATION
| GROUP_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION,
(PSECURITY_DESCRIPTOR) _bufSecDesc.QueryPtr(),
_bufSecDesc.QuerySize(),
&cbRequired ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
}
return NO_ERROR;
}
HRESULT
W3_FILE_INFO::MakeCacheable(
FILE_CACHE_USER * pFileUser
)
/*++
Routine Description:
Make the file cacheable by reading contents into memory, and caching
the security descriptor
Arguments:
pFileUser - User trying to open file
Return Value:
HRESULT
--*/
{
DWORD dwError;
HRESULT hr;
W3_FILE_INFO_CACHE* pFileCache;
if ( pFileUser == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
DBG_ASSERT( IsCacheable() );
//
// We must have a file handle if we're here
//
DBG_ASSERT( _hFile != INVALID_HANDLE_VALUE );
//
// Get the security descriptor
//
hr = ReadSecurityDescriptor();
if ( FAILED( hr ) )
{
return hr;
}
//
// On top of reading the security descriptor, we will also store the
// last sid accessing the file if available
//
if ( pFileUser->_pSid != NULL )
{
if ( GetLengthSid( pFileUser->_pSid ) <= sizeof( _abLastSid ) )
{
memcpy( _abLastSid,
pFileUser->_pSid,
GetLengthSid( pFileUser->_pSid ) );
_pLastSid = (PSID) _abLastSid;
}
}
//
// Now read the contents of the file into memory since we cannot cache
// the file handle itself
//
pFileCache = (W3_FILE_INFO_CACHE*) QueryCache();
dwError = pFileCache->ReadFileIntoMemoryCache( _hFile,
_nFileSizeLow,
(PVOID*) &_pFileBuffer );
if ( dwError == ERROR_SUCCESS )
{
//
// OK. The contents are now in memory. We can now close the file
//
CloseHandle( _hFile );
_hFile = INVALID_HANDLE_VALUE;
return NO_ERROR;
}
else
{
return HRESULT_FROM_WIN32( dwError );
}
}
BOOL
W3_FILE_INFO::IsCacheable(
VOID
) const
/*++
Routine Description:
Is this file cacheable? Specically, we should we even attempt to cache
this file?
Arguments:
None
Return Value:
TRUE if cacheable
--*/
{
LARGE_INTEGER liFileSize;
W3_FILE_INFO_CACHE * pFileCache;
pFileCache = (W3_FILE_INFO_CACHE*) QueryCache();
DBG_ASSERT( pFileCache != NULL );
//
// Are we past the limit of file entries?
//
if ( pFileCache->QueryElementLimitExceeded() )
{
return FALSE;
}
//
// No caching of directories
//
if ( _dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
{
return FALSE;
}
//
// No caching of file sizes greater than the configured threshold
//
liFileSize.LowPart = _nFileSizeLow;
liFileSize.HighPart = _nFileSizeHigh;
if ( liFileSize.QuadPart > pFileCache->QueryFileSizeThreshold() )
{
return FALSE;
}
return TRUE;
}
BOOL
W3_FILE_INFO::QueryIsOkToFlushDirmon(
WCHAR * pszPath,
DWORD cchPath
)
/*++
Routine Description:
Determine whether this file entry should be flushed, given the path
which has changed (dir monitor changed)
Arguments:
pszPath - Path which changed
cchPath - Size of path changed
Return Value:
TRUE if we should flush, else FALSE
--*/
{
DBG_ASSERT( _cacheKey._pszFileKey != NULL );
if ( _wcsnicmp( _cacheKey._pszFileKey,
pszPath,
cchPath ) == 0 )
{
return TRUE;
}
else
{
return FALSE;
}
}
//static
HRESULT
W3_FILE_INFO::Initialize(
VOID
)
/*++
Routine Description:
Initialize W3_FILE_INFO lookaside
Arguments:
None
Return Value:
HRESULT
--*/
{
ALLOC_CACHE_CONFIGURATION acConfig;
HRESULT hr;
//
// Initialize allocation lookaside
//
acConfig.nConcurrency = 1;
acConfig.nThreshold = 100;
acConfig.cbSize = sizeof( W3_FILE_INFO );
DBG_ASSERT( sm_pachW3FileInfo == NULL );
sm_pachW3FileInfo = new ALLOC_CACHE_HANDLER( "W3_FILE_INFO",
&acConfig );
if ( sm_pachW3FileInfo == NULL )
{
hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
DBGPRINTF(( DBG_CONTEXT,
"Error initializing sm_pachW3FileInfo. hr = 0x%x\n",
hr ));
return hr;
}
return NO_ERROR;
}
//static
VOID
W3_FILE_INFO::Terminate(
VOID
)
/*++
Routine Description:
Cleanup W3_FILE_INFO lookaside
Arguments:
None
Return Value:
None
--*/
{
if ( sm_pachW3FileInfo != NULL )
{
delete sm_pachW3FileInfo;
sm_pachW3FileInfo = NULL;
}
}
//static
VOID
W3_FILE_INFO_CACHE::MemoryCacheAdjustor(
PVOID pCache,
BOOLEAN TimerOrWaitFired
)
/*++
Routine Description:
Called to adjust our memory cache size if necessary
Arguments:
pCache - Points to file cache
Return Value:
None
--*/
{
W3_FILE_INFO_CACHE * pFileCache;
MEMORYSTATUSEX MemoryStatus;
pFileCache = (W3_FILE_INFO_CACHE*) pCache;
MemoryStatus.dwLength = sizeof( MemoryStatus );
GlobalMemoryStatusEx( &MemoryStatus );
EnterCriticalSection( &( pFileCache->_csMemCache ) );
pFileCache->_cbMemCacheLimit = min(
MemoryStatus.ullAvailPhys + pFileCache->_cbMemCacheCurrentSize,
MemoryStatus.ullTotalVirtual ) / 2;
LeaveCriticalSection( &( pFileCache->_csMemCache ) );
}
W3_FILE_INFO_CACHE::W3_FILE_INFO_CACHE()
{
_cbFileSizeThreshold = DEFAULT_FILE_SIZE_THRESHOLD;
_cbMemoryCacheSize = 0;
_cMaxFileEntries = 0;
_cbMemCacheLimit = 0;
_cbMemCacheCurrentSize = 0;
_cbMaxMemCacheSize = 0;
_hMemCacheHeap = NULL;
_hTimer = NULL;
_fEnableCache = TRUE;
}
W3_FILE_INFO_CACHE::~W3_FILE_INFO_CACHE()
{
}
HRESULT
W3_FILE_INFO_CACHE::InitializeMemoryCache(
VOID
)
/*++
Routine Description:
Initialize the memory cache
Arguments:
None
Return Value:
HRESULT
--*/
{
BOOL fRet;
HRESULT hr = NO_ERROR;
InitializeCriticalSection( &_csMemCache );
//
// If the memory cache size was not explicitly set, then we occasionally
// check memory status when determining what to cache
//
if ( _cbMemoryCacheSize == 0 )
{
MEMORYSTATUSEX MemoryStatus;
MemoryStatus.dwLength = sizeof( MemoryStatus );
//
// Get our own estimate of size of cache
//
GlobalMemoryStatusEx( &MemoryStatus );
_cbMemCacheLimit = min( MemoryStatus.ullAvailPhys,
MemoryStatus.ullTotalVirtual ) / 2;
//
// Setup timer so we can update our memory status
//
fRet = CreateTimerQueueTimer( &_hTimer,
NULL,
W3_FILE_INFO_CACHE::MemoryCacheAdjustor,
this,
30000,
30000,
WT_EXECUTELONGFUNCTION );
if ( !fRet )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
}
}
else
{
_cbMemCacheLimit = _cbMemoryCacheSize;
}
//
// Allocate a private heap
//
if ( SUCCEEDED( hr ) )
{
_hMemCacheHeap = HeapCreate( 0, 0, 0 );
if ( _hMemCacheHeap == NULL )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
}
}
if ( FAILED( hr ) )
{
if ( _hMemCacheHeap != NULL )
{
HeapDestroy( _hMemCacheHeap );
_hMemCacheHeap = NULL;
}
if ( _hTimer != NULL )
{
DeleteTimerQueueTimer( NULL,
_hTimer,
INVALID_HANDLE_VALUE );
_hTimer = NULL;
}
DeleteCriticalSection( &_csMemCache );
}
return hr;
}
HRESULT
W3_FILE_INFO_CACHE::ReadFileIntoMemoryCache(
IN HANDLE hFile,
IN DWORD cbFile,
OUT VOID ** ppvBuffer
)
/*++
Routine Description:
Read contents of file into a buffer
Arguments:
hFile - Handle to valid file
cbFile - Size of file ( ==> size of buffer )
ppvBuffer - Filled in with pointer to buffer with file contents. Set
to NULL on failure
Return Value:
HRESULT
--*/
{
BOOL bRet;
VOID * pvBuffer = NULL;
DWORD cbRead;
OVERLAPPED Overlapped;
HRESULT hr = NO_ERROR;
DBG_ASSERT( hFile && ( hFile != INVALID_HANDLE_VALUE ) );
DBG_ASSERT( ppvBuffer != NULL );
//
// First check whether there will be room in cache for the blob
//
EnterCriticalSection( &_csMemCache );
if ( ( _cbMemCacheCurrentSize + cbFile ) > _cbMemCacheLimit )
{
//
// Not enough room for cache
//
LeaveCriticalSection( &_csMemCache );
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
_cbMemCacheCurrentSize += cbFile;
_cbMaxMemCacheSize = max( _cbMaxMemCacheSize, _cbMemCacheCurrentSize );
LeaveCriticalSection( &_csMemCache );
//
// Allocate blob for file
//
DBG_ASSERT( _hMemCacheHeap != NULL );
pvBuffer = HeapAlloc( _hMemCacheHeap, 0, cbFile );
if ( pvBuffer == NULL )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
goto Finished;
}
//
// Read file into blob
//
Overlapped.Offset = 0;
Overlapped.OffsetHigh = 0;
Overlapped.hEvent = NULL;
bRet = ReadFile( hFile,
pvBuffer,
cbFile,
&cbRead,
&Overlapped );
if ( !bRet )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
if ( hr != HRESULT_FROM_WIN32( ERROR_IO_PENDING ) )
{
//
// Something bad happened
//
goto Finished;
}
else
{
//
// Reset the error lest we confuse ourselves later on cleanup
//
hr = NO_ERROR;
//
// Wait for async read to complete
//
bRet = GetOverlappedResult( hFile,
&Overlapped,
&cbRead,
TRUE );
if ( !bRet )
{
//
// Something bad happened
//
hr = HRESULT_FROM_WIN32( GetLastError() );
goto Finished;
}
}
}
//
// Ensure that we read the number of bytes we expected to
//
if ( cbRead != cbFile )
{
hr = HRESULT_FROM_WIN32( ERROR_INVALID_DATA );
}
Finished:
if ( FAILED( hr ) )
{
//
// Undo changes to memory cache statistics
//
EnterCriticalSection( &_csMemCache );
_cbMemCacheCurrentSize -= cbFile;
LeaveCriticalSection( &_csMemCache );
if ( pvBuffer != NULL )
{
HeapFree( _hMemCacheHeap, 0, pvBuffer );
pvBuffer = NULL;
}
}
*ppvBuffer = pvBuffer;
return hr;
}
HRESULT
W3_FILE_INFO_CACHE::ReleaseFromMemoryCache(
IN VOID * pvBuffer,
IN DWORD cbBuffer
)
/*++
Routine Description:
Release file content blob from cache
Arguments:
pvBuffer - Buffer to release
cbBuffer - Size of buffer
Return Value:
HRESULT
--*/
{
DBG_ASSERT( pvBuffer );
DBG_ASSERT( _hMemCacheHeap != NULL);
HeapFree( _hMemCacheHeap, 0, pvBuffer );
EnterCriticalSection( &_csMemCache );
_cbMemCacheCurrentSize -= cbBuffer;
LeaveCriticalSection( &_csMemCache );
return NO_ERROR;
}
VOID
W3_FILE_INFO_CACHE::TerminateMemoryCache(
VOID
)
/*++
Routine Description:
Terminate memory cache
Arguments:
Return Value:
None
--*/
{
if ( _hTimer != NULL )
{
DeleteTimerQueueTimer( NULL,
_hTimer,
INVALID_HANDLE_VALUE );
_hTimer = NULL;
}
if ( _hMemCacheHeap != NULL )
{
HeapDestroy( _hMemCacheHeap );
_hMemCacheHeap = NULL;
}
DeleteCriticalSection( &_csMemCache );
}
HRESULT
W3_FILE_INFO_CACHE::GetFileInfo(
STRU & strFileName,
DIRMON_CONFIG * pDirmonConfig,
FILE_CACHE_USER * pOpeningUser,
BOOL fDoCache,
W3_FILE_INFO ** ppFileInfo
)
/*++
Routine Description:
Returns a W3_FILE_INFO for the given file path. Depending on fDoCache,
this W3_FILE_INFO will be cached
Arguments:
strFileName - file name to find
pDirmonConfig - Dir monitor config
pOpeningUser - Token for user accessing the cache
fDoCache - Set to TRUE if we should attempt to cache if possible
ppFileInfo - Points to W3_FILE_INFO on success
Return Value:
HRESULT
--*/
{
W3_FILE_INFO_KEY fileKey;
DIRMON_CONFIG DefaultDirmonConfig;
STACK_STRU( strParentDir, MAX_PATH );
WCHAR * pszParentDir;
W3_FILE_INFO * pFileInfo;
HRESULT hr;
STACK_STRU( strFilePath, MAX_PATH );
BOOL fShouldCacheHint = FALSE;
if ( ppFileInfo == NULL ||
pOpeningUser == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
*ppFileInfo = NULL;
//
// We need to upper case the path to avoid a bunch of insensitive
// compares in the hash table lookup
//
hr = strFilePath.Copy( strFileName );
if ( FAILED( hr ) )
{
return hr;
}
_wcsupr( strFilePath.QueryStr() );
//
// If the cache is enabled, lookup there first
//
if ( QueryCacheEnabled() )
{
//
// Make a key for the lookup
//
hr = fileKey.CreateCacheKey( strFilePath.QueryStr(),
strFilePath.QueryCCH(),
FALSE );
if ( FAILED( hr ) )
{
return hr;
}
//
// Look it up
//
hr = FindCacheEntry( &fileKey,
(CACHE_ENTRY**) &pFileInfo,
&fShouldCacheHint );
if ( SUCCEEDED( hr ) )
{
DBG_ASSERT( pFileInfo != NULL );
hr = pFileInfo->DoAccessCheck( pOpeningUser );
if ( SUCCEEDED( hr ) )
{
*ppFileInfo = pFileInfo;
}
else
{
pFileInfo->DereferenceCacheEntry();
}
return hr;
}
}
//
// We will simply open the file and return the object
//
pFileInfo = new W3_FILE_INFO( this );
if ( pFileInfo == NULL )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
//
// Open the file
//
hr = pFileInfo->OpenFile( strFilePath,
pOpeningUser );
if ( FAILED( hr ) )
{
pFileInfo->DereferenceCacheEntry();
return hr;
}
//
// If we aren't asked to cache the file, OR the file is not cacheable
// then we can return it now
//
if ( !QueryCacheEnabled() ||
!fDoCache ||
!pFileInfo->IsCacheable() ||
!fShouldCacheHint )
{
*ppFileInfo = pFileInfo;
return NO_ERROR;
}
//
// If we're supposed to cache but no dirmon was configured, then just
// assume the directory to monitor is the parent directory (and token
// to use is NULL)
//
if ( pDirmonConfig == NULL )
{
DefaultDirmonConfig.hToken = NULL;
pszParentDir = wcsrchr( strFilePath.QueryStr(), L'\\' );
if ( pszParentDir != NULL )
{
hr = strParentDir.Copy( strFilePath.QueryStr(),
DIFF( pszParentDir - strFilePath.QueryStr() ) );
if ( SUCCEEDED( hr ) )
{
DefaultDirmonConfig.pszDirPath = strParentDir.QueryStr();
pDirmonConfig = &DefaultDirmonConfig;
}
}
}
//
// If we still don't have a dir mon configuration, then just don't cache
//
if ( pDirmonConfig == NULL )
{
*ppFileInfo = pFileInfo;
return NO_ERROR;
}
//
// Start monitoring the appropriate directory for changes
//
hr = pFileInfo->AddDirmonInvalidator( pDirmonConfig );
if ( FAILED( hr ) )
{
//
// If we can't monitor the directory, then just don't cache the item
//
*ppFileInfo = pFileInfo;
return NO_ERROR;
}
//
// Attempt to cache the file. Caching the file means reading the
// contents into memory, as well as caching the security descriptor
//
hr = pFileInfo->MakeCacheable( pOpeningUser );
if ( FAILED( hr ) )
{
*ppFileInfo = pFileInfo;
return NO_ERROR;
}
//
// Insert into the hash table. AddCacheEntry() will only error if some
// thing fatal happened. If someone else already added the item, that
// is not fatal and we will simply return this item and it will cleanup
// on dereference
//
AddCacheEntry( pFileInfo );
*ppFileInfo = pFileInfo;
return NO_ERROR;
}
HRESULT
W3_FILE_INFO_CACHE::Initialize(
VOID
)
/*++
Routine Description:
Initialize the file cache
Arguments:
None
Return Value:
HRESULT
--*/
{
DWORD dwError;
DWORD dwType;
DWORD dwValue;
DWORD cbData;
DWORD csecTTL = DEFAULT_W3_FILE_INFO_CACHE_TTL;
DWORD csecActivity = DEFAULT_W3_FILE_INFO_CACHE_ACTIVITY;
HKEY hKey = NULL;
HRESULT hr;
CACHE_HINT_CONFIG cacheHintConfig;
//
// Read the registry configuration of the file cache.
// For now, that is just the legacy confiugration from IIS 5.x
//
dwError = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
L"System\\CurrentControlSet\\Services\\inetinfo\\Parameters",
0,
KEY_READ,
&hKey );
if ( dwError == ERROR_SUCCESS )
{
DBG_ASSERT( hKey != NULL );
//
// Should we be file caching at all?
//
cbData = sizeof( DWORD );
dwError = RegQueryValueEx( hKey,
L"DisableMemoryCache",
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbData );
if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD )
{
_fEnableCache = dwValue ? FALSE : TRUE;
}
//
// What is the biggest file we should cache in user mode?
//
cbData = sizeof( DWORD );
dwError = RegQueryValueEx( hKey,
L"MaxCachedFileSize",
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbData );
if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD )
{
_cbFileSizeThreshold = dwValue;
}
//
// What is the size of our memory cache? Size is in MB
//
cbData = sizeof( DWORD );
dwError = RegQueryValueEx( hKey,
L"MemCacheSize",
NULL,
&dwType,
(LPBYTE)&dwValue,
&cbData );
if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD )
{
_cbMemoryCacheSize = dwValue * (1024 * 1024);
}
//
// Read the maximum # of files in cache
//
cbData = sizeof( DWORD );
dwError = RegQueryValueEx( hKey,
L"MaxOpenFiles",
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbData );
if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD )
{
_cMaxFileEntries = dwValue;
}
//
// What is the TTL for the file cache?
//
cbData = sizeof( DWORD );
dwError = RegQueryValueEx( hKey,
L"ObjectCacheTTL",
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbData );
if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD )
{
csecTTL = dwValue;
}
//
// What is the activity period before putting into cache
//
cbData = sizeof( DWORD );
dwError = RegQueryValueEx( hKey,
L"ActivityPeriod",
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbData );
if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD )
{
csecActivity = dwValue;
}
RegCloseKey( hKey );
}
//
// Initialize memory cache
//
hr = InitializeMemoryCache();
if ( FAILED( hr ) )
{
return hr;
}
//
// Setup cache hint config (for now hardcoded)
//
if ( csecActivity != 0 )
{
cacheHintConfig.cmsecActivityWindow = csecActivity * 1000;
cacheHintConfig.cmsecScavengeTime = cacheHintConfig.cmsecActivityWindow * 2;
cacheHintConfig.cmsecTTL = cacheHintConfig.cmsecActivityWindow * 2;
}
//
// We'll use TTL for scavenge period, and expect two inactive periods to
// flush
//
hr = SetCacheConfiguration( csecTTL * 1000,
csecTTL * 1000,
CACHE_INVALIDATION_DIRMON_FLUSH |
CACHE_INVALIDATION_DIRMON_SPECIFIC,
csecActivity ? &cacheHintConfig : NULL );
if ( FAILED( hr ) )
{
return hr;
}
//
// Initialize file info lookaside
//
return W3_FILE_INFO::Initialize();
}
VOID
W3_FILE_INFO_CACHE::Terminate(
VOID
)
/*++
Routine Description:
Terminate the file cache
Argument:
None
Return Value:
None
--*/
{
TerminateMemoryCache();
W3_FILE_INFO::Terminate();
}
VOID
W3_FILE_INFO_CACHE::DoDirmonInvalidationSpecific(
WCHAR * pszPath
)
/*++
Routine Description:
Handle dirmon invalidation
Arguments:
pszPath - Path which changed
Return Value:
HRESULT
--*/
{
HRESULT hr;
W3_FILE_INFO_KEY fileKey;
DBG_ASSERT( pszPath != NULL );
//
// We're not flushing all, then just lookup given file and flush it
//
hr = fileKey.CreateCacheKey( pszPath,
wcslen( pszPath ),
FALSE );
if ( SUCCEEDED( hr ) )
{
FlushCacheEntry( &fileKey );
}
}
//static
W3_FILE_INFO_CACHE *
W3_FILE_INFO_CACHE::GetFileCache(
VOID
)
{
DBG_ASSERT( g_pW3Server != NULL );
return g_pW3Server->QueryFileCache();
}