reactos/sdk/lib/ucrt/stdio/ungetwc.cpp
2025-01-16 14:18:53 +02:00

182 lines
5.9 KiB
C++

//
// ungetwc.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Defines ungetwc(), which pushes a wide character back into a stream.
//
#include <corecrt_internal_stdio.h>
// Pushes a character ("ungets" it) back into a stream. It is possible to push
// back one character. It may not be possible to push back more than one
// character in a row. Returns the pushed-back character on success; returns
// WEOF on failure. Ungetting WEOF is expressly forbidden.
extern "C" wint_t __cdecl ungetwc(wint_t const c, FILE* const stream)
{
_VALIDATE_RETURN(stream != nullptr, EINVAL, WEOF);
wint_t return_value = WEOF;
_lock_file(stream);
__try
{
return_value = _ungetwc_nolock(c, stream);
}
__finally
{
_unlock_file(stream);
}
__endtry
return return_value;
}
// Helper function for _ungetwc_nolock() that handles text mode ungetting.
static wint_t __cdecl ungetwc_text_mode_nolock(wint_t const c, __crt_stdio_stream const stream) throw()
{
// The stream is open in text mode, and we need to do the unget differently
// depending on whether the stream is open in ANSI or Unicode mode.
__crt_lowio_text_mode const text_mode = _textmode_safe(_fileno(stream.public_stream()));
int count = 0;
char characters[MB_LEN_MAX] = { 0 };
// If the file is open in ANSI mode, we need to convert the wide character
// to multibyte so that we can unget the multibyte character back into the
// stream:
if (text_mode == __crt_lowio_text_mode::ansi)
{
// If conversion fails, errno is set by wctomb_s and we can just return:
if (wctomb_s(&count, characters, MB_LEN_MAX, c) != 0)
return WEOF;
}
// Otherwise, the file is open in Unicode mode. This means the characters
// in the stream were originally Unicode (and not multibyte). Hence, we
// do not need to translate back to multibyte. This is true for both UTF-16
// and UTF-8, because the lowio read converts UTF-8 data to UTF-16.
else
{
char const* c_bytes = reinterpret_cast<char const*>(&c);
characters[0] = c_bytes[0];
characters[1] = c_bytes[1];
count = 2;
}
// At this point, the file must be buffered, so we know the base is non-null.
// First we need to ensure there is sufficient room in the buffer to store
// the translated data:
if (stream->_ptr < stream->_base + count)
{
if (stream->_cnt)
return WEOF;
if (count > stream->_bufsiz)
return WEOF;
stream->_ptr = count + stream->_base;
}
for (int i = count - 1; i >= 0; --i)
{
*--stream->_ptr = characters[i];
}
stream->_cnt += count;
stream.unset_flags(_IOEOF);
stream.set_flags(_IOREAD);
return static_cast<wint_t>(0xffff & c);
}
// Helper function for _ungetwc_nolock() that handles binary mode ungetting
static wint_t __cdecl ungetwc_binary_mode_nolock(wint_t const c, __crt_stdio_stream const stream) throw()
{
wchar_t const wide_c = static_cast<wchar_t>(c);
// At this point, the file must be buffered, so we know the base is non-null.
// First, we need to ensure there is sufficient room in the buffer to store
// the character:
if (stream->_ptr < stream->_base + sizeof(wchar_t))
{
// If we've already ungotten one character and it has not yet been read,
// there may not be room for this unget. In this case, there's nothing
// we can do so we simply fail:
if (stream->_cnt)
return WEOF;
if (sizeof(wchar_t) > stream->_bufsiz)
return WEOF;
stream->_ptr = sizeof(wchar_t) + stream->_base;
}
wchar_t*& wide_stream_ptr = reinterpret_cast<wchar_t*&>(stream->_ptr);
// If the stream is string-backed, we cannot modify the buffer. We retreat
// the stream pointer and test if the character being ungotten is the same
// as the character that was last read. If they are the same, then we allow
// the unget (because we don't have to modify the buffer). If they are not
// the same, then we re-advance the stream pointer and fail:
if (stream.is_string_backed())
{
if (*--wide_stream_ptr != wide_c)
{
++wide_stream_ptr;
return WEOF;
}
}
// Otherwise, the stream is file-backed and open in binary mode, and we can
// just write the character to the front of the stream:
else
{
*--wide_stream_ptr = wide_c;
}
stream->_cnt += sizeof(wchar_t);
stream.unset_flags(_IOEOF);
stream.set_flags(_IOREAD);
return static_cast<wint_t>(wide_c);
}
extern "C" wint_t __cdecl _ungetwc_nolock(wint_t const c, FILE* const public_stream)
{
__crt_stdio_stream const stream(public_stream);
// Ungetting WEOF is expressly forbidden:
if (c == WEOF)
return WEOF;
// To unget, the stream must currently be in read mode, _or_ it must be open
// for update (read and write) and must not _currently_ be in write mode:
bool const is_in_read_mode = stream.has_all_of(_IOREAD);
bool const is_in_update_mode = stream.has_all_of(_IOUPDATE);
bool const is_in_write_mode = stream.has_all_of(_IOWRITE);
if (!is_in_read_mode && !(is_in_update_mode && !is_in_write_mode))
return WEOF;
// If the stream is currently unbuffered, buffer it:
if (stream->_base == nullptr)
__acrt_stdio_allocate_buffer_nolock(stream.public_stream());
// If the stream is file-backed and is open in text mode, we need to perform
// text mode translations:
if (!stream.is_string_backed() && (_osfile_safe(_fileno(stream.public_stream())) & FTEXT) != 0)
{
return ungetwc_text_mode_nolock(c, stream);
}
// Otherwise, the stream is string-backed or is a file-backed file open in
// binary mode; we can simply push the character back into the stream:
return ungetwc_binary_mode_nolock(c, stream);
}