#include "stdinc.h"
/*-----------------------------------------------------------------------------
Fusion Thread Local Storage (aka Per Thread Data)
-----------------------------------------------------------------------------*/
#include "ntrtl.h"
#include "fusiontls.h"
#include "FusionDequeLinkage.h"
#include "FusionDeque.h"
#include "FusionEventLog.h"
#include "FusionHeap.h"
#include "SxsExceptionHandling.h"

//
// Don't use the FusionTrace functionality in this file.
//
// #include "fusiontracelight.h"
//

static DWORD s_dwFusionpThreadLocalIndex = TLS_OUT_OF_INDEXES;

// typedef CDeque<CFusionPerThreadData, offsetof( CFusionPerThreadData, m_Linkage )> CFusionTlsList;

static CRITICAL_SECTION s_TlsCriticalSection;
static LIST_ENTRY * g_FusionTlsList = NULL;
static __int64 storage_g_FusionTlsList[sizeof(LIST_ENTRY)/sizeof(__int64) + 1];

template <typename T>
T* PlacementNew(T* p)
{
    (void) (new (reinterpret_cast<PVOID>(p)) T);
    return p;
}

BOOL
FusionpPerThreadDataMain(
    HINSTANCE hInst,
    DWORD dwReason
    )
{
    BOOL fResult = FALSE;

/*
    INTERNAL_ERROR_CHECK(
        ( dwReason != DLL_THREAD_ATTACH ) &&
        ( dwReason != DLL_THREAD_DETACH )
    );
*/

    switch ( dwReason )
    {
    case DLL_PROCESS_ATTACH:

        __try
        {
            if ( !::InitializeCriticalSectionAndSpinCount(
                &s_TlsCriticalSection,
                INITIALIZE_CRITICAL_SECTION_AND_SPIN_COUNT_ALLOCATE_NOW
            ) )
            {
                ::FusionpDbgPrintEx(
                    FUSION_DBG_LEVEL_ERROR,
                    "SXS: %s - Failed creating TLS critical section, LastError=%08x\n",
                    __FUNCTION__,
                    ::FusionpGetLastWin32Error());
                goto Exit;
            }
        }
        __except( EXCEPTION_EXECUTE_HANDLER )
        {
            //
            // The exception code is interesting.  However, we don't have access
            // (directly) to SxspSetLastNTError yet - this is a hotfix.
            //
            // REVIEW: Move SxspSetLastNTError into one of the fusion\utils\*.cpp files
            // because it seems like a relatively good idea.
            //
            ::SetLastError(::FusionpNtStatusToDosError(GetExceptionCode()));
            goto Exit;
        }

        s_dwFusionpThreadLocalIndex = TlsAlloc();
        if ( s_dwFusionpThreadLocalIndex == TLS_OUT_OF_INDEXES )
        {
            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_ERROR,
                "SXS.DLL: %s(): TlsAlloc failed: last error %d\n", __FUNCTION__, FusionpGetLastWin32Error());
            goto Exit;
        }

        g_FusionTlsList = PlacementNew(reinterpret_cast<LIST_ENTRY *>(&storage_g_FusionTlsList));
        if ( g_FusionTlsList == NULL )
        {
            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_ERROR,
                "SXS.DLL: %s(): Really bad things: placement new of CFusionTlsList failed, 0x%08x\n",
                __FUNCTION__,
                ::FusionpGetLastWin32Error());
            ::SetLastError(ERROR_INTERNAL_ERROR);
            goto Exit;
        }

        InitializeListHead(g_FusionTlsList);

        break;

    case DLL_PROCESS_DETACH:

        if ( s_dwFusionpThreadLocalIndex != TLS_OUT_OF_INDEXES )
        {
            ::EnterCriticalSection( &s_TlsCriticalSection );
            __try
            {
                LIST_ENTRY *ple = g_FusionTlsList->Flink;

                while (ple != g_FusionTlsList)
                {
                    CFusionPerThreadData *pptd = CONTAINING_RECORD(ple, CFusionPerThreadData, m_Linkage);
                    ple = ple->Flink;
                    FUSION_DELETE_SINGLETON(pptd);
                }
            }
            __finally
            {
                ::LeaveCriticalSection( &s_TlsCriticalSection );
            }
            ::TlsFree( s_dwFusionpThreadLocalIndex );
            s_dwFusionpThreadLocalIndex = TLS_OUT_OF_INDEXES;
        }

        //
        // Re-entrance intelligence
        //
        if (g_FusionTlsList != NULL)
        {
            InitializeListHead(g_FusionTlsList);
            g_FusionTlsList = NULL;
        }

        ::DeleteCriticalSection( &s_TlsCriticalSection );

        break;
    }

    fResult = TRUE;

Exit:
    return fResult;
}



