msi/tests: Fix copy-paste errors in tests.
[wine] / dlls / msi / format.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Mike McCormack for CodeWeavers
5  * Copyright 2005 Aric Stewart for CodeWeavers
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include <stdarg.h>
23 #include <stdio.h>
24
25 #define COBJMACROS
26
27 #include "windef.h"
28 #include "winbase.h"
29 #include "winerror.h"
30 #include "wine/debug.h"
31 #include "msi.h"
32 #include "winnls.h"
33 #include "objbase.h"
34 #include "oleauto.h"
35
36 #include "msipriv.h"
37 #include "msiserver.h"
38 #include "wine/unicode.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(msi);
41
42 /* types arranged by precedence */
43 #define FORMAT_NULL         0x0001
44 #define FORMAT_LITERAL      0x0002
45 #define FORMAT_NUMBER       0x0004
46 #define FORMAT_LBRACK       0x0010
47 #define FORMAT_LBRACE       0x0020
48 #define FORMAT_RBRACK       0x0011
49 #define FORMAT_RBRACE       0x0021
50 #define FORMAT_ESCAPE       0x0040
51 #define FORMAT_PROPNULL     0x0080
52 #define FORMAT_ERROR        0x1000
53 #define FORMAT_FAIL         0x2000
54
55 #define left_type(x) (x & 0xF0)
56
57 typedef struct _tagFORMAT
58 {
59     MSIPACKAGE *package;
60     MSIRECORD *record;
61     LPWSTR deformatted;
62     int len;
63     int n;
64     BOOL propfailed;
65     BOOL groupfailed;
66     int groups;
67 } FORMAT;
68
69 typedef struct _tagFORMSTR
70 {
71     struct list entry;
72     int n;
73     int len;
74     int type;
75     BOOL propfound;
76     BOOL nonprop;
77 } FORMSTR;
78
79 typedef struct _tagSTACK
80 {
81     struct list items;
82 } STACK;
83
84 static STACK *create_stack(void)
85 {
86     STACK *stack = msi_alloc(sizeof(STACK));
87     list_init(&stack->items);
88     return stack;
89 }
90
91 static void free_stack(STACK *stack)
92 {
93     while (!list_empty(&stack->items))
94     {
95         FORMSTR *str = LIST_ENTRY(list_head(&stack->items), FORMSTR, entry);
96         list_remove(&str->entry);
97         msi_free(str);
98     }
99
100     msi_free(stack);
101 }
102
103 static void stack_push(STACK *stack, FORMSTR *str)
104 {
105     list_add_head(&stack->items, &str->entry);
106 }
107
108 static FORMSTR *stack_pop(STACK *stack)
109 {
110     FORMSTR *ret;
111
112     if (list_empty(&stack->items))
113         return NULL;
114
115     ret = LIST_ENTRY(list_head(&stack->items), FORMSTR, entry);
116     list_remove(&ret->entry);
117     return ret;
118 }
119
120 static FORMSTR *stack_find(STACK *stack, int type)
121 {
122     FORMSTR *str;
123
124     LIST_FOR_EACH_ENTRY(str, &stack->items, FORMSTR, entry)
125     {
126         if (str->type == type)
127             return str;
128     }
129
130     return NULL;
131 }
132
133 static FORMSTR *stack_peek(STACK *stack)
134 {
135     return LIST_ENTRY(list_head(&stack->items), FORMSTR, entry);
136 }
137
138 static LPCWSTR get_formstr_data(FORMAT *format, FORMSTR *str)
139 {
140     return &format->deformatted[str->n];
141 }
142
143 static LPWSTR dup_formstr(FORMAT *format, FORMSTR *str)
144 {
145     LPWSTR val;
146     LPCWSTR data;
147
148     if (str->len == 0)
149         return NULL;
150
151     val = msi_alloc((str->len + 1) * sizeof(WCHAR));
152     data = get_formstr_data(format, str);
153     lstrcpynW(val, data, str->len + 1);
154
155     return val;
156 }
157
158 static LPWSTR deformat_index(FORMAT *format, FORMSTR *str)
159 {
160     LPWSTR val, ret;
161
162     val = msi_alloc((str->len + 1) * sizeof(WCHAR));
163     lstrcpynW(val, get_formstr_data(format, str), str->len + 1);
164
165     ret = msi_dup_record_field(format->record, atoiW(val));
166
167     msi_free(val);
168     return ret;
169 }
170
171 static LPWSTR deformat_property(FORMAT *format, FORMSTR *str)
172 {
173     LPWSTR val, ret;
174
175     val = msi_alloc((str->len + 1) * sizeof(WCHAR));
176     lstrcpynW(val, get_formstr_data(format, str), str->len + 1);
177
178     ret = msi_dup_property(format->package->db, val);
179
180     msi_free(val);
181     return ret;
182 }
183
184 static LPWSTR deformat_component(FORMAT *format, FORMSTR *str)
185 {
186     LPWSTR key, ret = NULL;
187     MSICOMPONENT *comp;
188     BOOL source;
189
190     key = msi_alloc((str->len + 1) * sizeof(WCHAR));
191     lstrcpynW(key, get_formstr_data(format, str), str->len + 1);
192
193     comp = get_loaded_component(format->package, key);
194     if (!comp)
195         goto done;
196
197     source = (comp->Action == INSTALLSTATE_SOURCE) ? TRUE : FALSE;
198     ret = resolve_folder(format->package, comp->Directory, source, FALSE, TRUE, NULL);
199
200 done:
201     msi_free(key);
202     return ret;
203 }
204
205 static LPWSTR deformat_file(FORMAT *format, FORMSTR *str, BOOL shortname)
206 {
207     LPWSTR key, ret = NULL;
208     MSIFILE *file;
209     DWORD size;
210
211     key = msi_alloc((str->len + 1) * sizeof(WCHAR));
212     lstrcpynW(key, get_formstr_data(format, str), str->len + 1);
213
214     file = get_loaded_file(format->package, key);
215     if (!file)
216         goto done;
217
218     if (!shortname)
219     {
220         ret = strdupW(file->TargetPath);
221         goto done;
222     }
223
224     size = GetShortPathNameW(file->TargetPath, NULL, 0);
225     if (size <= 0)
226     {
227         ret = strdupW(file->TargetPath);
228         goto done;
229     }
230
231     size++;
232     ret = msi_alloc(size * sizeof(WCHAR));
233     GetShortPathNameW(file->TargetPath, ret, size);
234
235 done:
236     msi_free(key);
237     return ret;
238 }
239
240 static LPWSTR deformat_environment(FORMAT *format, FORMSTR *str)
241 {
242     LPWSTR key, ret = NULL;
243     DWORD sz;
244
245     key = msi_alloc((str->len + 1) * sizeof(WCHAR));
246     lstrcpynW(key, get_formstr_data(format, str), str->len + 1);
247
248     sz  = GetEnvironmentVariableW(key, NULL ,0);
249     if (sz <= 0)
250         goto done;
251
252     sz++;
253     ret = msi_alloc(sz * sizeof(WCHAR));
254     GetEnvironmentVariableW(key, ret, sz);
255
256 done:
257     msi_free(key);
258     return ret;
259 }
260
261 static LPWSTR deformat_literal(FORMAT *format, FORMSTR *str, BOOL *propfound,
262                                BOOL *nonprop, int *type)
263 {
264     LPCWSTR data = get_formstr_data(format, str);
265     LPWSTR replaced = NULL;
266     char ch = data[0];
267
268     if (ch == '\\')
269     {
270         str->n++;
271         if (str->len == 1)
272         {
273             str->len = 0;
274             replaced = NULL;
275         }
276         else
277         {
278             str->len = 1;
279             replaced = dup_formstr(format, str);
280         }
281     }
282     else if (ch == '~')
283     {
284         if (str->len != 1)
285             replaced = NULL;
286         else
287         {
288             replaced = msi_alloc(sizeof(WCHAR));
289             *replaced = '\0';
290         }
291     }
292     else if (ch == '%' || ch == '#' || ch == '!' || ch == '$')
293     {
294         str->n++;
295         str->len--;
296
297         switch (ch)
298         {
299         case '%':
300             replaced = deformat_environment(format, str); break;
301         case '#':
302             replaced = deformat_file(format, str, FALSE); break;
303         case '!':
304             replaced = deformat_file(format, str, TRUE); break;
305         case '$':
306             replaced = deformat_component(format, str); break;
307         }
308
309         *type = FORMAT_LITERAL;
310     }
311     else
312     {
313         replaced = deformat_property(format, str);
314         *type = FORMAT_LITERAL;
315
316         if (replaced)
317             *propfound = TRUE;
318         else
319             format->propfailed = TRUE;
320     }
321
322     return replaced;
323 }
324
325 static LPWSTR build_default_format(const MSIRECORD* record)
326 {
327     int i;  
328     int count;
329     LPWSTR rc, buf;
330     static const WCHAR fmt[] = {'%','i',':',' ','%','s',' ',0};
331     static const WCHAR fmt_null[] = {'%','i',':',' ',' ',0};
332     static const WCHAR fmt_index[] = {'%','i',0};
333     LPCWSTR str;
334     WCHAR index[10];
335     DWORD size, max_len, len;
336
337     count = MSI_RecordGetFieldCount(record);
338
339     max_len = MAX_PATH;
340     buf = msi_alloc((max_len + 1) * sizeof(WCHAR));
341
342     rc = NULL;
343     size = 1;
344     for (i = 1; i <= count; i++)
345     {
346         sprintfW(index, fmt_index, i);
347         str = MSI_RecordGetString(record, i);
348         len = (str) ? lstrlenW(str) : 0;
349         len += (sizeof(fmt_null)/sizeof(fmt_null[0]) - 3) + lstrlenW(index);
350         size += len;
351
352         if (len > max_len)
353         {
354             max_len = len;
355             buf = msi_realloc(buf, (max_len + 1) * sizeof(WCHAR));
356             if (!buf) return NULL;
357         }
358
359         if (str)
360             sprintfW(buf, fmt, i, str);
361         else
362             sprintfW(buf, fmt_null, i);
363
364         if (!rc)
365         {
366             rc = msi_alloc(size * sizeof(WCHAR));
367             lstrcpyW(rc, buf);
368         }
369         else
370         {
371             rc = msi_realloc(rc, size * sizeof(WCHAR));
372             lstrcatW(rc, buf);
373         }
374     }
375
376     msi_free(buf);
377     return rc;
378 }
379
380 static BOOL format_is_number(WCHAR x)
381 {
382     return ((x >= '0') && (x <= '9'));
383 }
384
385 static BOOL format_str_is_number(LPWSTR str)
386 {
387     LPWSTR ptr;
388
389     for (ptr = str; *ptr; ptr++)
390         if (!format_is_number(*ptr))
391             return FALSE;
392
393     return TRUE;
394 }
395
396 static BOOL format_is_alpha(WCHAR x)
397 {
398     return (!format_is_number(x) && x != '\0' &&
399             x != '[' && x != ']' && x != '{' && x != '}');
400 }
401
402 static BOOL format_is_literal(WCHAR x)
403 {
404     return (format_is_alpha(x) || format_is_number(x));
405 }
406
407 static int format_lex(FORMAT *format, FORMSTR **out)
408 {
409     int type, len = 1;
410     FORMSTR *str;
411     LPCWSTR data;
412     WCHAR ch;
413
414     *out = NULL;
415
416     if (!format->deformatted)
417         return FORMAT_NULL;
418
419     *out = msi_alloc_zero(sizeof(FORMSTR));
420     if (!*out)
421         return FORMAT_FAIL;
422
423     str = *out;
424     str->n = format->n;
425     str->len = 1;
426     data = get_formstr_data(format, str);
427
428     ch = data[0];
429     switch (ch)
430     {
431         case '{': type = FORMAT_LBRACE; break;
432         case '}': type = FORMAT_RBRACE; break;
433         case '[': type = FORMAT_LBRACK; break;
434         case ']': type = FORMAT_RBRACK; break;
435         case '~': type = FORMAT_PROPNULL; break;
436         case '\0': type = FORMAT_NULL; break;
437
438         default:
439             type = 0;
440     }
441
442     if (type)
443     {
444         str->type = type;
445         format->n++;
446         return type;
447     }
448
449     if (ch == '\\')
450     {
451         while (data[len] && data[len] != ']')
452             len++;
453
454         type = FORMAT_ESCAPE;
455     }
456     else if (format_is_alpha(ch))
457     {
458         while (format_is_literal(data[len]))
459             len++;
460
461         type = FORMAT_LITERAL;
462     }
463     else if (format_is_number(ch))
464     {
465         while (format_is_number(data[len]))
466             len++;
467
468         type = FORMAT_NUMBER;
469
470         if (data[len] != ']')
471         {
472             while (format_is_literal(data[len]))
473                 len++;
474
475             type = FORMAT_LITERAL;
476         }
477     }
478     else
479     {
480         ERR("Got unknown character %c(%x)\n", ch, ch);
481         return FORMAT_ERROR;
482     }
483
484     format->n += len;
485     str->len = len;
486     str->type = type;
487
488     return type;
489 }
490
491 static FORMSTR *format_replace(FORMAT *format, BOOL propfound, BOOL nonprop,
492                                int oldsize, int type, LPWSTR replace)
493 {
494     FORMSTR *ret;
495     LPWSTR str, ptr;
496     DWORD size = 0;
497     int n;
498
499     if (replace)
500     {
501         if (!*replace)
502             size = 1;
503         else
504             size = lstrlenW(replace);
505     }
506
507     size -= oldsize;
508     size = format->len + size + 1;
509
510     if (size <= 1)
511     {
512         msi_free(format->deformatted);
513         format->deformatted = NULL;
514         format->len = 0;
515         return NULL;
516     }
517
518     str = msi_alloc(size * sizeof(WCHAR));
519     if (!str)
520         return NULL;
521
522     str[0] = '\0';
523     memcpy(str, format->deformatted, format->n * sizeof(WCHAR));
524     n = format->n;
525
526     if (replace)
527     {
528         if (!*replace)
529         {
530             str[n] = '\0';
531             n++;
532         }
533         else
534         {
535             lstrcpyW(&str[n], replace);
536             n += lstrlenW(replace);
537         }
538     }
539
540     ptr = &format->deformatted[format->n + oldsize];
541     memcpy(&str[n], ptr, (lstrlenW(ptr) + 1) * sizeof(WCHAR));
542
543     msi_free(format->deformatted);
544     format->deformatted = str;
545     format->len = size - 1;
546
547     /* don't reformat the NULL */
548     if (replace && !*replace)
549         format->n++;
550
551     if (!replace)
552         return NULL;
553
554     ret = msi_alloc_zero(sizeof(FORMSTR));
555     if (!ret)
556         return NULL;
557
558     ret->len = lstrlenW(replace);
559     ret->type = type;
560     ret->n = format->n;
561     ret->propfound = propfound;
562     ret->nonprop = nonprop;
563
564     return ret;
565 }
566
567 static LPWSTR replace_stack_group(FORMAT *format, STACK *values,
568                                   BOOL *propfound, BOOL *nonprop,
569                                   int *oldsize, int *type)
570 {
571     LPWSTR replaced = NULL;
572     FORMSTR *content;
573     FORMSTR *node;
574     int n;
575
576     *nonprop = FALSE;
577     *propfound = FALSE;
578
579     node = stack_pop(values);
580     n = node->n;
581     *oldsize = node->len;
582     msi_free(node);
583
584     while ((node = stack_pop(values)))
585     {
586         *oldsize += node->len;
587
588         if (node->nonprop)
589             *nonprop = TRUE;
590
591         if (node->propfound)
592             *propfound = TRUE;
593
594         msi_free(node);
595     }
596
597     content = msi_alloc_zero(sizeof(FORMSTR));
598     content->n = n;
599     content->len = *oldsize;
600     content->type = FORMAT_LITERAL;
601
602     if (!format->groupfailed && (*oldsize == 2 ||
603         (format->propfailed && !*nonprop)))
604     {
605         msi_free(content);
606         return NULL;
607     }
608     else if (format->deformatted[content->n + 1] == '{' &&
609              format->deformatted[content->n + content->len - 2] == '}')
610     {
611         format->groupfailed = FALSE;
612         content->len = 0;
613     }
614     else if (*propfound && !*nonprop &&
615              !format->groupfailed && format->groups == 0)
616     {
617         content->n++;
618         content->len -= 2;
619     }
620     else
621     {
622         if (format->groups != 0)
623             format->groupfailed = TRUE;
624
625         *nonprop = TRUE;
626     }
627
628     replaced = dup_formstr(format, content);
629     *type = content->type;
630     msi_free(content);
631
632     if (format->groups == 0)
633         format->propfailed = FALSE;
634
635     return replaced;
636 }
637
638 static LPWSTR replace_stack_prop(FORMAT *format, STACK *values,
639                                  BOOL *propfound, BOOL *nonprop,
640                                  int *oldsize, int *type)
641 {
642     LPWSTR replaced = NULL;
643     FORMSTR *content;
644     FORMSTR *node;
645     int n;
646
647     *propfound = FALSE;
648     *nonprop = FALSE;
649
650     node = stack_pop(values);
651     n = node->n;
652     *oldsize = node->len;
653     *type = stack_peek(values)->type;
654     msi_free(node);
655
656     while ((node = stack_pop(values)))
657     {
658         *oldsize += node->len;
659
660         if (*type != FORMAT_ESCAPE &&
661             stack_peek(values) && node->type != *type)
662             *type = FORMAT_LITERAL;
663
664         msi_free(node);
665     }
666
667     content = msi_alloc_zero(sizeof(FORMSTR));
668     content->n = n + 1;
669     content->len = *oldsize - 2;
670     content->type = *type;
671
672     if (*type == FORMAT_NUMBER)
673     {
674         replaced = deformat_index(format, content);
675         if (replaced)
676             *propfound = TRUE;
677         else
678             format->propfailed = TRUE;
679
680         if (replaced)
681             *type = format_str_is_number(replaced) ?
682                 FORMAT_NUMBER : FORMAT_LITERAL;
683     }
684     else if (format->package)
685     {
686         replaced = deformat_literal(format, content, propfound, nonprop, type);
687     }
688     else
689     {
690         *nonprop = TRUE;
691         content->n--;
692         content->len += 2;
693         replaced = dup_formstr(format, content);
694     }
695
696     msi_free(content);
697     return replaced;
698 }
699
700 static UINT replace_stack(FORMAT *format, STACK *stack, STACK *values)
701 {
702     LPWSTR replaced = NULL;
703     FORMSTR *beg;
704     FORMSTR *top;
705     FORMSTR *node;
706     BOOL propfound = FALSE;
707     BOOL nonprop = FALSE;
708     BOOL group = FALSE;
709     int oldsize = 0;
710     int type, n;
711
712     node = stack_peek(values);
713     type = node->type;
714     n = node->n;
715
716     if (type == FORMAT_LBRACK)
717         replaced = replace_stack_prop(format, values, &propfound,
718                                       &nonprop, &oldsize, &type);
719     else if (type == FORMAT_LBRACE)
720     {
721         replaced = replace_stack_group(format, values, &propfound,
722                                        &nonprop, &oldsize, &type);
723         group = TRUE;
724     }
725
726     format->n = n;
727     beg = format_replace(format, propfound, nonprop, oldsize, type, replaced);
728     if (!beg)
729         return ERROR_SUCCESS;
730
731     msi_free(replaced);
732     format->n = beg->n + beg->len;
733
734     top = stack_peek(stack);
735     if (top)
736     {
737         type = top->type;
738
739         if ((type == FORMAT_LITERAL || type == FORMAT_NUMBER) &&
740             type == beg->type)
741         {
742             top->len += beg->len;
743
744             if (group)
745                 top->nonprop = FALSE;
746
747             if (type == FORMAT_LITERAL)
748                 top->nonprop = beg->nonprop;
749
750             if (beg->propfound)
751                 top->propfound = TRUE;
752
753             msi_free(beg);
754             return ERROR_SUCCESS;
755         }
756     }
757
758     stack_push(stack, beg);
759     return ERROR_SUCCESS;
760 }
761
762 static BOOL verify_format(LPWSTR data)
763 {
764     int count = 0;
765
766     while (*data)
767     {
768         if (*data == '[' && *(data - 1) != '\\')
769             count++;
770         else if (*data == ']')
771             count--;
772
773         data++;
774     }
775
776     if (count > 0)
777         return FALSE;
778
779     return TRUE;
780 }
781
782 static DWORD deformat_string_internal(MSIPACKAGE *package, LPCWSTR ptr, 
783                                       WCHAR** data, DWORD *len,
784                                       MSIRECORD* record, INT* failcount)
785 {
786     FORMAT format;
787     FORMSTR *str = NULL;
788     STACK *stack, *temp;
789     FORMSTR *node;
790     int type;
791
792     if (!ptr)
793     {
794         *data = NULL;
795         *len = 0;
796         return ERROR_SUCCESS;
797     }
798
799     *data = strdupW(ptr);
800     *len = lstrlenW(ptr);
801
802     ZeroMemory(&format, sizeof(FORMAT));
803     format.package = package;
804     format.record = record;
805     format.deformatted = *data;
806     format.len = *len;
807
808     if (!verify_format(*data))
809         return ERROR_SUCCESS;
810
811     stack = create_stack();
812     temp = create_stack();
813
814     while ((type = format_lex(&format, &str)) != FORMAT_NULL)
815     {
816         if (type == FORMAT_LBRACK || type == FORMAT_LBRACE ||
817             type == FORMAT_LITERAL || type == FORMAT_NUMBER ||
818             type == FORMAT_ESCAPE || type == FORMAT_PROPNULL)
819         {
820             if (type == FORMAT_LBRACE)
821             {
822                 format.propfailed = FALSE;
823                 format.groups++;
824             }
825             else if (type == FORMAT_ESCAPE &&
826                      !stack_find(stack, FORMAT_LBRACK))
827             {
828                 format.n -= str->len - 1;
829                 str->len = 1;
830             }
831
832             stack_push(stack, str);
833         }
834         else if (type == FORMAT_RBRACK || type == FORMAT_RBRACE)
835         {
836             if (type == FORMAT_RBRACE)
837                 format.groups--;
838
839             stack_push(stack, str);
840
841             if (stack_find(stack, left_type(type)))
842             {
843                 do
844                 {
845                     node = stack_pop(stack);
846                     stack_push(temp, node);
847                 } while (node->type != left_type(type));
848
849                 replace_stack(&format, stack, temp);
850             }
851         }
852     }
853
854     *data = format.deformatted;
855     *len = format.len;
856
857     msi_free(str);
858     free_stack(stack);
859     free_stack(temp);
860
861     return ERROR_SUCCESS;
862 }
863
864 UINT MSI_FormatRecordW( MSIPACKAGE* package, MSIRECORD* record, LPWSTR buffer,
865                         LPDWORD size )
866 {
867     LPWSTR deformated;
868     LPWSTR rec;
869     DWORD len;
870     UINT rc = ERROR_INVALID_PARAMETER;
871
872     TRACE("%p %p %p %p\n", package, record, buffer, size);
873
874     rec = msi_dup_record_field(record,0);
875     if (!rec)
876         rec = build_default_format(record);
877
878     TRACE("(%s)\n",debugstr_w(rec));
879
880     deformat_string_internal(package, rec, &deformated, &len, record, NULL);
881     if (buffer)
882     {
883         if (*size>len)
884         {
885             memcpy(buffer,deformated,len*sizeof(WCHAR));
886             rc = ERROR_SUCCESS;
887             buffer[len] = 0;
888         }
889         else
890         {
891             if (*size > 0)
892             {
893                 memcpy(buffer,deformated,(*size)*sizeof(WCHAR));
894                 buffer[(*size)-1] = 0;
895             }
896             rc = ERROR_MORE_DATA;
897         }
898     }
899     else
900         rc = ERROR_SUCCESS;
901
902     *size = len;
903
904     msi_free(rec);
905     msi_free(deformated);
906     return rc;
907 }
908
909 UINT WINAPI MsiFormatRecordW( MSIHANDLE hInstall, MSIHANDLE hRecord, 
910                               LPWSTR szResult, LPDWORD sz )
911 {
912     UINT r = ERROR_INVALID_HANDLE;
913     MSIPACKAGE *package;
914     MSIRECORD *record;
915
916     TRACE("%d %d %p %p\n", hInstall, hRecord, szResult, sz);
917
918     package = msihandle2msiinfo( hInstall, MSIHANDLETYPE_PACKAGE );
919     if (!package)
920     {
921         HRESULT hr;
922         IWineMsiRemotePackage *remote_package;
923         BSTR value = NULL;
924         awstring wstr;
925
926         remote_package = (IWineMsiRemotePackage *)msi_get_remote( hInstall );
927         if (remote_package)
928         {
929             hr = IWineMsiRemotePackage_FormatRecord( remote_package, hRecord,
930                                                      &value );
931             if (FAILED(hr))
932                 goto done;
933
934             wstr.unicode = TRUE;
935             wstr.str.w = szResult;
936             r = msi_strcpy_to_awstring( value, &wstr, sz );
937
938 done:
939             IWineMsiRemotePackage_Release( remote_package );
940             SysFreeString( value );
941
942             if (FAILED(hr))
943             {
944                 if (HRESULT_FACILITY(hr) == FACILITY_WIN32)
945                     return HRESULT_CODE(hr);
946
947                 return ERROR_FUNCTION_FAILED;
948             }
949
950             return r;
951         }
952     }
953
954     record = msihandle2msiinfo( hRecord, MSIHANDLETYPE_RECORD );
955
956     if (!record)
957         return ERROR_INVALID_HANDLE;
958     if (!sz)
959     {
960         msiobj_release( &record->hdr );
961         if (szResult)
962             return ERROR_INVALID_PARAMETER;
963         else
964             return ERROR_SUCCESS;
965     }
966
967     r = MSI_FormatRecordW( package, record, szResult, sz );
968     msiobj_release( &record->hdr );
969     if (package)
970         msiobj_release( &package->hdr );
971     return r;
972 }
973
974 UINT WINAPI MsiFormatRecordA( MSIHANDLE hInstall, MSIHANDLE hRecord,
975                               LPSTR szResult, LPDWORD sz )
976 {
977     UINT r;
978     DWORD len, save;
979     LPWSTR value;
980
981     TRACE("%d %d %p %p\n", hInstall, hRecord, szResult, sz);
982
983     if (!hRecord)
984         return ERROR_INVALID_HANDLE;
985
986     if (!sz)
987     {
988         if (szResult)
989             return ERROR_INVALID_PARAMETER;
990         else
991             return ERROR_SUCCESS;
992     }
993
994     r = MsiFormatRecordW( hInstall, hRecord, NULL, &len );
995     if (r != ERROR_SUCCESS)
996         return r;
997
998     value = msi_alloc(++len * sizeof(WCHAR));
999     if (!value)
1000         return ERROR_OUTOFMEMORY;
1001
1002     r = MsiFormatRecordW( hInstall, hRecord, value, &len );
1003     if (r != ERROR_SUCCESS)
1004         goto done;
1005
1006     save = len + 1;
1007     len = WideCharToMultiByte(CP_ACP, 0, value, len + 1, NULL, 0, NULL, NULL);
1008     WideCharToMultiByte(CP_ACP, 0, value, len, szResult, *sz, NULL, NULL);
1009
1010     if (szResult && len > *sz)
1011     {
1012         if (*sz) szResult[*sz - 1] = '\0';
1013         r = ERROR_MORE_DATA;
1014     }
1015
1016     *sz = save - 1;
1017
1018 done:
1019     msi_free(value);
1020     return r;
1021 }