/*++ 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); }