xref: /reactos/sdk/lib/conutils/stream.c (revision 3c774903)
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 */
303