//
// ioinit.cpp
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// Defines initialization and termination routines for the lowio library, along
// with global data shared by most of the lowio library.
//
#include <corecrt_internal_lowio.h>
#include <corecrt_internal_stdio.h>



// This is a special static lowio file object referenced only by the safe access
// functionality in the internal headers.  It is used in certain stdio-level
// functions to more gracefully handle a FILE with -1 as its lowio file id.
extern "C" __crt_lowio_handle_data __badioinfo =
{
    { },                         // lock
    static_cast<intptr_t>(-1),   // osfhnd
    0,                           // startpos
    FTEXT,                       // osfile
    __crt_lowio_text_mode::ansi, // textmode
    { LF, LF, LF },              // _pipe_lookahead
};



// This is the number of lowio file objects that have been allocated.  This
// includes both in-use and unused elements, since not all allocated files
// are necessarily in use at any given time.
//
// This number is in the range of [IOINFO_ARRAY_ELTS, _NHANDLE_]
extern "C" int _nhandle = 0;



// This is the global array of file object arrays:
extern "C" __crt_lowio_handle_data* __pioinfo[IOINFO_ARRAYS] = { 0 };



static DWORD __cdecl get_std_handle_id(int const fh) throw()
{
    // Convert the CRT file handle to the OS file handle for the three 
    // standard streams:
    switch (fh)
    {
    case 0: return STD_INPUT_HANDLE;
    case 1: return STD_OUTPUT_HANDLE;
    case 2: return STD_ERROR_HANDLE;
    }

    return STD_ERROR_HANDLE; // Unreachable, but the compiler can't know.
}



static void __cdecl initialize_inherited_file_handles_nolock() throw()
{
    STARTUPINFOW startup_info;
    GetStartupInfoW(&startup_info);

    // First check and see if we inherited any file handles.  If we didn't, then
    // we don't have anything to initialize:
    if (startup_info.cbReserved2 == 0 || startup_info.lpReserved2 == nullptr)
        return;

    // Get the number of inherited handles:
    int const handle_count = *reinterpret_cast<UNALIGNED int*>(startup_info.lpReserved2);

    // Compute the start of the passed file info and OS HANDLEs:
    unsigned char* const first_file =
        reinterpret_cast<unsigned char*>(startup_info.lpReserved2) + sizeof(int);

    UNALIGNED intptr_t* const first_handle =
        reinterpret_cast<UNALIGNED intptr_t*>(first_file + handle_count);

    // Do not attempt to inherit more than the maximum number of supported handles:
    int handles_to_inherit = handle_count < _NHANDLE_ 
        ? handle_count
        : _NHANDLE_;

    // Attempt to allocate the required number of handles.  If we fail for any
    // reason, we'll inherit as many handles as we can:
    __acrt_lowio_ensure_fh_exists(handles_to_inherit);
    if (handles_to_inherit > _nhandle)
        handles_to_inherit = _nhandle;

    // Validate and copy the provided file information:
    unsigned char*      it_file   = first_file;
    UNALIGNED intptr_t* it_handle = first_handle;

    for (int fh = 0; fh != handles_to_inherit; ++fh, ++it_file, ++it_handle)
    {
        HANDLE const real_handle = reinterpret_cast<HANDLE>(*it_handle);

        // If the provided information does not appear to describe an open,
        // valid file or device, skip it:
        if (real_handle == INVALID_HANDLE_VALUE)
            continue;

        if (*it_handle == _NO_CONSOLE_FILENO)
            continue;

        if ((*it_file & FOPEN) == 0)
            continue;

        // GetFileType cannot be called for pipe handles since it may "hang" if
        // there is a blocked read pending on the pipe in the parent.
        if ((*it_file & FPIPE) == 0 && GetFileType(real_handle) == FILE_TYPE_UNKNOWN)
            continue;

        // Okay, the file looks valid:
        __crt_lowio_handle_data* const pio = _pioinfo(fh);
        pio->osfhnd = *it_handle;
        pio->osfile = *it_file;
    }
}



