#include "TsunamiP.Hxx"
#pragma hdrstop


BOOL
TsAllocate(
    IN const TSVC_CACHE &TSvcCache,
    IN      ULONG           cbSize,
    IN OUT  PVOID *         ppvNewBlock
    )
{
    return( TsAllocateEx(  TSvcCache,
                           cbSize,
                           ppvNewBlock,
                           NULL ) );
} // TsAllocate

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.

  Arguments:

      pServiceInfo - An initialized SERVICE_INFO 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.

  Return Value:

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

      FALSE - The allocation failed.

--*/
{
    PBLOB_HEADER pbhNewBlock;

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

    //
    //  Set pbhNewBlock to NULL so that the exception-cleanup code
    //  can test against it to see if an allocation occurred before
    //  the exception.
    //

    pbhNewBlock = NULL;

    __try
    {
        //
        //  If asked to allocate zero bytes, we return FALSE and NULL,
        //  as if allocation failure had occurred.
        //

        if ( cbSize != 0 )
        {
            pbhNewBlock = ( PBLOB_HEADER )
                      ALLOC( cbSize + sizeof( BLOB_HEADER ) );

        }

        if ( pbhNewBlock != NULL )
        {
            //
            //  If the allocation succeeded, we return a pointer to the
            //  memory just following the BLOB_HEADER.
            //

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

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

            pbhNewBlock->IsCached        = FALSE;
            pbhNewBlock->pfnFreeRoutine  = pfnFreeRoutine;
            InitializeListHead( &pbhNewBlock->PFList );
        }
        else
        {
            //
            //  The allocation failed, and we need to return NULL
            //

            *ppvNewBlock = NULL;
        }
    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
        //
        //  An exception transpired.  The most likely causes are bogus input
        //  pointers for pServiceInfo and pbhNewBlock.  Whatever the case, we
        //  free up any memory that may have been allocated and return failure.
        //

        if ( pbhNewBlock != NULL )
        {

            FREE( pbhNewBlock );

            pbhNewBlock = NULL;
        }
    }

    //
    //  Return TRUE or FALSE, according to the result of the allocation.
    //

    if ( pbhNewBlock == NULL )
    {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY);
        return( FALSE );
    }

    INC_COUNTER( TSvcCache.GetServiceId(),
                 CurrentObjects );

    return( TRUE );
} // TsAllocate

BOOL
TsReallocate(
    IN const TSVC_CACHE &TSvcCache,
    IN      ULONG           cbSize,
    IN      PVOID           pvOldBlock,
    IN OUT  PVOID *         ppvNewBlock
    )
/*++

  Routine Description:

    This function will resize a previously allocated memory Blob
    for the calling server, possibly moving it in the process.

  Arguments:

      pServiceInfo - An initialized SERVICE_INFO structure.

      cbSize       - Number of bytes to resize the block to.
                     (Must be strictly greater than zero.)

      pvOldBlock   - Address of a pointer to a previously-allocated
                     block.

      ppvNewBlock  - Address of a pointer to store the new block's
                     address in.  If the allocation fails, NULL is
                     stored here.  Note that in many cases,
                     pvOldBlock will be stored here.

  Return Value:

      TRUE  - The reallocation succeeded, and *ppvNewBlock points to
              at least cbSize accessable bytes.  pvOldBlock is no
              longer a valid pointer, if *ppvNewBlock!=pvOldBlock.

      FALSE - The allocation failed.  *ppvNewBlock = NULL.
              pvOldBlock is still a valid pointer to the block that
              we wished to resize.

--*/
{
    PBLOB_HEADER pbhNewBlock;
    PBLOB_HEADER pbhOldBlock;

    ASSERT( pvOldBlock != NULL );

    //
    //  Set pbhNewBlock to NULL so that the exception-cleanup code
    //  can test against it to see if an allocation occurred before
    //  the exception.
    //

    pbhNewBlock = NULL;

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

        pbhOldBlock = (( PBLOB_HEADER )pvOldBlock ) - 1;

        //
        //  If the Blob is currently cached, we can't move it
        //  or change its size.  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 TsRealloc a BLOB that is cached.",
                        TSvcCache.GetServiceId() ));
            BREAKPOINT();
            SetLastError( ERROR_INVALID_PARAMETER );
        }
        else
        {
            //
            //  The following assignment probes ppvNewBlock for writeability.
            //  Hopefully, this ensures that we get an AV from writing to it
            //  before we call REALLOC and potentially free the old block.
            //

            *ppvNewBlock = NULL;

            pbhNewBlock = ( PBLOB_HEADER )REALLOC( pbhOldBlock, cbSize );

            if ( pbhNewBlock != NULL )
            {
                //
                //  Store a pointer to the caller-usable part of the new Blob in
                //  the output parameter.
                //

                *ppvNewBlock = ( PVOID )( pbhNewBlock + 1 );
            }
        }
    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
        //
        //  An exception occured.  If this was caught after a block was
        //  allocated, we must free the new block.  Unfortunately, this
        //  means that we may end up returning FALSE in a case where
        //  pbhOldBlock is no longer valid.
        //
        //  if ( pbhOldBlock == pbhNewBlock ), which implies the old
        //  pointer is still valid, we do not free the new block, but
        //  hope that the caller will.  In any other case, we must
        //  free the new block to avoid a memory leak, and assume that
        //  TCPSVCs are going down soon...
        //
        //  ISSUE: It might be best to reflect the exception up to the
        //  caller, so they can handle it and bail out of the current
        //  operation.
        //

        if ( pbhNewBlock != NULL && pbhOldBlock != pbhNewBlock )
        {
            FREE( pbhNewBlock );

            pbhNewBlock = NULL;

            SetLastError( ERROR_INVALID_PARAMETER );
        }
    }

    //
    //  Return TRUE or FALSE, according to the result of the allocation.
    //

    if ( pbhNewBlock == NULL )
    {
        return( FALSE );
    }

    return( TRUE );
} // TsReallocate

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:

    pServiceInfo - An initialized SERVICE_INFO 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.

