/**********************************************************************/
/**                       Microsoft Windows NT                       **/
/**                Copyright(c) Microsoft Corp., 1998                **/
/**********************************************************************/

/*
    tsunami.cxx

    This module contains most of the public Tsunami Cache routines.

    FILE HISTORY:
        MCourage    09-Dec-1997     Created
*/

#include <tsunami.hxx>
#include <inetinfo.h>
#include <issched.hxx>
#include "inetreg.h"
#include "globals.hxx"
#include "tsunamip.hxx"
#include <inetsvcs.h>
#include "metacach.hxx"
#include "filecach.hxx"
#include "blobcach.hxx"
#include "atq.h"
#include "tracelog.h"
#include <lkrhash.h>
#include "filehash.hxx"
#include "blobhash.hxx"
#include "tlcach.h"
#include "etagmb.h"

BOOL  g_fCacheSecDesc = TRUE;

//
// from TsInit.cxx
//

HANDLE g_hQuit = NULL;
HANDLE g_hNewItem   = NULL;
BOOL g_fW3OnlyNoAuth = FALSE;
BOOL  TsNoDirOpenSupport = FALSE;


#if TSUNAMI_REF_DEBUG
PTRACE_LOG RefTraceLog;
#endif  // TSUNAMI_REF_DEBUG

//
//  The TTL to scavenge the cache and the id of the scheduled work item of the
//  next scheduled scavenge
//

DWORD g_cmsecObjectCacheTTL = (INETA_DEF_OBJECT_CACHE_TTL * 1000);
DWORD g_dwObjectCacheCookie = 0;

# define MIN_CACHE_SCAVENGE_TIME (5*1000) // 5 seconds


//
// Disables Tsunami Caching
//

BOOL DisableTsunamiCaching = FALSE;

//
// DisableSPUD
//

BOOL DisableSPUD = FALSE;

//
// Allows us to mask the invalid flags
//

DWORD TsValidCreateFileOptions = TS_IIS_VALID_FLAGS;

//
// from globals.cxx
//
CONFIGURATION Configuration;
BOOL          g_fDisableCaching = FALSE;


//
// Initialization and cleanup
//


BOOL
Tsunami_Initialize(
            VOID
            )
