//
// exit.cpp
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// The exit() implementation
//
#include <corecrt_internal.h>
#include <eh.h>
#include <process.h>

static long c_termination_complete = FALSE;

extern "C" extern _onexit_table_t __acrt_atexit_table;
extern "C" extern _onexit_table_t __acrt_at_quick_exit_table;

// thread_local atexit dtor handling. The APPCRT exports a function to set the
// callback function. The exe main function will call this to set the callback
// function so exit() can invoke destructors for thread-storage objects owned
// by the main thread.
static _tls_callback_type thread_local_exit_callback_func;


// CRT_REFACTOR TODO This needs to be declared somewhere more accessible and we
// need to clean up the static CRT exit coordination.
#if !defined CRTDLL && defined _DEBUG
    extern "C" bool __cdecl __scrt_uninitialize_crt(bool is_terminating, bool from_exit);
#endif


// Enclaves have no support for managed apps
#ifdef _UCRT_ENCLAVE_BUILD

static bool __cdecl is_managed_app() throw() { return false; }

static void __cdecl try_cor_exit_process(UINT const) throw() { }

// This function never returns.  It causes the process to exit.
static void __cdecl exit_or_terminate_process(UINT const return_code) throw()
{
    TerminateProcess(GetCurrentProcess(), return_code);
}

#else /* ^^^ _UCRT_ENCLAVE_BUILD ^^^ // vvv !_UCRT_ENCLAVE_BUILD vvv */

typedef void (WINAPI* exit_process_pft)(UINT);

