/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
/*****************************************************************************
*
*   $/Plasma20/Sources/Plasma/NucleusLib/pnCrashExe/pnCreError.cpp
*   
***/

#include "../Pch.h"
#pragma hdrstop


namespace Crash {
/*****************************************************************************
*
*   Private
*
***/

struct Module {
    LINK(Module)    link;
    
    unsigned_ptr    address;
    unsigned        buildId;
    unsigned        branchId;
    wchar *         name;
    wchar *         buildString;
    
    ~Module () { FREE(name); FREE(buildString); }
};

struct EmailParams : AtomicRef {
    char *          smtp;
    char *          sender;
    char *          recipients;
    char *          username;
    char *          password;
    char *          replyTo;
    
    ~EmailParams () {
        FREE(smtp);
        FREE(sender);
        FREE(recipients);
        FREE(username);
        FREE(password);
        FREE(replyTo);
    }
};

struct ErrLog {
    unsigned    pos;
    wchar       name[MAX_PATH];
    char        buffer[512*1024];
    char        terminator;
};

struct DeadlockCheck {
    LINK(DeadlockCheck) link;
    HANDLE              thread;
    unsigned            deadlockEmailMs;
    unsigned            deadlockTerminateMs;
    bool                deadlocked;
    bool                emailSent;
    wchar               debugStr[256];
};


/*****************************************************************************
*
*   Private data
*
***/

// Assertion results from ProcessErrorLog()
const unsigned kErrFlagAssertionBreakpoint  = 0x01;
const unsigned kErrFlagAssertionExitProgram = 0x02;

// Exception results from ProcessErrorLog()
const unsigned kErrFlagExceptionBreakpoint  = 0x04;
const unsigned kErrFlagExceptionExecHandler = 0x08;

static const char   s_unknown[]         = "*unknown*";
static const char   s_sectionFmt_s[]    = "--------> %s <--------\r\n";

static const unsigned kStackDumpBytes   = 1024;
static const unsigned kCodeDumpBytes    = 64;

static unsigned                         s_deadlockEmailMs;      // sends an email if a thread exceeds this time. If set to zero this is disabled        
static unsigned                         s_deadlockTerminateMs;  // kills the process if a thread exceeds this time. If set to zero this is disabled
static CCritSect *                      s_critsect;
static LISTDECL(Module, link)           s_modules;
static EmailParams *                    s_params;
static bool                             s_running;

static bool                             s_deadlockEnabled;
static unsigned                         s_nextDeadlockCheck;
static LISTDECL(DeadlockCheck, link)    s_deadlockList;

static CCritSect                        s_spareCrit;
static TSpareList<DeadlockCheck>        s_deadlockSpares;


#define SAFE_CRITSECT_ENTER()   if (s_critsect) s_critsect->Enter()
#define SAFE_CRITSECT_LEAVE()   if (s_critsect) s_critsect->Leave()


/*****************************************************************************
*
*   Internal functions
*
***/

//============================================================================
static void DelayDeadlockChecking () {
    // Delay deadlock checking for the next 2 minutes
    s_nextDeadlockCheck = 1 | (TimeGetMs() + 2 * 60 * 1000);
}

//============================================================================
static void ReplaceEmailParams (EmailParams * newParams) {

    if (newParams)
        newParams->IncRef();    
    
    SAFE_CRITSECT_ENTER();
    {
        SWAP(newParams, s_params);
    }
    SAFE_CRITSECT_LEAVE();
    
    if (newParams)
        newParams->DecRef();
}

//============================================================================
static EmailParams * GetEmailParamsIncRef () {
    EmailParams * params;
    
    SAFE_CRITSECT_ENTER();
    {
        if (nil != (params = s_params))
            params->IncRef();
    }
    SAFE_CRITSECT_LEAVE();
    
    return params;
};

//============================================================================
#ifdef _M_IX86
static void __declspec(naked) CrashFunc () {

    *(int *) 0 = 0;
}
#endif // def _M_IX86

//============================================================================
#ifdef _M_IX86
static void MakeThreadCrashOnResume (HANDLE handle) {
    
    // Point the thread's instruction pointer to CrashFunc 
    // so that it will crash once we resume it.
    CONTEXT context;
    context.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(handle, &context);
    context.Eip = (DWORD) CrashFunc;
    SetThreadContext(handle, &context);
}
#endif // def _M_IX86

//============================================================================
static inline const char * GetExceptionString (unsigned code) {
    #if defined(HS_DEBUGGING) || defined(SERVER)
    switch (code) {
        #define EXCEPTION(x) case EXCEPTION_##x: return #x;
        EXCEPTION(ACCESS_VIOLATION)
        EXCEPTION(DATATYPE_MISALIGNMENT)
        EXCEPTION(BREAKPOINT)
        EXCEPTION(SINGLE_STEP)
        EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
        EXCEPTION(FLT_DENORMAL_OPERAND)
        EXCEPTION(FLT_DIVIDE_BY_ZERO)
        EXCEPTION(FLT_INEXACT_RESULT)
        EXCEPTION(FLT_INVALID_OPERATION)
        EXCEPTION(FLT_OVERFLOW)
        EXCEPTION(FLT_STACK_CHECK)
        EXCEPTION(FLT_UNDERFLOW)
        EXCEPTION(INT_DIVIDE_BY_ZERO)
        EXCEPTION(INT_OVERFLOW)
        EXCEPTION(PRIV_INSTRUCTION)
        EXCEPTION(IN_PAGE_ERROR)
        EXCEPTION(ILLEGAL_INSTRUCTION)
        EXCEPTION(NONCONTINUABLE_EXCEPTION)
        EXCEPTION(STACK_OVERFLOW)
        EXCEPTION(INVALID_DISPOSITION)
        EXCEPTION(GUARD_PAGE)
        EXCEPTION(INVALID_HANDLE)
        #undef EXCEPTION

        default:
        return s_unknown;
    }
    #else   // if defined(HS_DEBUGGING) || defined(SERVER)
    {
        ref(code);
        return "";
    }
    #endif  // if defined(HS_DEBUGGING) || defined(SERVER)
}

//============================================================================
static void LogWriteToDisk (ErrLog * const log) {

    HANDLE file = CreateFileW(
        log->name,
        GENERIC_WRITE,
        FILE_SHARE_READ,
        (LPSECURITY_ATTRIBUTES) nil,
        OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        (HANDLE) nil
    );
    if (file != INVALID_HANDLE_VALUE) {
        DWORD bytesWritten;
        SetFilePointer(file, 0, 0, FILE_END);
        WriteFile(file, log->buffer, StrLen(log->buffer), &bytesWritten, 0);
        CloseHandle(file);
    }
}

//============================================================================
static unsigned ProcessErrorLog (
    ErrLog * const  log,
    const char      programName[],
    const char      errorType[]
) {
    
    LogWriteToDisk(log);
    
    // Servers email the error and continue running 
    #ifdef SERVER
    {
        // @@@ TODO: Write log to file here
        
        if (EmailParams * params = GetEmailParamsIncRef()) {
            CrashSendEmail(
                params->smtp,
                params->sender,
                params->recipients,
                params->username,
                params->password,
                params->replyTo,
                programName,
                errorType,
                log->buffer
            );
            params->DecRef();
        }
        return kErrFlagAssertionBreakpoint | kErrFlagExceptionExecHandler;
    }
    
    // Client programs display an error dialog giving the user a choice of
    // sending the error or not
    #else
    {
        // Todo: make a dialog box to handle this
        return kErrFlagAssertionExitProgram | kErrFlagExceptionExecHandler;
    }
    #endif
}

//============================================================================
static unsigned ProcessBreakException () {

    #ifdef SERVER

        // Servers running as daemons should attempt to keep running
        if (ErrorGetOption(kErrOptNonGuiAsserts))
            return kErrFlagExceptionExecHandler;
        else
            return kErrFlagExceptionBreakpoint;

    #else

        return kErrFlagExceptionBreakpoint;

    #endif
}

//============================================================================
static ErrLog * CreateErrLog () {
    // Allocate log memory
    ErrLog * log = (ErrLog *) VirtualAlloc(nil, sizeof(*log), MEM_COMMIT, PAGE_READWRITE);
    log->pos        = 0;
    log->terminator = 0;

    // Initialize log filename
    wchar srcName[MAX_PATH];
    SYSTEMTIME currTime;
    GetLocalTime(&currTime);
    StrPrintf(
        srcName,
        arrsize(srcName),
        L"Crash%02u%02u%02u.log",
        currTime.wYear % 100,
        currTime.wMonth,
        currTime.wDay
    );

    // Set log directory + filename
    AsyncLogGetDirectory(log->name, arrsize(log->name));
    PathAddFilename(log->name, log->name, srcName, arrsize(log->name));

    return log;
}

//============================================================================
static void DestroyErrLog (ErrLog * errLog) {
    VirtualFree(errLog, 0, MEM_RELEASE);
}

//============================================================================
static void __cdecl LogPrintf (
    ErrLog * const  log,
    const char      fmt[],
    ...
) {
    va_list args;
    va_start(args, fmt);
    char * pos = log->buffer + log->pos;
    unsigned len = StrPrintfV(
        pos,
        arrsize(log->buffer) - log->pos,
        fmt,
        args
    );
    va_end(args);
    log->pos += len;
}

//============================================================================
static inline void LogSectionHeader (
    ErrLog * const  log,
    const char      section[]
) {
    LogPrintf(log, s_sectionFmt_s, section);
}

//============================================================================
#ifdef _M_IX86
static void LogStackWalk (
    ErrLog * const      log,
    const CImageHelp &  ih, 
    HANDLE              hThread, 
    const CONTEXT &     ctx
) {
    STACKFRAME stk;
    ZeroMemory(&stk, sizeof(stk));
    stk.AddrPC.Offset       = ctx.Eip;
    stk.AddrPC.Mode         = AddrModeFlat;
    stk.AddrStack.Offset    = ctx.Esp;
    stk.AddrStack.Mode      = AddrModeFlat;
    stk.AddrFrame.Offset    = ctx.Ebp;
    stk.AddrFrame.Mode      = AddrModeFlat;

    LogSectionHeader(log, "Trace");
    for (unsigned i = 0; i < 100; i++) {
        const bool result = ih.StackWalk(
            IMAGE_FILE_MACHINE_I386,
            ih.Process(),
            hThread,
            &stk,
            nil,    // context
            nil,    // read memory routine
            ih.SymFunctionTableAccess,
            ih.SymGetModuleBase,
            nil // translate address routine
        );
        if (!result)
            break;

        LogPrintf(
            log,
            "Pc:%08x Fr:%08x Rt:%08x Arg:%08x %08x %08x %08x ",
            stk.AddrPC.Offset,
            stk.AddrFrame.Offset,
            stk.AddrReturn.Offset,
            stk.Params[0],
            stk.Params[1],
            stk.Params[2],
            stk.Params[3]
        );

        LogPrintf(log, "\r\n");
    }

    LogPrintf(log, "\r\n");
}
#endif // _M_IX86

//============================================================================
static void LogThread (
    ErrLog * const      log,
    const CImageHelp &  ih,
    HANDLE              hThread,
    const wchar         name[],
    const CONTEXT &     ctx
) {
    char threadName[256];
    StrPrintf(threadName, arrsize(threadName), "%SThread %#x", name, hThread);
    LogSectionHeader(log, threadName);
    
    LogPrintf(
        log,
         "eax=%08lx ebx=%08lx ecx=%08lx edx=%08lx\r\n"
         "esi=%08lx edi=%08lx\r\n"
         "eip=%08lx esp=%08lx ebp=%08lx\r\n"
         "cs=%04lx ss=%04lx ds=%04lx es=%04lx fs=%04lx gs=%04lx efl=%08lx\r\n\r\n",
        ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.Esi, ctx.Edi,
        ctx.Eip, ctx.Esp, ctx.Ebp, ctx.EFlags,
        ctx.SegCs, ctx.SegSs, ctx.SegDs, ctx.SegEs, ctx.SegFs, ctx.SegGs, ctx.EFlags
    );

    #ifdef _M_IX86
    LogStackWalk(log, ih, hThread, ctx);
    #else
    ref(ih);
    ref(hThread);
    #endif
}

//============================================================================
static void LogCrashInfo (
    const char          msg[],
    ErrLog * const      log,
    const CImageHelp &  ih
) {
    // Log application information
    {
        LogSectionHeader(log, "Program");
        
        wchar productIdStr[64];
        GuidToString(ProductId(), productIdStr, arrsize(productIdStr));
        
        wchar productBuildTag[128];
        ProductString(productBuildTag, arrsize(productBuildTag));
        
        SYSTEMTIME currtime;
        GetLocalTime(&currtime);
        LogPrintf(
            log,
            // beginning of format string
            "App        : %s\r\n"
            "Build      : "
            #ifdef PLASMA_EXTERNAL_RELEASE
            "External "
            #else
            "Internal "
            #endif
            #ifdef HS_DEBUGGING
            "Debug"
            #else
            "Release"
            #endif
            "\r\n"
            "BuildMark  : %S\r\n"
            "ProductTag : %S\r\n"
            "ProductId  : %S\r\n"
            "Crashed    : %d/%d/%d %02d:%02d:%02d\r\n"
            "Msg        : %s\r\n"
            "\r\n",
            // end of format string
            ih.GetProgramName(),
            ProductBuildString(),
            productBuildTag,
            productIdStr,
            currtime.wMonth,
            currtime.wDay,
            currtime.wYear,
            currtime.wHour,
            currtime.wMinute,
            currtime.wSecond,
            msg
        );
    }

    // Log system information
    {
        LogSectionHeader(log, "System");

        char machineName[128];
        DWORD len = arrsize(machineName);
        if (!GetComputerName(machineName, &len))
            StrCopy(machineName, s_unknown, arrsize(machineName));
        LogPrintf(log, "Machine    : %s\r\n", machineName);

        for (;;) {
            WSADATA wsaData;
            if (WSAStartup(0x101, &wsaData) || (wsaData.wVersion != 0x101))
                break;

            wchar ipAddress[256];
            NetAddressNode addrNodes[16];
            unsigned addrCount = NetAddressGetLocal(16, addrNodes);
            LogPrintf(log, "IpAddrs    : ");
            for (unsigned i = 0; i < addrCount; ++i) {
                NetAddressNodeToString(addrNodes[i], ipAddress, arrsize(ipAddress));
                LogPrintf(log, "%S, ", ipAddress);
            }
            LogPrintf(log, "\r\n");

            WSACleanup();           
            break;
        }
        
        SYSTEM_INFO si;
        GetSystemInfo(&si);
        DWORD ver = GetVersion();

        LogPrintf(
            log,
            "OS Version : %u.%u\r\n"
            "CPU Count  : %u\r\n"
            "\r\n",
            LOBYTE(LOWORD(ver)), HIBYTE(LOWORD(ver)),
            si.dwNumberOfProcessors
       );
    }

    // Log loaded modules
    {
        LogSectionHeader(log, "Modules");
        SAFE_CRITSECT_ENTER();
        for (const Module * p = s_modules.Head(); p; p = p->link.Next()) {
            LogPrintf(
                log,
                "%p %S (%u.%u.%S)\r\n",
                p->address,
                p->name,
                p->buildId,
                p->branchId,
                p->buildString
            );
        }
        SAFE_CRITSECT_LEAVE();
        LogPrintf(log, "\r\n");
    }
}

//============================================================================
static LONG ProcessException (const char occasion[], EXCEPTION_POINTERS * ep) {

    // log non-breakpont exceptions
    unsigned result;
    if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT) {

        // if this is a stack fault, allocate a new stack so we have
        // enough space to log the error.
        static void * newStack;
        #define STACKBYTES (64 * 1024)
        if (ep->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) {
            newStack = VirtualAlloc(nil, STACKBYTES, MEM_COMMIT, PAGE_READWRITE);
            __asm {
                mov     eax, [newStack]         // get new memory block
                add     eax, STACKBYTES - 4     // point to end of block
                mov     [eax], esp              // save current stack pointer
                mov     esp, eax                // set new stack pointer
            }
        }

        DelayDeadlockChecking();

        // Allocate error log memory
        ErrLog * log = CreateErrLog();

        // get crash log data
        static const char s_exception[] = "Exception";
        LogSectionHeader(log, "Crash");
        LogPrintf(
            log,
            "%s: %08x %s\r\n",
            s_exception,
            ep->ExceptionRecord->ExceptionCode,
            GetExceptionString(ep->ExceptionRecord->ExceptionCode)
        );
        if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
            LogPrintf(
                log,
                "Memory at address %08x could not be %s\r\n",
                ep->ExceptionRecord->ExceptionInformation[1],
                ep->ExceptionRecord->ExceptionInformation[0] ? "written" : "read"
            );
        }
        LogPrintf(log, "\r\n");
        
