xref: /reactos/sdk/lib/ucrt/stdio/ungetwc.cpp (revision e3e520d1)
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