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