        CImageHelp ih((HINSTANCE)ModuleGetInstance());
        LogCrashInfo(occasion, log, ih);

        // log crashed thread
        LogThread(
            log,
            ih,
            GetCurrentThread(),
            L"",
            *ep->ContextRecord
        );

        // display the error
        result = ProcessErrorLog(
            log,
            ih.GetProgramName(),
            occasion
        );

        DestroyErrLog(log);
        ErrorSetOption(kErrOptDisableMemLeakChecking, true);

        // if this is a stack overflow, restore original stack
        if (ep->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) {
            __asm {
                mov     eax, [newStack]         // get new memory block
                add     eax, STACKBYTES - 4     // point to end of block
                mov     esp, [eax]              // restore old stack pointer
            }

            VirtualFree(newStack, 0, MEM_RELEASE);
        }
    }
    else {
        result = ProcessBreakException();
    }

    // If this is a debug build and the user pressed the "debug" button
    // then we return EXCEPTION_CONTINUE_SEARCH so the program will 
    // activate just-in-time debugging, or barring that, bring up the 
    // Microsoft error handler.
    if (result & kErrFlagExceptionBreakpoint)
        return EXCEPTION_CONTINUE_SEARCH;

    return EXCEPTION_EXECUTE_HANDLER;
}


//============================================================================
static LONG WINAPI ExceptionFilter (EXCEPTION_POINTERS * ep) {
    LONG result = ProcessException("Unhandled Exception", ep);

    // If the instruction pointer is inside CrashFunc then this exception
    // is a deadlock that couldn't be resumed, so terminate the program.
    #ifdef _M_IX86
    if ((ep->ContextRecord->Eip >= (DWORD) CrashFunc)
    &&  (ep->ContextRecord->Eip <= (DWORD) CrashFunc + 5))
        TerminateProcess(GetCurrentProcess(), 1);
    #else
    # error "ExceptionFilter not implemented for this CPU"
    #endif  // def _M_IX86

    return result;
}

