//
// _file.cpp
//
//      Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Defines the _iob array, the global __piob pointer, the stdio initialization
// and termination code, and the stream locking routines.
//
#include <corecrt_internal_stdio.h>



// FILE descriptors for stdin, stdout, and stderr
extern "C" __crt_stdio_stream_data _iob[_IOB_ENTRIES] =
{
    // ptr      _base,   _cnt, _flag,                   _file, _charbuf, _bufsiz
    {  nullptr, nullptr, 0,    _IOALLOCATED | _IOREAD,  0,     0,        0}, // stdin
    {  nullptr, nullptr, 0,    _IOALLOCATED | _IOWRITE, 1,     0,        0}, // stdout
    {  nullptr, nullptr, 0,    _IOALLOCATED | _IOWRITE, 2,     0,        0}, // stderr
};

extern "C" FILE* __cdecl __acrt_iob_func(unsigned const id)
{
    return &_iob[id]._public_file;
}



// Pointer to the array of FILE objects:
__crt_stdio_stream_data** __piob;



// Number of open streams (set to _NSTREAM by default):
#ifdef CRTDLL
    extern "C" { int _nstream = _NSTREAM_; }
#else
    extern "C" { int _nstream; }
#endif



// Initializer and terminator for the stdio library:
_CRT_LINKER_FORCE_INCLUDE(__acrt_stdio_initializer);
_CRT_LINKER_FORCE_INCLUDE(__acrt_stdio_terminator);



#ifndef CRTDLL
    // This variable is used by the statically linked CRT to ensure that if any
    // stdio functionality is used, the terminate_stdio() function will be
    // registered for call during CRT termination.
    extern "C" int _cflush = 0;
#endif



// Initializes the stdio library.  Returns 0 on success; -1 on failure.
extern "C" int __cdecl __acrt_initialize_stdio()
{
    #ifndef CRTDLL
    // If the user has not supplied a definition of _nstream, set it to _NSTREAM_.
    // If the user has supplied a value that is too small, set _nstream to the
    // minimum acceptable value (_IOB_ENTRIES):
    if (_nstream == 0)
    {
        _nstream = _NSTREAM_;
    }
    else if (_nstream < _IOB_ENTRIES)
    {
        _nstream = _IOB_ENTRIES;
    }
    #endif

    // Allocate the __piob array.  Try for _nstream entries first.  If this
    // fails, then reset _nstream to _IOB_ENTRIES an try again.  If it still
    // fails, bail out and cause CRT initialization to fail:
    __piob = _calloc_crt_t(__crt_stdio_stream_data*, _nstream).detach();
    if (!__piob)
    {
        _nstream = _IOB_ENTRIES;

        __piob = _calloc_crt_t(__crt_stdio_stream_data*, _nstream).detach();
        if (!__piob)
        {
            return -1;
        }
    }

    // Initialize the first _IOB_ENTRIES to point to the corresponding entries
    // in _iob[]:
    for (int i = 0; i != _IOB_ENTRIES; ++i)
    {
        __acrt_InitializeCriticalSectionEx(&_iob[i]._lock, _CORECRT_SPINCOUNT, 0);
        __piob[i] = &_iob[i];

        // For stdin, stdout, and stderr, we use _NO_CONSOLE_FILENO to allow
        // callers to distinguish between failure to open a file (-1) and a
        // program run without a console.
        intptr_t const os_handle = _osfhnd(i);
        bool const has_no_console =
            os_handle == reinterpret_cast<intptr_t>(INVALID_HANDLE_VALUE) ||
            os_handle == _NO_CONSOLE_FILENO ||
            os_handle == 0;

        if (has_no_console)
        {
            _iob[i]._file = _NO_CONSOLE_FILENO;
        }
    }

    return 0;
}



// Terminates the stdio library.  All streams are flushed (this is done even if
// we are going to close them since that stream won't do anything to the standard
// streams) and closed.
extern "C" void __cdecl __acrt_uninitialize_stdio()
{
    _flushall();
    _fcloseall();

    for (int i = 0; i != _IOB_ENTRIES; ++i)
    {
        __acrt_stdio_free_buffer_nolock(&__piob[i]->_public_file);
        DeleteCriticalSection(&__piob[i]->_lock);
    }

    _free_crt(__piob);
    __piob = nullptr;
}



// Locks a stdio stream.
extern "C" void __cdecl _lock_file(FILE* const stream)
{
    EnterCriticalSection(&__crt_stdio_stream(stream)->_lock);
}



// Unlocks a stdio stream.
extern "C" void __cdecl _unlock_file(FILE* const stream)
{
    LeaveCriticalSection(&__crt_stdio_stream(stream)->_lock);
}



extern "C" errno_t __cdecl _get_stream_buffer_pointers(
    FILE*   const public_stream,
    char*** const base,
    char*** const ptr,
    int**   const count
    )
{
    _VALIDATE_RETURN_ERRCODE(public_stream != nullptr, EINVAL);

    __crt_stdio_stream const stream(public_stream);
    if (base)
    {
        *base = &stream->_base;
    }

    if (ptr)
    {
        *ptr = &stream->_ptr;
    }

    if (count)
    {
        *count = &stream->_cnt;
    }

    return 0;
}
