//
// fwrite.cpp
//
//      Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Defines fwrite() and related functions, which write unformatted data to a
// stdio stream.
//
#include <corecrt_internal_stdio.h>



// Writes data from the provided buffer to the specified stream.  The function
// writes 'count' elements of 'size' size to the stream, and returns when
// either all of the elements have been written or no more data can be written
// (e.g. if EOF is encountered or an error occurs).
//
// Returns the number of "whole" elements that were written to the stream.  This
// may be fewer than the requested number of an error occurs or EOF is encountered.
// In this case, ferror() or feof() should be used to distinguish between the two
// conditions.
extern "C" size_t __cdecl fwrite(
    void const* const buffer,
    size_t      const size,
    size_t      const count,
    FILE*       const stream
    )
{
    if (size == 0 || count == 0)
        return 0;

    // The _nolock version will do the rest of the validation.
    _VALIDATE_RETURN(stream != nullptr, EINVAL, 0);

    return __acrt_lock_stream_and_call(stream, [&]() -> size_t
    {
        __acrt_stdio_temporary_buffering_guard const buffering(stream);

        return _fwrite_nolock(buffer, size, count, stream);
    });
}



extern "C" size_t __cdecl _fwrite_nolock(
    void const* const buffer,
    size_t      const element_size,
    size_t      const element_count,
    FILE*       const public_stream
    )
{
    if (element_size == 0 || element_count == 0)
    {
        return 0;
    }

    __crt_stdio_stream const stream(public_stream);

    _VALIDATE_RETURN(stream.valid(),                             EINVAL, 0);
    _VALIDATE_RETURN(buffer != nullptr,                          EINVAL, 0);
    _VALIDATE_RETURN(element_count <= (SIZE_MAX / element_size), EINVAL, 0);

    // Figure out how big the buffer is; if the stream doesn't currently have a
    // buffer, we assume that we'll get one with the usual internal buffer size:
    unsigned stream_buffer_size = stream.has_any_buffer()
        ? stream->_bufsiz
        : _INTERNAL_BUFSIZ;

    // The total number of bytes to be written to the stream:
    size_t const total_bytes = element_size * element_count;

    char const* data = static_cast<char const*>(buffer);

    // Write blocks of data from the buffer until there is no more data left:
    size_t remaining_bytes = total_bytes;
    while (remaining_bytes != 0)
    {
        // If the buffer is big and is not full, copy data into the buffer:
        if (stream.has_big_buffer() && stream->_cnt != 0)
        {
            if (stream->_cnt < 0)
            {
                _ASSERTE(("Inconsistent Stream Count. Flush between consecutive read and write", stream->_cnt >= 0));
                stream.set_flags(_IOERROR);
                return (total_bytes - remaining_bytes) / element_size;
            }

            size_t const bytes_to_write = __min(remaining_bytes, static_cast<size_t>(stream->_cnt));

            memcpy(stream->_ptr, data, bytes_to_write);

            remaining_bytes -= bytes_to_write;
            stream->_cnt    -= static_cast<int>(bytes_to_write);
            stream->_ptr    += bytes_to_write;
            data            += bytes_to_write;
        }
        // If we have more than stream_buffer_size bytes to write, write data by
        // calling _write() with an integral number of stream_buffer_size blocks.
        else if (remaining_bytes >= stream_buffer_size)
        {
            // If we reach here and we have a big buffer, it must be full, so
            // flush it.  If the flush fails, there's nothing we can do to
            // recover:
            if (stream.has_big_buffer() && __acrt_stdio_flush_nolock(stream.public_stream()))
            {
                return (total_bytes - remaining_bytes) / element_size;
            }

            // Calculate the number of bytes to write.  The _write API takes a
            // 32-bit unsigned byte count, so clamp the value to UINT_MAX:
            size_t const max_bytes_to_write = stream_buffer_size > 0
                ? remaining_bytes - remaining_bytes % stream_buffer_size
                : remaining_bytes;

            unsigned const bytes_to_write = static_cast<unsigned>(__min(max_bytes_to_write, UINT_MAX));

            unsigned const bytes_actually_written = _write(_fileno(stream.public_stream()), data, bytes_to_write);
            if (bytes_actually_written == static_cast<unsigned>(-1))
            {
                stream.set_flags(_IOERROR);
                return (total_bytes - remaining_bytes) / element_size;
            }

            // VSWhidbey #326224:  _write can return more bytes than we requested
            // due to Unicode conversions in text files.  We do not care how many
            // bytes were written as long as the number is as least as large as we
            // requested:
            unsigned const bytes_written = bytes_actually_written > bytes_to_write
                ? bytes_to_write
                : bytes_actually_written;

            // Update the remaining bytes and data to reflect the write:
            remaining_bytes -= bytes_written;
            data            += bytes_written;

            if (bytes_actually_written < bytes_to_write)
            {
                stream.set_flags(_IOERROR);
                return (total_bytes - remaining_bytes) / element_size;
            }
        }
        // Otherwise, the stream does not have a buffer, or the buffer is full
        // and there are not enough characters to do a direct write, so use
        // __acrt_stdio_flush_and_write_narrow_nolock:
        else
        {
            // Write the first character.  If this fails, there is nothing we can
            // do.  (Note that if this fails, it will update the stream error state.)
            if (__acrt_stdio_flush_and_write_narrow_nolock(*data, stream.public_stream()) == EOF)
            {
                return (total_bytes - remaining_bytes) / element_size;
            }

            // Update the remaining bytes to account for the byte we just wrote:
            ++data;
            --remaining_bytes;

            stream_buffer_size = stream->_bufsiz > 0
                ? stream->_bufsiz
                : 1;
        }
    }

    return element_count; // Success!
}