static void initialize_stdio_handles_nolock() throw()
{
    for (int fh = 0; fh != STDIO_HANDLES_COUNT; ++fh)
    {
        __crt_lowio_handle_data* const pio = _pioinfo(fh);

        // If this handle was inherited from the parent process and initialized
        // already, make sure it has the FTEXT flag and continue:
        if (pio->osfhnd != reinterpret_cast<intptr_t>(INVALID_HANDLE_VALUE) &&
            pio->osfhnd != _NO_CONSOLE_FILENO)
        {
            pio->osfile |= FTEXT;
            continue;
        }

        // Regardless what happens next, the file will be treated as if it is
        // open in text mode:
        pio->osfile = FOPEN | FTEXT;

        // This handle has not yet been initialized, so  let's see if we can get
        // the handle from the OS:
        intptr_t const os_handle = reinterpret_cast<intptr_t>(GetStdHandle(get_std_handle_id(fh)));

        bool const is_valid_handle = 
            os_handle != reinterpret_cast<intptr_t>(INVALID_HANDLE_VALUE) &&
            os_handle != reinterpret_cast<intptr_t>(nullptr);

        DWORD const handle_type = is_valid_handle
            ? GetFileType(reinterpret_cast<HANDLE>(os_handle))
            : FILE_TYPE_UNKNOWN;


        if (handle_type != FILE_TYPE_UNKNOWN)
        {
            // The file type is known, so we obtained a valid handle from the
            // OS.  Finish initializing the lowio file object for this handle,
            // including the flag specifying whether this is a character device
            // or a pipe:
            pio->osfhnd = os_handle;

            if ((handle_type & 0xff) == FILE_TYPE_CHAR)
                pio->osfile |= FDEV;

            else if ((handle_type & 0xff) == FILE_TYPE_PIPE)
                pio->osfile |= FPIPE;
        }
        else
        {
            // We were unable to get the handles from the OS.  For stdin, stdout,
            // and stderr, if there is no valid OS handle, treat the CRT handle
            // as being open in text mode on a device with _NO_CONSOLE_FILENO
            // underlying it.  We use this value instead of INVALID_HANDLE_VALUE
            // to distinguish between a failure in opening a file and a program
            // run without a console:
            pio->osfile |= FDEV;
            pio->osfhnd = _NO_CONSOLE_FILENO;

            // Also update the corresponding stdio stream, unless stdio was
            // already terminated:
            if (__piob)
                __piob[fh]->_file = _NO_CONSOLE_FILENO;
        }
    }
}



// Initializes the lowio library.  This initialization comprises several steps:
//
// [1] An initial array of __crt_lowio_handle_data structures is allocated.
//
// [2] Inherited file handles are initialized.  To do this, sthe startup info
//     is obtained from the OS, via the lpReserved2 member.  The format of the
//     information is as follows:
//
//     [Bytes 0 - 3]       Integer value N, which is the number of handles that
//                         are provided by the parent process.
//
//     [Bytes 4 - N+3]     The N osfile values.
//
//     [Bytes N+4 - 5*N+3] The N OS HANDLE values, as DWORDs
//
// [3] Next, the first three lowio files (corresponding to stdin, stdout, and
//     stderr) are initialized as follows:  If the value in osfhnd is 
//     INVALID_HANDLE_VALUE, then we try to obtain a HANDLE from the OS.  These
//     handles are forced to text mode, as standard input, output, and error
//     always start out in text mode.
//
// Notes:
//
// [1] In general, not all of the pased info from the parent process will
//     describe open handles.  If, for example, only C handle 1 (stdout) and
//     C handle 6 are open in the parent, info for C handles 0 through 6 are
//     passed to the child.  0, 2, 3, 4, and 5 will not describe open handles.
//
// [2] Care is taken not to "overflow" the arrays of lowio file objects.
//
// [3] See the dospawn logic for the encoding of the file handle info to be
//     passed to a child process.
//
// This funtion returns 0 on success; -1 on failure.
extern "C" bool __cdecl __acrt_initialize_lowio()
{
    __acrt_lock(__acrt_lowio_index_lock);
    bool result = false;
    __try
    {
        // First, allocate and initialize the initial array of lowio files:
        if (__acrt_lowio_ensure_fh_exists(0) != 0)
            __leave;

        // Next, process and initialize all inherited file handles:
        initialize_inherited_file_handles_nolock();

        // Finally, initialize the stdio handles, if they were not inherited:
        initialize_stdio_handles_nolock();
        result = true;
    }
    __finally
    {
        __acrt_unlock(__acrt_lowio_index_lock);
    }

    return result;
}



// Shuts down the lowio library, freeing all allocated memory and destroying all
// created synchronization objects.
extern "C" bool __cdecl __acrt_uninitialize_lowio(bool const /* terminating */)
{
    for (size_t i = 0; i < IOINFO_ARRAYS; ++i)
    {
        if (!__pioinfo[i])
            continue;

        __acrt_lowio_destroy_handle_array(__pioinfo[i]);
        __pioinfo[i] = nullptr;
    }

    return true;
}
