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.
ungetwc(wint_t const c,FILE * const stream)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.
ungetwc_text_mode_nolock(wint_t const c,__crt_stdio_stream const stream)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
ungetwc_binary_mode_nolock(wint_t const c,__crt_stdio_stream const stream)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
_ungetwc_nolock(wint_t const c,FILE * const public_stream)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