0b137ea2f7b5eb2825edc7827c9857af815a8413
[reactos.git] / sdk / lib / conutils / stream.c
1 /*
2 * PROJECT: ReactOS Console Utilities Library
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Provides basic abstraction wrappers around CRT streams or
5 * Win32 console API I/O functions, to deal with i18n + Unicode
6 * related problems.
7 * COPYRIGHT: Copyright 2017-2018 ReactOS Team
8 * Copyright 2017-2018 Hermes Belusca-Maito
9 */
10
11 /**
12 * @file stream.c
13 * @ingroup ConUtils
14 *
15 * @brief Console I/O streams
16 **/
17
18 /*
19 * Enable this define if you want to only use CRT functions to output
20 * UNICODE stream to the console, as in the way explained by
21 * http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html
22 */
23 /** NOTE: Experimental! Don't use USE_CRT yet because output to console is a bit broken **/
24 // #define USE_CRT
25
26 /* FIXME: Temporary HACK before we cleanly support UNICODE functions */
27 #define UNICODE
28 #define _UNICODE
29
30 #ifdef USE_CRT
31 #include <fcntl.h>
32 #include <io.h>
33 #endif /* USE_CRT */
34
35 #include <windef.h>
36 #include <winbase.h>
37 #include <winnls.h>
38 // #include <winuser.h> // MAKEINTRESOURCEW, RT_STRING
39 #include <wincon.h> // Console APIs (only if kernel32 support included)
40 #include <strsafe.h>
41
42 #include "conutils.h"
43 #include "stream.h"
44 #include "stream_private.h"
45
46
47 /*
48 * Standard console streams, initialized by
49 * calls to ConStreamInit/ConInitStdStreams.
50 */
51 #if 0 // FIXME!
52 CON_STREAM StdStreams[3] =
53 {
54 {0}, // StdIn
55 {0}, // StdOut
56 {0}, // StdErr
57 };
58 #else
59 CON_STREAM csStdIn;
60 CON_STREAM csStdOut;
61 CON_STREAM csStdErr;
62 #endif
63
64
65 /* Stream translation modes */
66 #ifdef USE_CRT
67 /* Lookup table to convert CON_STREAM_MODE to CRT mode */
68 static int ConToCRTMode[] =
69 {
70 _O_BINARY, // Binary (untranslated)
71 _O_TEXT, // AnsiText (translated)
72 _O_WTEXT, // WideText (UTF16 with BOM; translated)
73 _O_U16TEXT, // UTF16Text (UTF16 without BOM; translated)
74 _O_U8TEXT, // UTF8Text (UTF8 without BOM; translated)
75 };
76 #endif
77
78 #ifdef USE_CRT
79
80 /*
81 * See http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html
82 * and http://archives.miloush.net/michkap/archive/2009/08/14/9869928.html
83 * for more details.
84 */
85
86 // NOTE: May the translated mode be cached somehow?
87 // NOTE2: We may also call IsConsoleHandle to directly set the mode to
88 // _O_U16TEXT if it's ok??
89 // NOTE3: _setmode returns the previous mode, or -1 if failure.
90 #define CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage) \
91 do { \
92 fflush((Stream)->fStream); \
93 if ((Mode) < ARRAYSIZE(ConToCRTMode)) \
94 _setmode(_fileno((Stream)->fStream), ConToCRTMode[(Mode)]); \
95 else \
96 _setmode(_fileno((Stream)->fStream), _O_TEXT); /* Default to ANSI text */ \
97 } while(0)
98
99 #else /* defined(USE_CRT) */
100
101 /*
102 * We set Stream->CodePage to INVALID_CP (= -1) to signal that the codepage
103 * is either not assigned (if the mode is Binary, WideText, or UTF16Text), or
104 * is not cached yet (if the mode is AnsiText). In this latter case the cache
105 * is resolved inside ConWrite. Finally, if the mode is UTF8Text, the codepage
106 * cache is set to CP_UTF8.
107 * The codepage cache can be reset by an explicit call to CON_STREAM_SET_MODE
108 * (i.e. by calling ConStreamSetMode, or by reinitializing the stream with
109 * ConStreamInit(Ex)).
110 *
111 * NOTE: the magic value could not be '0' since it is reserved for CP_ACP.
112 */
113 #define CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage) \
114 do { \
115 (Stream)->Mode = (Mode); \
116 \
117 if ((Mode) == AnsiText) \
118 (Stream)->CodePage = CacheCodePage; /* Possibly assigned */ \
119 else if ((Mode) == UTF8Text) \
120 (Stream)->CodePage = CP_UTF8; /* Fixed */ \
121 else /* Mode == Binary, WideText, UTF16Text */ \
122 (Stream)->CodePage = INVALID_CP; /* Not assigned (meaningless) */ \
123 } while(0)
124
125 #endif /* defined(USE_CRT) */
126
127
128 BOOL
129 ConStreamInitEx(
130 OUT PCON_STREAM Stream,
131 IN PVOID Handle,
132 IN CON_STREAM_MODE Mode,
133 IN UINT CacheCodePage OPTIONAL,
134 // IN CON_READ_FUNC ReadFunc OPTIONAL,
135 IN CON_WRITE_FUNC WriteFunc OPTIONAL)
136 {
137 /* Parameters validation */
138 if (!Stream || !Handle || (Mode > UTF8Text))
139 return FALSE;
140
141 #ifdef USE_CRT
142
143 Stream->fStream = (FILE*)Handle;
144
145 #else
146
147 if ((HANDLE)Handle == INVALID_HANDLE_VALUE)
148 return FALSE;
149
150 /*
151 * As the user calls us by giving us an existing handle to attach on,
152 * it is not our duty to close it if we are called again. The user
153 * is responsible for having opened those handles, and is responsible
154 * for closing them!
155 */
156 #if 0
157 /* Attempt to close the handle of the old stream */
158 if (/* Stream->IsInitialized && */ Stream->hHandle &&
159 Stream->hHandle != INVALID_HANDLE_VALUE)
160 {
161 CloseHandle(Stream->hHandle);
162 }
163 #endif
164
165 /* Initialize the stream critical section if not already done */
166 if (!Stream->IsInitialized)
167 {
168 InitializeCriticalSection/*AndSpinCount*/(&Stream->Lock /* , 4000 */);
169 Stream->IsInitialized = TRUE;
170 }
171
172 Stream->hHandle = (HANDLE)Handle;
173 Stream->IsConsole = IsConsoleHandle(Stream->hHandle);
174
175 #endif /* defined(USE_CRT) */
176
177 /* Set the correct file translation mode */
178 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
179
180 /* Use the default 'ConWrite' helper if nothing is specified */
181 Stream->WriteFunc = (WriteFunc ? WriteFunc : ConWrite);
182
183 return TRUE;
184 }
185
186 BOOL
187 ConStreamInit(
188 OUT PCON_STREAM Stream,
189 IN PVOID Handle,
190 IN CON_STREAM_MODE Mode,
191 IN UINT CacheCodePage OPTIONAL)
192 {
193 return ConStreamInitEx(Stream, Handle, Mode, CacheCodePage, ConWrite);
194 }
195
196 BOOL
197 ConStreamSetMode(
198 IN PCON_STREAM Stream,
199 IN CON_STREAM_MODE Mode,
200 IN UINT CacheCodePage OPTIONAL)
201 {
202 /* Parameters validation */
203 if (!Stream || (Mode > UTF8Text))
204 return FALSE;
205
206 #ifdef USE_CRT
207 if (!Stream->fStream)
208 return FALSE;
209 #endif
210
211 /* Set the correct file translation mode */
212 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
213 return TRUE;
214 }
215
216 BOOL
217 ConStreamSetCacheCodePage(
218 IN PCON_STREAM Stream,
219 IN UINT CacheCodePage)
220 {
221 #ifdef USE_CRT
222 // FIXME!
223 #warning The ConStreamSetCacheCodePage function does not make much sense with the CRT!
224 #else
225 CON_STREAM_MODE Mode;
226
227 /* Parameters validation */
228 if (!Stream)
229 return FALSE;
230
231 /*
232 * Keep the original stream mode but set the correct file codepage
233 * (will be reset only if Mode == AnsiText).
234 */
235 Mode = Stream->Mode;
236 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
237 return TRUE;
238 #endif
239 }
240
241 HANDLE
242 ConStreamGetOSHandle(
243 IN PCON_STREAM Stream)
244 {
245 /* Parameters validation */
246 if (!Stream)
247 return INVALID_HANDLE_VALUE;
248
249 /*
250 * See https://support.microsoft.com/kb/99173
251 * for more details.
252 */
253
254 #ifdef USE_CRT
255 if (!Stream->fStream)
256 return INVALID_HANDLE_VALUE;
257
258 return (HANDLE)_get_osfhandle(_fileno(Stream->fStream));
259 #else
260 return Stream->hHandle;
261 #endif
262 }
263
264 BOOL
265 ConStreamSetOSHandle(
266 IN PCON_STREAM Stream,
267 IN HANDLE Handle)
268 {
269 /* Parameters validation */
270 if (!Stream)
271 return FALSE;
272
273 /*
274 * See https://support.microsoft.com/kb/99173
275 * for more details.
276 */
277
278 #ifdef USE_CRT
279 if (!Stream->fStream)
280 return FALSE;
281
282 int fdOut = _open_osfhandle(Handle, _O_TEXT /* FIXME! */);
283 FILE* fpOut = _fdopen(fdOut, "w");
284 *Stream->fStream = *fpOut;
285 /// setvbuf(Stream->fStream, NULL, _IONBF, 0);
286
287 return TRUE;
288 #else
289 /* Flush the stream and reset its handle */
290 if (Stream->hHandle != INVALID_HANDLE_VALUE)
291 FlushFileBuffers(Stream->hHandle);
292
293 Stream->hHandle = Handle;
294 Stream->IsConsole = IsConsoleHandle(Stream->hHandle);
295
296 // NOTE: Mode reset??
297
298 return TRUE;
299 #endif
300 }
301
302 /* EOF */