VOID
FusionpClearPerThreadData(
    VOID
    )
{
    CFusionPerThreadData *pPerThread;
    PVOID pvPerThread;

    ASSERT_NTC(s_dwFusionpThreadLocalIndex != TLS_OUT_OF_INDEXES);

    //
    // Acquire, then clear, this thread's per-thread data
    //
    pvPerThread = ::TlsGetValue(s_dwFusionpThreadLocalIndex);

    //
    // If the TlsSetValue failed with STATUS_NO_MEMORY, that's just dandy.
    // Otherwise, something wierd has happened along the line, and maybe
    // people care to know.
    //
    if ( !::TlsSetValue( s_dwFusionpThreadLocalIndex, NULL ) )
    {
        SOFT_ASSERT_NTC(::FusionpGetLastWin32Error() != ERROR_NOT_ENOUGH_MEMORY);
    }

    //
    // If we got something interesting back from TlsGetValue, then cast it
    // to the Right Thing and delete it.
    //
    pPerThread = reinterpret_cast<CFusionPerThreadData*>(pvPerThread);
    if (pPerThread != NULL)
        FUSION_DELETE_SINGLETON(pPerThread);
}

CFusionPerThreadData*
FusionpGetPerThreadData(
    EFusionpTls e
    )
{
    const DWORD dwLastError = ::FusionpGetLastWin32Error();
    CFusionPerThreadData* pTls = NULL;

    __try
    {

        // the use of "temp" here mimics what you would do with a destructor;
        // have a temp that is unconditionally freed, unless it is nulled by commiting it
        // into the return value "return pt.Detach();"
        CFusionPerThreadData* pTlsTemp = reinterpret_cast<CFusionPerThreadData*>(::TlsGetValue(s_dwFusionpThreadLocalIndex));
        if ((pTlsTemp == NULL) && ((e & eFusionpTlsCreate) != 0))
        {
            if (::FusionpGetLastWin32Error() != NO_ERROR)
            {
                ::FusionpDbgPrintEx(
                    FUSION_DBG_LEVEL_ERROR,
                    "SXS.DLL: %s() called TlsGetValue() which failed; win32 error = %d\n", __FUNCTION__, ::FusionpGetLastWin32Error());

                FUSION_DEBUG_BREAK();

                return NULL;
            }

            pTlsTemp = reinterpret_cast<CFusionPerThreadData*>(FUSION_RAW_ALLOC(sizeof(*pTlsTemp), 'tsxs'));
            if (pTlsTemp == NULL)
            {
                ::FusionpDbgPrintEx(
                    FUSION_DBG_LEVEL_ERROR,
                    "SXS.DLL: TLS allocation failed in %s()\n", __FUNCTION__);

                return NULL;
            }

            if (!::TlsSetValue(s_dwFusionpThreadLocalIndex, pTlsTemp))
            {
                ::FusionpDbgPrintEx(
                    FUSION_DBG_LEVEL_ERROR,
                    "SXS.DLL: TlsSetValue failed in %s(), lastError: %d\n", __FUNCTION__, FusionpGetLastWin32Error());

                FUSION_RAW_DEALLOC(pTlsTemp);

                return NULL;
            }

            ::EnterCriticalSection( &s_TlsCriticalSection );
            __try
            {
                InsertTailList(g_FusionTlsList, &pTlsTemp->m_Linkage);
            }
            __finally
            {
                ::LeaveCriticalSection( &s_TlsCriticalSection );
            }
        }

        pTls = pTlsTemp;
        pTlsTemp = NULL;
    } __finally {
        ::SetLastError(dwLastError);
    }

    return pTls;
}

CFusionPerThreadData *
FusionpSetPerThreadData(
    CFusionPerThreadData *pNewTls,
    EFusionSetTls Action
    )
{
    CSxsPreserveLastError ple;
    CFusionPerThreadData *pExisting = NULL;

    if (s_dwFusionpThreadLocalIndex == TLS_OUT_OF_INDEXES)
    {
        ::FusionpDbgPrintEx(
            FUSION_DBG_LEVEL_ERROR,
            "SXS.DLL: Attempt to set per thread data when TLS index was not allocated.");

        FUSION_DEBUG_BREAK();
        ple.Restore();
        return NULL;
    }

    if ( ( Action != eFusionpTlsSetReplaceExisting ) &&
         ( Action != eFusionpTlsSetIfNull ) &&
         ( Action != 0 ) )
    {
        ::FusionpDbgPrintEx(
            FUSION_DBG_LEVEL_ERROR,
            "SXS.DLL: Invalid action parameter passed to %s\n", __FUNCTION__);

        FUSION_DEBUG_BREAK();
        ple.Restore();
        return NULL;
    }
    else if (Action == 0)
    {
        Action = eFusionpTlsSetIfNull;
    }

    pExisting = ::FusionpGetPerThreadData();

    //
    // If the existing one is null, and we're supposed to set it
    // if it's null, then set it.  Or, if we're suppose to set it
    // anyhow, then that's fine.
    //
    if ((Action == eFusionpTlsSetReplaceExisting) ||
        ((Action == eFusionpTlsSetIfNull) &&
         (pExisting == NULL)))
    {
        if ( !::TlsSetValue(s_dwFusionpThreadLocalIndex, pNewTls))
        {
            ple.Restore();
            return NULL;
        }

        pExisting = pNewTls;
    }

    ple.Restore();

    return pExisting;
}