/*++

   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 <mbstring.h>
#include <lonsi.hxx>
#include <dbgutil.h>

//
//  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; index<MAX_BINS; index++ ) {
        InitializeListHead( &CacheTable.Items[ index ] );
    }

    return( TRUE );
} // Cache_Initialize

BOOL
TsCacheDirectoryBlob(
    IN const TSVC_CACHE             &TSvcCache,
    IN      PCSTR                   pszDirectoryName,
    IN      ULONG                   iDemultiplexor,
    IN      PVOID                   pvBlob,
    IN      BOOLEAN                 bKeepCheckedOut,
    IN      PSECURITY_DESCRIPTOR    pSecDesc
    )
/*++

  Routine Description:

    This function associates the Blob given as input with the specified
    directory and demultiplexing number.  Services should use this
    function to add a Blob to the cache.

    Callers must not cache the same Blob twice.  Once a Blob is cached,
    its contents must not be modified, and it must not be freed or re-cached.

  Arguments

--*/
{
    CACHE_OBJECT *cache = NULL;
    PBLOB_HEADER  pbhBlob;
    BOOLEAN       bSuccess;
    ULONG         iBin;
    PLIST_ENTRY   pEntry;
    PCACHE_OBJECT pCache;
    PCHAR         pszTemp;
    HASH_TYPE     htHash;
    ULONG         cchLength;

    ASSERT( pszDirectoryName != NULL );
    ASSERT( pvBlob != NULL );

    IF_DEBUG( CACHE) {

        DBGPRINTF( (DBG_CONTEXT,
                    "TsCacheDirectoryBlob called with"
                    " Dir=%S, DeMux=%u, PvBlob=%08x, ChkedOut=%d\n",
                    pszDirectoryName,
                    iDemultiplexor,
                    pvBlob,
                    bKeepCheckedOut
                    ));
    }

    if ( g_fDisableCaching )
    {
        goto Cannot_Cache;
    }

    //
    //  The caller will have passed their pointer to the usable area of the
    //  Blob, so we have to adjust it to point to the beginning.
    //

    pbhBlob = (( PBLOB_HEADER )pvBlob ) - 1;

    ASSERT( !pbhBlob->IsCached );

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


