/*++ Copyright (c) 1995 Microsoft Corporation Module Name : cache.cxx Abstract: This module contains the tsunami caching routines Author: Murali R. Krishnan ( MuraliK ) 16-Jan-1995 --*/ #include "TsunamiP.Hxx" #pragma hdrstop #include #include #include // // Items in a Bin list beyond this position will get moved to the front // on an object cache hit // #define REORDER_LIST_THRESHOLD 5 // // Current count of cached file handles across a UNC connection // DWORD cCachedUNCHandles = 0; // // Enable caching of security descriptor & AccessCheck // BOOL g_fCacheSecDesc = TRUE; BOOL g_fEnableCaching = TRUE; BOOL RemoveLruHandleCacheItem( VOID ); CACHE_TABLE CacheTable; #if TSUNAMI_REF_DEBUG PTRACE_LOG RefTraceLog; #endif // TSUNAMI_REF_DEBUG BOOL Cache_Initialize( IN DWORD MaxOpenFileInUse ) { int index; // // Initialize configuration block // ZeroMemory(&Configuration,sizeof( Configuration )); InitializeCriticalSection( &CacheTable.CriticalSection ); SET_CRITICAL_SECTION_SPIN_COUNT( &CacheTable.CriticalSection, IIS_DEFAULT_CS_SPIN_COUNT); InitializeListHead( &CacheTable.MruList ); CacheTable.OpenFileInUse = 0; CacheTable.MaxOpenFileInUse = MaxOpenFileInUse; for ( index=0; indexIsCached ); // // Hash the directory name. // htHash = CalculateHashAndLengthOfPathName( pszDirectoryName, &cchLength ); // // Allocate the cache object. We (effectively) allocate cchLength + 1 // bytes, to allow for the trailing NULL. // cache = (PCACHE_OBJECT)ALLOC( sizeof(CACHE_OBJECT) + cchLength); if ( cache == NULL ) { IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, "Unable to alloc Cache Object. Failure.\n")); } goto Cannot_Cache; } cache->Signature = CACHE_OBJ_SIGNATURE; cache->hash = htHash; cache->cchLength = cchLength; // // Store the Blob in the new object. // cache->pbhBlob = pbhBlob; // // Store the security descriptor in the new object. // cache->pSecDesc = pSecDesc; cache->hLastSuccessAccessToken = NULL; // // We need to be able to find the cache entry from the Blob. // pbhBlob->pCache = cache; // // Initialize the check-out count. // cache->references = ( bKeepCheckedOut) ? 2 : 1; cache->iDemux = iDemultiplexor; cache->dwService = TSvcCache.GetServiceId(); cache->dwInstance = TSvcCache.GetInstanceId(); cache->TTL = 1; TSUNAMI_TRACE( cache->references, cache ); IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsCacheDirectoryBlob(%s) iDemux=%08lx, cache=%08lx, references=%d\n", pszDirectoryName, iDemultiplexor, cache, cache->references )); } InitializeListHead( &cache->DirChangeList ); // // Lock the cache table against changes. We need to take the lock // before we add the new object to the directory change death list, // so that a directory change that kills this object will not find // the cache table without the object present. // EnterCriticalSection( &CacheTable.CriticalSection ); // // Copy the directory name to the cache object. // memcpy( cache->szPath, pszDirectoryName, cache->cchLength + 1 ); // // Add the object to the directory change expiry list. // // There's an ugly, disgusting hack here making this code aware // of the structure of URI info, but it's better than going // through everywhere and fixing the call to this routine to pass // in the file path as well as the cache key name. // if (iDemultiplexor == RESERVED_DEMUX_URI_INFO) { PW3_URI_INFO pURIInfo = (PW3_URI_INFO)pvBlob; pszTemp = pURIInfo->pszName; } else { pszTemp = (PCHAR)pszDirectoryName; } bSuccess = DcmAddNewItem( (PIIS_SERVER_INSTANCE)TSvcCache.GetServerInstance(), pszTemp, cache ); if ( !bSuccess ) { // // For whatever reason, we cannot get notifications of changes // in the directory containing the to-be-cached item. We won't // be adding this object to the cache table, so we unlock the // table and jump to the failure-handling code. // LeaveCriticalSection( &CacheTable.CriticalSection ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Unable to cache. Due to rejection by DirChngMgr\n")); } goto Cannot_Cache; } // // Mark this blob as cached, since we'll either cache it or throw it // away hereafter. // pbhBlob->IsCached = TRUE; // // Add the object to the cache table, as the most-recently-used object. // iBin = HASH_TO_BIN( cache->hash ); // // Look for a previously cached object for the same directory. If we // find one, remove it. // for ( pEntry = CacheTable.Items[ iBin ].Flink; pEntry != &CacheTable.Items[ iBin ]; pEntry = pEntry->Flink ) { pCache = CONTAINING_RECORD( pEntry, CACHE_OBJECT, BinList ); if ( pCache->cchLength == cache->cchLength && pCache->hash == cache->hash && pCache->iDemux == cache->iDemux && pCache->dwService == cache->dwService && pCache->dwInstance== cache->dwInstance && !_memicmp( cache->szPath, pCache->szPath, cache->cchLength ) ) { // // We found a matching cache object. We remove it, since it // has been replaced by this new object. // IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsCacheDirectoryBlob - Decache(%s)\n", pCache->szPath )); } DeCache( pCache, FALSE ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Matching cache object found." " Throwing that object ( %08x) out of cache\n", pEntry)); } break; } } // // Add this object to the cache. // InsertHeadList( &CacheTable.Items[ iBin ], &cache->BinList ); // // Since this object was just added, put it at the head of the MRU list. // InsertHeadList( &CacheTable.MruList, &cache->MruList ); // // Increase the running size of cached objects by the size of the one // just cached. // IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsCacheDirectoryBlob(%s)\n", pszDirectoryName)); } // // Limit number of open file entries in cache. // Note that in the current scenario pOpenFileInfo is set only after the URI_INFO // blob is inserted in cache, so TsCreateFileFromURI also has to check for // # of open file in cache. // if ( (iDemultiplexor == RESERVED_DEMUX_OPEN_FILE) || (iDemultiplexor == RESERVED_DEMUX_URI_INFO && ((W3_URI_INFO*)pvBlob)->bFileInfoValid && ((W3_URI_INFO*)pvBlob)->pOpenFileInfo != NULL) ) { TsIncreaseFileHandleCount( TRUE ); } // // Unlock the cache table. // LeaveCriticalSection( &CacheTable.CriticalSection ); ASSERT( BLOB_IS_OR_WAS_CACHED( pvBlob ) ); // // Return success. // IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Cached object(%08x) contains Blob (%08x)." " Returning TRUE\n", cache, pvBlob)); } return( TRUE ); Cannot_Cache: // // The cleanup code does not cleanup a directory change item. // if ( cache != NULL ) { cache->Signature = CACHE_OBJ_SIGNATURE_X; FREE( cache ); cache = NULL; } ASSERT( !BLOB_IS_OR_WAS_CACHED( pvBlob ) ); IF_DEBUG( CACHE) { DBGPRINTF( (DBG_CONTEXT, " Failure to cache the object ( %08x)\n", pvBlob)); } return( FALSE ); } // TsCacheDirectoryBlob BOOL TsCheckOutCachedBlob( IN const TSVC_CACHE &TSvcCache, IN PCSTR pszDirectoryName, IN ULONG iDemultiplexor, IN PVOID * ppvBlob, IN HANDLE hAccessToken, IN BOOL fMayCacheAccessToken, IN PSECURITY_DESCRIPTOR* ppSecDesc ) { HASH_TYPE hash; ULONG cchLength; int iBin; BOOL Result; LONG refCount; PLIST_ENTRY pEntry; PCACHE_OBJECT pCache; DWORD Position = 0; BOOL fSkipIdCheck = (iDemultiplexor != RESERVED_DEMUX_OPEN_FILE) && (iDemultiplexor != RESERVED_DEMUX_URI_INFO); ASSERT( pszDirectoryName != NULL ); ASSERT( ppvBlob != NULL ); // // Prepare the return value such that we fail by default. // Result = FALSE; if ( ppSecDesc ) { *ppSecDesc = NULL; } // // Calculate the hash and length of the path name. // hash = CalculateHashAndLengthOfPathName( pszDirectoryName, &cchLength ); // // Calculate the bin of the hash table that should head the list // containing the sought-after item. // iBin = HASH_TO_BIN( hash ); EnterCriticalSection( &CacheTable.CriticalSection ); __try { // // Look for a previously cached object for the same directory. If we // find one, return it. // for ( pEntry = CacheTable.Items[ iBin ].Flink; pEntry != &CacheTable.Items[ iBin ]; pEntry = pEntry->Flink, Position++ ) { pCache = CONTAINING_RECORD( pEntry, CACHE_OBJECT, BinList ); ASSERT( pCache->Signature == CACHE_OBJ_SIGNATURE ); ASSERT( pCache->pbhBlob->IsCached ); ASSERT( pCache->pbhBlob->pCache == pCache ); if ( pCache->cchLength == cchLength && pCache->hash == hash && pCache->iDemux == iDemultiplexor && pCache->references > 0 && ( fSkipIdCheck || ( pCache->dwService == TSvcCache.GetServiceId() && pCache->dwInstance == TSvcCache.GetInstanceId() ) ) && !_memicmp( pCache->szPath, pszDirectoryName, cchLength ) ) { // // Check access rights // if ( pCache->pSecDesc && hAccessToken && hAccessToken != pCache->hLastSuccessAccessToken ) { BOOL fAccess; DWORD dwGrantedAccess; BYTE psFile[SIZE_PRIVILEGE_SET]; DWORD dwPS = sizeof( psFile ); if ( !::AccessCheck( pCache->pSecDesc, hAccessToken, FILE_GENERIC_READ, &g_gmFile, (PRIVILEGE_SET*)psFile, &dwPS, &dwGrantedAccess, &fAccess ) || !fAccess ) { DBGPRINTF( (DBG_CONTEXT, "[TsCheckOutCachedBlob] AccessCheck failed error %d\n", GetLastError() )); Result = FALSE; goto Exit; } if ( fMayCacheAccessToken ) { pCache->hLastSuccessAccessToken = hAccessToken; } } // // We found a matching cache object. We return it and increase // its reference count. // *ppvBlob = pCache->pbhBlob + 1; ASSERT( pCache->pbhBlob->IsCached ); // // Increase the reference count of the cached object, to prevent // it from expiration while it is checked out. // refCount = REFERENCE_CACHE_OBJ( pCache ); if( refCount == 1 ) { // // The reference count was zero before we incremented // it, meaning this cache entry is in the midst of // getting deleted. We'll restore the reference count // and ignore this entry. // DEREFERENCE_CACHE_OBJ( pCache ); continue; } TSUNAMI_TRACE( refCount, pCache ); IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsCheckOutCachedBlob(%s) iDemux=%08lx, cache=%08lx, references=%d\n", pszDirectoryName, pCache->iDemux, pCache, refCount )); } pCache->TTL = 1; Result = TRUE; // // If the found item is far enough back in the list, move // it to the front so the next hit will be quicker // if ( Position > REORDER_LIST_THRESHOLD ) { RemoveEntryList( pEntry ); InsertHeadList( &CacheTable.Items[ iBin ], pEntry ); IF_DEBUG( CACHE ) { DBGPRINTF(( DBG_CONTEXT, "[TsCheckOutCachedBlobW] Reordered list for item at %d position\n", Position )); } } if ( ppSecDesc && pCache->pSecDesc ) { if ( *ppSecDesc = (PSECURITY_DESCRIPTOR)LocalAlloc( LMEM_FIXED, GetSecurityDescriptorLength(pCache->pSecDesc) ) ) { memcpy( *ppSecDesc, pCache->pSecDesc, GetSecurityDescriptorLength(pCache->pSecDesc) ); } } break; } } } __except( EXCEPTION_EXECUTE_HANDLER ) { // // As far as I can see, the only way we can end up here with // Result == TRUE is an exception on LeaveCriticalSection(). If // that happens, we're toast anyway, since noone will ever get to // the CacheTable again. // ASSERT( !Result ); Result = FALSE; } Exit: LeaveCriticalSection( &CacheTable.CriticalSection ); if ( Result) { INC_COUNTER( TSvcCache.GetServiceId(), CacheHits ); } else { INC_COUNTER( TSvcCache.GetServiceId(), CacheMisses ); } return( Result ); } // TsCheckOutCachedBlobW VOID InsertHeadPhysFile( IN PPHYS_OPEN_FILE_INFO lpPFInfo, IN PVOID pvBlob ) { PBLOB_HEADER pbhBlob; ASSERT( lpPFInfo->Signature == PHYS_OBJ_SIGNATURE ); pbhBlob = (( PBLOB_HEADER )pvBlob ) - 1; EnterCriticalSection( &CacheTable.CriticalSection ); ASSERT( IsListEmpty( &pbhBlob->PFList ) ); InsertHeadList( &lpPFInfo->OpenReferenceList, &pbhBlob->PFList ); LeaveCriticalSection( &CacheTable.CriticalSection ); } BOOL TsCheckOutCachedPhysFile( IN const TSVC_CACHE &TSvcCache, IN PCSTR pszDirectoryName, IN PVOID * ppvBlob ) { HASH_TYPE hash; ULONG cchLength; int iBin; BOOL Result; BOOL Found; LONG refCount; PLIST_ENTRY pEntry; PCACHE_OBJECT pCache = NULL; DWORD Position = 0; PBLOB_HEADER pbhBlob; PPHYS_OPEN_FILE_INFO pPhysFileInfo; ASSERT( pszDirectoryName != NULL ); ASSERT( ppvBlob != NULL ); // // Prepare the return value such that we fail by default. // Result = FALSE; Found = FALSE; *ppvBlob = NULL; // // Calculate the hash and length of the path name. // hash = CalculateHashAndLengthOfPathName( pszDirectoryName, &cchLength ); // // Calculate the bin of the hash table that should head the list // containing the sought-after item. // iBin = HASH_TO_BIN( hash ); EnterCriticalSection( &CacheTable.CriticalSection ); __try { // // Look for a previously cached object for the same directory. If we // find one, return it. // for ( pEntry = CacheTable.Items[ iBin ].Flink; pEntry != &CacheTable.Items[ iBin ]; pEntry = pEntry->Flink, Position++ ) { pCache = CONTAINING_RECORD( pEntry, CACHE_OBJECT, BinList ); ASSERT( pCache->Signature == CACHE_OBJ_SIGNATURE ); ASSERT( pCache->pbhBlob->IsCached ); ASSERT( pCache->pbhBlob->pCache == pCache ); if ( pCache->cchLength == cchLength && pCache->hash == hash && pCache->iDemux == RESERVED_DEMUX_PHYSICAL_OPEN_FILE && pCache->references > 0 && !_memicmp( pCache->szPath, pszDirectoryName, cchLength ) ) { // // We found a matching cache object. We return it and increase // its reference count. // *ppvBlob = pCache->pbhBlob + 1; ASSERT( pCache->pbhBlob->IsCached ); // // Increase the reference count of the cached object, to prevent // it from expiration while it is checked out. // refCount = REFERENCE_CACHE_OBJ( pCache ); if( refCount == 1 ) { // // The reference count was zero before we incremented // it, meaning this cache entry is in the midst of // getting deleted. We'll restore the reference count // and ignore this entry. // DEREFERENCE_CACHE_OBJ( pCache ); continue; } TSUNAMI_TRACE( refCount, pCache ); IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsCheckOutCachedPhysFile(%s) iDemux=%08lx, cache=%08lx, references=%d\n", pszDirectoryName, pCache->iDemux, pCache, refCount )); } pCache->TTL = 1; Result = TRUE; Found = TRUE; // // If the found item is far enough back in the list, move // it to the front so the next hit will be quicker // if ( Position > REORDER_LIST_THRESHOLD ) { RemoveEntryList( pEntry ); InsertHeadList( &CacheTable.Items[ iBin ], pEntry ); IF_DEBUG( OPLOCKS ) { DBGPRINTF(( DBG_CONTEXT, "[TsCheckOutCachedBlobW] Reordered list for item at %d position\n", Position )); } } break; } } } __except( EXCEPTION_EXECUTE_HANDLER ) { // // As far as I can see, the only way we can end up here with // Result == TRUE is an exception on LeaveCriticalSection(). If // that happens, we're toast anyway, since noone will ever get to // the CacheTable again. // ASSERT( !Result ); Result = FALSE; } // // If we don't find a cache entry for the file, create one // pCache = NULL; if ( !Result ) { Result = TsAllocateEx( TSvcCache, sizeof( PHYS_OPEN_FILE_INFO ), ppvBlob, DisposePhysOpenFileInfo ); if ( Result ) { pPhysFileInfo = (PPHYS_OPEN_FILE_INFO)*ppvBlob; TSUNAMI_TRACE( TRACE_PHYS_CREATE, pPhysFileInfo ); pPhysFileInfo->Signature = PHYS_OBJ_SIGNATURE; pPhysFileInfo->hOpenFile = INVALID_HANDLE_VALUE; pPhysFileInfo->fInitComplete = FALSE; pPhysFileInfo->dwLastError = ERROR_FILE_NOT_FOUND; pPhysFileInfo->fSecurityDescriptor = FALSE; pPhysFileInfo->fDeleteOnClose = FALSE; pPhysFileInfo->fIsCached = FALSE; InitializeListHead( &pPhysFileInfo->OpenReferenceList ); pPhysFileInfo->abSecurityDescriptor = (BYTE *)ALLOC( SECURITY_DESC_DEFAULT_SIZE ); if ( pPhysFileInfo->abSecurityDescriptor == NULL ) { TsFree( TSvcCache, *ppvBlob ); *ppvBlob = NULL; Result = FALSE; goto Exit; } else { pPhysFileInfo->cbSecDescMaxSize = SECURITY_DESC_DEFAULT_SIZE; } // // *ppvBlob points to the usable area of the // Blob, so we have to adjust it to point to the beginning. // pbhBlob = (( PBLOB_HEADER )*ppvBlob ) - 1; ASSERT( !pbhBlob->IsCached ); if ( g_fDisableCaching ) { goto Cannot_Cache; } // // Allocate the cache object. We (effectively) allocate cchLength + 1 // bytes, to allow for the trailing NULL. // pCache = (PCACHE_OBJECT)ALLOC( sizeof(CACHE_OBJECT) + cchLength); if ( pCache == NULL ) { IF_DEBUG( OPLOCKS ) { DBGPRINTF( ( DBG_CONTEXT, "Unable to alloc Cache Object. Failure.\n")); } TsFree( TSvcCache, *ppvBlob ); *ppvBlob = NULL; Result = FALSE; goto Exit; } pCache->Signature = CACHE_OBJ_SIGNATURE; pCache->hash = hash; pCache->cchLength = cchLength; // // Store the Blob in the new object. // pCache->pbhBlob = pbhBlob; // // Store the security descriptor in the new object. // pCache->pSecDesc = NULL; pCache->hLastSuccessAccessToken = NULL; // // We need to be able to find the cache entry from the Blob. // pbhBlob->pCache = pCache; // // Initialize the check-out count. // pCache->references = 1; pCache->iDemux = RESERVED_DEMUX_PHYSICAL_OPEN_FILE; pCache->dwService = TSvcCache.GetServiceId(); pCache->dwInstance = TSvcCache.GetInstanceId(); pCache->TTL = 1; TSUNAMI_TRACE( pCache->references, pCache ); IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsCheckOutCachedPhysFile(%s) cache=%08lx, references=%d\n", pszDirectoryName, pCache, pCache->references )); } InitializeListHead( &pCache->DirChangeList ); // // Copy the directory name to the cache object. // memcpy( pCache->szPath, pszDirectoryName, pCache->cchLength + 1 ); #if 0 Result = DcmAddNewItem( (PIIS_SERVER_INSTANCE)TSvcCache.GetServerInstance(), (PCHAR)pszDirectoryName, pCache ); if ( !Result ) { // // For whatever reason, we cannot get notifications of changes // in the directory containing the to-be-cached item. We won't // be adding this object to the cache table, so we unlock the // table and jump to the failure-handling code. // IF_DEBUG( OPLOCKS) { DBGPRINTF( ( DBG_CONTEXT, " Unable to cache. Due to rejection by DirChngMgr\n")); } goto Cannot_Cache; } #endif // // Mark this blob as cached, since we'll either cache it or throw it // away hereafter. // pbhBlob->IsCached = TRUE; pPhysFileInfo->fIsCached = TRUE; // // Add the object to the cache table, as the most-recently-used object. // iBin = HASH_TO_BIN( pCache->hash ); // // Add this object to the cache. // InsertHeadList( &CacheTable.Items[ iBin ], &pCache->BinList ); // // Since this object was just added, put it at the head of the MRU list. // InsertHeadList( &CacheTable.MruList, &pCache->MruList ); // // Increase the running size of cached objects by the size of the one // just cached. // IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsCheckoutCachedPhysFile(%s)\n", pszDirectoryName )); } ASSERT( BLOB_IS_OR_WAS_CACHED( *ppvBlob ) ); // // Return success. // IF_DEBUG( OPLOCKS) { DBGPRINTF( ( DBG_CONTEXT, " Cached object(%08x) contains Blob (%08x)." " Returning TRUE\n", pCache, *ppvBlob)); } goto Exit; } else { IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsCheckOutCachedPhysFile(%s) Alloc Failed!\n", pszDirectoryName )); } } } else { goto Exit; } Cannot_Cache: // // The cleanup code does not cleanup a directory change item. // if ( pCache != NULL ) { pCache->Signature = CACHE_OBJ_SIGNATURE_X; FREE( pCache ); pCache = NULL; } ASSERT( !BLOB_IS_OR_WAS_CACHED( *ppvBlob ) ); IF_DEBUG( OPLOCKS) { DBGPRINTF( (DBG_CONTEXT, " Failure to cache the object ( %08x)\n", *ppvBlob)); } Result = FALSE; Exit: LeaveCriticalSection( &CacheTable.CriticalSection ); if ( Result) { INC_COUNTER( TSvcCache.GetServiceId(), CacheHits ); } else { INC_COUNTER( TSvcCache.GetServiceId(), CacheMisses ); } return( Found ); } // TsCheckOutCachedPhysFile BOOL TsCheckInCachedBlob( IN PVOID pvBlob ) { PBLOB_HEADER pbhBlob; PCACHE_OBJECT pCache; BOOL bEjected; pbhBlob = (( PBLOB_HEADER )pvBlob ) - 1; ASSERT( pbhBlob->IsCached ); pCache = pbhBlob->pCache; ASSERT( pCache->Signature == CACHE_OBJ_SIGNATURE ); ASSERT( pCache->pbhBlob == pbhBlob ); ASSERT( pCache->references > 0 ); TsDereferenceCacheObj( pCache, TRUE ); return( TRUE ); } // TsCheckInCachedBlob BOOL TsAddRefCachedBlob( IN PVOID pvBlob ) { PBLOB_HEADER pbhBlob; PCACHE_OBJECT pCache; BOOL bEjected; LONG refCount; pbhBlob = (( PBLOB_HEADER )pvBlob ) - 1; ASSERT( pbhBlob->IsCached ); pCache = pbhBlob->pCache; ASSERT( pCache->Signature == CACHE_OBJ_SIGNATURE ); ASSERT( pCache->pbhBlob == pbhBlob ); ASSERT( pCache->references > 0 ); refCount = REFERENCE_CACHE_OBJ( pCache ); TSUNAMI_TRACE( refCount, pCache ); return( TRUE ); } // TsCheckInCachedBlob BOOL TsExpireCachedBlob( IN const TSVC_CACHE &TSvcCache, IN PVOID pvBlob ) { PBLOB_HEADER pbhBlob; PCACHE_OBJECT pCache; pbhBlob = (( PBLOB_HEADER )pvBlob ) - 1; ASSERT( pbhBlob->IsCached ); pCache = pbhBlob->pCache; ASSERT( pCache->Signature == CACHE_OBJ_SIGNATURE ); ASSERT( pCache->pbhBlob == pbhBlob ); ASSERT( pCache->references > 0 ); return( DeCache( pCache, TRUE ) ); } // TsExpireCachedBlob VOID TsDereferenceCacheObj( IN PCACHE_OBJECT pCache, IN BOOL fLockCacheTable ) { LONG refCount; ASSERT( pCache->Signature == CACHE_OBJ_SIGNATURE ); ASSERT( pCache->references > 0 ); ASSERT( pCache->pbhBlob->IsCached ); refCount = DEREFERENCE_CACHE_OBJ( pCache ); TSUNAMI_TRACE( refCount, pCache ); IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsDereferenceCacheObj(%s) iDemux=%08lx, cache=%08lx, references=%d\n", pCache->szPath, pCache->iDemux, pCache, refCount )); } if( refCount == 0 ) { EnterCriticalSection( &CacheTable.CriticalSection ); if ( pCache->references != 0 ) { LeaveCriticalSection( &CacheTable.CriticalSection ); return; } if ( pCache->iDemux == RESERVED_DEMUX_PHYSICAL_OPEN_FILE ) { RemoveCacheObjFromLists( pCache, FALSE ); } if (!DisableSPUD) { if (!IsListEmpty( &pCache->pbhBlob->PFList ) ) { RemoveEntryList( &pCache->pbhBlob->PFList ); } } LeaveCriticalSection( &CacheTable.CriticalSection ); // // We best not be on a list if we're about to be freed here // ASSERT( IsListEmpty( &pCache->BinList ) ); // // We really want to call TsFree here, but we don't have a TsvcCache // IF_DEBUG( CACHE ) { DBGPRINTF(( DBG_CONTEXT, "[DeCache] Free routine: 0x%lx, Blob: 0x%lx Cache obj: 0x%lx\n", pCache->pbhBlob->pfnFreeRoutine, pCache->pbhBlob, pCache )); } if ( pCache->pbhBlob->pfnFreeRoutine ) pCache->pbhBlob->pfnFreeRoutine( pCache->pbhBlob + 1); IF_DEBUG(OPLOCKS) { DBGPRINTF( (DBG_CONTEXT,"TsDereferenceCacheObj(%s)\n", pCache->szPath )); } DEC_COUNTER( pCache->dwService, CurrentObjects ); if ( pCache->pSecDesc ) { LocalFree( pCache->pSecDesc ); } if ( pCache->iDemux == RESERVED_DEMUX_OPEN_FILE ) { TsDecreaseFileHandleCount(); } pCache->Signature = CACHE_OBJ_SIGNATURE_X; FREE( pCache->pbhBlob ); FREE( pCache ); } } // TsDereferenceCacheObj VOID TsDecreaseFileHandleCount( VOID ) { ASSERT( CacheTable.OpenFileInUse != 0 ); if ( CacheTable.OpenFileInUse ) { InterlockedDecrement( (LONG*)&CacheTable.OpenFileInUse ); } } VOID TsIncreaseFileHandleCount( BOOL fInCacheLock ) { if ( (UINT)(pfnInterlockedExchangeAdd( (LONG*)&CacheTable.OpenFileInUse, 1) ) >= CacheTable.MaxOpenFileInUse ) { if ( !fInCacheLock ) { EnterCriticalSection( &CacheTable.CriticalSection ); } RemoveLruHandleCacheItem(); if ( !fInCacheLock ) { LeaveCriticalSection( &CacheTable.CriticalSection ); } } } BOOL RemoveLruHandleCacheItem( VOID ) /*++ Routine Description: Remove the least recently used cached item referencing a file handle THE CACHE TABLE LOCK MUST BE TAKEN PRIOR TO CALLING THIS FUNCTION Arguments: None --*/ { PLIST_ENTRY pEntry; for ( pEntry = CacheTable.MruList.Blink ; pEntry != &CacheTable.MruList ; pEntry = pEntry->Blink ) { // // The least recently used entry is the one at the tail of the MRU // list. // PCACHE_OBJECT pCacheObject = CONTAINING_RECORD( pEntry, CACHE_OBJECT, MruList ); PW3_URI_INFO pURI = (PW3_URI_INFO)(pCacheObject->pbhBlob+1); if ( (pCacheObject->iDemux == RESERVED_DEMUX_OPEN_FILE) || (pCacheObject->iDemux == RESERVED_DEMUX_URI_INFO && pURI->bFileInfoValid && pURI->pOpenFileInfo != NULL) ) { DeCache( pCacheObject, FALSE ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Throwing out object ( %08x) to reduce file handle ref\n", pCacheObject)); } return TRUE; } } return FALSE; } // RemoveLruCacheItem BOOL TsCacheQueryStatistics( IN DWORD Level, IN DWORD dwServerMask, IN INETA_CACHE_STATISTICS * pCacheCtrs ) /*++ Routine Description: This function returns the statistics for the global cache or for the individual services Arguments: Level - Only valid value is 0 dwServerMask - Server mask to retrieve statistics for or 0 for the sum of the services pCacheCtrs - Receives the statistics for cache Notes: CacheBytesTotal and CacheBytesInUse are not kept on a per-server basis so they are only returned when retrieving summary statistics. Returns: TRUE on success, FALSE on failure --*/ { if ( Level != 0 || dwServerMask > LAST_PERF_CTR_SVC || !pCacheCtrs ) { SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } if ( dwServerMask ) { memcpy( pCacheCtrs, &Configuration.Stats[ MaskIndex(dwServerMask) ], sizeof( Configuration.Stats[ 0 ] ) ); } else { // // Add up all of the statistics // memset( pCacheCtrs, 0, sizeof( *pCacheCtrs )); for ( int i = 0; i < MAX_PERF_CTR_SVCS; i++ ) { DWORD index = MaskIndex( 1 << i ); pCacheCtrs->CurrentOpenFileHandles+= Configuration.Stats[index].CurrentOpenFileHandles; pCacheCtrs->CurrentDirLists += Configuration.Stats[index].CurrentDirLists; pCacheCtrs->CurrentObjects += Configuration.Stats[index].CurrentObjects; pCacheCtrs->FlushesFromDirChanges += Configuration.Stats[index].FlushesFromDirChanges; pCacheCtrs->CacheHits += Configuration.Stats[index].CacheHits; pCacheCtrs->CacheMisses += Configuration.Stats[index].CacheMisses; #if 0 pCacheCtrs->TotalSuccessGetSecDesc+= Configuration.Stats[index].TotalSuccessGetSecDesc; pCacheCtrs->TotalFailGetSecDesc += Configuration.Stats[index].TotalFailGetSecDesc; if ( pCacheCtrs->CurrentSizeSecDesc < Configuration.Stats[index].CurrentSizeSecDesc ) { pCacheCtrs->CurrentSizeSecDesc = Configuration.Stats[index].CurrentSizeSecDesc; } pCacheCtrs->TotalAccessCheck += Configuration.Stats[index].TotalAccessCheck; #endif } } return TRUE; } BOOL TsCacheClearStatistics( IN DWORD dwServerMask ) /*++ Routine Description: Clears the the specified service's statistics --*/ { if ( dwServerMask > LAST_PERF_CTR_SVC ) { SetLastError( ERROR_INVALID_PARAMETER ); return FALSE; } // // Currently this function isn't supported // SetLastError( ERROR_NOT_SUPPORTED ); return FALSE; } // TsCacheClearStatistics BOOL TsCacheFlush( IN DWORD dwServerMask ) /*++ Routine Description: This function flushes the cache of all items for the specified service or for all services if dwServerMask is zero. --*/ { LIST_ENTRY * pEntry; LIST_ENTRY * pNext; if ( dwServerMask == 0 ) { return(TRUE); } EnterCriticalSection( &CacheTable.CriticalSection ); for ( pEntry = CacheTable.MruList.Flink; pEntry != &CacheTable.MruList; ) { pNext = pEntry->Flink; PCACHE_OBJECT pCacheObject = CONTAINING_RECORD( pEntry, CACHE_OBJECT, MruList ); if ( pCacheObject->iDemux == RESERVED_DEMUX_PHYSICAL_OPEN_FILE ) { pEntry = pNext; continue; } if ( dwServerMask == pCacheObject->dwService ) { DeCache( pCacheObject, FALSE ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Throwing out object ( %08x) due to manual flush\n", pCacheObject)); } } pEntry = pNext; } LeaveCriticalSection( &CacheTable.CriticalSection ); return TRUE; } // TsCacheFlush BOOL TsCacheFlushUser( IN HANDLE hUserToken, IN BOOL fDefer ) /*++ Routine Description: This function flushes all file handles associated the passed user context Arguments: hUserToken - User token to flush from the cache fDefer - Build list but close handles later in worker thread (Not supported) --*/ { LIST_ENTRY * pEntry; LIST_ENTRY * pNext; ASSERT( !fDefer ); EnterCriticalSection( &CacheTable.CriticalSection ); for ( pEntry = CacheTable.MruList.Flink; pEntry != &CacheTable.MruList; ) { pNext = pEntry->Flink; PCACHE_OBJECT pCacheObject = CONTAINING_RECORD( pEntry, CACHE_OBJECT, MruList ); ASSERT( pCacheObject->Signature == CACHE_OBJ_SIGNATURE ); if ( pCacheObject->iDemux == RESERVED_DEMUX_PHYSICAL_OPEN_FILE ) { pEntry = pNext; continue; } // // Find all occurrences of the matching user token in the cache and // decache them // if ( pCacheObject->iDemux == RESERVED_DEMUX_OPEN_FILE && ((TS_OPEN_FILE_INFO *)(pCacheObject->pbhBlob + 1))-> QueryOpeningUser() == hUserToken ) { DeCache( pCacheObject, FALSE ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Throwing out object ( %08x) due to user token flush\n", pCacheObject)); } } else if ( pCacheObject->iDemux == RESERVED_DEMUX_DIRECTORY_LISTING && ((TS_DIRECTORY_HEADER *)(pCacheObject->pbhBlob + 1))-> QueryListingUser() == hUserToken ) { DeCache( pCacheObject, FALSE ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Throwing out object ( %08x) due to user token flush\n", pCacheObject)); } } else if ( (pCacheObject->hLastSuccessAccessToken == hUserToken) ) { // // If security descriptor is present, simply cancel Last successful access token // otherwise must decache cache object, as security check are entirely dependent // on last successful access token in this case // if ( pCacheObject->pSecDesc ) { pCacheObject->hLastSuccessAccessToken = NULL; } else { DeCache( pCacheObject, FALSE ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Throwing out object ( %08x) due to user token flush\n", pCacheObject)); } } } pEntry = pNext; } LeaveCriticalSection( &CacheTable.CriticalSection ); return TRUE; } // TsCacheFlushUser BOOL TsCacheFlushDemux( IN ULONG iDemux ) /*++ Routine Description: Flush all cache items whose demultiplexor matches that specified. Arguments: iDemux - Value of demux whose cache items are to be flushed. --*/ { LIST_ENTRY * pEntry; LIST_ENTRY * pNext; EnterCriticalSection( &CacheTable.CriticalSection ); for ( pEntry = CacheTable.MruList.Flink; pEntry != &CacheTable.MruList; ) { pNext = pEntry->Flink; PCACHE_OBJECT pCacheObject = CONTAINING_RECORD( pEntry, CACHE_OBJECT, MruList ); ASSERT( pCacheObject->Signature == CACHE_OBJ_SIGNATURE ); if ( pCacheObject->iDemux == iDemux ) { DeCache( pCacheObject, FALSE ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Throwing out object ( %08x) due to demux flush\n", pCacheObject)); } // // The last Decache may have thrown out the next entry. // Since this is just a shutdown path restart the scan // of the list from the beginning // pEntry = CacheTable.MruList.Flink; continue; } pEntry = pNext; } LeaveCriticalSection( &CacheTable.CriticalSection ); return TRUE; } // TsCacheFlushDemux VOID TsFlushURL( IN const TSVC_CACHE &TSvcCache, IN PCSTR pszURL, IN DWORD dwURLLength, IN ULONG iDemultiplexor ) /*++ Routine Description: This routine takes as input a URL and removes from the cache all cached objects that have the input URL as their prefix. This is mostly called when we get a change notify for metadata. Arguments TSvcCache - Service cache pszURL - The URL prefix to be flushed. iDemultiplexor - The demultiplexor for the caller's entries. Returns Nothing --*/ { PLIST_ENTRY pEntry; PLIST_ENTRY pNext; LIST_ENTRY ListHead; PCACHE_OBJECT pCacheObject; BOOL bIsRoot; // The basic algorithm is to lock the cache table, then walk the cache // table looking for matches and decaching those. This could get // expensive if the table is big and this routine is called frequently - // in that case we may need to schedule the decaches for later, or // periodically free and reaquire the critical section. InitializeListHead( &ListHead ); if (!memcmp(pszURL, "/", sizeof("/"))) { bIsRoot = TRUE; } else { bIsRoot = FALSE; } EnterCriticalSection( &CacheTable.CriticalSection ); pEntry = CacheTable.MruList.Flink; while (pEntry != &CacheTable.MruList) { pNext = pEntry->Flink; pCacheObject = CONTAINING_RECORD( pEntry, CACHE_OBJECT, MruList ); ASSERT( pCacheObject->Signature == CACHE_OBJ_SIGNATURE ); // Check this cache object to see if it matches. if ( pCacheObject->iDemux == iDemultiplexor && pCacheObject->dwService == TSvcCache.GetServiceId() && pCacheObject->dwInstance == TSvcCache.GetInstanceId() && (bIsRoot ? TRUE : ( !_mbsnbicmp( (PUCHAR)pCacheObject->szPath, (PUCHAR)pszURL, dwURLLength) && (pCacheObject->szPath[dwURLLength] == '/' || pCacheObject->szPath[dwURLLength] == '\0'))) ) { if ( !RemoveCacheObjFromLists( pCacheObject, FALSE ) ) { ASSERT( FALSE ); continue; } InsertTailList( &ListHead, &pCacheObject->DirChangeList ); IF_DEBUG( CACHE) { DBGPRINTF( ( DBG_CONTEXT, " Throwing cache object ( %08x) out of cache because of URL match\n", pCacheObject)); } } pEntry = pNext; } for ( pEntry = ListHead.Flink; pEntry != &ListHead; pEntry = pNext ) { pNext = pEntry->Flink; pCacheObject = CONTAINING_RECORD( pEntry, CACHE_OBJECT, DirChangeList ); ASSERT( pCacheObject->Signature == CACHE_OBJ_SIGNATURE ); TsDereferenceCacheObj( pCacheObject, FALSE ); } LeaveCriticalSection( &CacheTable.CriticalSection ); }