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.
 
 
 
 
 
 

2324 lines
54 KiB

/*++
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 = INVALID_HANDLE_VALUE;
}
}
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 ( _hFile != INVALID_HANDLE_VALUE )
{
if ( FAILED( ReadSecurityDescriptor() ) )
{
return NULL;
}
}
else
{
//
// The file is cached, therefore we must have security already
//
}
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(
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;
}
}
if ( ISUNC( QueryPhysicalPath() ) )
{
//
// If this is a UNC file, and the webserver and the UNC server
// are not on a domain, the sid of the user on the webserver will
// not match the sid in the security-descriptor, so AccessCheck will
// fail, instead do GetFileAttributes
//
if ( !SetThreadToken( NULL, pFileCacheUser->_hToken ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
ThreadPoolSetInfo( ThreadPoolIncMaxPoolThreads, 0 );
DWORD dwAttributes = GetFileAttributes(QueryPhysicalPath());
DWORD dwErr = GetLastError();
ThreadPoolSetInfo( ThreadPoolDecMaxPoolThreads, 0 );
DBG_REQUIRE( RevertToSelf() );
if ( dwAttributes == INVALID_FILE_ATTRIBUTES )
{
return HRESULT_FROM_WIN32( dwErr );
}
}
else
{
//
// 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,
CACHE_USER * pOpeningUser,
BOOL fBufferFile
)
/*++
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
fBufferFile - Should the file be opened with FILE_FLAG_NO_BUFFERING?
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;
DWORD dwFileType;
BOOL bRet;
if ( pOpeningUser == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
//
// Turn off NT file canonicalization
//
hr = MakePathCanonicalizationProof( strFileName.QueryStr(),
&strFilePath );
if ( FAILED( hr ) )
{
goto Finished;
}
//
// Avoid the infamous ::$DATA bug
//
if ( wcschr( strFileName.QueryStr() + 6, L':' ) != NULL )
{
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
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. CreateFile() perf can be underwhelming. We'll need to
// potentially let out another thread while making the call. Much
// like we do for calls into ISAPI extensions
//
ThreadPoolSetInfo( ThreadPoolIncMaxPoolThreads, 0 );
hFile = CreateFile( strFilePath.QueryStr(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_ENCRYPTED | FILE_DIRECTORY_FILE | FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS | (fBufferFile ? 0 : FILE_FLAG_NO_BUFFERING),
NULL );
if ( hFile == INVALID_HANDLE_VALUE )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
}
//
// Undo the threshold adjustment
//
ThreadPoolSetInfo( ThreadPoolDecMaxPoolThreads, 0 );
if ( FAILED(hr) )
{
goto Finished;
}
//
// Stop impersonating
//
if ( fImpersonated )
{
RevertToSelf();
fImpersonated = FALSE;
}
//
// We shouldn't be opening byte streams (like COM, LPT)
//
dwFileType = GetFileType( hFile );
if ( dwFileType != FILE_TYPE_DISK )
{
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
CloseHandle( hFile );
hFile = INVALID_HANDLE_VALUE;
goto Finished;
}
//
// Get file attributes
//
bRet = GetFileInformationByHandle( hFile, &FileInfo );
if ( !bRet )
{
hr = HRESULT_FROM_WIN32( GetLastError() );
CloseHandle( hFile );
hFile = INVALID_HANDLE_VALUE;
goto Finished;
}
//
// Set the minimum properties now
//
_hFile = hFile;
hFile = INVALID_HANDLE_VALUE;
_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;
}
_msLastAttributeCheckTime = GetTickCount();
//
// 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(
CACHE_USER *pFileUser,
FILE_CACHE_ASYNC_CONTEXT *pAsyncContext,
BOOL *pfHandledSync,
BOOL fCheckForExistenceOnly
)
/*++
Routine Description:
Make the file cacheable by reading contents into memory, and caching
the security descriptor
Arguments:
pFileUser - User trying to open file
pAsyncContext - Provides the information necessary to notify the caller
when the file has been read when done async
pfHandledSync - If provided, on return tells whether the open completed
synchronously
fCheckForExistenceOnly - Are we caching the state of existence only?
Return Value:
HRESULT
--*/
{
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
//
if ( _nFileSizeHigh > 0 )
{
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
}
//
// If we're just caching a descriptor to check the existence of the file,
// then we don't need to read the file contents
//
if ( fCheckForExistenceOnly )
{
CloseHandle( _hFile );
_hFile = INVALID_HANDLE_VALUE;
DBG_ASSERT( _pFileBuffer == NULL );
return NO_ERROR;
}
//
// Set up the context for completion
//
if ( pAsyncContext != NULL )
{
pAsyncContext->pFileInfo = this;
}
pFileCache = (W3_FILE_INFO_CACHE*) QueryCache();
//
// Read the file
//
hr = pFileCache->ReadFileIntoMemoryCache( _hFile,
_nFileSizeLow,
(PVOID*) &_pFileBuffer,
pAsyncContext,
pfHandledSync );
if ( FAILED( hr ) )
{
if ( pAsyncContext != NULL )
{
pAsyncContext->pFileInfo = NULL;
}
}
else if ( pfHandledSync == NULL || *pfHandledSync )
{
CloseHandle( _hFile );
_hFile = INVALID_HANDLE_VALUE;
}
return hr;
}
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
--*/
{
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;
}
return IsUlCacheable();
}
BOOL
W3_FILE_INFO::IsUlCacheable(
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 );
//
// No caching of directories
//
if ( _dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
{
return FALSE;
}
//
// No caching of encrypted files
//
if ( _dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED )
{
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;
_cmsecFileAttributeCheckThreshold = DEFAULT_FILE_ATTRIBUTE_CHECK_THRESHOLD * 1000;
_cbMemoryCacheSize = 0;
_cMaxFileEntries = 0;
_cbMemCacheLimit = 0;
_cbMemCacheCurrentSize = 0;
_cbMaxMemCacheSize = 0;
_hMemCacheHeap = NULL;
_hTimer = NULL;
_fEnableCache = TRUE;
_fDoDirmonForUnc = FALSE;
}
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;
fRet = INITIALIZE_CRITICAL_SECTION( &_csMemCache );
if ( !fRet )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
//
// 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,
IN FILE_CACHE_ASYNC_CONTEXT *pAsyncContext,
OUT BOOL *pfHandledSync
)
/*++
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
pAsyncContext - Provides the information necessary to notify the caller
when the file has been read when done async
pfHandledSync - If provided, on return tells whether the open completed
synchronously
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 );
ZeroMemory( &Overlapped, sizeof(Overlapped) );
//
// 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;
}
*ppvBuffer = pvBuffer;
if (pAsyncContext != NULL)
{
ZeroMemory( &pAsyncContext->Overlapped, sizeof(pAsyncContext->Overlapped) );
//
// We can restrict what files we do async reads on further
//
if (!ThreadPoolBindIoCompletionCallback( hFile,
W3_FILE_INFO::FileReadCompletion,
0 ))
{
pAsyncContext = NULL;
*pfHandledSync = TRUE;
pfHandledSync = NULL;
}
}
//
// Read file into blob
//
bRet = ReadFile( hFile,
pvBuffer,
cbFile,
pAsyncContext ? NULL : &cbRead,
pAsyncContext ? &pAsyncContext->Overlapped : &Overlapped );
if (!bRet)
{
hr = HRESULT_FROM_WIN32( GetLastError() );
if ( hr != HRESULT_FROM_WIN32( ERROR_IO_PENDING ) )
{
//
// Something bad happened
//
goto Finished;
}
//
// Reset the error lest we confuse ourselves later on cleanup
//
hr = NO_ERROR;
//
// If async is ok, return now
//
if (pAsyncContext != NULL)
{
*pfHandledSync = FALSE;
goto Finished;
}
//
// Wait for async read to complete (if async is not ok)
//
bRet = GetOverlappedResult( hFile,
&Overlapped,
&cbRead,
TRUE );
if ( !bRet )
{
//
// Something bad happened
//
hr = HRESULT_FROM_WIN32( GetLastError() );
goto Finished;
}
}
if (pAsyncContext != NULL)
{
*pfHandledSync = FALSE;
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 ) )
{
*ppvBuffer = NULL;
//
// Undo changes to memory cache statistics
//
EnterCriticalSection( &_csMemCache );
_cbMemCacheCurrentSize -= cbFile;
LeaveCriticalSection( &_csMemCache );
if ( pvBuffer != NULL )
{
HeapFree( _hMemCacheHeap, 0, pvBuffer );
pvBuffer = NULL;
}
}
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,
CACHE_USER * pOpeningUser,
BOOL fDoCache,
W3_FILE_INFO ** ppFileInfo,
FILE_CACHE_ASYNC_CONTEXT *pAsyncContext,
BOOL *pfHandledSync,
BOOL fAllowNoBuffering,
BOOL fCheckForExistenceOnly
)
/*++
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
pAsyncContext - Provides the information necessary to notify the caller
when the file has been read when done async
pfHandledSync - If provided, on return tells whether the open completed
synchronously
fAllowNoBuffering - Allow the file to be opened with FILE_FLAG_NO_BUFFERING
fCheckForExistenceOnly - If TRUE, ensure the file is accessible but do
not cache if it is not already cached
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( strFilePathKey, MAX_PATH );
BOOL fShouldCacheHint = FALSE;
BOOL fBufferFile = TRUE;
if ( ppFileInfo == NULL ||
pOpeningUser == NULL )
{
DBG_ASSERT( FALSE );
return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
}
//
// Both of them should be null (for sync) or non-null (for async)
//
if ( ( pAsyncContext != NULL && pfHandledSync == NULL ) ||
( pAsyncContext == NULL && pfHandledSync != 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 = strFilePathKey.Copy( strFileName );
if ( FAILED( hr ) )
{
return hr;
}
_wcsupr( strFilePathKey.QueryStr() );
//
// If the cache is enabled, lookup there first
//
if ( QueryCacheEnabled() )
{
//
// Make a key for the lookup
//
hr = fileKey.CreateCacheKey( strFilePathKey.QueryStr(),
strFilePathKey.QueryCCH(),
FALSE );
if ( FAILED( hr ) )
{
return hr;
}
//
// Look it up
//
hr = FindCacheEntry( &fileKey,
(CACHE_ENTRY **)&pFileInfo,
&fShouldCacheHint );
if ( SUCCEEDED( hr ) )
{
BOOL fHasChanged = FALSE;
DBG_ASSERT( pFileInfo != NULL );
//
// If it is a UNC file, we do not do dirmon but rather make sure
// it has not changed on each open
// Otherwise, we need to do an accesscheck against the current user
//
if ( !_fDoDirmonForUnc &&
ISUNC( strFilePathKey.QueryStr() ) &&
( GetTickCount() - pFileInfo->QueryLastAttributeCheckTime() ) >
_cmsecFileAttributeCheckThreshold )
{
hr = pFileInfo->CheckIfFileHasChanged( &fHasChanged,
pOpeningUser );
}
else
{
hr = pFileInfo->DoAccessCheck( pOpeningUser );
}
if ( FAILED( hr ) )
{
pFileInfo->DereferenceCacheEntry();
return hr;
}
if ( !fHasChanged )
{
//
// We have found a cached entry which we have access to.
//
// One more complication: If this file entry exists without
// a handle/memory-buffer, then it is there for file
// existence purposes only. If we're currently checking
// for existence only, then great! If not, this entry is
// bogus and we'll have to stuff in a new content entry
//
if ( pFileInfo->QueryFileBuffer() == NULL &&
pFileInfo->QueryFileHandle() == INVALID_HANDLE_VALUE &&
!fCheckForExistenceOnly )
{
//
// We need a real cache entry. Get rid of this entry
//
FlushCacheEntry( pFileInfo->QueryCacheKey() );
pFileInfo->DereferenceCacheEntry();
pFileInfo = NULL;
//
// Fall thru so that we try to cache it again
// (this time completely).
//
}
else
{
//
// We've satisfied the file cache request. Return the
// descriptor
//
*ppFileInfo = pFileInfo;
if ( pfHandledSync != NULL )
{
*pfHandledSync = TRUE;
}
return NO_ERROR;
}
}
else
{
pFileInfo->DereferenceCacheEntry();
pFileInfo = NULL;
//
// Release this entry but continue on to get a fresh one
//
}
}
}
//
// OK. We have to open the file. Figure out whether we should open the file
// buffered or not
//
if ( !QueryCacheEnabled() ||
!fDoCache ||
!fShouldCacheHint )
{
if ( fAllowNoBuffering )
{
fBufferFile = FALSE;
}
}
//
// We will simply open the file and return the object
//
pFileInfo = new W3_FILE_INFO( this );
if ( pFileInfo == NULL )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
//
// Setup the cache key (in case we want to cache it)
//
hr = ((W3_FILE_INFO_KEY*) pFileInfo->QueryCacheKey())->CreateCacheKey(
strFilePathKey.QueryStr(),
strFilePathKey.QueryCCH(),
TRUE );
if ( FAILED( hr ) )
{
pFileInfo->DereferenceCacheEntry();
return hr;
}
//
// Open the file. Note that we use the original strFileName
// parameter (which is not upper cased). This is done to support
// case sensitive file systems
//
hr = pFileInfo->OpenFile( strFileName,
pOpeningUser,
fBufferFile );
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 ||
!fShouldCacheHint ||
!pFileInfo->IsUlCacheable() )
{
*ppFileInfo = pFileInfo;
if (pfHandledSync != NULL)
{
*pfHandledSync = TRUE;
}
return NO_ERROR;
}
pFileInfo->AllowUlCache();
if ( !pFileInfo->IsCacheable() )
{
*ppFileInfo = pFileInfo;
if ( pfHandledSync != NULL )
{
*pfHandledSync = TRUE;
}
return NO_ERROR;
}
//
// In general, caching a file means dirmoning it. The exception is if this is a UNC
// file and we're configured to do file validation
//
if ( _fDoDirmonForUnc ||
!ISUNC( strFilePathKey.QueryStr() ) )
{
//
// 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( strFilePathKey.QueryStr(), L'\\' );
if ( pszParentDir != NULL )
{
hr = strParentDir.Copy( strFilePathKey.QueryStr(),
DIFF( pszParentDir - strFilePathKey.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;
if (pfHandledSync != NULL)
{
*pfHandledSync = TRUE;
}
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;
if (pfHandledSync != NULL)
{
*pfHandledSync = TRUE;
}
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,
pAsyncContext,
pfHandledSync,
fCheckForExistenceOnly );
if ( FAILED( hr ) )
{
*ppFileInfo = pFileInfo;
if (pfHandledSync != NULL)
{
*pfHandledSync = TRUE;
}
return NO_ERROR;
}
//
// If an async read is pending, then return out now
//
if (pfHandledSync != NULL &&
!*pfHandledSync)
{
*ppFileInfo = NULL;
return NO_ERROR;
}
//
// Insert into the hash table. AddCacheEntry() will only error if
// we cannot add 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;
}
//
// Do we do dirmonitoring for UNC's?
//
cbData = sizeof( DWORD );
dwError = RegQueryValueEx( hKey,
L"DoDirMonitoringForUnc",
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbData );
if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD )
{
_fDoDirmonForUnc = dwValue;
}
//
// What is the file attribute threshold time (for UNCs without dirmon)
//
cbData = sizeof( DWORD );
dwError = RegQueryValueEx( hKey,
L"FileAttributeCheckThreshold",
NULL,
&dwType,
(LPBYTE) &dwValue,
&cbData );
if ( dwError == ERROR_SUCCESS && dwType == REG_DWORD )
{
_cmsecFileAttributeCheckThreshold = dwValue * 1000;
}
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 ) )
{
TerminateMemoryCache();
return hr;
}
//
// Initialize file info lookaside
//
hr = W3_FILE_INFO::Initialize();
if ( FAILED( hr ) )
{
TerminateMemoryCache();
}
return hr;
}
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();
}
HRESULT
W3_FILE_INFO::CheckIfFileHasChanged(
BOOL *pfHasChanged,
CACHE_USER *pOpeningUser
)
/*++
This function determines whether the file has changed for what is
in the cache. This is useful when we do not do directory change
monitoring on UNC files.
--*/
{
HRESULT hr = S_OK;
BOOL fImpersonated = FALSE;
//
// We may need to impersonate some other user to open the file
//
if ( pOpeningUser->_hToken != NULL )
{
if ( !SetThreadToken( NULL, pOpeningUser->_hToken ) )
{
return HRESULT_FROM_WIN32( GetLastError() );
}
fImpersonated = TRUE;
}
//
// Going to a UNC, let us bump up our thread count
//
ThreadPoolSetInfo( ThreadPoolIncMaxPoolThreads, 0 );
WIN32_FILE_ATTRIBUTE_DATA fileData;
if (!GetFileAttributesEx(_cacheKey._pszFileKey,
GetFileExInfoStandard,
&fileData))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Finished;
}
//
// If the attributes, WriteTime, and if it is a file, size are same
// then the file has not changed
//
if (fileData.dwFileAttributes == _dwFileAttributes &&
*(__int64 *)&fileData.ftLastWriteTime == *(__int64 *)&_ftLastWriteTime &&
((fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
(fileData.nFileSizeHigh == _nFileSizeHigh &&
fileData.nFileSizeLow == _nFileSizeLow)
)
)
{
*pfHasChanged = FALSE;
_msLastAttributeCheckTime = GetTickCount();
}
else
{
*pfHasChanged = TRUE;
}
Finished:
//
// We are back, we can bump down the count again
//
ThreadPoolSetInfo( ThreadPoolDecMaxPoolThreads, 0 );
if (fImpersonated)
{
DBG_REQUIRE(RevertToSelf());
}
return hr;
}
BOOL
W3_FILE_INFO::Checkout(
CACHE_USER *pOpeningUser
)
{
BOOL fHasChanged = FALSE;
//
// If it is a UNC file, we do not do dirmon but rather make sure
// it has not changed on each open
//
if (!g_pW3Server->QueryFileCache()->QueryDoDirmonForUnc() &&
ISUNC(_cacheKey._pszFileKey) &&
( GetTickCount() - QueryLastAttributeCheckTime() ) >
g_pW3Server->QueryFileCache()->QueryFileAttributeCheckThreshold() )
{
if (FAILED(CheckIfFileHasChanged(&fHasChanged,
pOpeningUser)) ||
fHasChanged)
{
return FALSE;
}
}
return CACHE_ENTRY::Checkout(pOpeningUser);
}
// static
VOID CALLBACK W3_FILE_INFO::FileReadCompletion(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped)
{
FILE_CACHE_ASYNC_CONTEXT *pAsyncContext =
CONTAINING_RECORD(lpOverlapped,
FILE_CACHE_ASYNC_CONTEXT,
Overlapped);
HRESULT hr = S_OK;
if (dwErrorCode != 0)
{
hr = HRESULT_FROM_WIN32(dwErrorCode);
}
else
{
ULARGE_INTEGER liSize;
W3_FILE_INFO *pFileInfo = pAsyncContext->pFileInfo;
pFileInfo->QuerySize(&liSize);
DBG_ASSERT(dwNumberOfBytesTransfered == liSize.QuadPart);
CloseHandle(pFileInfo->_hFile);
pFileInfo->_hFile = INVALID_HANDLE_VALUE;
g_pW3Server->QueryFileCache()->AddCacheEntry( pFileInfo );
}
pAsyncContext->pfnCallback(pAsyncContext, hr);
}