1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5/*++
6
7
8
9Module Name:
10
11 fmtmessage.c
12
13Abstract:
14
15 Implementation of FormatMessage function.
16
17Revision History:
18
19
20
21--*/
22
23#include "pal/palinternal.h"
24#include "pal/dbgmsg.h"
25#include "pal/unicode_data.h"
26#include "pal/critsect.h"
27#include "pal/module.h"
28#include "pal/misc.h"
29
30#include "pal/printfcpp.hpp"
31
32#include "errorstrings.h"
33
34#include <stdarg.h>
35#if NEED_DLCOMPAT
36#include "dlcompat.h"
37#else // NEED_DLCOMPAT
38#include <dlfcn.h>
39#endif // NEED_DLCOMPAT
40#include <errno.h>
41
42SET_DEFAULT_DEBUG_CHANNEL(MISC);
43
44/* Defines */
45
46#define MAX_ERROR_STRING_LENGTH 32
47
48/*++
49Function:
50
51 FMTMSG_GetMessageString
52
53Returns the message as a wide string.
54--*/
55static LPWSTR FMTMSG_GetMessageString( DWORD dwErrCode )
56{
57 TRACE("Entered FMTMSG_GetMessageString\n");
58
59 LPCWSTR lpErrorString = GetPalErrorString(dwErrCode);
60 int allocChars;
61
62 if (lpErrorString != NULL)
63 {
64 allocChars = PAL_wcslen(lpErrorString) + 1;
65 }
66 else
67 {
68 allocChars = MAX_ERROR_STRING_LENGTH + 1;
69 }
70
71 LPWSTR lpRetVal = (LPWSTR)LocalAlloc(LMEM_FIXED, allocChars * sizeof(WCHAR));
72
73 if (lpRetVal)
74 {
75 if (lpErrorString != NULL)
76 {
77 PAL_wcscpy(lpRetVal, lpErrorString);
78 }
79 else
80 {
81 swprintf_s(lpRetVal, MAX_ERROR_STRING_LENGTH, W("Error %u"), dwErrCode);
82 }
83 }
84 else
85 {
86 ERROR("Unable to allocate memory.\n");
87 }
88
89 return lpRetVal;
90}
91
92/*++
93
94Function :
95
96 FMTMSG__watoi
97
98 Converts a wide string repersentation of an integer number
99 into a interger number.
100
101 Returns a integer number, or 0 on failure. 0 is not a valid number
102 for FormatMessage inserts.
103
104--*/
105static INT FMTMSG__watoi( LPWSTR str )
106{
107 CONST UINT MAX_NUMBER_LENGTH = 3;
108 CHAR buf[ MAX_NUMBER_LENGTH ];
109 INT nRetVal = 0;
110
111 nRetVal = WideCharToMultiByte( CP_ACP, 0, str, -1, buf,
112 MAX_NUMBER_LENGTH, NULL, 0 );
113
114 if ( nRetVal != 0 )
115 {
116 return atoi( buf );
117 }
118 else
119 {
120 ERROR( "Unable to convert the string to a number.\n" );
121 return 0;
122 }
123}
124
125/* Adds the character to the working string. */
126#define _ADD_TO_STRING( c ) \
127{\
128 TRACE( "Adding %c to the string.\n", (CHAR)c );\
129 *lpWorkingString = c;\
130 lpWorkingString++;\
131 nCount++;\
132}
133
134/* Grows the buffer. */
135#define _GROW_BUFFER() \
136{\
137 if ( bIsLocalAlloced ) \
138 { \
139 LPWSTR lpTemp = NULL; \
140 UINT NumOfBytes = 0; \
141 nSize *= 2; \
142 NumOfBytes = nSize * sizeof( WCHAR ); \
143 lpTemp = static_cast<WCHAR *>( LocalAlloc( LMEM_FIXED, NumOfBytes ) ); \
144 TRACE( "Growing the buffer.\n" );\
145 \
146 if ( !lpTemp ) \
147 { \
148 ERROR( "Out of buffer\n" ); \
149 SetLastError( ERROR_NOT_ENOUGH_MEMORY ); \
150 nCount = 0; \
151 lpWorkingString = NULL; \
152 goto exit; \
153 } \
154 \
155 *lpWorkingString = '\0';\
156 PAL_wcscpy( lpTemp, lpReturnString );\
157 LocalFree( lpReturnString ); \
158 lpWorkingString = lpReturnString = lpTemp; \
159 lpWorkingString += nCount; \
160 } \
161 else \
162 { \
163 WARN( "Out of buffer.\n" ); \
164 SetLastError( ERROR_INSUFFICIENT_BUFFER ); \
165 nCount = 0; \
166 lpWorkingString = NULL; \
167 goto exit; \
168 } \
169}
170/* Adds a character to the working string. This is a safer version
171of _ADD_TO_STRING, as we will resize the buffer if necessary. */
172#define _CHECKED_ADD_TO_STRING( c ) \
173{\
174 if ( nCount+1 == nSize ) \
175 {\
176 _GROW_BUFFER();\
177 } \
178 _ADD_TO_STRING( c );\
179}
180
181
182/*++
183Function :
184
185 FMTMSG_ProcessPrintf
186
187 Processes the printf formatters based on the format.
188
189 Returns the LPWSTR string, or NULL on failure.
190*/
191
192static LPWSTR FMTMSG_ProcessPrintf( wchar_t c ,
193 LPWSTR lpPrintfString,
194 LPWSTR lpInsertString)
195{
196 LPWSTR lpBuffer = NULL;
197 LPWSTR lpBuffer2 = NULL;
198 LPWSTR lpFormat = NULL;
199#if _DEBUG
200 // small size for _DEBUG to exercise buffer reallocation logic
201 int tmpSize = 4;
202#else
203 int tmpSize = 64;
204#endif
205 UINT nFormatLength = 0;
206 int nBufferLength = 0;
207
208 TRACE( "FMTMSG_ProcessPrintf( %C, %S, %p )\n", c,
209 lpPrintfString, lpInsertString );
210
211 switch ( c )
212 {
213 case 'e' :
214 /* Fall through */
215 case 'E' :
216 /* Fall through */
217 case 'f' :
218 /* Fall through */
219 case 'g' :
220 /* Fall through */
221 case 'G' :
222 ERROR( "%%%c is not supported by FormatMessage.\n", c );
223 SetLastError( ERROR_INVALID_PARAMETER );
224 return NULL;
225 }
226
227 nFormatLength = PAL_wcslen( lpPrintfString ) + 2; /* Need to count % AND NULL */
228 lpFormat = (LPWSTR)PAL_malloc( nFormatLength * sizeof( WCHAR ) );
229 if ( !lpFormat )
230 {
231 ERROR( "Unable to allocate memory.\n" );
232 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
233 return NULL;
234 }
235 /* Create the format string. */
236 memset( lpFormat, 0, nFormatLength * sizeof(WCHAR) );
237 *lpFormat = '%';
238
239 PAL_wcscat( lpFormat, lpPrintfString );
240
241 lpBuffer = (LPWSTR) PAL_malloc(tmpSize*sizeof(WCHAR));
242
243 /* try until the buffer is big enough */
244 while (TRUE)
245 {
246 if (!lpBuffer)
247 {
248 ERROR("Unable to allocate memory\n");
249 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
250 PAL_free(lpFormat);
251 return NULL;
252 }
253 nBufferLength = _snwprintf_s( lpBuffer, tmpSize, tmpSize,
254 lpFormat, lpInsertString);
255
256 if ((nBufferLength >= 0) && (nBufferLength != tmpSize))
257 {
258 break; /* succeeded */
259 }
260 else
261 {
262 tmpSize *= 2;
263 lpBuffer2 = static_cast<WCHAR *>(
264 PAL_realloc(lpBuffer, tmpSize*sizeof(WCHAR)));
265 if (lpBuffer2 == NULL)
266 PAL_free(lpBuffer);
267 lpBuffer = lpBuffer2;
268 }
269 }
270
271 PAL_free( lpFormat );
272 lpFormat = NULL;
273
274 return lpBuffer;
275}
276
277/*++
278Function:
279 FormatMessageW
280
281See MSDN doc.
282--*/
283DWORD
284PALAPI
285FormatMessageW(
286 IN DWORD dwFlags,
287 IN LPCVOID lpSource,
288 IN DWORD dwMessageId,
289 IN DWORD dwLanguageId,
290 OUT LPWSTR lpBuffer,
291 IN DWORD nSize,
292 IN va_list *Arguments)
293{
294 BOOL bIgnoreInserts = FALSE;
295 BOOL bIsVaList = TRUE;
296 BOOL bIsLocalAlloced = FALSE;
297 LPWSTR lpSourceString = NULL;
298 UINT nCount = 0;
299 LPWSTR lpReturnString = NULL;
300 LPWSTR lpWorkingString = NULL;
301
302 PERF_ENTRY(FormatMessageW);
303 ENTRY( "FormatMessageW(dwFlags=%#x, lpSource=%p, dwMessageId=%#x, "
304 "dwLanguageId=%#x, lpBuffer=%p, nSize=%u, va_list=%p)\n",
305 dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, nSize,
306 Arguments);
307
308 /* Sanity checks. */
309 if ( dwFlags & FORMAT_MESSAGE_FROM_STRING && !lpSource )
310 {
311 /* This behavior is different then in Windows.
312 Windows would just crash.*/
313 ERROR( "lpSource cannot be NULL.\n" );
314 SetLastError( ERROR_INVALID_PARAMETER );
315 goto exit;
316 }
317
318 if ( !(dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER ) && !lpBuffer )
319 {
320 /* This behavior is different then in Windows.
321 Windows would just crash.*/
322 ERROR( "lpBuffer cannot be NULL, if "
323 " FORMAT_MESSAGE_ALLOCATE_BUFFER is not specified.\n" );
324 SetLastError( ERROR_INVALID_PARAMETER );
325 goto exit;
326 }
327
328 if ( ( dwFlags & FORMAT_MESSAGE_FROM_STRING ) &&
329 ( dwFlags & FORMAT_MESSAGE_FROM_SYSTEM ) )
330 {
331 ERROR( "These flags cannot co-exist. You can either "
332 "specify FORMAT_MESSAGE_FROM_STRING, or "
333 "FORMAT_MESSAGE_FROM_SYSTEM.\n" );
334 SetLastError( ERROR_INVALID_PARAMETER );
335 goto exit;
336 }
337
338 if ( !( dwFlags & FORMAT_MESSAGE_FROM_STRING ) &&
339 ( dwLanguageId != 0) )
340 {
341 ERROR( "Invalid language indentifier.\n" );
342 SetLastError( ERROR_RESOURCE_LANG_NOT_FOUND );
343 goto exit;
344 }
345
346 /* Parameter processing. */
347 if ( dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER )
348 {
349 TRACE( "Allocated %d TCHARs. Don't forget to call LocalFree to "
350 "free the memory when done.\n", nSize );
351 bIsLocalAlloced = TRUE;
352 }
353
354 if ( dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS )
355 {
356 bIgnoreInserts = TRUE;
357 }
358
359 if ( dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY )
360 {
361 if ( !Arguments && !bIgnoreInserts )
362 {
363 ERROR( "The va_list cannot be NULL.\n" );
364 SetLastError( ERROR_INVALID_PARAMETER );
365 goto exit;
366 }
367 else
368 {
369 bIsVaList = FALSE;
370 }
371 }
372
373 if ( dwFlags & FORMAT_MESSAGE_FROM_STRING )
374 {
375 lpSourceString = (LPWSTR)lpSource;
376 }
377 else if ( dwFlags & FORMAT_MESSAGE_FROM_SYSTEM )
378 {
379 if ((dwMessageId & 0xFFFF0000) == 0x80070000)
380 {
381 // This message has been produced by HRESULT_FROM_WIN32. Undo its work.
382 dwMessageId &= 0xFFFF;
383 }
384
385 lpWorkingString = lpReturnString =
386 FMTMSG_GetMessageString( dwMessageId );
387
388 if ( !lpWorkingString )
389 {
390 ERROR( "Unable to find the message %d.\n", dwMessageId );
391 SetLastError( ERROR_INTERNAL_ERROR );
392 nCount = 0;
393 goto exit;
394 }
395
396 nCount = PAL_wcslen( lpWorkingString );
397
398 if ( !bIsLocalAlloced && nCount > nSize )
399 {
400 ERROR( "Insufficient buffer.\n" );
401 SetLastError( ERROR_INSUFFICIENT_BUFFER );
402 lpWorkingString = NULL;
403 nCount = 0;
404 goto exit;
405 }
406 if ( !lpWorkingString )
407 {
408 ERROR( "Invalid error indentifier.\n" );
409 SetLastError( ERROR_INVALID_ADDRESS );
410 }
411 goto exit;
412 }
413 else
414 {
415 ERROR( "Unknown flag.\n" );
416 SetLastError( ERROR_INVALID_PARAMETER );
417 goto exit;
418 }
419
420 if ( nSize == 0 && bIsLocalAlloced )
421 {
422 nSize = 1;
423 }
424
425 lpWorkingString = static_cast<WCHAR *>(
426 LocalAlloc( LMEM_FIXED, nSize * sizeof( WCHAR ) ) );
427 if ( !lpWorkingString )
428 {
429 ERROR( "Unable to allocate memory for the working string.\n" );
430 SetLastError( ERROR_INSUFFICIENT_BUFFER );
431 goto exit;
432 }
433
434
435 /* Process the string. */
436 lpReturnString = lpWorkingString;
437 while ( *lpSourceString )
438 {
439 if ( *lpSourceString == '%' && !bIgnoreInserts )
440 {
441 lpSourceString++;
442 /* Escape sequences. */
443 if ( *lpSourceString == '0' )
444 {
445 /* Terminates a message without a newline character. */
446 *lpWorkingString = '\0';
447 goto exit;
448 }
449 else if ( PAL_iswdigit( *lpSourceString ) )
450 {
451 /* Get the insert number. */
452 WCHAR Number[] = { '\0', '\0', '\0' };
453 SIZE_T Index = 0;
454
455 Number[ 0 ] = *lpSourceString;
456 lpSourceString++;
457
458 if ( PAL_iswdigit( *lpSourceString ) )
459 {
460 Number[ 1 ] = *lpSourceString;
461 lpSourceString++;
462 if ( PAL_iswdigit( *lpSourceString ) )
463 {
464 ERROR( "Invalid insert indentifier.\n" );
465 SetLastError( ERROR_INVALID_PARAMETER );
466 lpWorkingString = NULL;
467 nCount = 0;
468 goto exit;
469 }
470 }
471 Index = FMTMSG__watoi( Number );
472 if ( Index == 0 )
473 {
474 ERROR( "Invalid insert indentifier.\n" );
475 SetLastError( ERROR_INVALID_PARAMETER );
476 lpWorkingString = NULL;
477 nCount = 0;
478 goto exit;
479 }
480 if ( *lpSourceString == '!' )
481 {
482 LPWSTR lpInsertString = NULL;
483 LPWSTR lpPrintfString = NULL;
484 LPWSTR lpStartOfFormattedString = NULL;
485 UINT nPrintfLength = 0;
486 LPWSTR lpFormattedString = NULL;
487 UINT nFormattedLength = 0;
488
489 if ( !bIsVaList )
490 {
491 lpInsertString = ((LPWSTR*)Arguments)[ Index - 1 ];
492 }
493 else
494 {
495 va_list TheArgs;
496
497 va_copy(TheArgs, *Arguments);
498 UINT i = 0;
499 for ( ; i < Index; i++ )
500 {
501 lpInsertString = va_arg( TheArgs, LPWSTR );
502 }
503 }
504
505 /* Calculate the length, and extract the printf string.*/
506 lpSourceString++;
507 {
508 LPWSTR p = PAL_wcschr( lpSourceString, '!' );
509
510 if ( NULL == p )
511 {
512 nPrintfLength = 0;
513 }
514 else
515 {
516 nPrintfLength = p - lpSourceString;
517 }
518 }
519
520 lpPrintfString =
521 (LPWSTR)PAL_malloc( ( nPrintfLength + 1 ) * sizeof( WCHAR ) );
522
523 if ( !lpPrintfString )
524 {
525 ERROR( "Unable to allocate memory.\n" );
526 SetLastError( ERROR_NOT_ENOUGH_MEMORY );
527 lpWorkingString = NULL;
528 nCount = 0;
529 goto exit;
530 }
531
532 PAL_wcsncpy( lpPrintfString, lpSourceString, nPrintfLength );
533 *( lpPrintfString + nPrintfLength ) = '\0';
534
535 lpStartOfFormattedString = lpFormattedString =
536 FMTMSG_ProcessPrintf( *lpPrintfString,
537 lpPrintfString,
538 lpInsertString);
539
540 if ( !lpFormattedString )
541 {
542 ERROR( "Unable to process the format string.\n" );
543 /* Function will set the error code. */
544 PAL_free( lpPrintfString );
545 lpWorkingString = NULL;
546 goto exit;
547 }
548
549
550 nFormattedLength = PAL_wcslen( lpFormattedString );
551
552 /* Append the processed printf string into the working string */
553 while ( *lpFormattedString )
554 {
555 _CHECKED_ADD_TO_STRING( *lpFormattedString );
556 lpFormattedString++;
557 }
558
559 lpSourceString += nPrintfLength + 1;
560 PAL_free( lpPrintfString );
561 PAL_free( lpStartOfFormattedString );
562 lpPrintfString = lpFormattedString = NULL;
563 }
564 else
565 {
566 /* The printf format string defaults to 's'.*/
567 LPWSTR lpInsert = NULL;
568
569 if ( !bIsVaList )
570 {
571 lpInsert = ((LPWSTR*)Arguments)[Index - 1];
572 }
573 else
574 {
575 va_list TheArgs;
576 va_copy(TheArgs, *Arguments);
577 UINT i = 0;
578 for ( ; i < Index; i++ )
579 {
580 lpInsert = va_arg( TheArgs, LPWSTR );
581 }
582 }
583
584 while ( *lpInsert )
585 {
586 _CHECKED_ADD_TO_STRING( *lpInsert );
587 lpInsert++;
588 }
589 }
590 }
591 /* Format specifiers. */
592 else if ( *lpSourceString == '%' )
593 {
594 _CHECKED_ADD_TO_STRING( '%' );
595 lpSourceString++;
596 }
597 else if ( *lpSourceString == 'n' )
598 {
599 /* Hard line break. */
600 _CHECKED_ADD_TO_STRING( '\n' );
601 lpSourceString++;
602 }
603 else if ( *lpSourceString == '.' )
604 {
605 _CHECKED_ADD_TO_STRING( '.' );
606 lpSourceString++;
607 }
608 else if ( *lpSourceString == '!' )
609 {
610 _CHECKED_ADD_TO_STRING( '!' );
611 lpSourceString++;
612 }
613 else if ( !*lpSourceString )
614 {
615 ERROR( "Invalid parameter.\n" );
616 SetLastError( ERROR_INVALID_PARAMETER );
617 lpWorkingString = NULL;
618 nCount = 0;
619 goto exit;
620 }
621 else /* Append the character. */
622 {
623 _CHECKED_ADD_TO_STRING( *lpSourceString );
624 lpSourceString++;
625 }
626 }/* END if ( *lpSourceString == '%' ) */
627 else
628 {
629 /* In Windows if FormatMessage is called with ignore inserts,
630 then FormatMessage strips %1!s! down to %1, since string is the
631 default. */
632 if ( bIgnoreInserts && *lpSourceString == '!' &&
633 *( lpSourceString + 1 ) == 's' )
634 {
635 LPWSTR lpLastBang = PAL_wcschr( lpSourceString + 1, '!' );
636
637 if ( lpLastBang && ( 2 == lpLastBang - lpSourceString ) )
638 {
639 lpSourceString = lpLastBang + 1;
640 }
641 else
642 {
643 ERROR( "Mal-formed string\n" );
644 SetLastError( ERROR_INVALID_PARAMETER );
645 lpWorkingString = NULL;
646 nCount = 0;
647 goto exit;
648 }
649 }
650 else
651 {
652 /* Append to the string. */
653 _CHECKED_ADD_TO_STRING( *lpSourceString );
654 lpSourceString++;
655 }
656 }
657 }
658
659 /* Terminate the message. */
660 _CHECKED_ADD_TO_STRING( '\0' );
661 /* NULL does not count. */
662 nCount--;
663
664exit: /* Function clean-up and exit. */
665 if ( lpWorkingString )
666 {
667 if ( bIsLocalAlloced )
668 {
669 TRACE( "Assigning the buffer to the pointer.\n" );
670 // when FORMAT_MESSAGE_ALLOCATE_BUFFER is specified, nSize
671 // does not specify the size of lpBuffer, rather it specifies
672 // the minimum size of the string
673 // as such we have to blindly assume that lpBuffer has enough space to
674 // store PVOID
675 // might cause a prefast warning, but there is no good way to suppress it yet
676 _ASSERTE(dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER);
677 *((LPVOID*)lpBuffer) = (LPVOID)lpReturnString;
678 }
679 else /* Only delete lpReturnString if the caller has their own buffer.*/
680 {
681 TRACE( "Copying the string into the buffer.\n" );
682 PAL_wcsncpy( lpBuffer, lpReturnString, nCount + 1 );
683 LocalFree( lpReturnString );
684 }
685 }
686 else /* Error, something occurred. */
687 {
688 if ( lpReturnString )
689 {
690 LocalFree( lpReturnString );
691 }
692 }
693 LOGEXIT( "FormatMessageW returns %d.\n", nCount );
694 PERF_EXIT(FormatMessageW);
695 return nCount;
696}
697