/*++
Routine Description:

    Sets up all the core caches.  Call this before using any
    cache routines.

Arguments:

    None.

Return Values:

    TRUE on success
--*/
{
    HRESULT hr;
    HKEY  hKey;
    DWORD dwType;
    DWORD nBytes;
    DWORD dwValue;
    DWORD dwMaxFile;
    DWORD err;

#if TSUNAMI_REF_DEBUG
    RefTraceLog = CreateRefTraceLog(
                      256,              // LogSize
                      0                 // ExtraBytesInHeader
                      );
#endif  // TSUNAMI_REF_DEBUG

    //
    // Initialize global events
    //

    g_hQuit = IIS_CREATE_EVENT(
                  "g_hQuit",
                  &g_hQuit,
                  TRUE,
                  FALSE
                  );

    g_hNewItem = IIS_CREATE_EVENT(
                     "g_hNewItem",
                     &g_hNewItem,
                     FALSE,
                     FALSE
                     );

    if ( (g_hQuit == NULL) || (g_hNewItem == NULL) ) {
        goto Failure;
    }

    //
    // Set defaults
    //

    MEMORYSTATUS ms;
    ms.dwLength = sizeof(MEMORYSTATUS);
    GlobalMemoryStatus( &ms );

    //
    // default is 1K files per 32MB of physical memory after the 1st 8MB,
    // minimum INETA_MIN_DEF_FILE_HANDLE
    //

    if ( ms.dwTotalPhys > 8 * 1024 * 1024 )
    {
        dwMaxFile = (DWORD)(ms.dwTotalPhys - 8 * 1024 * 1024) / ( 32 * 1024 );
        if ( dwMaxFile < INETA_MIN_DEF_FILE_HANDLE )
        {
            dwMaxFile = INETA_MIN_DEF_FILE_HANDLE;
        }
    }
    else
    {
        dwMaxFile = INETA_MIN_DEF_FILE_HANDLE;
    }

    //
    // If this is not a NTS, disable tsunami caching by default
    //

    DisableSPUD = !AtqSpudInitialized();


    if ( !TsIsNtServer() ) {
        DisableTsunamiCaching = TRUE;
        DisableSPUD = TRUE;
    }

    DisableSPUD = FALSE;

    //
    // no overlapped i/o in win95.
    //

    if ( TsIsWindows95() ) {
        TsCreateFileFlags = FILE_FLAG_SEQUENTIAL_SCAN;
        TsCreateFileShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
        TsNoDirOpenSupport = TRUE;
        // |FILE_FLAG_BACKUP_SEMANTICS;
    }

    //
    // Read the registry key to see whether tsunami caching is enabled
    //

    err = RegOpenKeyEx(
                HKEY_LOCAL_MACHINE,
                INETA_PARAMETERS_KEY,
                0,
                KEY_READ,
                &hKey
                );

    if ( err == ERROR_SUCCESS ) {

        //
        // This cannot be overridded in win95
        //

        if ( !TsIsWindows95() ) {
            nBytes = sizeof(dwValue);
            err = RegQueryValueEx(
                        hKey,
                        INETA_DISABLE_TSUNAMI_CACHING,
                        NULL,
                        &dwType,
                        (LPBYTE)&dwValue,
                        &nBytes
                        );

            if ( (err == ERROR_SUCCESS) && (dwType == REG_DWORD) ) {
                DisableTsunamiCaching = (BOOL)dwValue;
            }

            nBytes = sizeof(dwValue);
            err = RegQueryValueEx(
                        hKey,
                        INETA_DISABLE_TSUNAMI_SPUD,
                        NULL,
                        &dwType,
                        (LPBYTE)&dwValue,
                        &nBytes
                        );

            if ( (err == ERROR_SUCCESS) && (dwType == REG_DWORD) ) {
                DisableSPUD = (BOOL)dwValue;
                if ( DisableSPUD ) {
                    DbgPrint("DisableCacheOplocks set to TRUE in Registry.\n");
                } else {
                    DbgPrint("DisableCacheOplocks set to FALSE in Registry.\n");
                }
                DbgPrint("The Registry Setting will override the default.\n");
            }

            //
            // How big do files have to be before we stop caching them
            //

            err = RegQueryValueEx(
                hKey,
                INETA_MAX_CACHED_FILE_SIZE,
                NULL,
                &dwType,
                (LPBYTE)&dwValue,
                &nBytes
                );

            if ( (err == ERROR_SUCCESS) && (dwType == REG_DWORD) ) {

                g_liFileCacheByteThreshold.LowPart = dwValue;
            } else {

                g_liFileCacheByteThreshold.LowPart = INETA_DEF_MAX_CACHED_FILE_SIZE;
            }
            
            g_liFileCacheByteThreshold.HighPart = 0;    // Sorry Mr. > 4gb file.


            //
            // How big is the memory cache in megabytes
            //
            err = RegQueryValueEx(
                hKey,
                INETA_MEM_CACHE_SIZE,
                NULL,
                &dwType,
                (LPBYTE)&dwValue,
                &nBytes
                );

            if ( (err == ERROR_SUCCESS) && (dwType == REG_DWORD) ) {

                //
                // set the size in megabytes
                //
                g_dwMemCacheSize = dwValue * (1024 * 1024);
            } else {

                g_dwMemCacheSize = INETA_DEF_MEM_CACHE_SIZE;
            }
            
            //
            // Do we use the sequential read flag to read files?
            //
            err = RegQueryValueEx(
                hKey,
                INETA_ENABLE_SEQUENTIAL_READ,
                NULL,
                &dwType,
                (LPBYTE)&dwValue,
                &nBytes
                );

            if ( (err == ERROR_SUCCESS) && (dwType == REG_DWORD) ) {

                g_bEnableSequentialRead = dwValue ? 1 : 0;
            } else {

                g_bEnableSequentialRead = INETA_DEF_ENABLE_SEQUENTIAL_READ;
            }

        }

        if ( g_fW3OnlyNoAuth )
        {
            //
            // TODO: investigate is security descriptor caching
            // can be used in the non-SYSTEM account case.
            //

            g_fCacheSecDesc = FALSE;
        }
        else
        {
            //
            // read the enable cache sec desc flag
            //

            nBytes = sizeof(dwValue);
            err = RegQueryValueEx(
                            hKey,
                            INETA_CACHE_USE_ACCESS_CHECK,
                            NULL,
                            &dwType,
                            (LPBYTE)&dwValue,
                            &nBytes
                            );

            if ( (err == ERROR_SUCCESS) && (dwType == REG_DWORD) ) {
                g_fCacheSecDesc = !!dwValue;
            }
            else {
                 g_fCacheSecDesc = INETA_DEF_CACHE_USE_ACCESS_CHECK;
            }
        }

        //
        // Read the maximum # of files in cache
        //

        nBytes = sizeof(dwValue);
        if ( RegQueryValueEx(
                            hKey,
                            INETA_MAX_OPEN_FILE,
                            NULL,
                            &dwType,
                            (LPBYTE) &dwValue,
                            &nBytes
                            ) == ERROR_SUCCESS && dwType == REG_DWORD )
        {
            dwMaxFile = dwValue;
        }

        RegCloseKey( hKey );

    }

    //
    // if tsunami caching is disabled, set the flags accordingly
    //

    if ( DisableTsunamiCaching ) {
        g_fDisableCaching = TRUE;
        TsValidCreateFileOptions = TS_PWS_VALID_FLAGS;
        g_fCacheSecDesc = FALSE;
    }

    //
    // Initialize the ETag Metabase Change Number
    //

    hr = ETagChangeNumber::Create();
    if ( FAILED(hr) ) {
        goto Failure;
    }

    //
    // Initialize the directory change manager
    //
    if ( !DcmInitialize( ) ) {
        goto Failure;
    }

    //
    // Initialize the tsunami cache manager
    //

    if ( !FileCache_Initialize( dwMaxFile )) {
        goto Failure;
    }


    if ( !MetaCache_Initialize() ) {
        goto Failure;
    }

    if ( !BlobCache_Initialize() ) {
        goto Failure;
    }

    return( TRUE );

Failure:

    IIS_PRINTF( ( buff, "Tsunami_Initialize() Failed. Error = %d\n",
                GetLastError()));

    if ( g_hQuit )
    {
        CloseHandle( g_hQuit );
        g_hQuit = NULL;
    }

    if ( g_hNewItem )
    {
        CloseHandle( g_hNewItem );
        g_hNewItem = NULL;
    }

    return FALSE;
} // Tsunami_Initialize