//============================================================================
static void ProcessDeadlock_CS (const char occasion[], bool crashIt = true) {

    unsigned currTimeMs = TimeGetMs();
    unsigned crashCount = 0;

    // Suspend all threads so that we can dump their callstacks
    DeadlockCheck * next, * check = s_deadlockList.Head();
    for (; check; check = next) {
        next = s_deadlockList.Next(check);
        SuspendThread(check->thread);
    }
    
    // Allocate error log memory
    ErrLog * log = CreateErrLog();

    // Log report header and system data
    CImageHelp ih((HINSTANCE)ModuleGetInstance());
    LogCrashInfo(occasion, log, ih);

    // Log all threads
    if (!s_deadlockList.Head())
        LogPrintf(log, "*** No threads queued for deadlock check ***\r\n\r\n");
        
    check = s_deadlockList.Head();
    for (; check; check = next) {
        next = s_deadlockList.Next(check);
        CONTEXT ctx;
        ctx.ContextFlags = CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_CONTROL;
        GetThreadContext(
            check->thread,
            &ctx
        );
        if (true == (check->deadlocked = (int)(check->deadlockEmailMs - currTimeMs) < 0) && !check->emailSent && s_deadlockEmailMs) {
            LogPrintf(log, "This thread has hit the min deadlock check time\r\n");
            check->emailSent = true;
        }
        else if (true == (check->deadlocked = (int)(check->deadlockTerminateMs - currTimeMs) < 0) && s_deadlockTerminateMs) {
            LogPrintf(log, "This thread has hit the max deadlock check time\r\n");
            if (crashIt) {
                MakeThreadCrashOnResume(check->thread);
                ++crashCount;
            }
        }

        if(check->deadlocked) {
            LogPrintf(log, "Debug information: ");
            LogPrintf(log, "%S\r\n", check->debugStr);
        }

        LogThread(
            log,
            ih,
            check->thread,
            L"",
            ctx
        );
    }   

    (void) ProcessErrorLog(
        log,
        ih.GetProgramName(),
        occasion
    );
    
    DestroyErrLog(log);
    ErrorSetOption(kErrOptDisableMemLeakChecking, true);

    // Resume all threads
    unsigned elapsedMs = TimeGetMs() - currTimeMs;
    check = s_deadlockList.Head();
    for (; check; check = next) {
        next = s_deadlockList.Next(check);

        if (check->deadlocked && crashIt)
            // remove 'check' from list since we're crashing the thread on resume
            check->link.Unlink();
        else {
            // Update deadlock time to offset by the amount of time we had them suspended.
            check->deadlockEmailMs += elapsedMs;
            check->deadlockTerminateMs += elapsedMs;
        }

        ResumeThread(check->thread);
    }

    // Allow the resumed thread a bit of time to crash and
    // send the resulting email then terminate the process
    if (crashCount) {
        Sleep(60 * 1000);
        TerminateProcess(GetCurrentProcess(), 1);
    }   
}

