[CONUTILS] In ConWrite(), emit an \r when a \n is encountered but not already precede...
[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
77 /*
78 * See http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html
79 * and http://archives.miloush.net/michkap/archive/2009/08/14/9869928.html
80 * for more details.
81 */
82
83 // NOTE1: May the translated mode be cached somehow?
84 // NOTE2: We may also call IsConsoleHandle to directly set the mode to
85 // _O_U16TEXT if it's ok??
86 // NOTE3: _setmode returns the previous mode, or -1 if failure.
87 #define CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage) \
88 do { \
89 fflush((Stream)->fStream); \
90 if ((Mode) < ARRAYSIZE(ConToCRTMode)) \
91 _setmode(_fileno((Stream)->fStream), ConToCRTMode[(Mode)]); \
92 else \
93 _setmode(_fileno((Stream)->fStream), _O_TEXT); /* Default to ANSI text */ \
94 } while(0)
95
96 #else /* defined(USE_CRT) */
97
98 /*
99 * We set Stream->CodePage to INVALID_CP (== -1) to signal that the code page
100 * is either not assigned (if the mode is Binary, WideText, or UTF16Text), or
101 * is not cached (if the mode is AnsiText). In this latter case the code page
102 * is resolved inside ConWrite. Finally, if the mode is UTF8Text, the code page
103 * cache is always set to CP_UTF8.
104 * The code page cache can be reset by an explicit call to CON_STREAM_SET_MODE
105 * (i.e. by calling ConStreamSetMode, or by reinitializing the stream with
106 * ConStreamInit(Ex)).
107 *
108 * NOTE: the reserved values are: 0 (CP_ACP), 1 (CP_OEMCP), 2 (CP_MACCP),
109 * 3 (CP_THREAD_ACP), 42 (CP_SYMBOL), 65000 (CP_UTF7) and 65001 (CP_UTF8).
110 */
111 #define CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage) \
112 do { \
113 (Stream)->Mode = (Mode); \
114 \
115 if ((Mode) == AnsiText) \
116 (Stream)->CodePage = CacheCodePage; /* Possibly assigned */ \
117 else if ((Mode) == UTF8Text) \
118 (Stream)->CodePage = CP_UTF8; /* Fixed */ \
119 else /* Mode == Binary, WideText, UTF16Text */ \
120 (Stream)->CodePage = INVALID_CP; /* Not assigned (meaningless) */ \
121 } while(0)
122
123 #endif /* defined(USE_CRT) */
124
125
126 BOOL
127 ConStreamInitEx(
128 OUT PCON_STREAM Stream,
129 IN PVOID Handle,
130 IN CON_STREAM_MODE Mode,
131 IN UINT CacheCodePage OPTIONAL,
132 // IN CON_READ_FUNC ReadFunc OPTIONAL,
133 IN CON_WRITE_FUNC WriteFunc OPTIONAL)
134 {
135 /* Parameters validation */
136 if (!Stream || !Handle || (Mode > UTF8Text))
137 return FALSE;
138
139 #ifdef USE_CRT
140
141 Stream->fStream = (FILE*)Handle;
142
143 #else
144
145 if ((HANDLE)Handle == INVALID_HANDLE_VALUE)
146 return FALSE;
147
148 /*
149 * As the user calls us by giving us an existing handle to attach on,
150 * it is not our duty to close it if we are called again. The user
151 * is responsible for having opened those handles, and is responsible
152 * for closing them!
153 */
154 #if 0
155 /* Attempt to close the handle of the old stream */
156 if (/* Stream->IsInitialized && */ Stream->hHandle &&
157 Stream->hHandle != INVALID_HANDLE_VALUE)
158 {
159 CloseHandle(Stream->hHandle);
160 }
161 #endif
162
163 /* Initialize the stream critical section if not already done */
164 if (!Stream->IsInitialized)
165 {
166 InitializeCriticalSection/*AndSpinCount*/(&Stream->Lock /* , 4000 */);
167 Stream->IsInitialized = TRUE;
168 }
169
170 Stream->hHandle = (HANDLE)Handle;
171 Stream->IsConsole = IsConsoleHandle(Stream->hHandle);
172
173 #endif /* defined(USE_CRT) */
174
175 /* Set the correct file translation mode */
176 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
177
178 /* Use the default 'ConWrite' helper if nothing is specified */
179 Stream->WriteFunc = (WriteFunc ? WriteFunc : ConWrite);
180
181 return TRUE;
182 }
183
184 BOOL
185 ConStreamInit(
186 OUT PCON_STREAM Stream,
187 IN PVOID Handle,
188 IN CON_STREAM_MODE Mode,
189 IN UINT CacheCodePage OPTIONAL)
190 {
191 return ConStreamInitEx(Stream, Handle, Mode, CacheCodePage, ConWrite);
192 }
193
194 BOOL
195 ConStreamSetMode(
196 IN PCON_STREAM Stream,
197 IN CON_STREAM_MODE Mode,
198 IN UINT CacheCodePage OPTIONAL)
199 {
200 /* Parameters validation */
201 if (!Stream || (Mode > UTF8Text))
202 return FALSE;
203
204 #ifdef USE_CRT
205 if (!Stream->fStream)
206 return FALSE;
207 #endif
208
209 /* Set the correct file translation mode */
210 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
211 return TRUE;
212 }
213
214 BOOL
215 ConStreamSetCacheCodePage(
216 IN PCON_STREAM Stream,
217 IN UINT CacheCodePage)
218 {
219 #ifdef USE_CRT
220 // FIXME!
221 #warning The ConStreamSetCacheCodePage function does not make much sense with the CRT!
222 #else
223 CON_STREAM_MODE Mode;
224
225 /* Parameters validation */
226 if (!Stream)
227 return FALSE;
228
229 /*
230 * Keep the original stream mode but set the correct file code page
231 * (will be reset only if Mode == AnsiText).
232 */
233 Mode = Stream->Mode;
234 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
235 return TRUE;
236 #endif
237 }
238
239 HANDLE
240 ConStreamGetOSHandle(
241 IN PCON_STREAM Stream)
242 {
243 /* Parameters validation */
244 if (!Stream)
245 return INVALID_HANDLE_VALUE;
246
247 /*
248 * See https://support.microsoft.com/kb/99173
249 * for more details.
250 */
251
252 #ifdef USE_CRT
253 if (!Stream->fStream)
254 return INVALID_HANDLE_VALUE;
255
256 return (HANDLE)_get_osfhandle(_fileno(Stream->fStream));
257 #else
258 return Stream->hHandle;
259 #endif
260 }
261
262 BOOL
263 ConStreamSetOSHandle(
264 IN PCON_STREAM Stream,
265 IN HANDLE Handle)
266 {
267 /* Parameters validation */
268 if (!Stream)
269 return FALSE;
270
271 /*
272 * See https://support.microsoft.com/kb/99173
273 * for more details.
274 */
275
276 #ifdef USE_CRT
277 if (!Stream->fStream)
278 return FALSE;
279
280 int fdOut = _open_osfhandle(Handle, _O_TEXT /* FIXME! */);
281 FILE* fpOut = _fdopen(fdOut, "w");
282 *Stream->fStream = *fpOut;
283 /// setvbuf(Stream->fStream, NULL, _IONBF, 0);
284
285 return TRUE;
286 #else
287 /* Flush the stream and reset its handle */
288 if (Stream->hHandle != INVALID_HANDLE_VALUE)
289 FlushFileBuffers(Stream->hHandle);
290
291 Stream->hHandle = Handle;
292 Stream->IsConsole = IsConsoleHandle(Stream->hHandle);
293
294 // NOTE: Mode reset??
295
296 return TRUE;
297 #endif
298 }
299
300 /* EOF */