VOID
Tsunami_Terminate(
    VOID
    )
/*++
Routine Description:

    Cleans up all the core caches.

Arguments:

    None.

Return Values:

    None.
--*/
{
    DWORD dwResult;

    if ( !SetEvent( g_hQuit ) ) {
        IIS_PRINTF((buff,
                "No Quit event posted for Tsunami. No Cleanup\n"));
        return;
    }

    //
    //  Flush all items from the cache
    //

    TsCacheFlush( 0 );

    //
    //  Synchronize with our thread so we don't leave here before the
    //  thread has finished cleaning up
    //


    CloseHandle( g_hQuit );
    CloseHandle( g_hNewItem );


    BlobCache_Terminate();
    MetaCache_Terminate();
    FileCache_Terminate();
    DcmTerminate();

    ETagChangeNumber::Destroy();
    
#if TSUNAMI_REF_DEBUG
    if( RefTraceLog != NULL ) {
        DestroyRefTraceLog( RefTraceLog );
        RefTraceLog = NULL;
    }
#endif  // TSUNAMI_REF_DEBUG

} // Tsunami_Terminate


//
// Scavenger routines
//


BOOL
FileFlushFilterTTL(
    TS_OPEN_FILE_INFO * pFileInfo,
    PVOID               pv
    )
{
    if (pFileInfo->GetIORefCount()) {
        //
        // Try not to time out entries which are in use for I/O.
        //
        return FALSE;
    }

    if (pFileInfo->GetTTL() == 0) {
        pFileInfo->TraceCheckpointEx(TS_MAGIC_TIMEOUT, 0, 0);
        return TRUE;
    } else {
        if (pFileInfo->IsInitialized()) {
            pFileInfo->DecrementTTL();
        }
        return FALSE;
    }
}

BOOL
BlobFlushFilterTTL(
    PBLOB_HEADER pBlob,
    PVOID        pv
    )
{
    if (pBlob->TTL == 0) {
        pBlob->TraceCheckpointEx(TS_MAGIC_TIMEOUT, 0, 0);
        return TRUE;
    } else {
        pBlob->TTL--;
        return FALSE;
    }
}


VOID
WINAPI
CacheScavenger(
    VOID * pContext
    )
{
    FilteredFlushFileCache(FileFlushFilterTTL, NULL);
    FilteredFlushBlobCache(BlobFlushFilterTTL, NULL);
}


BOOL
InitializeCacheScavenger(
    VOID
    )
/*++
Routine Description:

    This function kicks off the scheduled tsunami object cache scavenger

Arguments:

    None.

Return Values:

    TRUE on success
--*/
{
    HKEY hkey;

    //
    //  Schedule a scavenger to close all of the objects that haven't been
    //  referenced in the last ttl
    //

    if ( !RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                        INETA_PARAMETERS_KEY,
                        0,
                        KEY_READ,
                        &hkey ))
    {
        DWORD dwType;
        DWORD nBytes;
        DWORD dwValue;

        nBytes = sizeof(dwValue);
        if ( RegQueryValueEx(
                            hkey,
                            INETA_OBJECT_CACHE_TTL,
                            NULL,
                            &dwType,
                            (LPBYTE) &dwValue,
                            &nBytes
                            ) == ERROR_SUCCESS && dwType == REG_DWORD )
        {
            g_cmsecObjectCacheTTL = dwValue;
        } else {
            g_cmsecObjectCacheTTL = 0;
        }


        //
        //  Don't schedule anything if the scavenger should be disabled
        //

        if ( g_cmsecObjectCacheTTL == 0xffffffff )
        {
            RegCloseKey( hkey );
            return TRUE;
        }

        //
        //  The registry setting is in seconds, convert to milliseconds
        //

        g_cmsecObjectCacheTTL *= 1000;

        //
        //  Supply the default if no value was specified
        //

        if ( !g_cmsecObjectCacheTTL )
        {
            g_cmsecObjectCacheTTL = INETA_DEF_OBJECT_CACHE_TTL * 1000;
        }

        RegCloseKey( hkey );
    }

    //
    //  Require a minimum of thirty seconds
    //

    g_cmsecObjectCacheTTL = max( g_cmsecObjectCacheTTL,
                                 MIN_CACHE_SCAVENGE_TIME );

    g_dwObjectCacheCookie = ScheduleWorkItem(
                                        CacheScavenger,
                                        NULL,
                                        g_cmsecObjectCacheTTL,
                                        TRUE );     // Periodic

    if ( !g_dwObjectCacheCookie )
    {
        return FALSE;
    }

    return TRUE;
}