//============================================================================
static void DeadlockCheckProc (void *) {
    while (s_running) {
        Sleep(5 * 1000);

        if (!s_deadlockEnabled)
            continue;

        unsigned currTimeMs = TimeGetMs();

        // Check for a forced delay in deadlock checking
        if (s_nextDeadlockCheck && (int)(s_nextDeadlockCheck - currTimeMs) > 0)
            continue;
        s_nextDeadlockCheck = 0;
        
        SAFE_CRITSECT_ENTER();
        for (;;) {
            DeadlockCheck * next, * check = s_deadlockList.Head();
            for (; check; check = next) {
                next = s_deadlockList.Next(check);
                if ((int)(check->deadlockEmailMs - currTimeMs) <= 0 && !check->emailSent && s_deadlockEmailMs) {
                    // we have hit our minimum deadlock check time. Send and email but dont crash the process, yet.
                    ProcessDeadlock_CS("DeadlockChecker", false);
                    check->emailSent = true;
                    break;
                }
                else if ((int)(check->deadlockTerminateMs - currTimeMs) <= 0 && s_deadlockTerminateMs){
                    // we have hit out max deadlock check time. This will send an email and crash the process so the service can restart.
                    ProcessDeadlock_CS("DeadlockChecker(Process Terminated)");
                    break;
                }
            }
            break;
        }
        SAFE_CRITSECT_LEAVE(); 
    }
}

