xref: /reactos/sdk/lib/conutils/stream.c (revision d7fd62d4)
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 #endif
236     return TRUE;
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((intptr_t)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 */
301