VOID
TerminateCacheScavenger(
    VOID
    )
/*++
Routine Description:

    Stops the cache scavenger
Arguments:

    None.

Return Values:

    None.
--*/
{
    if ( g_dwObjectCacheCookie )
    {
        RemoveWorkItem( g_dwObjectCacheCookie );
        g_dwObjectCacheCookie = 0;
    }
}


//
// Blob memory management
//


BOOL
TsAllocate(
    IN const TSVC_CACHE &TSvcCache,
    IN      ULONG           cbSize,
    IN OUT  PVOID *         ppvNewBlock
    )
{
    return( TsAllocateEx(  TSvcCache,
                           cbSize,
                           ppvNewBlock,
                           NULL ) );
}

BOOL
TsAllocateEx(
    IN const TSVC_CACHE &TSvcCache,
    IN      ULONG           cbSize,
    IN OUT  PVOID *         ppvNewBlock,
    OPTIONAL PUSER_FREE_ROUTINE pfnFreeRoutine
    )
/*++

  Routine Description:

      This function allocates a memory block for the calling server.

      The returned block is suitable for use as a parameter to
      TsCacheDirectoryBlob().  Blocks allocated by this function
      must either be cached or freed with TsFree().  Freeing of
      cached blocks will be handled by the cache manager.

      Anything allocated with this routine MUST be derived from
      BLOB_HEADER!

  Arguments:

      TSvcCache      - An initialized TSVC_CACHE structure.

      cbSize         - Number of bytes to allocate.  (Must be strictly
                       greater than zero.)

      ppvNewBlock    - Address of a pointer to store the new block's
                       address in.

      pfnFreeRoutine - pointer to a routine that will be called to
                       clean up the block when it is decached.

  Return Value:

      TRUE  - The allocation succeeded, and *ppvNewBlock points to
              at least cbSize accessable bytes.

      FALSE - The allocation failed.

--*/
{
    CBlobKey *   pBlobKey;
    PBLOB_HEADER pbhNewBlock;

    DBG_ASSERT( cbSize > 0 );
    DBG_ASSERT( ppvNewBlock != NULL );

    //
    // allocate the blob and the key while we're at it.
    //
    pBlobKey = (CBlobKey *) ALLOC(cbSize + sizeof(CBlobKey));

    if ( pBlobKey != NULL )
    {
        //
        //  If the allocation succeeded, we return a pointer to
        //  the new structure which is directly preceded by it's key.
        //

        pbhNewBlock = (PBLOB_HEADER) (pBlobKey + 1);
        *ppvNewBlock = ( PVOID )( pbhNewBlock );

        //
        //  Set up the BLOB_HEADER: Normal flags and stored allocation
        //  size.
        //

        pbhNewBlock->Signature      = TS_BLOB_SIGNATURE;
        pbhNewBlock->pBlobKey       = pBlobKey;

        pbhNewBlock->IsCached       = FALSE;
        pbhNewBlock->pfnFreeRoutine = pfnFreeRoutine;
        pbhNewBlock->lRefCount      = 0;
        pbhNewBlock->TTL            = 1;
        pbhNewBlock->pSecDesc       = NULL;
        pbhNewBlock->hLastSuccessAccessToken = INVALID_HANDLE_VALUE;

        pBlobKey->m_pszPathName = NULL;
        pBlobKey->m_cbPathName  = 0;
        pBlobKey->m_dwService   = TSvcCache.GetServiceId();
        pBlobKey->m_dwInstance  = TSvcCache.GetInstanceId();
        pBlobKey->m_dwDemux     = 0;

        pbhNewBlock->TraceCheckpointEx(TS_MAGIC_ALLOCATE, (PVOID) (ULONG_PTR) cbSize, pfnFreeRoutine);
    }
    else
    {
        //
        //  The allocation failed, and we need to return NULL
        //

        *ppvNewBlock = NULL;
        return FALSE;
    }

return TRUE;

}

BOOL
TsFree(
    IN const TSVC_CACHE &TSvcCache,
    IN      PVOID           pvOldBlock
    )