//============================================================================
#ifdef SERVER
static void StartDeadlockThread () {
    (void)_beginthread(DeadlockCheckProc, 0, nil);
}
#endif

//============================================================================
#ifdef SERVER
static void DeadlockCheckNowProc (void *) {
    SAFE_CRITSECT_ENTER();
    {
        ProcessDeadlock_CS("Deadlock Check");
    }
    SAFE_CRITSECT_LEAVE();
}
#endif

//============================================================================
#ifdef SERVER
static void ThreadReportProc (void *) {
    SAFE_CRITSECT_ENTER();
    {
        ProcessDeadlock_CS("Thread Report", false);
    }
    SAFE_CRITSECT_LEAVE();
}
#endif

//============================================================================
static void pnCrashExeShutdown () {
    s_running = false;

    ReplaceEmailParams(nil);
    ASSERT(!s_deadlockList.Head());
    s_deadlockSpares.CleanUp();
}

//============================================================================
AUTO_INIT_FUNC(pnCrashExe) {
    // The critical section has to be initialized
    // before program startup and never freed
    static byte rawMemory[sizeof CCritSect];
    s_critsect = new(rawMemory) CCritSect;

    s_running = true;
    
    atexit(pnCrashExeShutdown);

    #ifdef SERVER
    SetUnhandledExceptionFilter(ExceptionFilter);
    StartDeadlockThread();
    #endif
}