--*/
{
    BOOLEAN bSuccess;
    PBLOB_HEADER pbhOldBlock;

    ASSERT( pvOldBlock != NULL );

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

        pbhOldBlock = (( PBLOB_HEADER )pvOldBlock ) - 1;

        if (!DisableSPUD) {
            EnterCriticalSection( &CacheTable.CriticalSection );
            if ( !IsListEmpty( &pbhOldBlock->PFList ) ) {
                RemoveEntryList( &pbhOldBlock->PFList );
            }
            LeaveCriticalSection( &CacheTable.CriticalSection );
        }

        //
        //  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
        {
            if ( pbhOldBlock->pfnFreeRoutine )
            {
                bSuccess = pbhOldBlock->pfnFreeRoutine( pvOldBlock );
            }
            else
            {
                bSuccess = TRUE;
            }

            if ( bSuccess )
            {
                //
                //  Free the memory used by the Blob.
                //

                bSuccess = !!FREE( pbhOldBlock );

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

        }

    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
        //
        //  Handle exception.  Obviously, it's time to return failure.
        //  It's hardly possible to get here after succefully freeing
        //  the block, so it's likely that an app will either:
        //     - not check the return value and leak some memory.
        //     - check the return value and try again, probably
        //       only to fail forever.
        //
        //  So, it is not advisable for callers to keep trying this
        //  call until it succeeds.
        //

        bSuccess = FALSE;
    }

    return( bSuccess );
} // TsFree


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:

    pServiceInfo - An initialized SERVICE_INFO 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().

--*/
{
    BOOLEAN bSuccess;
    PBLOB_HEADER pbhOldBlock;

    ASSERT( pvOldBlock != NULL );

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

        pbhOldBlock = (( PBLOB_HEADER )pvOldBlock ) - 1;

        if (!DisableSPUD) {
            EnterCriticalSection( &CacheTable.CriticalSection );
            if (!IsListEmpty( &pbhOldBlock->PFList ) ) {
                RemoveEntryList( &pbhOldBlock->PFList );
            }
            LeaveCriticalSection( &CacheTable.CriticalSection );
        }

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

            if ( bSuccess )
            {
                //
                //  Free the memory used by the Blob.
                //

                bSuccess = !!FREE( pbhOldBlock );
            }
        }

    }
    __except( EXCEPTION_EXECUTE_HANDLER )
    {
        //
        //  Handle exception.  Obviously, it's time to return failure.
        //  It's hardly possible to get here after succefully freeing
        //  the block, so it's likely that an app will either:
        //     - not check the return value and leak some memory.
        //     - check the return value and try again, probably
        //       only to fail forever.
        //
        //  So, it is not advisable for callers to keep trying this
        //  call until it succeeds.
        //

        ASSERT(FALSE);
        bSuccess = FALSE;
    }

    return( bSuccess );
} // TsCheckInOrFree


