Update the address of the Free Software Foundation.
[wine] / dlls / kernel / format_msg.c
1 /*
2  * FormatMessage implementation
3  *
4  * Copyright 1996 Marcus Meissner
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "ntstatus.h"
28 #define WIN32_NO_STATUS
29 #include "windef.h"
30 #include "winbase.h"
31 #include "winerror.h"
32 #include "winternl.h"
33 #include "winuser.h"
34 #include "winnls.h"
35 #include "wine/unicode.h"
36 #include "kernel_private.h"
37 #include "wine/debug.h"
38
39 WINE_DEFAULT_DEBUG_CHANNEL(resource);
40
41
42 /* Messages...used by FormatMessage (KERNEL32.something)
43  *
44  * They can be specified either directly or using a message ID and
45  * loading them from the resource.
46  *
47  * The resourcedata has following format:
48  * start:
49  * 0: DWORD nrofentries
50  * nrofentries * subentry:
51  *      0: DWORD firstentry
52  *      4: DWORD lastentry
53  *      8: DWORD offset from start to the stringentries
54  *
55  * (lastentry-firstentry) * stringentry:
56  * 0: WORD len (0 marks end)    [ includes the 4 byte header length ]
57  * 2: WORD flags
58  * 4: CHAR[len-4]
59  *      (stringentry i of a subentry refers to the ID 'firstentry+i')
60  *
61  * Yes, ANSI strings in win32 resources. Go figure.
62  */
63
64 static const WCHAR PCNTFMTWSTR[] = { '%','%','%','s',0 };
65 static const WCHAR FMTWSTR[] = { '%','s',0 };
66
67 /**********************************************************************
68  *      load_messageW           (internal)
69  */
70 static LPWSTR load_messageW( HMODULE module, UINT id, WORD lang )
71 {
72     const MESSAGE_RESOURCE_ENTRY *mre;
73     WCHAR *buffer;
74
75     TRACE("module = %p, id = %08x\n", module, id );
76
77     if (!module) module = GetModuleHandleW( NULL );
78     if (RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &mre ) != STATUS_SUCCESS) return NULL;
79
80     if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
81     {
82         int len = (strlenW( (const WCHAR *)mre->Text ) + 1) * sizeof(WCHAR);
83         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
84         memcpy( buffer, mre->Text, len );
85     }
86     else
87     {
88         int len = MultiByteToWideChar( CP_ACP, 0, (const char *)mre->Text, -1, NULL, 0 );
89         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return NULL;
90         MultiByteToWideChar( CP_ACP, 0, (const char*)mre->Text, -1, buffer, len );
91     }
92     TRACE("returning %s\n", wine_dbgstr_w(buffer));
93     return buffer;
94 }
95
96
97 /**********************************************************************
98  *      load_messageA           (internal)
99  */
100 static LPSTR load_messageA( HMODULE module, UINT id, WORD lang )
101 {
102     const MESSAGE_RESOURCE_ENTRY *mre;
103     char *buffer;
104
105     TRACE("module = %p, id = %08x\n", module, id );
106
107     if (!module) module = GetModuleHandleW( NULL );
108     if (RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &mre ) != STATUS_SUCCESS) return NULL;
109
110     if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
111     {
112         int len = WideCharToMultiByte( CP_ACP, 0, (const WCHAR *)mre->Text, -1, NULL, 0, NULL, NULL );
113         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
114         WideCharToMultiByte( CP_ACP, 0, (const WCHAR *)mre->Text, -1, buffer, len, NULL, NULL );
115     }
116     else
117     {
118         int len = strlen((const char*)mre->Text) + 1;
119         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
120         memcpy( buffer, mre->Text, len );
121     }
122     TRACE("returning %s\n", wine_dbgstr_a(buffer));
123     return buffer;
124 }
125
126
127 /***********************************************************************
128  *           FormatMessageA   (KERNEL32.@)
129  * FIXME: missing wrap,
130  */
131 DWORD WINAPI FormatMessageA(
132         DWORD   dwFlags,
133         LPCVOID lpSource,
134         DWORD   dwMessageId,
135         DWORD   dwLanguageId,
136         LPSTR   lpBuffer,
137         DWORD   nSize,
138         va_list* _args )
139 {
140     LPDWORD args=(LPDWORD)_args;
141 #if defined(__i386__) || defined(__sparc__)
142 /* This implementation is completely dependent on the format of the va_list on x86 CPUs */
143     LPSTR       target,t;
144     DWORD       talloced;
145     LPSTR       from,f;
146     DWORD       width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
147     BOOL    eos = FALSE;
148     CHAR        ch;
149
150     TRACE("(0x%lx,%p,%ld,0x%lx,%p,%ld,%p)\n",
151           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
152     if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
153         &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
154            || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
155
156     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
157         FIXME("line wrapping (%lu) not supported.\n", width);
158     from = NULL;
159     if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
160     {
161         from = HeapAlloc( GetProcessHeap(), 0, strlen((LPCSTR)lpSource)+1 );
162         strcpy( from, (LPCSTR)lpSource );
163     }
164     else {
165         from = NULL;
166         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
167             from = load_messageA( (HMODULE)lpSource, dwMessageId, dwLanguageId );
168         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
169             from = load_messageA( kernel32_handle, dwMessageId, dwLanguageId );
170
171         if (!from)
172         {
173             SetLastError (ERROR_RESOURCE_LANG_NOT_FOUND);
174             return 0;
175         }
176     }
177     target      = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100);
178     t   = target;
179     talloced= 100;
180
181 #define ADD_TO_T(c) do { \
182         *t++=c;\
183         if (t-target == talloced) {\
184             target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2);\
185             t = target+talloced;\
186             talloced*=2;\
187       }\
188 } while (0)
189
190     if (from) {
191         f=from;
192         if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
193             while (*f && !eos)
194                 ADD_TO_T(*f++);
195         }
196         else {
197             while (*f && !eos) {
198                 if (*f=='%') {
199                     int insertnr;
200                     char *fmtstr,*x,*lastf;
201                     DWORD *argliststart;
202
203                     fmtstr = NULL;
204                     lastf = f;
205                     f++;
206                     if (!*f) {
207                         ADD_TO_T('%');
208                         continue;
209                     }
210                     switch (*f) {
211                     case '1':case '2':case '3':case '4':case '5':
212                     case '6':case '7':case '8':case '9':
213                         insertnr=*f-'0';
214                         switch (f[1]) {
215                         case '0':case '1':case '2':case '3':
216                         case '4':case '5':case '6':case '7':
217                         case '8':case '9':
218                             f++;
219                             insertnr=insertnr*10+*f-'0';
220                             f++;
221                             break;
222                         default:
223                             f++;
224                             break;
225                         }
226                         if (*f=='!') {
227                             f++;
228                             if (NULL!=(x=strchr(f,'!'))) {
229                                 *x='\0';
230                                 fmtstr=HeapAlloc(GetProcessHeap(),0,strlen(f)+2);
231                                 sprintf(fmtstr,"%%%s",f);
232                                 f=x+1;
233                             } else {
234                                 fmtstr=HeapAlloc(GetProcessHeap(),0,strlen(f)+2);
235                                 sprintf(fmtstr,"%%%s",f);
236                                 f+=strlen(f); /*at \0*/
237                             }
238                         } else {
239                             if(!args) break;
240                             fmtstr = HeapAlloc(GetProcessHeap(),0,3);
241                             strcpy( fmtstr, "%s" );
242                         }
243                         if (args) {
244                             int sz;
245                             LPSTR b;
246
247                             if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
248                                 argliststart=args+insertnr-1;
249                             else
250                                 argliststart=(*(DWORD**)args)+insertnr-1;
251
252                                 /* FIXME: precision and width components are not handled correctly */
253                             if ( (strcmp(fmtstr, "%ls") == 0) || (strcmp(fmtstr,"%S") == 0) ) {
254                                 sz = WideCharToMultiByte( CP_ACP, 0, *(WCHAR**)argliststart, -1, NULL, 0, NULL, NULL);
255                                 b = HeapAlloc(GetProcessHeap(), 0, sz);
256                                 WideCharToMultiByte( CP_ACP, 0, *(WCHAR**)argliststart, -1, b, sz, NULL, NULL);
257                             } else {
258                                 b = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sz = 1000);
259                                 /* CMF - This makes a BIG assumption about va_list */
260                                 TRACE("A BIG assumption\n");
261                                 vsnprintf(b, sz, fmtstr, (va_list) argliststart);
262                             }
263                             for (x=b; *x; x++) ADD_TO_T(*x);
264
265                             HeapFree(GetProcessHeap(),0,b);
266                         } else {
267                                 /* NULL args - copy formatstr
268                                  * (probably wrong)
269                                  */
270                             while ((lastf<f)&&(*lastf)) {
271                                 ADD_TO_T(*lastf++);
272                             }
273                         }
274                         HeapFree(GetProcessHeap(),0,fmtstr);
275                         break;
276                     case 'n':
277                         ADD_TO_T('\r');
278                         ADD_TO_T('\n');
279                         f++;
280                         break;
281                     case '0':
282                         eos = TRUE;
283                         f++;
284                         break;
285                     default:
286                         ADD_TO_T(*f++);
287                         break;
288                     }
289                 } else {
290                     ch = *f;
291                     f++;
292                     if (ch == '\r') {
293                         if (*f == '\n')
294                             f++;
295                         if(width)
296                             ADD_TO_T(' ');
297                         else
298                         {
299                             ADD_TO_T('\r');
300                             ADD_TO_T('\n');
301                         }
302                     } else {
303                         if (ch == '\n')
304                         {
305                             if(width)
306                                 ADD_TO_T(' ');
307                             else
308                             {
309                                 ADD_TO_T('\r');
310                                 ADD_TO_T('\n');
311                             }
312                         }
313                         else
314                             ADD_TO_T(ch);
315                     }
316                 }
317             }
318         }
319         *t='\0';
320     }
321     talloced = strlen(target)+1;
322     if (nSize && talloced<nSize) {
323         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,nSize);
324     }
325     TRACE("-- %s\n",debugstr_a(target));
326     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
327         *((LPVOID*)lpBuffer) = (LPVOID)LocalAlloc(LMEM_ZEROINIT,max(nSize, talloced));
328         memcpy(*(LPSTR*)lpBuffer,target,talloced);
329     } else {
330         lstrcpynA(lpBuffer,target,nSize);
331     }
332     HeapFree(GetProcessHeap(),0,target);
333     HeapFree(GetProcessHeap(),0,from);
334     TRACE("-- returning %d\n", (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ?  strlen(*(LPSTR*)lpBuffer):strlen(lpBuffer));
335     return (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ?
336         strlen(*(LPSTR*)lpBuffer):
337             strlen(lpBuffer);
338 #else
339     return 0;
340 #endif /* __i386__ */
341 }
342 #undef ADD_TO_T
343
344
345 /***********************************************************************
346  *           FormatMessageW   (KERNEL32.@)
347  */
348 DWORD WINAPI FormatMessageW(
349         DWORD   dwFlags,
350         LPCVOID lpSource,
351         DWORD   dwMessageId,
352         DWORD   dwLanguageId,
353         LPWSTR  lpBuffer,
354         DWORD   nSize,
355         va_list* _args )
356 {
357     LPDWORD args=(LPDWORD)_args;
358 #if defined(__i386__) || defined(__sparc__)
359 /* This implementation is completely dependent on the format of the va_list on x86 CPUs */
360     LPWSTR target,t;
361     DWORD talloced;
362     LPWSTR from,f;
363     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
364     BOOL eos = FALSE;
365     CHAR ch;
366
367     TRACE("(0x%lx,%p,%ld,0x%lx,%p,%ld,%p)\n",
368           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
369     if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
370         &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
371            || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
372
373     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
374         FIXME("line wrapping not supported.\n");
375     from = NULL;
376     if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
377         from = HeapAlloc( GetProcessHeap(), 0, (strlenW((LPCWSTR)lpSource) + 1) *
378             sizeof(WCHAR) );
379         strcpyW( from, (LPCWSTR)lpSource );
380     }
381     else {
382         from = NULL;
383         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
384             from = load_messageW( (HMODULE)lpSource, dwMessageId, dwLanguageId );
385         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
386             from = load_messageW( kernel32_handle, dwMessageId, dwLanguageId );
387
388         if (!from)
389         {
390             SetLastError (ERROR_RESOURCE_LANG_NOT_FOUND);
391             return 0;
392         }
393     }
394     target = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100 * sizeof(WCHAR) );
395     t = target;
396     talloced= 100;
397
398 #define ADD_TO_T(c)  do {\
399     *t++=c;\
400     if (t-target == talloced) {\
401         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2*sizeof(WCHAR));\
402         t = target+talloced;\
403         talloced*=2;\
404     } \
405 } while (0)
406
407     if (from) {
408         f=from;
409         if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
410             while (*f && !eos)
411                 ADD_TO_T(*f++);
412         }
413         else {
414             while (*f && !eos) {
415                 if (*f=='%') {
416                     int insertnr;
417                     WCHAR *fmtstr,*sprintfbuf,*x;
418                     DWORD *argliststart;
419
420                     fmtstr = NULL;
421                     f++;
422                     if (!*f) {
423                         ADD_TO_T('%');
424                         continue;
425                     }
426
427                     switch (*f) {
428                     case '1':case '2':case '3':case '4':case '5':
429                     case '6':case '7':case '8':case '9':
430                         insertnr=*f-'0';
431                         switch (f[1]) {
432                         case '0':case '1':case '2':case '3':
433                         case '4':case '5':case '6':case '7':
434                         case '8':case '9':
435                             f++;
436                             insertnr=insertnr*10+*f-'0';
437                             f++;
438                             break;
439                         default:
440                             f++;
441                             break;
442                         }
443                         if (*f=='!') {
444                             f++;
445                             if (NULL!=(x=strchrW(f,'!'))) {
446                                 *x='\0';
447                                 fmtstr=HeapAlloc( GetProcessHeap(), 0,(strlenW(f)+2)*sizeof(WCHAR));
448                                 sprintfW(fmtstr,PCNTFMTWSTR,f);
449                                 f=x+1;
450                             } else {
451                                 fmtstr=HeapAlloc(GetProcessHeap(),0,(strlenW(f)+2)*sizeof(WCHAR));
452                                 sprintfW(fmtstr,PCNTFMTWSTR,f);
453                                 f+=strlenW(f); /*at \0*/
454                             }
455                         } else {
456                             if(!args) break;
457                             fmtstr = HeapAlloc( GetProcessHeap(),0,3*sizeof(WCHAR));
458                             strcpyW( fmtstr, FMTWSTR );
459                         }
460                         if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
461                             argliststart=args+insertnr-1;
462                         else
463                             argliststart=(*(DWORD**)args)+insertnr-1;
464
465                         if (fmtstr[strlenW(fmtstr)-1]=='s' && argliststart[0]) {
466                             DWORD xarr[3];
467
468                             xarr[0]=*(argliststart+0);
469                             /* possible invalid pointers */
470                             xarr[1]=*(argliststart+1);
471                             xarr[2]=*(argliststart+2);
472                             sprintfbuf=HeapAlloc(GetProcessHeap(),0,(strlenW((LPWSTR)argliststart[0])*2+1)*sizeof(WCHAR));
473
474                             /* CMF - This makes a BIG assumption about va_list */
475                             vsprintfW(sprintfbuf, fmtstr, (va_list) xarr);
476                         } else {
477                             sprintfbuf=HeapAlloc(GetProcessHeap(),0,100);
478
479                             /* CMF - This makes a BIG assumption about va_list */
480                             vsprintfW(sprintfbuf, fmtstr, (va_list) argliststart);
481                         }
482                         x=sprintfbuf;
483                         while (*x) {
484                             ADD_TO_T(*x++);
485                         }
486                         HeapFree(GetProcessHeap(),0,sprintfbuf);
487                         HeapFree(GetProcessHeap(),0,fmtstr);
488                         break;
489                     case 'n':
490                         ADD_TO_T('\r');
491                         ADD_TO_T('\n');
492                         f++;
493                         break;
494                     case '0':
495                         eos = TRUE;
496                         f++;
497                         break;
498                     default:
499                         ADD_TO_T(*f++);
500                         break;
501                     }
502                 } else {
503                     ch = *f;
504                     f++;
505                     if (ch == '\r') {
506                         if (*f == '\n')
507                             f++;
508                         if(width)
509                             ADD_TO_T(' ');
510                         else
511                         {
512                             ADD_TO_T('\r');
513                             ADD_TO_T('\n');
514                         }
515                     } else {
516                         if (ch == '\n')
517                         {
518                             if(width)
519                                 ADD_TO_T(' ');
520                             else
521                             {
522                                 ADD_TO_T('\r');
523                                 ADD_TO_T('\n');
524                             }
525                         }
526                         else
527                             ADD_TO_T(ch);
528                     }
529                 }
530             }
531         }
532         *t='\0';
533     }
534     talloced = strlenW(target)+1;
535     if (nSize && talloced<nSize)
536         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,nSize*sizeof(WCHAR));
537     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
538         /* nSize is the MINIMUM size */
539         DWORD len = strlenW(target) + 1;
540         *((LPVOID*)lpBuffer) = (LPVOID)LocalAlloc(LMEM_ZEROINIT,len*sizeof(WCHAR));
541         strcpyW(*(LPWSTR*)lpBuffer, target);
542     }
543     else lstrcpynW(lpBuffer, target, nSize);
544
545     HeapFree(GetProcessHeap(),0,target);
546     HeapFree(GetProcessHeap(),0,from);
547     TRACE("ret=%s\n", wine_dbgstr_w((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ?
548         *(LPWSTR*)lpBuffer : lpBuffer));
549     return (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ?
550         strlenW(*(LPWSTR*)lpBuffer):
551             strlenW(lpBuffer);
552 #else
553     return 0;
554 #endif /* __i386__ */
555 }
556 #undef ADD_TO_T