/*****************************************************************************
*
*   Module functions
*
***/


} // namespace Crash


/*****************************************************************************
*
*   Exports
*
***/

//============================================================================
void CrashExceptionDump (const char occasion[], void * info) {

    (void) ProcessException(occasion, (EXCEPTION_POINTERS *) info);
}

//============================================================================
void CrashSetEmailParameters (
    const char      smtp[],
    const char      sender[],
    const char      recipients[],
    const char      username[],
    const char      password[],
    const char      replyTo[]
) {
    ASSERT(smtp);
    ASSERT(sender);
    ASSERT(recipients);
    
    EmailParams * params = NEWZERO(EmailParams);
    
    params->smtp        = StrDup(smtp);
    params->sender      = StrDup(sender);
    params->recipients  = StrDup(recipients);
    if (username)
        params->username = StrDup(username);
    if (password)
        params->password = StrDup(password);
    if (replyTo)
        params->replyTo  = StrDup(replyTo);
        
    ReplaceEmailParams(params);
}

//============================================================================
void * CrashAddModule (
    unsigned_ptr    address,
    unsigned        buildId,
    unsigned        branchId,
    const wchar     name[],
    const wchar     buildString[]
) {
    ASSERT(name);
    Module * module = NEWZERO(Module);
    
    module->address     = address;
    module->buildId     = buildId;
    module->branchId    = branchId;
    module->name        = StrDup(name);
    module->buildString = StrDup(buildString);
    // trim trailing spaces from buildString
    for (unsigned i = StrLen(buildString) - 1; i > 0; --i) {
        if (module->buildString[i] != L' ')
            break;
        module->buildString[i] = 0;
    }
    
    SAFE_CRITSECT_ENTER();
    {
        s_modules.Link(module);
    }
    SAFE_CRITSECT_LEAVE();
    
    return module;
}

