[CONUTILS] Fix macro definition.
[reactos.git] / sdk / lib / conutils / stream.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Console Utilities Library
4 * FILE: sdk/lib/conutils/stream.c
5 * PURPOSE: Provides basic abstraction wrappers around CRT streams or
6 * Win32 console API I/O functions, to deal with i18n + Unicode
7 * related problems.
8 * PROGRAMMERS: - Hermes Belusca-Maito (for the library);
9 * - All programmers who wrote the different console applications
10 * from which I took those functions and improved them.
11 */
12
13 /*
14 * Enable this define if you want to only use CRT functions to output
15 * UNICODE stream to the console, as in the way explained by
16 * http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html
17 */
18 /** NOTE: Experimental! Don't use USE_CRT yet because output to console is a bit broken **/
19 // #define USE_CRT
20
21 /* FIXME: Temporary HACK before we cleanly support UNICODE functions */
22 #define UNICODE
23 #define _UNICODE
24
25 #ifdef USE_CRT
26 #include <fcntl.h>
27 #include <io.h>
28 #endif /* USE_CRT */
29
30 #include <stdlib.h> // limits.h // For MB_LEN_MAX
31
32 #include <windef.h>
33 #include <winbase.h>
34 #include <winnls.h>
35 #include <winuser.h> // MAKEINTRESOURCEW, RT_STRING
36 #include <wincon.h> // Console APIs (only if kernel32 support included)
37 #include <strsafe.h>
38
39 /* PSEH for SEH Support */
40 #include <pseh/pseh2.h>
41
42 #include "conutils.h"
43 #include "stream.h"
44
45
46 // #define RC_STRING_MAX_SIZE 4096
47 #define CON_RC_STRING_MAX_SIZE 4096
48 // #define MAX_BUFFER_SIZE 4096 // Some programs (wlanconf, shutdown) set it to 5024
49 // #define OUTPUT_BUFFER_SIZE 4096 // Name given in cmd/console.c
50 // MAX_STRING_SIZE // Name given in diskpart
51
52 // #define MAX_MESSAGE_SIZE 512 // See shutdown...
53
54
55 /*
56 * Console I/O streams
57 */
58
59 typedef struct _CON_STREAM
60 {
61 CON_WRITE_FUNC WriteFunc;
62
63 #ifdef USE_CRT
64 FILE* fStream;
65 #else
66 BOOL IsInitialized;
67 CRITICAL_SECTION Lock;
68
69 HANDLE hHandle;
70
71 /*
72 * TRUE if 'hHandle' refers to a console, in which case I/O UTF-16
73 * is directly used. If 'hHandle' refers to a file or a pipe, the
74 * 'Mode' flag is used.
75 */
76 BOOL IsConsole;
77
78 /*
79 * The 'Mode' flag is used to know the translation mode
80 * when 'hHandle' refers to a file or a pipe.
81 */
82 CON_STREAM_MODE Mode;
83 UINT CodePage; // Used to convert UTF-16 text to some ANSI codepage.
84 #endif /* defined(USE_CRT) */
85 } CON_STREAM, *PCON_STREAM;
86
87 /*
88 * Standard console streams, initialized by
89 * calls to ConStreamInit/ConInitStdStreams.
90 */
91 #if 0 // FIXME!
92 CON_STREAM StdStreams[3] =
93 {
94 {0}, // StdIn
95 {0}, // StdOut
96 {0}, // StdErr
97 };
98 #else
99 CON_STREAM csStdIn;
100 CON_STREAM csStdOut;
101 CON_STREAM csStdErr;
102 #endif
103
104
105 /* Stream translation modes */
106 #ifdef USE_CRT
107 /* Lookup table to convert CON_STREAM_MODE to CRT mode */
108 static int ConToCRTMode[] =
109 {
110 _O_BINARY, // Binary (untranslated)
111 _O_TEXT, // AnsiText (translated)
112 _O_WTEXT, // WideText (UTF16 with BOM; translated)
113 _O_U16TEXT, // UTF16Text (UTF16 without BOM; translated)
114 _O_U8TEXT, // UTF8Text (UTF8 without BOM; translated)
115 };
116 #endif
117
118 #ifdef USE_CRT
119
120 /*
121 * See http://archives.miloush.net/michkap/archive/2008/03/18/8306597.html
122 * and http://archives.miloush.net/michkap/archive/2009/08/14/9869928.html
123 * for more details.
124 */
125
126 // NOTE: May the translated mode be cached somehow?
127 // NOTE2: We may also call IsConsoleHandle to directly set the mode to
128 // _O_U16TEXT if it's ok??
129 // NOTE3: _setmode returns the previous mode, or -1 if failure.
130 #define CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage) \
131 do { \
132 fflush((Stream)->fStream); \
133 if ((Mode) < ARRAYSIZE(ConToCRTMode)) \
134 _setmode(_fileno((Stream)->fStream), ConToCRTMode[(Mode)]); \
135 else \
136 _setmode(_fileno((Stream)->fStream), _O_TEXT); /* Default to ANSI text */ \
137 } while(0)
138
139 #else /* defined(USE_CRT) */
140
141 /*
142 * We set Stream->CodePage to INVALID_CP (= -1) to signal that the codepage
143 * is either not assigned (if the mode is Binary, WideText, or UTF16Text), or
144 * is not cached yet (if the mode is AnsiText). In this latter case the cache
145 * is resolved inside ConWrite. Finally, if the mode is UTF8Text, the codepage
146 * cache is set to CP_UTF8.
147 * The codepage cache can be reset by an explicit call to CON_STREAM_SET_MODE
148 * (i.e. by calling ConStreamSetMode, or by reinitializing the stream with
149 * ConStreamInit(Ex)).
150 *
151 * NOTE: the magic value could not be '0' since it is reserved for CP_ACP.
152 */
153 #define CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage) \
154 do { \
155 (Stream)->Mode = (Mode); \
156 \
157 if ((Mode) == AnsiText) \
158 (Stream)->CodePage = CacheCodePage; /* Possibly assigned */ \
159 else if ((Mode) == UTF8Text) \
160 (Stream)->CodePage = CP_UTF8; /* Fixed */ \
161 else /* Mode == Binary, WideText, UTF16Text */ \
162 (Stream)->CodePage = INVALID_CP; /* Not assigned (meaningless) */ \
163 } while(0)
164
165 #endif /* defined(USE_CRT) */
166
167
168 BOOL
169 ConStreamInitEx(
170 OUT PCON_STREAM Stream,
171 IN PVOID Handle,
172 IN CON_STREAM_MODE Mode,
173 IN UINT CacheCodePage OPTIONAL,
174 // IN CON_READ_FUNC ReadFunc OPTIONAL,
175 IN CON_WRITE_FUNC WriteFunc OPTIONAL)
176 {
177 /* Parameters validation */
178 if (!Stream || !Handle || (Mode > UTF8Text))
179 return FALSE;
180
181 #ifdef USE_CRT
182
183 Stream->fStream = (FILE*)Handle;
184
185 #else
186
187 if ((HANDLE)Handle == INVALID_HANDLE_VALUE)
188 return FALSE;
189
190 /*
191 * As the user calls us by giving us an existing handle to attach on,
192 * it is not our duty to close it if we are called again. The user
193 * is responsible for having opened those handles, and is responsible
194 * for closing them!
195 */
196 #if 0
197 /* Attempt to close the handle of the old stream */
198 if (/* Stream->IsInitialized && */ Stream->hHandle &&
199 Stream->hHandle != INVALID_HANDLE_VALUE)
200 {
201 CloseHandle(Stream->hHandle);
202 }
203 #endif
204
205 /* Initialize the stream critical section if not already done */
206 if (!Stream->IsInitialized)
207 {
208 InitializeCriticalSection/*AndSpinCount*/(&Stream->Lock /* , 4000 */);
209 Stream->IsInitialized = TRUE;
210 }
211
212 Stream->hHandle = (HANDLE)Handle;
213 Stream->IsConsole = IsConsoleHandle(Stream->hHandle);
214
215 #endif /* defined(USE_CRT) */
216
217 /* Set the correct file translation mode */
218 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
219
220 /* Use the default 'ConWrite' helper if nothing is specified */
221 Stream->WriteFunc = (WriteFunc ? WriteFunc : ConWrite);
222
223 return TRUE;
224 }
225
226 BOOL
227 ConStreamInit(
228 OUT PCON_STREAM Stream,
229 IN PVOID Handle,
230 IN CON_STREAM_MODE Mode,
231 IN UINT CacheCodePage OPTIONAL)
232 {
233 return ConStreamInitEx(Stream, Handle, Mode, CacheCodePage, ConWrite);
234 }
235
236 BOOL
237 ConStreamSetMode(
238 IN PCON_STREAM Stream,
239 IN CON_STREAM_MODE Mode,
240 IN UINT CacheCodePage OPTIONAL)
241 {
242 /* Parameters validation */
243 if (!Stream || (Mode > UTF8Text))
244 return FALSE;
245
246 #ifdef USE_CRT
247 if (!Stream->fStream)
248 return FALSE;
249 #endif
250
251 /* Set the correct file translation mode */
252 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
253 return TRUE;
254 }
255
256 BOOL
257 ConStreamSetCacheCodePage(
258 IN PCON_STREAM Stream,
259 IN UINT CacheCodePage)
260 {
261 #ifdef USE_CRT
262 // FIXME!
263 #warning The ConStreamSetCacheCodePage function does not make much sense with the CRT!
264 #else
265 CON_STREAM_MODE Mode;
266
267 /* Parameters validation */
268 if (!Stream)
269 return FALSE;
270
271 /*
272 * Keep the original stream mode but set the correct file codepage
273 * (will be reset only if Mode == AnsiText).
274 */
275 Mode = Stream->Mode;
276 CON_STREAM_SET_MODE(Stream, Mode, CacheCodePage);
277 return TRUE;
278 #endif
279 }
280
281 HANDLE
282 ConStreamGetOSHandle(
283 IN PCON_STREAM Stream)
284 {
285 /* Parameters validation */
286 if (!Stream)
287 return INVALID_HANDLE_VALUE;
288
289 /*
290 * See https://support.microsoft.com/kb/99173
291 * for more details.
292 */
293
294 #ifdef USE_CRT
295 if (!Stream->fStream)
296 return INVALID_HANDLE_VALUE;
297
298 return (HANDLE)_get_osfhandle(_fileno(Stream->fStream));
299 #else
300 return Stream->hHandle;
301 #endif
302 }
303
304 BOOL
305 ConStreamSetOSHandle(
306 IN PCON_STREAM Stream,
307 IN HANDLE Handle)
308 {
309 /* Parameters validation */
310 if (!Stream)
311 return FALSE;
312
313 /*
314 * See https://support.microsoft.com/kb/99173
315 * for more details.
316 */
317
318 #ifdef USE_CRT
319 if (!Stream->fStream)
320 return FALSE;
321
322 int fdOut = _open_osfhandle(Handle, _O_TEXT /* FIXME! */);
323 FILE* fpOut = _fdopen(fdOut, "w");
324 *Stream->fStream = *fpOut;
325 /// setvbuf(Stream->fStream, NULL, _IONBF, 0);
326
327 return TRUE;
328 #else
329 /* Flush the stream and reset its handle */
330 if (Stream->hHandle != INVALID_HANDLE_VALUE)
331 FlushFileBuffers(Stream->hHandle);
332
333 Stream->hHandle = Handle;
334 Stream->IsConsole = IsConsoleHandle(Stream->hHandle);
335
336 // NOTE: Mode reset??
337
338 return TRUE;
339 #endif
340 }
341
342
343 /*
344 * Console I/O utility API
345 * (for the moment, only Output)
346 */
347
348 // NOTE: Should be called with the stream locked.
349 INT
350 __stdcall
351 ConWrite(
352 IN PCON_STREAM Stream,
353 IN PTCHAR szStr,
354 IN DWORD len)
355 {
356 #ifndef USE_CRT
357 DWORD TotalLen = len, dwNumBytes = 0;
358 PVOID p;
359
360 // CHAR strOem[CON_RC_STRING_MAX_SIZE]; // Some static buffer...
361
362 /* If we do not write anything, just return */
363 if (!szStr || len == 0)
364 return 0;
365
366 /* Check whether we are writing to a console */
367 // if (IsConsoleHandle(Stream->hHandle))
368 if (Stream->IsConsole)
369 {
370 // TODO: Check if (ConStream->Mode == WideText or UTF16Text) ??
371
372 /*
373 * This code is inspired from _cputws, in particular from the fact that,
374 * according to MSDN: https://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx
375 * the buffer size must be less than 64 KB.
376 *
377 * A similar code can be used for implementing _cputs too.
378 */
379
380 DWORD cchWrite;
381 TotalLen = len, dwNumBytes = 0;
382
383 while (len > 0)
384 {
385 cchWrite = min(len, 65535 / sizeof(WCHAR));
386
387 // FIXME: Check return value!
388 WriteConsole(Stream->hHandle, szStr, cchWrite, &dwNumBytes, NULL);
389
390 szStr += cchWrite;
391 len -= cchWrite;
392 }
393
394 return (INT)TotalLen; // FIXME: Really return the number of chars written!
395 }
396
397 /*
398 * We are redirected and writing to a file or pipe instead of the console.
399 * Convert the string from TCHARs to the desired output format, if the two differ.
400 *
401 * Implementation NOTE:
402 * MultiByteToWideChar (resp. WideCharToMultiByte) are equivalent to
403 * OemToCharBuffW (resp. CharToOemBuffW), but the latters uselessly
404 * depend on user32.dll, while MultiByteToWideChar and WideCharToMultiByte
405 * only need kernel32.dll.
406 */
407 if ((Stream->Mode == WideText) || (Stream->Mode == UTF16Text))
408 {
409 #ifndef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16
410 /* Convert from the current process/thread's codepage to UTF-16 */
411 WCHAR *buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR));
412 if (!buffer)
413 {
414 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
415 return 0;
416 }
417 len = (DWORD)MultiByteToWideChar(CP_THREAD_ACP, // CP_ACP, CP_OEMCP
418 0, szStr, (INT)len, buffer, (INT)len);
419 szStr = (PVOID)buffer;
420 #else
421 /*
422 * Do not perform any conversion since we are already in UTF-16,
423 * that is the same encoding as the stream.
424 */
425 #endif
426
427 /*
428 * Find any newline character in the buffer,
429 * write the part BEFORE the newline, then write
430 * a carriage-return + newline, and then write
431 * the remaining part of the buffer.
432 *
433 * This fixes output in files and serial console.
434 */
435 while (len > 0)
436 {
437 /* Loop until we find a \r or \n character */
438 // FIXME: What about the pair \r\n ?
439 p = szStr;
440 while (len > 0 && *(PWCHAR)p != L'\r' && *(PWCHAR)p != L'\n')
441 {
442 /* Advance one character */
443 p = (PVOID)((PWCHAR)p + 1);
444 len--;
445 }
446
447 /* Write everything up to \r or \n */
448 dwNumBytes = ((PWCHAR)p - (PWCHAR)szStr) * sizeof(WCHAR);
449 WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL);
450
451 /* If we hit \r or \n ... */
452 if (len > 0 && (*(PWCHAR)p == L'\r' || *(PWCHAR)p == L'\n'))
453 {
454 /* ... send a carriage-return + newline sequence and skip \r or \n */
455 WriteFile(Stream->hHandle, L"\r\n", 2 * sizeof(WCHAR), &dwNumBytes, NULL);
456 szStr = (PVOID)((PWCHAR)p + 1);
457 len--;
458 }
459 }
460
461 #ifndef _UNICODE
462 HeapFree(GetProcessHeap(), 0, buffer);
463 #endif
464 }
465 else if ((Stream->Mode == UTF8Text) || (Stream->Mode == AnsiText))
466 {
467 CHAR *buffer;
468
469 /*
470 * Resolve the codepage cache if it was not assigned yet
471 * (only if the stream is in ANSI mode; in UTF8 mode the
472 * codepage was already set to CP_UTF8).
473 */
474 if (/*(Stream->Mode == AnsiText) &&*/ (Stream->CodePage == INVALID_CP))
475 Stream->CodePage = GetConsoleOutputCP(); // CP_ACP, CP_OEMCP
476
477 #ifdef _UNICODE // UNICODE means that TCHAR == WCHAR == UTF-16
478 /* Convert from UTF-16 to either UTF-8 or ANSI, using stream codepage */
479 // NOTE: MB_LEN_MAX defined either in limits.h or in stdlib.h .
480 buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * MB_LEN_MAX);
481 if (!buffer)
482 {
483 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
484 return 0;
485 }
486 len = WideCharToMultiByte(Stream->CodePage, 0,
487 szStr, len, buffer, len * MB_LEN_MAX,
488 NULL, NULL);
489 szStr = (PVOID)buffer;
490 #else
491 /*
492 * Convert from the current process/thread's codepage to either
493 * UTF-8 or ANSI, using stream codepage.
494 * We need to perform a double conversion, by going through UTF-16.
495 */
496 // TODO!
497 #error "Need to implement double conversion!"
498 #endif
499
500 /*
501 * Find any newline character in the buffer,
502 * write the part BEFORE the newline, then write
503 * a carriage-return + newline, and then write
504 * the remaining part of the buffer.
505 *
506 * This fixes output in files and serial console.
507 */
508 while (len > 0)
509 {
510 /* Loop until we find a \r or \n character */
511 // FIXME: What about the pair \r\n ?
512 p = szStr;
513 while (len > 0 && *(PCHAR)p != '\r' && *(PCHAR)p != '\n')
514 {
515 /* Advance one character */
516 p = (PVOID)((PCHAR)p + 1);
517 len--;
518 }
519
520 /* Write everything up to \r or \n */
521 dwNumBytes = ((PCHAR)p - (PCHAR)szStr) * sizeof(CHAR);
522 WriteFile(Stream->hHandle, szStr, dwNumBytes, &dwNumBytes, NULL);
523
524 /* If we hit \r or \n ... */
525 if (len > 0 && (*(PCHAR)p == '\r' || *(PCHAR)p == '\n'))
526 {
527 /* ... send a carriage-return + newline sequence and skip \r or \n */
528 WriteFile(Stream->hHandle, "\r\n", 2, &dwNumBytes, NULL);
529 szStr = (PVOID)((PCHAR)p + 1);
530 len--;
531 }
532 }
533
534 #ifdef _UNICODE
535 HeapFree(GetProcessHeap(), 0, buffer);
536 #else
537 // TODO!
538 #endif
539 }
540 else // if (Stream->Mode == Binary)
541 {
542 /* Directly output the string */
543 WriteFile(Stream->hHandle, szStr, len, &dwNumBytes, NULL);
544 }
545
546 // FIXME!
547 return (INT)TotalLen;
548
549 #else /* defined(USE_CRT) */
550
551 DWORD total = len;
552 DWORD written = 0;
553
554 /* If we do not write anything, just return */
555 if (!szStr || len == 0)
556 return 0;
557
558 #if 1
559 /*
560 * There is no "counted" printf-to-stream or puts-like function, therefore
561 * we use this trick to output the counted string to the stream.
562 */
563 while (1)
564 {
565 written = fwprintf(Stream->fStream, L"%.*s", total, szStr);
566 if (written < total)
567 {
568 /*
569 * Some embedded NULL or special character
570 * was encountered, print it apart.
571 */
572 if (written == 0)
573 {
574 fputwc(*szStr, Stream->fStream);
575 written++;
576 }
577
578 szStr += written;
579 total -= written;
580 }
581 else
582 {
583 break;
584 }
585 }
586 return (INT)len;
587 #else
588 /* ANSI text or Binary output only */
589 _setmode(_fileno(Stream->fStream), _O_TEXT); // _O_BINARY
590 return fwrite(szStr, sizeof(*szStr), len, Stream->fStream);
591 #endif
592
593 #endif /* defined(USE_CRT) */
594 }
595
596
597 #define CON_STREAM_WRITE_CALL(Stream, Str, Len) \
598 (Stream)->WriteFunc((Stream), (Str), (Len));
599
600 /* Lock the stream only in non-USE_CRT mode (otherwise use the CRT stream lock) */
601 #ifndef USE_CRT
602
603 #define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \
604 do { \
605 EnterCriticalSection(&(Stream)->Lock); \
606 (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \
607 LeaveCriticalSection(&(Stream)->Lock); \
608 } while(0)
609
610 #define CON_STREAM_WRITE(Stream, Str, Len) \
611 do { \
612 EnterCriticalSection(&(Stream)->Lock); \
613 CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \
614 LeaveCriticalSection(&(Stream)->Lock); \
615 } while(0)
616
617 #else
618
619 #define CON_STREAM_WRITE2(Stream, Str, Len, RetLen) \
620 do { \
621 (RetLen) = CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \
622 } while(0)
623
624 #define CON_STREAM_WRITE(Stream, Str, Len) \
625 do { \
626 CON_STREAM_WRITE_CALL((Stream), (Str), (Len)); \
627 } while(0)
628
629 #endif
630
631
632 INT
633 ConStreamWrite(
634 IN PCON_STREAM Stream,
635 IN PTCHAR szStr,
636 IN DWORD len)
637 {
638 INT Len;
639 CON_STREAM_WRITE2(Stream, szStr, len, Len);
640 return Len;
641 }
642
643 INT
644 ConPuts(
645 IN PCON_STREAM Stream,
646 IN LPWSTR szStr)
647 {
648 INT Len;
649
650 Len = wcslen(szStr);
651 CON_STREAM_WRITE2(Stream, szStr, Len, Len);
652
653 /* Fixup returned length in case of errors */
654 if (Len < 0)
655 Len = 0;
656
657 return Len;
658 }
659
660 INT
661 ConPrintfV(
662 IN PCON_STREAM Stream,
663 IN LPWSTR szStr,
664 IN va_list args) // arg_ptr
665 {
666 INT Len;
667 WCHAR bufSrc[CON_RC_STRING_MAX_SIZE];
668
669 // Len = vfwprintf(Stream->fStream, szStr, args); // vfprintf for direct ANSI
670
671 /*
672 * Reuse szStr as the pointer to end-of-string, to compute
673 * the string length instead of calling wcslen().
674 */
675 // StringCchVPrintfW(bufSrc, ARRAYSIZE(bufSrc), szStr, args);
676 // Len = wcslen(bufSrc);
677 StringCchVPrintfExW(bufSrc, ARRAYSIZE(bufSrc), &szStr, NULL, 0, szStr, args);
678 Len = szStr - bufSrc;
679
680 CON_STREAM_WRITE2(Stream, bufSrc, Len, Len);
681
682 /* Fixup returned length in case of errors */
683 if (Len < 0)
684 Len = 0;
685
686 return Len;
687 }
688
689 INT
690 __cdecl
691 ConPrintf(
692 IN PCON_STREAM Stream,
693 IN LPWSTR szStr,
694 ...)
695 {
696 INT Len;
697 va_list args;
698
699 // Len = vfwprintf(Stream->fStream, szMsgBuf, args); // vfprintf for direct ANSI
700
701 // StringCchPrintfW
702 va_start(args, szStr);
703 Len = ConPrintfV(Stream, szStr, args);
704 va_end(args);
705
706 return Len;
707 }
708
709 INT
710 ConResPutsEx(
711 IN PCON_STREAM Stream,
712 IN HINSTANCE hInstance OPTIONAL,
713 IN UINT uID)
714 {
715 INT Len;
716 PWCHAR szStr = NULL;
717
718 Len = K32LoadStringW(hInstance, uID, (PWSTR)&szStr, 0);
719 if (szStr && Len)
720 // Len = ConPuts(Stream, szStr);
721 CON_STREAM_WRITE2(Stream, szStr, Len, Len);
722
723 /* Fixup returned length in case of errors */
724 if (Len < 0)
725 Len = 0;
726
727 return Len;
728 }
729
730 INT
731 ConResPuts(
732 IN PCON_STREAM Stream,
733 IN UINT uID)
734 {
735 return ConResPutsEx(Stream, NULL /*GetModuleHandleW(NULL)*/, uID);
736 }
737
738 INT
739 ConResPrintfExV(
740 IN PCON_STREAM Stream,
741 IN HINSTANCE hInstance OPTIONAL,
742 IN UINT uID,
743 IN va_list args) // arg_ptr
744 {
745 INT Len;
746 WCHAR bufSrc[CON_RC_STRING_MAX_SIZE];
747
748 // NOTE: We may use the special behaviour where nBufMaxSize == 0
749 Len = K32LoadStringW(hInstance, uID, bufSrc, ARRAYSIZE(bufSrc));
750 if (Len)
751 Len = ConPrintfV(Stream, bufSrc, args);
752
753 return Len;
754 }
755
756 INT
757 ConResPrintfV(
758 IN PCON_STREAM Stream,
759 IN UINT uID,
760 IN va_list args) // arg_ptr
761 {
762 return ConResPrintfExV(Stream, NULL /*GetModuleHandleW(NULL)*/, uID, args);
763 }
764
765 INT
766 __cdecl
767 ConResPrintfEx(
768 IN PCON_STREAM Stream,
769 IN HINSTANCE hInstance OPTIONAL,
770 IN UINT uID,
771 ...)
772 {
773 INT Len;
774 va_list args;
775
776 va_start(args, uID);
777 Len = ConResPrintfExV(Stream, hInstance, uID, args);
778 va_end(args);
779
780 return Len;
781 }
782
783 INT
784 __cdecl
785 ConResPrintf(
786 IN PCON_STREAM Stream,
787 IN UINT uID,
788 ...)
789 {
790 INT Len;
791 va_list args;
792
793 va_start(args, uID);
794 Len = ConResPrintfV(Stream, uID, args);
795 va_end(args);
796
797 return Len;
798 }
799
800 INT
801 ConMsgPuts(
802 IN PCON_STREAM Stream,
803 IN DWORD dwFlags,
804 IN LPCVOID lpSource OPTIONAL,
805 IN DWORD dwMessageId,
806 IN DWORD dwLanguageId)
807 {
808 INT Len;
809 DWORD dwLength = 0;
810 LPWSTR lpMsgBuf = NULL;
811
812 /*
813 * Sanitize dwFlags. This version always ignore explicitely the inserts
814 * as we emulate the behaviour of the *puts function.
815 */
816 dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer.
817 dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; // Ignore inserts for FormatMessage.
818 dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY;
819
820 dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK;
821
822 /*
823 * Retrieve the message string without appending extra newlines.
824 * Wrap in SEH to protect from invalid string parameters.
825 */
826 _SEH2_TRY
827 {
828 dwLength = FormatMessageW(dwFlags,
829 lpSource,
830 dwMessageId,
831 dwLanguageId,
832 (LPWSTR)&lpMsgBuf,
833 0, NULL);
834 }
835 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
836 {
837 }
838 _SEH2_END;
839
840 Len = (INT)dwLength;
841
842 if (!lpMsgBuf)
843 {
844 // ASSERT(dwLength == 0);
845 }
846 else
847 {
848 // ASSERT(dwLength != 0);
849
850 /* lpMsgBuf is NULL-terminated by FormatMessage */
851 // Len = ConPuts(Stream, lpMsgBuf);
852 CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len);
853
854 /* Fixup returned length in case of errors */
855 if (Len < 0)
856 Len = 0;
857
858 /* Free the buffer allocated by FormatMessage */
859 LocalFree(lpMsgBuf);
860 }
861
862 return Len;
863 }
864
865 INT
866 ConMsgPrintf2V(
867 IN PCON_STREAM Stream,
868 IN DWORD dwFlags,
869 IN LPCVOID lpSource OPTIONAL,
870 IN DWORD dwMessageId,
871 IN DWORD dwLanguageId,
872 IN va_list args) // arg_ptr
873 {
874 INT Len;
875 DWORD dwLength = 0;
876 LPWSTR lpMsgBuf = NULL;
877
878 /*
879 * Sanitize dwFlags. This version always ignore explicitely the inserts.
880 * The string that we will return to the user will not be pre-formatted.
881 */
882 dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer.
883 dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; // Ignore inserts for FormatMessage.
884 dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY;
885
886 dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK;
887
888 /*
889 * Retrieve the message string without appending extra newlines.
890 * Wrap in SEH to protect from invalid string parameters.
891 */
892 _SEH2_TRY
893 {
894 dwLength = FormatMessageW(dwFlags,
895 lpSource,
896 dwMessageId,
897 dwLanguageId,
898 (LPWSTR)&lpMsgBuf,
899 0, NULL);
900 }
901 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
902 {
903 }
904 _SEH2_END;
905
906 Len = (INT)dwLength;
907
908 if (!lpMsgBuf)
909 {
910 // ASSERT(dwLength == 0);
911 }
912 else
913 {
914 // ASSERT(dwLength != 0);
915
916 /* lpMsgBuf is NULL-terminated by FormatMessage */
917 Len = ConPrintfV(Stream, lpMsgBuf, args);
918 // CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len);
919
920 /* Fixup returned length in case of errors */
921 if (Len < 0)
922 Len = 0;
923
924 /* Free the buffer allocated by FormatMessage */
925 LocalFree(lpMsgBuf);
926 }
927
928 return Len;
929 }
930
931 INT
932 ConMsgPrintfV(
933 IN PCON_STREAM Stream,
934 IN DWORD dwFlags,
935 IN LPCVOID lpSource OPTIONAL,
936 IN DWORD dwMessageId,
937 IN DWORD dwLanguageId,
938 IN va_list args) // arg_ptr
939 {
940 INT Len;
941 DWORD dwLength = 0;
942 LPWSTR lpMsgBuf = NULL;
943
944 /* Sanitize dwFlags */
945 dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER; // Always allocate an internal buffer.
946 // dwFlags &= ~FORMAT_MESSAGE_IGNORE_INSERTS; // We always use arguments.
947 dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; // We always use arguments of type 'va_list'.
948
949 //
950 // NOTE: Technique taken from eventvwr.c!GetMessageStringFromDll()
951 //
952
953 dwFlags |= FORMAT_MESSAGE_MAX_WIDTH_MASK;
954
955 /*
956 * Retrieve the message string without appending extra newlines.
957 * Use the "safe" FormatMessage version (SEH-protected) to protect
958 * from invalid string parameters.
959 */
960 dwLength = FormatMessageSafeW(dwFlags,
961 lpSource,
962 dwMessageId,
963 dwLanguageId,
964 (LPWSTR)&lpMsgBuf,
965 0, &args);
966
967 Len = (INT)dwLength;
968
969 if (!lpMsgBuf)
970 {
971 // ASSERT(dwLength == 0);
972 }
973 else
974 {
975 // ASSERT(dwLength != 0);
976
977 // Len = ConPrintfV(Stream, lpMsgBuf, args);
978 CON_STREAM_WRITE2(Stream, lpMsgBuf, dwLength, Len);
979
980 /* Fixup returned length in case of errors */
981 if (Len < 0)
982 Len = 0;
983
984 /* Free the buffer allocated by FormatMessage */
985 LocalFree(lpMsgBuf);
986 }
987
988 return Len;
989 }
990
991 INT
992 __cdecl
993 ConMsgPrintf(
994 IN PCON_STREAM Stream,
995 IN DWORD dwFlags,
996 IN LPCVOID lpSource OPTIONAL,
997 IN DWORD dwMessageId,
998 IN DWORD dwLanguageId,
999 ...)
1000 {
1001 INT Len;
1002 va_list args;
1003
1004 va_start(args, dwLanguageId);
1005 // ConMsgPrintf2V
1006 Len = ConMsgPrintfV(Stream,
1007 dwFlags,
1008 lpSource,
1009 dwMessageId,
1010 dwLanguageId,
1011 args);
1012 va_end(args);
1013
1014 return Len;
1015 }
1016
1017
1018
1019 VOID
1020 ConClearLine(IN PCON_STREAM Stream)
1021 {
1022 HANDLE hOutput = ConStreamGetOSHandle(Stream);
1023
1024 /*
1025 * Erase the full line where the cursor is, and move
1026 * the cursor back to the beginning of the line.
1027 */
1028
1029 if (IsConsoleHandle(hOutput))
1030 {
1031 CONSOLE_SCREEN_BUFFER_INFO csbi;
1032 DWORD dwWritten;
1033
1034 GetConsoleScreenBufferInfo(hOutput, &csbi);
1035
1036 csbi.dwCursorPosition.X = 0;
1037 // csbi.dwCursorPosition.Y;
1038
1039 FillConsoleOutputCharacterW(hOutput, L' ',
1040 csbi.dwSize.X,
1041 csbi.dwCursorPosition,
1042 &dwWritten);
1043 SetConsoleCursorPosition(hOutput, csbi.dwCursorPosition);
1044 }
1045 else if (IsTTYHandle(hOutput))
1046 {
1047 ConPuts(Stream, L"\x1B[2K\x1B[1G"); // FIXME: Just use WriteFile
1048 }
1049 // else, do nothing for files
1050 }
1051