// CRT_REFACTOR TODO This is duplicated in the VCStartup utility_desktop.cpp.
static bool __cdecl is_managed_app() throw()
{
    PIMAGE_DOS_HEADER const dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(GetModuleHandleW(nullptr));
    if (dos_header == nullptr)
        return false;

    if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
        return false;

    PIMAGE_NT_HEADERS const pe_header = reinterpret_cast<PIMAGE_NT_HEADERS>(
        reinterpret_cast<BYTE*>(dos_header) + dos_header->e_lfanew);

    if (pe_header->Signature != IMAGE_NT_SIGNATURE)
        return false;

    if (pe_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
        return false;

    // prefast assumes we are overrunning __ImageBase
    #pragma warning(push)
    #pragma warning(disable: 26000)

    if (pe_header->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
        return false;

    #pragma warning(pop)

    if (pe_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress == 0)
        return false;

    return true;
}

static void __cdecl try_cor_exit_process(UINT const return_code) throw()
{
    __crt_unique_hmodule mscoree;
    if (!GetModuleHandleExW(0, L"mscoree.dll", mscoree.get_address_of()))
        return;

    auto const cor_exit_process = __crt_get_proc_address<exit_process_pft>(mscoree.get(), "CorExitProcess");
    if (!cor_exit_process)
        return;

    cor_exit_process(return_code);
}



// For Windows Store apps, starting in Windows 10, we use TerminateProcess
// instead of ExitProcess.  ExitProcess will allow threads to run during
// termination in an unordered fashion. This has lead to problems in various
// apps.  TerminateProcess does not allow threads to run while the process is
// being torn down.  See also CoreApplication::Run, which does the same.
//
// For non-Windows Store app processes, we continue to call ExitProcess, for
// compatibility with the legacy runtimes.
static bool __cdecl should_call_terminate_process() throw()
{
    if (__acrt_get_process_end_policy() == process_end_policy_exit_process)
    {
        return false;
    }

    // If application verifier is running, we still want to call ExitProcess,
    // to enable tools that require DLLs to be unloaded cleanly at process exit
    // to do their work.
    if (__acrt_app_verifier_enabled())
    {
        return false;
    }

    return true;
}



// This function never returns.  It causes the process to exit.
static void __cdecl exit_or_terminate_process(UINT const return_code) throw()
{
    if (should_call_terminate_process())
    {
        TerminateProcess(GetCurrentProcess(), return_code);
    }

    try_cor_exit_process(return_code);

    // If that returned, then the exe for this process is not managed or we
    // failed to exit via a call to CorExitProcess.  Exit the normal way:
    ExitProcess(return_code);
}

#endif /* _UCRT_ENCLAVE_BUILD */


static int __cdecl atexit_exception_filter(unsigned long const _exception_code) throw()
{
    if (_exception_code == ('msc' | 0xE0000000))
    {
        return EXCEPTION_EXECUTE_HANDLER;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}



extern "C" void __cdecl __acrt_initialize_thread_local_exit_callback(void * encoded_null)
{
    thread_local_exit_callback_func = reinterpret_cast<_tls_callback_type>(encoded_null);
}

// Register the dynamic TLS dtor callback. Called from the vcstartup library
// as part of the EXE common_main to allow the acrt to call back into the scrt.
extern "C" void __cdecl _register_thread_local_exe_atexit_callback(_In_ _tls_callback_type const _Callback)
{
    // Can only set the callback once.
    if (thread_local_exit_callback_func != __crt_fast_encode_pointer(nullptr))
    {
        terminate();
    }

    thread_local_exit_callback_func = __crt_fast_encode_pointer(_Callback);
}



static void __cdecl common_exit(
    int                    const return_code,
    _crt_exit_cleanup_mode const cleanup_mode,
    _crt_exit_return_mode  const return_mode
    ) throw()
{
    // First, check to see if we're loaded in a managed app.  If we are, try to
    // call CorExitProcess to let the CLR handle the process termination.  If
    // the call to CorExitProcess is successful, then it will call back through
    // this function with a return mode of _crt_exit_return_to_caller, at which
    // point we will run the C termination routines.  It will then terminate the
    // process itself and not return.
    if (return_mode == _crt_exit_terminate_process && is_managed_app())
    {
        try_cor_exit_process(return_code);
    }

    // Run the C termination:
    bool crt_uninitialization_required = false;

    __acrt_lock_and_call(__acrt_select_exit_lock(), [&]
    {
        static bool c_exit_complete = false;
        if (c_exit_complete)
        {
            return;
        }

        _InterlockedExchange(&c_termination_complete, TRUE);

        __try
        {
            if (cleanup_mode == _crt_exit_full_cleanup)
            {

                // If this module has any dynamically initialized
                // __declspec(thread) variables, then we invoke their
                // destruction for the primary thread. All thread_local
                // destructors are sequenced before any atexit calls or static
                // object destructors (3.6.3/1)
                if (thread_local_exit_callback_func != __crt_fast_encode_pointer(nullptr))
                {
                    (__crt_fast_decode_pointer(thread_local_exit_callback_func))(nullptr, DLL_PROCESS_DETACH, nullptr);
                }

                _execute_onexit_table(&__acrt_atexit_table);
            }
            else if (cleanup_mode == _crt_exit_quick_cleanup)
            {
                _execute_onexit_table(&__acrt_at_quick_exit_table);
            }
        }
        __except (atexit_exception_filter(GetExceptionCode()))
        {
            terminate();
        }

        #ifndef CRTDLL
        // When the CRT is statically linked, we are responsible for executing
        // the terminators here, because the CRT code is present in this module.
        // When the CRT DLLs are used, the terminators will be executed when
        // the CRT DLLs are unloaded, after the call to ExitProcess.
        if (cleanup_mode == _crt_exit_full_cleanup)
        {
            _initterm(__xp_a, __xp_z);
        }

        _initterm(__xt_a, __xt_z);
        #endif // CRTDLL

        if (return_mode == _crt_exit_terminate_process)
        {
            c_exit_complete = true;
            crt_uninitialization_required = true;
        }
    });

    // Do NOT try to uninitialize the CRT while holding one of its locks.
    if (crt_uninitialization_required)
    {
        // If we are about to terminate the process, if the debug CRT is linked
        // statically into this module and this module is an EXE, we need to
        // ensure that we fully and correctly uninitialize the CRT so that the
        // debug on-exit() checks (e.g. debug heap leak detection) have a chance
        // to run.
        //
        // We do not need to uninitialize the CRT when it is statically linked
        // into a DLL because its DllMain will be called for DLL_PROCESS_DETACH
        // and we can uninitialize the CRT there.
        //
        // We never need to uninitialize the retail CRT during exit() because
        // the process is about to terminate.
        #if !CRTDLL && _DEBUG
        __scrt_uninitialize_crt(true, true);
        #endif
    }

    if (return_mode == _crt_exit_terminate_process)
    {
        exit_or_terminate_process(return_code);
    }
}

extern "C" int __cdecl _is_c_termination_complete()
{
    return static_cast<int>(__crt_interlocked_read(&c_termination_complete));
}



extern "C" void __cdecl exit(int const return_code)
{
    common_exit(return_code, _crt_exit_full_cleanup, _crt_exit_terminate_process);
}

extern "C" void __cdecl _exit(int const return_code)
{
    common_exit(return_code, _crt_exit_no_cleanup, _crt_exit_terminate_process);
}

extern "C" void __cdecl _Exit(int const return_code)
{
    common_exit(return_code, _crt_exit_no_cleanup, _crt_exit_terminate_process);
}

extern "C" void __cdecl quick_exit(int const return_code)
{
    common_exit(return_code, _crt_exit_quick_cleanup, _crt_exit_terminate_process);
}

extern "C" void __cdecl _cexit()
{
    common_exit(0, _crt_exit_full_cleanup, _crt_exit_return_to_caller);
}

extern "C" void __cdecl _c_exit()
{
    common_exit(0, _crt_exit_no_cleanup, _crt_exit_return_to_caller);
}
