mirror of
https://github.com/reactos/reactos.git
synced 2025-06-03 08:20:27 +00:00
755 lines
26 KiB
C++
755 lines
26 KiB
C++
//
|
|
// write.cpp
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// Defines _write(), which writes a buffer to a file.
|
|
//
|
|
#include <corecrt_internal_lowio.h>
|
|
#include <corecrt_internal_mbstring.h>
|
|
#include <corecrt_internal_ptd_propagation.h>
|
|
#include <ctype.h>
|
|
#include <locale.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
|
|
|
|
|
|
namespace
|
|
{
|
|
struct write_result
|
|
{
|
|
DWORD error_code;
|
|
DWORD char_count;
|
|
DWORD lf_count;
|
|
};
|
|
}
|
|
|
|
|
|
|
|
// This is the normal size of the LF => CRLF translation buffer. The default
|
|
// buffer is 4K, plus extra room for LF characters. Not all buffers are exactly
|
|
// this size, but this is used as the base size.
|
|
static size_t const BUF_SIZE = 5 * 1024;
|
|
|
|
|
|
|
|
// Writes a buffer to a file. The way in which the buffer is written depends on
|
|
// the mode in which the file was opened (e.g., if the file is a text mode file,
|
|
// linefeed translation will take place).
|
|
//
|
|
// On success, this function returns the number of bytes actually written (note
|
|
// that "bytes" here is "bytes from the original buffer;" more or fewer bytes
|
|
// may have actually been written, due to linefeed translation, codepage
|
|
// translation, and other transformations). On failure, this function returns 0
|
|
// and sets errno.
|
|
extern "C" int __cdecl _write_internal(int const fh, void const* const buffer, unsigned const size, __crt_cached_ptd_host& ptd)
|
|
{
|
|
_UCRT_CHECK_FH_CLEAR_OSSERR_RETURN(ptd, fh, EBADF, -1);
|
|
_UCRT_VALIDATE_CLEAR_OSSERR_RETURN(ptd, (fh >= 0 && (unsigned)fh < (unsigned)_nhandle), EBADF, -1);
|
|
_UCRT_VALIDATE_CLEAR_OSSERR_RETURN(ptd, (_osfile(fh) & FOPEN), EBADF, -1);
|
|
|
|
__acrt_lowio_lock_fh(fh);
|
|
int result = -1;
|
|
__try
|
|
{
|
|
if ((_osfile(fh) & FOPEN) == 0)
|
|
{
|
|
ptd.get_errno().set(EBADF);
|
|
ptd.get_doserrno().set(0);
|
|
_ASSERTE(("Invalid file descriptor. File possibly closed by a different thread",0));
|
|
__leave;
|
|
}
|
|
|
|
result = _write_nolock(fh, buffer, size, ptd);
|
|
}
|
|
__finally
|
|
{
|
|
__acrt_lowio_unlock_fh(fh);
|
|
}
|
|
__endtry
|
|
return result;
|
|
}
|
|
|
|
extern "C" int __cdecl _write(int const fh, void const* const buffer, unsigned const size)
|
|
{
|
|
__crt_cached_ptd_host ptd;
|
|
return _write_internal(fh, buffer, size, ptd);
|
|
}
|
|
|
|
static bool __cdecl write_requires_double_translation_nolock(int const fh, __crt_cached_ptd_host& ptd) throw()
|
|
{
|
|
// Double translation is required if both [a] the current locale is not the C
|
|
// locale or the file is open in a non-ANSI mode and [b] we are writing to the
|
|
// console.
|
|
|
|
// If this isn't a TTY or a text mode screen, then it isn't the console:
|
|
if (!_isatty(fh))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ((_osfile(fh) & FTEXT) == 0) {
|
|
return false;
|
|
}
|
|
|
|
// Get the current locale. If we're in the C locale and the file is open
|
|
// in ANSI mode, we don't need double translation:
|
|
bool const is_c_locale = ptd.get_locale()->locinfo->locale_name[LC_CTYPE] == nullptr;
|
|
if (is_c_locale && _textmode(fh) == __crt_lowio_text_mode::ansi)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If we can't get the console mode, it's not the console:
|
|
DWORD mode;
|
|
if (!GetConsoleMode(reinterpret_cast<HANDLE>(_osfhnd(fh)), &mode))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, double translation is required:
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
static write_result __cdecl write_double_translated_ansi_nolock(
|
|
int const fh,
|
|
_In_reads_(buffer_size) char const* const buffer,
|
|
unsigned const buffer_size,
|
|
__crt_cached_ptd_host& ptd
|
|
) throw()
|
|
{
|
|
HANDLE const os_handle = reinterpret_cast<HANDLE>(_osfhnd(fh));
|
|
char const* const buffer_end = buffer + buffer_size;
|
|
UINT const console_cp = GetConsoleOutputCP();
|
|
_locale_t const locale = ptd.get_locale();
|
|
bool const is_utf8 = locale->locinfo->_public._locale_lc_codepage == CP_UTF8;
|
|
|
|
write_result result = { 0 };
|
|
|
|
for (char const* source_it = buffer; source_it < buffer_end; )
|
|
{
|
|
char const c = *source_it;
|
|
|
|
// We require double conversion, to convert from the source multibyte
|
|
// to Unicode, then from Unicode back to multibyte, but in the console
|
|
// codepage.
|
|
//
|
|
// Here, we have to take into account that _write() might be called
|
|
// byte-by-byte, so when we see a lead byte without a trail byte, we
|
|
// have to store it and return no error. When this function is called
|
|
// again, that byte will be combined with the next available character.
|
|
wchar_t wc[2] = { 0 };
|
|
int wc_used = 1;
|
|
if (is_utf8)
|
|
{
|
|
_ASSERTE(!_dbcsBufferUsed(fh));
|
|
const int mb_buf_size = sizeof(_mbBuffer(fh));
|
|
int mb_buf_used;
|
|
for (mb_buf_used = 0; mb_buf_used < mb_buf_size && _mbBuffer(fh)[mb_buf_used]; ++mb_buf_used)
|
|
{}
|
|
|
|
if (mb_buf_used > 0)
|
|
{
|
|
const int mb_len = _utf8_no_of_trailbytes(_mbBuffer(fh)[0]) + 1;
|
|
_ASSERTE(1 < mb_len && mb_buf_used < mb_len);
|
|
const int remaining_bytes = mb_len - mb_buf_used;
|
|
if (remaining_bytes <= (buffer_end - source_it))
|
|
{
|
|
// We now have enough bytes to complete the code point
|
|
char mb_buffer[MB_LEN_MAX];
|
|
|
|
for (int i = 0; i < mb_buf_used; ++i)
|
|
{
|
|
mb_buffer[i] = _mbBuffer(fh)[i];
|
|
}
|
|
for (int i = 0; i < remaining_bytes; ++i)
|
|
{
|
|
mb_buffer[i + mb_buf_used] = source_it[i];
|
|
}
|
|
|
|
// Clear out the temp buffer
|
|
for (int i = 0; i < mb_buf_used; ++i)
|
|
{
|
|
_mbBuffer(fh)[i] = 0;
|
|
}
|
|
|
|
mbstate_t state{};
|
|
const char* str = mb_buffer;
|
|
if (mb_len == 4)
|
|
{
|
|
wc_used = 2;
|
|
}
|
|
if (__crt_mbstring::__mbsrtowcs_utf8(wc, &str, wc_used, &state, ptd) == -1)
|
|
{
|
|
return result;
|
|
}
|
|
source_it += (remaining_bytes - 1);
|
|
}
|
|
else
|
|
{
|
|
// Need to add some more bytes to the buffer for later
|
|
const auto bytes_to_add = buffer_end - source_it;
|
|
_ASSERTE(mb_buf_used + bytes_to_add < mb_buf_size);
|
|
for (int i = 0; i < bytes_to_add; ++i)
|
|
{
|
|
_mbBuffer(fh)[i + mb_buf_used] = source_it[i];
|
|
}
|
|
// Pretend we wrote the bytes, because this isn't an error *yet*.
|
|
result.char_count += static_cast<DWORD>(bytes_to_add);
|
|
return result;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int mb_len = _utf8_no_of_trailbytes(*source_it) + 1;
|
|
const auto available_bytes = buffer_end - source_it;
|
|
if (mb_len <= (available_bytes))
|
|
{
|
|
// We have enough bytes to write the entire code point
|
|
mbstate_t state{};
|
|
const char* str = source_it;
|
|
if (mb_len == 4)
|
|
{
|
|
wc_used = 2;
|
|
}
|
|
if (__crt_mbstring::__mbsrtowcs_utf8(wc, &str, wc_used, &state, ptd) == -1)
|
|
{
|
|
return result;
|
|
}
|
|
source_it += (mb_len - 1);
|
|
}
|
|
else
|
|
{
|
|
// Not enough bytes for this code point
|
|
_ASSERTE(available_bytes <= sizeof(_mbBuffer(fh)));
|
|
for (int i = 0; i < available_bytes; ++i)
|
|
{
|
|
_mbBuffer(fh)[i] = source_it[i];
|
|
}
|
|
// Pretend we wrote the bytes, because this isn't an error *yet*.
|
|
result.char_count += static_cast<DWORD>(available_bytes);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
else if (_dbcsBufferUsed(fh))
|
|
{
|
|
// We already have a DBCS lead byte buffered. Take the current
|
|
// character, combine it with the lead byte, and convert:
|
|
_ASSERTE(_isleadbyte_fast_internal(_dbcsBuffer(fh), locale));
|
|
|
|
char mb_buffer[MB_LEN_MAX];
|
|
mb_buffer[0] = _dbcsBuffer(fh);
|
|
mb_buffer[1] = *source_it;
|
|
|
|
_dbcsBufferUsed(fh) = false;
|
|
|
|
if (_mbtowc_internal(wc, mb_buffer, 2, ptd) == -1)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_isleadbyte_fast_internal(*source_it, locale))
|
|
{
|
|
if ((source_it + 1) < buffer_end)
|
|
{
|
|
// And we have more bytes to read, just convert...
|
|
if (_mbtowc_internal(wc, source_it, 2, ptd) == -1)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// Increment the source_it to accomodate the DBCS character:
|
|
++source_it;
|
|
}
|
|
else
|
|
{
|
|
// And we ran out of bytes to read, so buffer the lead byte:
|
|
_dbcsBuffer(fh) = *source_it;
|
|
_dbcsBufferUsed(fh) = true;
|
|
|
|
// We lie here that we actually wrote the last character, to
|
|
// ensure we don't consider this an error:
|
|
++result.char_count;
|
|
return result;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// single character conversion:
|
|
if (_mbtowc_internal(wc, source_it, 1, ptd) == -1)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
++source_it;
|
|
|
|
// Translate the Unicode character into Multibyte in the console codepage
|
|
// and write the character to the file:
|
|
char mb_buffer[MB_LEN_MAX];
|
|
DWORD const size = static_cast<DWORD>(__acrt_WideCharToMultiByte(
|
|
console_cp, 0, wc, wc_used, mb_buffer, sizeof(mb_buffer), nullptr, nullptr));
|
|
|
|
if(size == 0)
|
|
return result;
|
|
|
|
DWORD written;
|
|
if (!WriteFile(os_handle, mb_buffer, size, &written, nullptr))
|
|
{
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
// When we are converting, some conversions may result in:
|
|
//
|
|
// 2 MBCS characters => 1 wide character => 1 MBCS character.
|
|
//
|
|
// For example, when printing Japanese characters in the English console
|
|
// codepage, each source character is transformed into a single question
|
|
// mark. Therefore, we want to track the number of bytes we converted,
|
|
// plus the linefeed count, instead of how many bytes we actually wrote.
|
|
result.char_count = result.lf_count + static_cast<DWORD>(source_it - buffer);
|
|
|
|
// If the write succeeded but didn't write all of the characters, return:
|
|
if (written < size)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// If the original character that we read was an LF, write a CR too:
|
|
// CRT_REFACTOR TODO Doesn't this write LFCR instead of CRLF?
|
|
if (c == LF)
|
|
{
|
|
wchar_t const cr = CR;
|
|
if (!WriteFile(os_handle, &cr, 1, &written, nullptr))
|
|
{
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
if (written < 1)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
++result.lf_count;
|
|
++result.char_count;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static write_result __cdecl write_double_translated_unicode_nolock(
|
|
_In_reads_(buffer_size) char const* const buffer,
|
|
_In_ _Pre_satisfies_((buffer_size % 2) == 0) unsigned const buffer_size
|
|
) throw()
|
|
{
|
|
// When writing to a Unicode file (UTF-8 or UTF-16LE) that corresponds to
|
|
// the console, we don't actually need double translation. We just need to
|
|
// print each character to the console, one-by-one. (This function is
|
|
// named what it is because its use is guarded by the double translation
|
|
// check, and to match the name of the corresponding ANSI function.)
|
|
|
|
write_result result = { 0 };
|
|
|
|
// Needed for SAL to clarify that buffer_size is even.
|
|
_Analysis_assume_((buffer_size/2) != ((buffer_size-1)/2));
|
|
char const* const buffer_end = buffer + buffer_size;
|
|
for (char const* pch = buffer; pch < buffer_end; pch += 2)
|
|
{
|
|
wchar_t const c = *reinterpret_cast<wchar_t const*>(pch);
|
|
|
|
// _putwch_nolock does not depend on global state, no PTD needed to be propagated.
|
|
if (_putwch_nolock(c) == c)
|
|
{
|
|
result.char_count += 2;
|
|
}
|
|
else
|
|
{
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
// If the character was a carriage return, also emit a line feed.
|
|
// CRT_REFACTOR TODO Doesn't this print LFCR instead of CRLF?
|
|
if (c == LF)
|
|
{
|
|
// _putwch_nolock does not depend on global state, no PTD needed to be propagated.
|
|
if (_putwch_nolock(CR) != CR)
|
|
{
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
++result.char_count;
|
|
++result.lf_count;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static write_result __cdecl write_text_ansi_nolock(
|
|
int const fh,
|
|
_In_reads_(buffer_size) char const* const buffer,
|
|
unsigned const buffer_size
|
|
) throw()
|
|
{
|
|
HANDLE const os_handle = reinterpret_cast<HANDLE>(_osfhnd(fh));
|
|
char const* const buffer_end = buffer + buffer_size;
|
|
|
|
write_result result = { 0 };
|
|
|
|
for (char const* source_it = buffer; source_it < buffer_end; )
|
|
{
|
|
char lfbuf[BUF_SIZE]; // The LF => CRLF translation buffer
|
|
|
|
// One-past-the-end of the translation buffer. Note that we subtract
|
|
// one to account for the case where we're pointing to the last element
|
|
// in the buffer and we need to write both a CR and an LF.
|
|
char* const lfbuf_end = lfbuf + sizeof(lfbuf) - 1;
|
|
|
|
// Translate the source buffer into the translation buffer. Note that
|
|
// both source_it and lfbuf_it are incremented in the loop.
|
|
char* lfbuf_it = lfbuf;
|
|
while (lfbuf_it < lfbuf_end && source_it < buffer_end)
|
|
{
|
|
char const c = *source_it++;
|
|
|
|
if (c == LF)
|
|
{
|
|
++result.lf_count;
|
|
*lfbuf_it++ = CR;
|
|
}
|
|
|
|
*lfbuf_it++ = c;
|
|
}
|
|
|
|
DWORD const lfbuf_length = static_cast<DWORD>(lfbuf_it - lfbuf);
|
|
|
|
DWORD written;
|
|
if (!WriteFile(os_handle, lfbuf, lfbuf_length, &written, nullptr))
|
|
{
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
result.char_count += written;
|
|
if (written < lfbuf_length)
|
|
{
|
|
return result; // The write succeeded but didn't write everything
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static write_result __cdecl write_text_utf16le_nolock(
|
|
int const fh,
|
|
_In_reads_(buffer_size) char const* const buffer,
|
|
unsigned const buffer_size
|
|
) throw()
|
|
{
|
|
HANDLE const os_handle = reinterpret_cast<HANDLE>(_osfhnd(fh));
|
|
wchar_t const* const buffer_end = reinterpret_cast<wchar_t const*>(buffer + buffer_size);
|
|
|
|
write_result result = { 0 };
|
|
|
|
wchar_t const* source_it = reinterpret_cast<wchar_t const*>(buffer);
|
|
while (source_it < buffer_end)
|
|
{
|
|
wchar_t lfbuf[BUF_SIZE / sizeof(wchar_t)]; // The translation buffer
|
|
|
|
// One-past-the-end of the translation buffer. Note that we subtract
|
|
// one to account for the case where we're pointing to the last element
|
|
// in the buffer and we need to write both a CR and an LF.
|
|
wchar_t const* lfbuf_end = lfbuf + BUF_SIZE / sizeof(wchar_t) - 1;
|
|
|
|
// Translate the source buffer into the translation buffer. Note that
|
|
// both source_it and lfbuf_it are incremented in the loop.
|
|
wchar_t* lfbuf_it = lfbuf;
|
|
while (lfbuf_it < lfbuf_end && source_it < buffer_end)
|
|
{
|
|
wchar_t const c = *source_it++;
|
|
|
|
if (c == LF)
|
|
{
|
|
result.lf_count += 2;
|
|
*lfbuf_it++ = CR;
|
|
}
|
|
|
|
*lfbuf_it++ = c;
|
|
}
|
|
|
|
// Note that this length is in bytes, not wchar_t elemnts, since we need
|
|
// to tell WriteFile how many bytes (not characters) to write:
|
|
DWORD const lfbuf_length = static_cast<DWORD>(lfbuf_it - lfbuf) * sizeof(wchar_t);
|
|
|
|
|
|
// Attempt the write and return immediately if it fails:
|
|
DWORD written;
|
|
if (!WriteFile(os_handle, lfbuf, lfbuf_length, &written, nullptr))
|
|
{
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
result.char_count += written;
|
|
if (written < lfbuf_length)
|
|
{
|
|
return result; // The write succeeded, but didn't write everything
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static write_result __cdecl write_text_utf8_nolock(
|
|
int const fh,
|
|
_In_reads_(buffer_size) char const* const buffer,
|
|
unsigned const buffer_size
|
|
) throw()
|
|
{
|
|
HANDLE const os_handle = reinterpret_cast<HANDLE>(_osfhnd(fh));
|
|
wchar_t const* const buffer_end = reinterpret_cast<wchar_t const*>(buffer + buffer_size);
|
|
|
|
write_result result = { 0 };
|
|
|
|
wchar_t const* source_it = reinterpret_cast<wchar_t const*>(buffer);
|
|
while (source_it < buffer_end)
|
|
{
|
|
// The translation buffer. We use two buffers: the first is used to
|
|
// store the UTF-16 LF => CRLF translation (this is that buffer here).
|
|
// The second is used for storing the conversion to UTF-8 (defined
|
|
// below). The sizes are selected to handle the worst-case scenario
|
|
// where each UTF-8 character is four bytes long.
|
|
wchar_t utf16_buf[BUF_SIZE / 6];
|
|
|
|
// One-past-the-end of the translation buffer. Note that we subtract
|
|
// one to account for the case where we're pointing to the last element
|
|
// in the buffer and we need to write both a CR and an LF.
|
|
wchar_t const* utf16_buf_end = utf16_buf + (BUF_SIZE / 6 - 1);
|
|
|
|
// Translate the source buffer into the translation buffer. Note that
|
|
// both source_it and lfbuf_it are incremented in the loop.
|
|
wchar_t* utf16_buf_it = utf16_buf;
|
|
while (utf16_buf_it < utf16_buf_end && source_it < buffer_end)
|
|
{
|
|
wchar_t const c = *source_it++;
|
|
|
|
if (c == LF)
|
|
{
|
|
// No need to count the number of line-feeds translated; we
|
|
// track the number of written characters by counting the total
|
|
// number of characters written from the UTF8 buffer (see below
|
|
// where we update the char_count).
|
|
*utf16_buf_it++ = CR;
|
|
}
|
|
|
|
*utf16_buf_it++ = c;
|
|
}
|
|
|
|
// Note that this length is in characters, not bytes.
|
|
DWORD const utf16_buf_length = static_cast<DWORD>(utf16_buf_it - utf16_buf);
|
|
|
|
|
|
// This is the second translation, where we translate the UTF-16 text to
|
|
// UTF-8, into the UTF-8 buffer:
|
|
char utf8_buf[(BUF_SIZE * 2) / 3];
|
|
DWORD const bytes_converted = static_cast<DWORD>(__acrt_WideCharToMultiByte(
|
|
CP_UTF8,
|
|
0,
|
|
utf16_buf,
|
|
utf16_buf_length,
|
|
utf8_buf,
|
|
sizeof(utf8_buf),
|
|
nullptr,
|
|
nullptr));
|
|
|
|
if (bytes_converted == 0)
|
|
{
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
// Here, we need to make every attempt to write all of the converted
|
|
// characters to avoid corrupting the stream. If, for example, we write
|
|
// only half of the bytes of a UTF-8 character, the stream may be
|
|
// corrupted.
|
|
//
|
|
// This loop will ensure that we exit only if either (a) all of the
|
|
// bytes are written, ensuring that no partial MBCSes are written, or
|
|
// (b) there is an error in the stream.
|
|
for (DWORD bytes_written = 0; bytes_written < bytes_converted; )
|
|
{
|
|
char const* const current = utf8_buf + bytes_written;
|
|
DWORD const current_size = bytes_converted - bytes_written;
|
|
|
|
DWORD written;
|
|
if (!WriteFile(os_handle, current, current_size, &written, nullptr))
|
|
{
|
|
result.error_code = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
bytes_written += written;
|
|
}
|
|
|
|
// If this chunk was committed successfully, update the character count:
|
|
result.char_count = static_cast<DWORD>(reinterpret_cast<char const*>(source_it) - buffer);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static write_result __cdecl write_binary_nolock(
|
|
int const fh,
|
|
_In_reads_(buffer_size) char const* const buffer,
|
|
unsigned const buffer_size
|
|
) throw()
|
|
{
|
|
HANDLE const os_handle = reinterpret_cast<HANDLE>(_osfhnd(fh));
|
|
|
|
// Compared to text files, binary files are easy...
|
|
write_result result = { 0 };
|
|
if (!WriteFile(os_handle, buffer, buffer_size, &result.char_count, nullptr))
|
|
{
|
|
result.error_code = GetLastError();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
extern "C" int __cdecl _write_nolock(int const fh, void const* const buffer, unsigned const buffer_size, __crt_cached_ptd_host& ptd)
|
|
{
|
|
// If the buffer is empty, there is nothing to be written:
|
|
if (buffer_size == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// If the buffer is null, though... well, that is not allowed:
|
|
_UCRT_VALIDATE_CLEAR_OSSERR_RETURN(ptd, buffer != nullptr, EINVAL, -1);
|
|
|
|
__crt_lowio_text_mode const fh_textmode = _textmode(fh);
|
|
|
|
// If the file is open for Unicode, the buffer size must always be even:
|
|
if (fh_textmode == __crt_lowio_text_mode::utf16le || fh_textmode == __crt_lowio_text_mode::utf8)
|
|
{
|
|
_UCRT_VALIDATE_CLEAR_OSSERR_RETURN(ptd, buffer_size % 2 == 0, EINVAL, -1);
|
|
}
|
|
|
|
// If the file is opened for appending, seek to the end of the file. We
|
|
// ignore errors because the underlying file may not allow seeking.
|
|
if (_osfile(fh) & FAPPEND)
|
|
{
|
|
(void)_lseeki64_nolock_internal(fh, 0, FILE_END, ptd);
|
|
}
|
|
|
|
char const* const char_buffer = static_cast<char const*>(buffer);
|
|
|
|
// Dispatch the actual writing to one of the helper routines based on the
|
|
// text mode of the file and whether or not the file refers to the console.
|
|
//
|
|
// Note that in the event that the handle belongs to the console, WriteFile
|
|
// will generate garbage output. To print to the console correctly, we need
|
|
// to print ANSI. Also note that when printing to the console, we need to
|
|
// convert the characters to the console codepge.
|
|
write_result result = { 0 };
|
|
if (write_requires_double_translation_nolock(fh, ptd))
|
|
{
|
|
switch (fh_textmode)
|
|
{
|
|
case __crt_lowio_text_mode::ansi:
|
|
result = write_double_translated_ansi_nolock(fh, char_buffer, buffer_size, ptd);
|
|
break;
|
|
|
|
case __crt_lowio_text_mode::utf16le:
|
|
case __crt_lowio_text_mode::utf8:
|
|
_Analysis_assume_((buffer_size % 2) == 0);
|
|
result = write_double_translated_unicode_nolock(char_buffer, buffer_size);
|
|
break;
|
|
}
|
|
}
|
|
else if (_osfile(fh) & FTEXT)
|
|
{
|
|
switch (fh_textmode)
|
|
{
|
|
case __crt_lowio_text_mode::ansi:
|
|
result = write_text_ansi_nolock(fh, char_buffer, buffer_size);
|
|
break;
|
|
|
|
case __crt_lowio_text_mode::utf16le:
|
|
result = write_text_utf16le_nolock(fh, char_buffer, buffer_size);
|
|
break;
|
|
|
|
case __crt_lowio_text_mode::utf8:
|
|
result = write_text_utf8_nolock(fh, char_buffer, buffer_size);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = write_binary_nolock(fh, char_buffer, buffer_size);
|
|
}
|
|
|
|
|
|
// Why did we not write anything? Lettuce find out...
|
|
if (result.char_count == 0)
|
|
{
|
|
// If nothing was written, check to see if it was due to an OS error:
|
|
if (result.error_code != 0)
|
|
{
|
|
// An OS error occurred. ERROR_ACCESS_DENIED should be mapped in
|
|
// this case to EBADF, not EACCES. All other errors are mapped
|
|
// normally:
|
|
if (result.error_code == ERROR_ACCESS_DENIED)
|
|
{
|
|
ptd.get_errno().set(EBADF);
|
|
ptd.get_doserrno().set(result.error_code);
|
|
}
|
|
else
|
|
{
|
|
__acrt_errno_map_os_error_ptd(result.error_code, ptd);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// If this file is a device and the first character was Ctrl+Z, then
|
|
// writing nothing is the expected behavior and is not an error:
|
|
if ((_osfile(fh) & FDEV) && *char_buffer == CTRLZ)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Otherwise, the error is reported as ENOSPC:
|
|
ptd.get_errno().set(ENOSPC);
|
|
ptd.get_doserrno().set(0);
|
|
return -1;
|
|
}
|
|
|
|
// The write succeeded. Return the adjusted number of bytes written:
|
|
return result.char_count - result.lf_count;
|
|
}
|