/*++

Routine Description:

    This function frees a memory block allocated with TsAllocate().

    Blocks that are currently cached cannot be freed with this
    function.

Arguments:

    TSvcCache      - An initialized TSVC_CACHE structure.


    pvOldBlock   - The address of the block to free.  (Must be
                   non-NULL.)

Return Value:

    TRUE  - The block was freed.  The pointer pvOldBlock is no longer
            valid.

    FALSE - The block was not freed.  Possible reasons include:

             -  pvOldBlock does not point to a block allocated with
                TsAllocate().

             -  pvOldBlock points to a block that has been cached
                with CacheDirectoryBlob().

             -  pServiceInfo does not point to a valid SERVICE_INFO
                structure.

--*/
{
    BOOL         bSuccess;
    PBLOB_HEADER pbhOldBlock;
    CBlobKey *   pRealOldBlock;

    DBG_ASSERT( pvOldBlock != NULL );

    //
    //  Adjust the input pointer to refer to the BLOB_HEADER.
    //

    pbhOldBlock = (( PBLOB_HEADER )pvOldBlock );

    DBG_ASSERT( TS_BLOB_SIGNATURE == pbhOldBlock->Signature );

    //
    // Track memory corruption in free builds.
    //

    if ( TS_BLOB_SIGNATURE != pbhOldBlock->Signature ) {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    //
    //  If the Blob is currently in the cache, we can't free it.
    //  Check for this in the Blob's flags, and fail if it
    //  occurs.
    //

    if ( pbhOldBlock->IsCached )
    {
        DBGPRINTF(( DBG_CONTEXT,
                   "A service (%d) has attempted to TsFree a BLOB that it put in the cache.",
                    TSvcCache.GetServiceId() ));
        BREAKPOINT();

        bSuccess = FALSE;
    }
    else
    {
        pbhOldBlock->Signature = TS_FREE_BLOB_SIGNATURE;

        if ( pbhOldBlock->pfnFreeRoutine )
        {
            bSuccess = pbhOldBlock->pfnFreeRoutine( pvOldBlock );
        }
        else
        {
            bSuccess = TRUE;
        }

        if ( bSuccess )
        {
            //
            //  Free the memory used by the Blob.
            //
            pRealOldBlock = ((CBlobKey *) pvOldBlock) - 1;

            DBG_ASSERT( NULL == pRealOldBlock->m_pszPathName );

            pbhOldBlock->TraceCheckpointEx(TS_MAGIC_DELETE_NC,
                                           (PVOID) (ULONG_PTR) (pRealOldBlock->m_dwDemux),
                                           pbhOldBlock->pfnFreeRoutine);

            bSuccess = !!FREE( pRealOldBlock );


/*
            DEC_COUNTER( TSvcCache.GetServiceId(),
                         CurrentObjects );
 */
        }

    }

    return( bSuccess );
} // TsFree


//
// Standard cache operations
//

BOOL
TsCacheDirectoryBlob(
    IN const TSVC_CACHE             &TSvcCache,
    IN      PCSTR                   pszDirectoryName,
    IN      ULONG                   cchDirectoryName,
    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:

    TSvcCache        - An initialized TSVC_CACHE structure.
    pszDirectoryName - The name that will be used as a key in the cache.
    iDemultiplexor   - Identifies the type of the object to be stored
    pvBlob           - Pointer to the actual object to be stored
    bKeepCheckedOut  - If TRUE, the caller can keep a reference to the cached object.
    pSecDesc         - An optional SECURITY_DESCRIPTOR that goes along with the object

Return Values:

    TRUE  - The block successfully added to the cache
    FALSE - The block could not be added to the cache

--*/
{
    BOOL bSuccess;
    PBLOB_HEADER pBlob = (PBLOB_HEADER)pvBlob;
    DBG_ASSERT( TS_BLOB_SIGNATURE == pBlob->Signature );

    //
    // set up the key
    //
    CBlobKey * pbk = pBlob->pBlobKey;
    DBG_ASSERT( NULL != pbk );

    pbk->m_cbPathName = cchDirectoryName;
    pbk->m_pszPathName = (PCHAR) ALLOC(pbk->m_cbPathName + 1);
    if (NULL != pbk->m_pszPathName) {
        memcpy(pbk->m_pszPathName, pszDirectoryName, pbk->m_cbPathName + 1);
    } else {
        pbk->m_cbPathName = 0;
        pbk->m_pszPathName = NULL;
        return FALSE;
    }

    IISstrupr( (PUCHAR)pbk->m_pszPathName );

    pbk->m_dwService = TSvcCache.GetServiceId();
    pbk->m_dwInstance = TSvcCache.GetInstanceId();
    pbk->m_dwDemux = iDemultiplexor;

    //
    // try to cache
    //

    bSuccess = CacheBlob(pBlob);

    if (bSuccess && !bKeepCheckedOut) {
        CheckinBlob(pBlob);
    }

    if (!bSuccess) {
        FREE(pbk->m_pszPathName);
        pbk->m_pszPathName = NULL;
        pbk->m_cbPathName = 0;
    }

    return bSuccess;
} // TsCacheDirectoryBlob


BOOL
TsDeCacheCachedBlob(
    PVOID   pBlobPayload
    )
/*++
Description:

    This function removes a blob payload object from the cache

Arguments:

    pCacheObject - Object to decache

Return Values:

    TRUE on success
--*/
{
    DecacheBlob( (PBLOB_HEADER)pBlobPayload );
    return TRUE;
}


BOOL
TsCheckOutCachedBlob(
    IN const TSVC_CACHE             &TSvcCache,
    IN      PCSTR                   pszDirectoryName,
    IN      ULONG                   cchDirectoryName,
    IN      ULONG                   iDemultiplexor,
    IN      PVOID *                 ppvBlob,
    IN      HANDLE                  ,
    IN      BOOL                    ,
    IN      PSECURITY_DESCRIPTOR*   )
/*++
Routine Description:

    Searches the cache for a named cache entry. If the entry is found,
    it is checked out and returned to the caller.

Arguments:

    TSvcCache            - An initialized TSVC_CACHE structure.
    pszDirectoryName     - The name used as a key in the cache.
    iDemultiplexor       - Identifies the type of the object to be stored
    ppvBlob              - If the entry is found, a pointer to it will be
                           placed here.
    hAccessToken         - Optional parameter used to determine if the
                           caller is allowed to access the cached object.
    fMayCacheAccessToken - If this is TRUE, and the caller succesfully gains
                           access to the cached object, the hAccessToken will
                           be saved with the object in the cache.
    ppSecDesc            - If this is non-NULL, the caller will be given a
                           copy of the objects security descriptor.

Return Values:

    None.
--*/
{
    CHAR achUpName[MAX_PATH+1];
    BOOL bSuccess;

    //  People really do use this.
    //  DBG_ASSERT( ppSecDesc == NULL );

    //
    // Make sure the path is upper case
    //
    IISstrncpy(achUpName, pszDirectoryName, MAX_PATH);
    achUpName[MAX_PATH] = 0;
    cchDirectoryName = min(cchDirectoryName, MAX_PATH);

    IISstrupr( reinterpret_cast<PUCHAR>(achUpName) );

    bSuccess = CheckoutBlob(achUpName,
                            cchDirectoryName,
                            TSvcCache.GetServiceId(),
                            TSvcCache.GetInstanceId(),
                            iDemultiplexor,
                            (PBLOB_HEADER *) ppvBlob);

    if (bSuccess) {
        //
        // Security handled by the caller
        //
        ((PBLOB_HEADER)*ppvBlob)->TTL = 1;
        return TRUE;
    } else {
        return FALSE;
    }
}

BOOL
TsCheckInCachedBlob(
    IN      PVOID           pvBlob
    )
/*++
Routine Description:

    When a client is done with a blob it must check it back into the cache.

Arguments:

    pvBlob - The object to be checked in

Return Values:

    TRUE for success
--*/
{
    CheckinBlob((PBLOB_HEADER) pvBlob);

    return( TRUE );
} // TsCheckInCachedBlob


BOOL
TsCheckInOrFree(
    IN      PVOID           pvOldBlock
    )
/*++

Routine Description:

    This function checks in a cached memory block or
    frees a non-cached memory block allocated with TsAllocate().

Arguments:

    pvOldBlock   - The address of the block to free.  (Must be
                   non-NULL.)

Return Value:

    TRUE  - The block was freed.  The pointer pvOldBlock is no longer
            valid.

    FALSE - The block was not freed.  Possible reasons include:

             -  pvOldBlock does not point to a block allocated with
                TsAllocate().

--*/
{
    PBLOB_HEADER pBlob = (PBLOB_HEADER) pvOldBlock;
    TSVC_CACHE dummy;

    if (pBlob->IsCached) {
        CheckinBlob(pBlob);
    } else {
        TsFree(dummy, (PVOID)pBlob);
    }
    return( TRUE );
} // TsCheckInOrFree



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.

--*/
{
    if (RESERVED_DEMUX_OPEN_FILE == iDemux) {
        FlushFileCache();
    } else {
        //
        // Only place where this function is called from is from odbc with
        // a demux of RESERVED_DEMUX_QUERY_CACHE.  We do not need to worry
        // about other cases
        //
        FlushBlobCache();
    }

    return TRUE;
} // TsCacheFlushDemux


BOOL
FlushFilterService(
    PBLOB_HEADER pBlob,
    PVOID        pv
    )
{
    DWORD dwServerMask = * (DWORD *)pv;
    return (pBlob->pBlobKey->m_dwService == dwServerMask);
}



BOOL
TsCacheFlush(
    IN  DWORD       dwServerMask
    )
/*++

  Routine Description:

    This function flushes the blob cache of all items for the specified service
    or for all services if dwServerMask is zero.

--*/
{
    if (dwServerMask) {
        FilteredFlushBlobCache(FlushFilterService, &dwServerMask);
    } else {
        FlushBlobCache();
    }

    return TRUE;
} // TsCacheFlush


BOOL
FlushFilterUser(
    TS_OPEN_FILE_INFO *pOpenFile,
    PVOID              pv
    )
{
    HANDLE hUser = * (HANDLE *)pv;
    return (pOpenFile->QueryUser() == hUser);
}


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)

--*/
{
    FilteredFlushFileCache(FlushFilterUser, &hUserToken);

    return TRUE;
} // TsCacheFlushUser


typedef struct _FLUSH_URL_PARAM {
    PCSTR pszURL;
    DWORD cbURL;
    DWORD dwService;
    DWORD dwInstance;
} FLUSH_URL_PARAM;


BOOL
FlushFilterURL(
    PBLOB_HEADER pBlob,
    PVOID        pv
    )
{
    DBG_ASSERT( pBlob );
    DBG_ASSERT( pBlob->pBlobKey );

    FLUSH_URL_PARAM * fup = (FLUSH_URL_PARAM *)pv;
    CBlobKey * pbk = pBlob->pBlobKey;
    BOOL bAtRoot;

    //
    // If we're flushing everything, then don't bother
    // with the string comparison
    //
    bAtRoot = (fup->cbURL == 1) && (fup->pszURL[0] == '/');

    //
    // If the service, instance, and URL prefixes match then we flush.
    //
    return ( (pbk->m_dwService == fup->dwService)
             && (pbk->m_dwInstance == fup->dwInstance)
             && (bAtRoot
                || ((pbk->m_cbPathName >= fup->cbURL)
                   && (memcmp(pbk->m_pszPathName, fup->pszURL, fup->cbURL) == 0))) );
}


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

--*/
{
    FLUSH_URL_PARAM fuparam;
    CHAR achUpName[MAX_PATH+1];

    //
    // It really only makes sense to flush the URI cache
    // with this function.
    //
    DBG_ASSERT( RESERVED_DEMUX_URI_INFO == iDemultiplexor );

    DBG_ASSERT( MAX_PATH >= dwURLLength );

    //
    // Make sure the path is upper case
    //
    IISstrncpy(achUpName, pszURL, MAX_PATH);
    achUpName[dwURLLength] = 0;

    IISstrupr( (PUCHAR) achUpName );

    fuparam.pszURL     = achUpName;
    fuparam.cbURL      = dwURLLength;
    fuparam.dwService  = TSvcCache.GetServiceId();
    fuparam.dwInstance = TSvcCache.GetInstanceId();

    FilteredFlushURIBlobCache(FlushFilterURL, &fuparam);
}


BOOL
TsExpireCachedBlob(
    IN const TSVC_CACHE &TSvcCache,
    IN      PVOID           pvBlob
    )
{
    DecacheBlob((PBLOB_HEADER) pvBlob);

    return TRUE;
} // TsExpireCachedBlob

//
// Misc cache management
//


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 ( dwServerMask > LAST_PERF_CTR_SVC )
    {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    if ( g_pFileCacheStats
         && g_pURICacheStats
         && g_pBlobCacheStats
         && (dwServerMask == 0) ) {

        pCacheCtrs->FilesCached        = g_pFileCacheStats->GetFilesCached();
        pCacheCtrs->TotalFilesCached   = g_pFileCacheStats->GetTotalFilesCached();
        pCacheCtrs->FileHits           = g_pFileCacheStats->GetHits();
        pCacheCtrs->FileMisses         = g_pFileCacheStats->GetMisses();
        pCacheCtrs->FileFlushes        = g_pFileCacheStats->GetFlushes();
        pCacheCtrs->FlushedEntries     = g_pFileCacheStats->GetFlushedEntries();
        pCacheCtrs->TotalFlushed       = g_pFileCacheStats->GetTotalFlushed();

        pCacheCtrs->URICached          = g_pURICacheStats->GetBlobsCached();
        pCacheCtrs->TotalURICached     = g_pURICacheStats->GetTotalBlobsCached();
        pCacheCtrs->URIHits            = g_pURICacheStats->GetHits();
        pCacheCtrs->URIMisses          = g_pURICacheStats->GetMisses();
        pCacheCtrs->URIFlushes         = g_pURICacheStats->GetFlushes();
        pCacheCtrs->TotalURIFlushed    = g_pURICacheStats->GetTotalFlushed();

        pCacheCtrs->BlobCached         = g_pBlobCacheStats->GetBlobsCached();
        pCacheCtrs->TotalBlobCached    = g_pBlobCacheStats->GetTotalBlobsCached();
        pCacheCtrs->BlobHits           = g_pBlobCacheStats->GetHits();
        pCacheCtrs->BlobMisses         = g_pBlobCacheStats->GetMisses();
        pCacheCtrs->BlobFlushes        = g_pBlobCacheStats->GetFlushes();
        pCacheCtrs->TotalBlobFlushed   = g_pBlobCacheStats->GetTotalFlushed();

        QueryMemoryCacheStatistics( pCacheCtrs, FALSE ); 

    } else {
        //
        // Either we're reporting for a specific service
        // or stats are not set up. Set all cache
        // counters to zero.
        //
        pCacheCtrs->FilesCached = 0;
        pCacheCtrs->TotalFilesCached = 0;
        pCacheCtrs->FileHits = 0;
        pCacheCtrs->FileMisses = 0;
        pCacheCtrs->FileFlushes = 0;
        pCacheCtrs->FlushedEntries = 0;
        pCacheCtrs->TotalFlushed = 0;

        pCacheCtrs->URICached = 0;
        pCacheCtrs->TotalURICached = 0;
        pCacheCtrs->URIHits = 0;
        pCacheCtrs->URIMisses = 0;
        pCacheCtrs->URIFlushes = 0;
        pCacheCtrs->TotalURIFlushed = 0;

        pCacheCtrs->BlobCached = 0;
        pCacheCtrs->TotalBlobCached = 0;
        pCacheCtrs->BlobHits = 0;
        pCacheCtrs->BlobMisses = 0;
        pCacheCtrs->BlobFlushes = 0;
        pCacheCtrs->TotalBlobFlushed = 0;
        
        QueryMemoryCacheStatistics( pCacheCtrs, TRUE );
    }

    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



const char * g_IISAuxCounterNames[] =
{
    "Aac Open URI Files",
    "Cac Calls to TsOpenURI()",
    "Cac Calls to TsCloseURI()",
    "Max Counters"
};



extern "C"
VOID
TsDumpCacheCounters( OUT CHAR * pchBuffer, IN OUT LPDWORD lpcbBuffer )
{
    DWORD  cb = 0;

    *lpcbBuffer = cb;
    return ;
} // TsDumpCacheCounters()






VOID
TsDumpHashTableStats( IN OUT CHAR * pchBuffer, IN OUT LPDWORD lpcbBuffer )
{
    CLKRHashTableStats hts;

    if (!g_pFileInfoTable) {
        *lpcbBuffer = 0;
        return;
    }

    hts = g_pFileInfoTable->GetStatistics();

    *lpcbBuffer = sprintf( pchBuffer,
                           "<TABLE>"
                           "<TR><TD>Record Count</TD><TD>%d</TD></TR>"
                           "<TR><TD>Table Size</TD><TD>%d</TD></TR>"
                           "<TR><TD>Directory Size</TD><TD>%d</TD></TR>"
                           "<TR><TD>Longest Chain</TD><TD>%d</TD></TR>"
                           "<TR><TD>Empty Slots</TD><TD>%d</TD></TR>"
                           "<TR><TD>Split Factor</TD><TD>%f</TD></TR>"
                           "<TR><TD>Average Search Length</TD><TD>%f</TD></TR>"
                           "<TR><TD>Expected Search Length</TD><TD>%f</TD></TR>"
                           "<TR><TD>Average Unsuccessful Search Length</TD><TD>%f</TD></TR>"
                           "<TR><TD>Expected Unsuccessful Search Length</TD><TD>%f</TD></TR>"
                           "</TABLE>",
                           hts.RecordCount,
                           hts.TableSize,
                           hts.DirectorySize,
                           hts.LongestChain,
                           hts.EmptySlots,
                           hts.SplitFactor,
                           hts.AvgSearchLength,
                           hts.ExpSearchLength,
                           hts.AvgUSearchLength,
                           hts.ExpUSearchLength );

}

VOID
TsDumpCacheToHtml( OUT CHAR * pchBuffer, IN OUT LPDWORD lpcbBuffer )
{
    LIST_ENTRY * pEntry;
    DWORD        cItemsOnBin = 0;
    DWORD        cTotalItems = 0;
    DWORD        i, c, cb;
    DWORD        cbTable;

    cb = wsprintf( pchBuffer,
                   " <h4>File Hash Table Stats</h4> " );

    TsDumpHashTableStats( pchBuffer + cb, &cbTable );
    cb += cbTable;

    cb += wsprintf( pchBuffer + cb,
                    " <h4>Some other stats</h4> ");

    if (g_pFileCacheStats) {
        g_pFileCacheStats->DumpToHtml(pchBuffer + cb, &cbTable);
        cb += cbTable;
    }
    
    DumpMemoryCacheToHtml( pchBuffer + cb, &cbTable );
    cb += cbTable;
    
    *lpcbBuffer = cb;

    return;
}  // TsDumpCacheToHtml()


//
// tsunami.cxx
//