//============================================================================
void CrashRemoveModule (
    void *          param
) {
    Module * module = (Module *) param;
    SAFE_CRITSECT_ENTER();
    {
        DEL(module);
    }
    SAFE_CRITSECT_LEAVE();
}


/*****************************************************************************
*
*   Deadlock detection (server only)
*
***/

//============================================================================
#ifdef SERVER
void * CrashAddDeadlockCheck (
    void *      thread,
    const wchar debugStr[]
) {
    s_spareCrit.Enter();
    DeadlockCheck * check = (DeadlockCheck *)s_deadlockSpares.Alloc();
    s_spareCrit.Leave();
    
    (void) new(check) DeadlockCheck;

    check->deadlockEmailMs = TimeGetMs() + s_deadlockEmailMs;
    check->deadlockTerminateMs = TimeGetMs() + s_deadlockTerminateMs;
    check->thread = (HANDLE) thread;
    check->emailSent = false;
    StrCopy(check->debugStr, debugStr, arrsize(check->debugStr));
    
    SAFE_CRITSECT_ENTER();
    {
        s_deadlockList.Link(check);
    }
    SAFE_CRITSECT_LEAVE();
    
    return check;
}
#endif

//============================================================================
#ifdef SERVER
void CrashRemoveDeadlockCheck (
    void *      ptr
) {
    ASSERT(ptr);
    DeadlockCheck * check = (DeadlockCheck *)ptr;
    SAFE_CRITSECT_ENTER();
    {
        s_deadlockList.Unlink(check);
    }
    SAFE_CRITSECT_LEAVE();
    
    check->~DeadlockCheck();
    s_spareCrit.Enter();
    s_deadlockSpares.Free(check);
    s_spareCrit.Leave();
}
#endif

//============================================================================
#ifdef SERVER
bool CrashEnableDeadlockChecking (
    bool            enable
) {
    SWAP(s_deadlockEnabled, enable);
    return enable;
}
#endif

//============================================================================
#ifdef SERVER
void CrashSetDeadlockCheckTimes(unsigned emailSec, unsigned terminateSec) {
    if(!emailSec && !terminateSec)
        CrashEnableDeadlockChecking(false);
    else {
        CrashEnableDeadlockChecking(true);
        s_deadlockEmailMs = emailSec * 1000;
        s_deadlockTerminateMs = terminateSec * 1000;
    }
}
#endif

//============================================================================
#ifdef SERVER
void CrashDeadlockCheckNow () {
    // Perform in a thread not queued for deadlock checking
    (void)_beginthread(DeadlockCheckNowProc, 0, nil);
}
#endif

//============================================================================
#ifdef SERVER
void CrashSendThreadReport () {
    // Perform in a thread not queued for deadlock checking
    (void)_beginthread(ThreadReportProc, 0, nil);
}
#endif