1 // 2 // ungetwc.cpp 3 // 4 // Copyright (c) Microsoft Corporation. All rights reserved. 5 // 6 // Defines ungetwc(), which pushes a wide character back into a stream. 7 // 8 #include <corecrt_internal_stdio.h> 9 10 11 12 // Pushes a character ("ungets" it) back into a stream. It is possible to push 13 // back one character. It may not be possible to push back more than one 14 // character in a row. Returns the pushed-back character on success; returns 15 // WEOF on failure. Ungetting WEOF is expressly forbidden. 16 extern "C" wint_t __cdecl ungetwc(wint_t const c, FILE* const stream) 17 { 18 _VALIDATE_RETURN(stream != nullptr, EINVAL, WEOF); 19 20 wint_t return_value = WEOF; 21 22 _lock_file(stream); 23 __try 24 { 25 return_value = _ungetwc_nolock(c, stream); 26 } 27 __finally 28 { 29 _unlock_file(stream); 30 } 31 __endtry 32 33 return return_value; 34 } 35 36 37 38 // Helper function for _ungetwc_nolock() that handles text mode ungetting. 39 static wint_t __cdecl ungetwc_text_mode_nolock(wint_t const c, __crt_stdio_stream const stream) throw() 40 { 41 // The stream is open in text mode, and we need to do the unget differently 42 // depending on whether the stream is open in ANSI or Unicode mode. 43 __crt_lowio_text_mode const text_mode = _textmode_safe(_fileno(stream.public_stream())); 44 45 int count = 0; 46 char characters[MB_LEN_MAX] = { 0 }; 47 48 // If the file is open in ANSI mode, we need to convert the wide character 49 // to multibyte so that we can unget the multibyte character back into the 50 // stream: 51 if (text_mode == __crt_lowio_text_mode::ansi) 52 { 53 // If conversion fails, errno is set by wctomb_s and we can just return: 54 if (wctomb_s(&count, characters, MB_LEN_MAX, c) != 0) 55 return WEOF; 56 } 57 // Otherwise, the file is open in Unicode mode. This means the characters 58 // in the stream were originally Unicode (and not multibyte). Hence, we 59 // do not need to translate back to multibyte. This is true for both UTF-16 60 // and UTF-8, because the lowio read converts UTF-8 data to UTF-16. 61 else 62 { 63 char const* c_bytes = reinterpret_cast<char const*>(&c); 64 characters[0] = c_bytes[0]; 65 characters[1] = c_bytes[1]; 66 count = 2; 67 } 68 69 // At this point, the file must be buffered, so we know the base is non-null. 70 // First we need to ensure there is sufficient room in the buffer to store 71 // the translated data: 72 if (stream->_ptr < stream->_base + count) 73 { 74 if (stream->_cnt) 75 return WEOF; 76 77 if (count > stream->_bufsiz) 78 return WEOF; 79 80 stream->_ptr = count + stream->_base; 81 } 82 83 for (int i = count - 1; i >= 0; --i) 84 { 85 *--stream->_ptr = characters[i]; 86 } 87 88 stream->_cnt += count; 89 90 stream.unset_flags(_IOEOF); 91 stream.set_flags(_IOREAD); 92 return static_cast<wint_t>(0xffff & c); 93 } 94 95 96 97 // Helper function for _ungetwc_nolock() that handles binary mode ungetting 98 static wint_t __cdecl ungetwc_binary_mode_nolock(wint_t const c, __crt_stdio_stream const stream) throw() 99 { 100 wchar_t const wide_c = static_cast<wchar_t>(c); 101 102 // At this point, the file must be buffered, so we know the base is non-null. 103 // First, we need to ensure there is sufficient room in the buffer to store 104 // the character: 105 if (stream->_ptr < stream->_base + sizeof(wchar_t)) 106 { 107 // If we've already ungotten one character and it has not yet been read, 108 // there may not be room for this unget. In this case, there's nothing 109 // we can do so we simply fail: 110 if (stream->_cnt) 111 return WEOF; 112 113 if (sizeof(wchar_t) > stream->_bufsiz) 114 return WEOF; 115 116 stream->_ptr = sizeof(wchar_t) + stream->_base; 117 } 118 119 wchar_t*& wide_stream_ptr = reinterpret_cast<wchar_t*&>(stream->_ptr); 120 121 // If the stream is string-backed, we cannot modify the buffer. We retreat 122 // the stream pointer and test if the character being ungotten is the same 123 // as the character that was last read. If they are the same, then we allow 124 // the unget (because we don't have to modify the buffer). If they are not 125 // the same, then we re-advance the stream pointer and fail: 126 if (stream.is_string_backed()) 127 { 128 if (*--wide_stream_ptr != wide_c) 129 { 130 ++wide_stream_ptr; 131 return WEOF; 132 } 133 } 134 // Otherwise, the stream is file-backed and open in binary mode, and we can 135 // just write the character to the front of the stream: 136 else 137 { 138 *--wide_stream_ptr = wide_c; 139 } 140 141 stream->_cnt += sizeof(wchar_t); 142 143 stream.unset_flags(_IOEOF); 144 stream.set_flags(_IOREAD); 145 146 return static_cast<wint_t>(wide_c); 147 } 148 149 150 151 extern "C" wint_t __cdecl _ungetwc_nolock(wint_t const c, FILE* const public_stream) 152 { 153 __crt_stdio_stream const stream(public_stream); 154 155 // Ungetting WEOF is expressly forbidden: 156 if (c == WEOF) 157 return WEOF; 158 159 // To unget, the stream must currently be in read mode, _or_ it must be open 160 // for update (read and write) and must not _currently_ be in write mode: 161 bool const is_in_read_mode = stream.has_all_of(_IOREAD); 162 bool const is_in_update_mode = stream.has_all_of(_IOUPDATE); 163 bool const is_in_write_mode = stream.has_all_of(_IOWRITE); 164 165 if (!is_in_read_mode && !(is_in_update_mode && !is_in_write_mode)) 166 return WEOF; 167 168 // If the stream is currently unbuffered, buffer it: 169 if (stream->_base == nullptr) 170 __acrt_stdio_allocate_buffer_nolock(stream.public_stream()); 171 172 // If the stream is file-backed and is open in text mode, we need to perform 173 // text mode translations: 174 if (!stream.is_string_backed() && (_osfile_safe(_fileno(stream.public_stream())) & FTEXT) != 0) 175 { 176 return ungetwc_text_mode_nolock(c, stream); 177 } 178 179 // Otherwise, the stream is string-backed or is a file-backed file open in 180 // binary mode; we can simply push the character back into the stream: 181 return ungetwc_binary_mode_nolock(c, stream); 182 } 183