msi: Fix an uninitialized variable causing random conformance test failures.
[wine] / dlls / msi / database.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2002,2003,2004,2005 Mike McCormack for CodeWeavers
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 <stdarg.h>
22
23 #define COBJMACROS
24 #define NONAMELESSUNION
25
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winreg.h"
29 #include "winnls.h"
30 #include "wine/debug.h"
31 #include "wine/unicode.h"
32 #include "msi.h"
33 #include "msiquery.h"
34 #include "msipriv.h"
35 #include "objidl.h"
36 #include "objbase.h"
37
38 #include "initguid.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(msi);
41
42 DEFINE_GUID( CLSID_MsiDatabase, 0x000c1084, 0x0000, 0x0000,
43              0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);
44 DEFINE_GUID( CLSID_MsiPatch, 0x000c1086, 0x0000, 0x0000,
45              0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);
46
47 /*
48  *  .MSI  file format
49  *
50  *  An .msi file is a structured storage file.
51  *  It contains a number of streams.
52  *  A stream for each table in the database.
53  *  Two streams for the string table in the database.
54  *  Any binary data in a table is a reference to a stream.
55  */
56
57 static VOID MSI_CloseDatabase( MSIOBJECTHDR *arg )
58 {
59     MSIDATABASE *db = (MSIDATABASE *) arg;
60     DWORD r;
61
62     msi_free(db->path);
63     free_cached_tables( db );
64     msi_free_transforms( db );
65     msi_destroy_stringtable( db->strings );
66     r = IStorage_Release( db->storage );
67     if( r )
68         ERR("database reference count was not zero (%d)\n", r);
69     if (db->deletefile)
70     {
71         DeleteFileW( db->deletefile );
72         msi_free( db->deletefile );
73     }
74 }
75
76 UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
77 {
78     IStorage *stg = NULL;
79     HRESULT r;
80     MSIDATABASE *db = NULL;
81     UINT ret = ERROR_FUNCTION_FAILED;
82     LPCWSTR szMode, save_path;
83     STATSTG stat;
84     BOOL created = FALSE;
85     WCHAR path[MAX_PATH];
86
87     static const WCHAR backslash[] = {'\\',0};
88
89     TRACE("%s %s\n",debugstr_w(szDBPath),debugstr_w(szPersist) );
90
91     if( !pdb )
92         return ERROR_INVALID_PARAMETER;
93
94     save_path = szDBPath;
95     szMode = szPersist;
96     if( HIWORD( szPersist ) )
97     {
98         if (!CopyFileW( szDBPath, szPersist, FALSE ))
99             return ERROR_OPEN_FAILED;
100
101         szDBPath = szPersist;
102         szPersist = MSIDBOPEN_TRANSACT;
103         created = TRUE;
104     }
105
106     if( szPersist == MSIDBOPEN_READONLY )
107     {
108         r = StgOpenStorage( szDBPath, NULL,
109               STGM_DIRECT|STGM_READ|STGM_SHARE_DENY_WRITE, NULL, 0, &stg);
110     }
111     else if( szPersist == MSIDBOPEN_CREATE || szPersist == MSIDBOPEN_CREATEDIRECT )
112     {
113         /* FIXME: MSIDBOPEN_CREATE should case STGM_TRANSACTED flag to be
114          * used here: */
115         r = StgCreateDocfile( szDBPath,
116               STGM_CREATE|STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg);
117         if( r == ERROR_SUCCESS )
118         {
119             IStorage_SetClass( stg, &CLSID_MsiDatabase );
120             r = init_string_table( stg );
121         }
122         created = TRUE;
123     }
124     else if( szPersist == MSIDBOPEN_TRANSACT )
125     {
126         /* FIXME: MSIDBOPEN_TRANSACT should case STGM_TRANSACTED flag to be
127          * used here: */
128         r = StgOpenStorage( szDBPath, NULL,
129               STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg);
130     }
131     else if( szPersist == MSIDBOPEN_DIRECT )
132     {
133         r = StgOpenStorage( szDBPath, NULL,
134               STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg);
135     }
136     else
137     {
138         ERR("unknown flag %p\n",szPersist);
139         return ERROR_INVALID_PARAMETER;
140     }
141
142     if( FAILED( r ) )
143     {
144         FIXME("open failed r = %08x!\n",r);
145         return ERROR_FUNCTION_FAILED;
146     }
147
148     r = IStorage_Stat( stg, &stat, STATFLAG_NONAME );
149     if( FAILED( r ) )
150     {
151         FIXME("Failed to stat storage\n");
152         goto end;
153     }
154
155     if ( !IsEqualGUID( &stat.clsid, &CLSID_MsiDatabase ) &&
156          !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) ) 
157     {
158         ERR("storage GUID is not a MSI database GUID %s\n",
159              debugstr_guid(&stat.clsid) );
160         goto end;
161     }
162
163     db = alloc_msiobject( MSIHANDLETYPE_DATABASE, sizeof (MSIDATABASE),
164                               MSI_CloseDatabase );
165     if( !db )
166     {
167         FIXME("Failed to allocate a handle\n");
168         goto end;
169     }
170
171     if (!strchrW( save_path, '\\' ))
172     {
173         GetCurrentDirectoryW( MAX_PATH, path );
174         lstrcatW( path, backslash );
175         lstrcatW( path, save_path );
176     }
177     else
178         lstrcpyW( path, save_path );
179
180     db->path = strdupW( path );
181
182     if( TRACE_ON( msi ) )
183         enum_stream_names( stg );
184
185     db->storage = stg;
186     db->mode = szMode;
187     if (created)
188         db->deletefile = strdupW( szDBPath );
189     else
190         db->deletefile = NULL;
191     list_init( &db->tables );
192     list_init( &db->transforms );
193
194     db->strings = load_string_table( stg );
195     if( !db->strings )
196         goto end;
197
198     ret = ERROR_SUCCESS;
199
200     msiobj_addref( &db->hdr );
201     IStorage_AddRef( stg );
202     *pdb = db;
203
204 end:
205     if( db )
206         msiobj_release( &db->hdr );
207     if( stg )
208         IStorage_Release( stg );
209
210     return ret;
211 }
212
213 UINT WINAPI MsiOpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIHANDLE *phDB)
214 {
215     MSIDATABASE *db;
216     UINT ret;
217
218     TRACE("%s %s %p\n",debugstr_w(szDBPath),debugstr_w(szPersist), phDB);
219
220     ret = MSI_OpenDatabaseW( szDBPath, szPersist, &db );
221     if( ret == ERROR_SUCCESS )
222     {
223         *phDB = alloc_msihandle( &db->hdr );
224         if (! *phDB)
225             ret = ERROR_NOT_ENOUGH_MEMORY;
226         msiobj_release( &db->hdr );
227     }
228
229     return ret;
230 }
231
232 UINT WINAPI MsiOpenDatabaseA(LPCSTR szDBPath, LPCSTR szPersist, MSIHANDLE *phDB)
233 {
234     HRESULT r = ERROR_FUNCTION_FAILED;
235     LPWSTR szwDBPath = NULL, szwPersist = NULL;
236
237     TRACE("%s %s %p\n", debugstr_a(szDBPath), debugstr_a(szPersist), phDB);
238
239     if( szDBPath )
240     {
241         szwDBPath = strdupAtoW( szDBPath );
242         if( !szwDBPath )
243             goto end;
244     }
245
246     if( HIWORD(szPersist) )
247     {
248         szwPersist = strdupAtoW( szPersist );
249         if( !szwPersist )
250             goto end;
251     }
252     else
253         szwPersist = (LPWSTR)(DWORD_PTR)szPersist;
254
255     r = MsiOpenDatabaseW( szwDBPath, szwPersist, phDB );
256
257 end:
258     if( HIWORD(szPersist) )
259         msi_free( szwPersist );
260     msi_free( szwDBPath );
261
262     return r;
263 }
264
265 static LPWSTR msi_read_text_archive(LPCWSTR path)
266 {
267     HANDLE file;
268     LPSTR data = NULL;
269     LPWSTR wdata = NULL;
270     DWORD read, size = 0;
271
272     file = CreateFileW( path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
273     if (file == INVALID_HANDLE_VALUE)
274         return NULL;
275
276     size = GetFileSize( file, NULL );
277     data = msi_alloc( size + 1 );
278     if (!data)
279         goto done;
280
281     if (!ReadFile( file, data, size, &read, NULL ))
282         goto done;
283
284     data[size] = '\0';
285     wdata = strdupAtoW( data );
286
287 done:
288     CloseHandle( file );
289     msi_free( data );
290     return wdata;
291 }
292
293 static void msi_parse_line(LPWSTR *line, LPWSTR **entries, DWORD *num_entries)
294 {
295     LPWSTR ptr = *line, save;
296     DWORD i, count = 1;
297
298     *entries = NULL;
299
300     /* stay on this line */
301     while (*ptr && *ptr != '\n')
302     {
303         /* entries are separated by tabs */
304         if (*ptr == '\t')
305             count++;
306
307         ptr++;
308     }
309
310     *entries = msi_alloc(count * sizeof(LPWSTR));
311     if (!*entries)
312         return;
313
314     /* store pointers into the data */
315     for (i = 0, ptr = *line; i < count; i++)
316     {
317         save = ptr;
318
319         while (*ptr && *ptr != '\t' && *ptr != '\n') ptr++;
320
321         /* NULL-separate the data */
322         if (*ptr)
323             *ptr++ = '\0';
324
325         (*entries)[i] = save;
326     }
327
328     /* move to the next line if there's more, else EOF */
329     *line = ptr;
330
331     if (num_entries)
332         *num_entries = count;
333 }
334
335 static LPWSTR msi_build_createsql_prelude(LPWSTR table)
336 {
337     LPWSTR prelude;
338     DWORD size;
339
340     static const WCHAR create_fmt[] = {'C','R','E','A','T','E',' ','T','A','B','L','E',' ','`','%','s','`',' ','(',' ',0};
341
342     size = sizeof(create_fmt) + lstrlenW(table) - 2;
343     prelude = msi_alloc(size * sizeof(WCHAR));
344     if (!prelude)
345         return NULL;
346
347     sprintfW(prelude, create_fmt, table);
348     return prelude;
349 }
350
351 static LPWSTR msi_build_createsql_columns(LPWSTR *columns_data, LPWSTR *types, DWORD num_columns)
352 {
353     LPWSTR columns;
354     LPCWSTR type;
355     DWORD sql_size = 1, i, len;
356     WCHAR expanded[128], *ptr;
357     WCHAR size[10], comma[2], extra[10];
358
359     static const WCHAR column_fmt[] = {'`','%','s','`',' ','%','s','%','s','%','s','%','s',' ',0};
360     static const WCHAR size_fmt[] = {'(','%','s',')',0};
361     static const WCHAR type_char[] = {'C','H','A','R',0};
362     static const WCHAR type_int[] = {'I','N','T',0};
363     static const WCHAR type_long[] = {'L','O','N','G',0};
364     static const WCHAR type_notnull[] = {' ','N','O','T',' ','N','U','L','L',0};
365
366     columns = msi_alloc_zero(sql_size * sizeof(WCHAR));
367     if (!columns)
368         return NULL;
369
370     for (i = 0; i < num_columns; i++)
371     {
372         type = NULL;
373         comma[1] = size[0] = extra[0] = '\0';
374
375         if (i == num_columns - 1)
376             comma[0] = '\0';
377         else
378             comma[0] = ',';
379
380         ptr = &types[i][1];
381         len = atolW(ptr);
382
383         switch (types[i][0])
384         {
385             case 'l': case 's':
386                 lstrcpyW(extra, type_notnull);
387             case 'L': case 'S':
388                 type = type_char;
389                 sprintfW(size, size_fmt, ptr);
390                 break;
391             case 'I': case 'i':
392                 if (len == 2)
393                     type = type_int;
394                 else
395                     type = type_long;
396                 break;
397         }
398
399         sprintfW(expanded, column_fmt, columns_data[i], type, size, extra, comma);
400         sql_size += lstrlenW(expanded);
401
402         columns = msi_realloc(columns, sql_size * sizeof(WCHAR));
403         if (!columns)
404             return NULL;
405
406         lstrcatW(columns, expanded);
407     }
408
409     return columns;
410 }
411
412 static LPWSTR msi_build_createsql_postlude(LPWSTR primary_key)
413 {
414     LPWSTR postlude;
415     DWORD size;
416
417     static const WCHAR postlude_fmt[] = {'P','R','I','M','A','R','Y',' ','K','E','Y',' ','`','%','s','`',')',' ','H','O','L','D',0};
418
419     size = sizeof(postlude_fmt) + lstrlenW(primary_key) - 2;
420     postlude = msi_alloc(size * sizeof(WCHAR));
421     if (!postlude)
422         return NULL;
423
424     sprintfW(postlude, postlude_fmt, primary_key);
425     return postlude;
426 }
427
428 static UINT msi_add_table_to_db(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types, LPWSTR *labels, DWORD num_columns)
429 {
430     UINT r;
431     DWORD size;
432     MSIQUERY *view;
433     LPWSTR create_sql;
434     LPWSTR prelude, columns_sql, postlude;
435
436     prelude = msi_build_createsql_prelude(labels[0]);
437     columns_sql = msi_build_createsql_columns(columns, types, num_columns);
438     postlude = msi_build_createsql_postlude(labels[1]);
439
440     if (!prelude || !columns_sql || !postlude)
441         return ERROR_OUTOFMEMORY;
442
443     size = lstrlenW(prelude) + lstrlenW(columns_sql) + lstrlenW(postlude) + 1;
444     create_sql = msi_alloc(size * sizeof(WCHAR));
445     if (!create_sql)
446         return ERROR_OUTOFMEMORY;
447
448     lstrcpyW(create_sql, prelude);
449     lstrcatW(create_sql, columns_sql);
450     lstrcatW(create_sql, postlude);
451
452     msi_free(prelude);
453     msi_free(columns_sql);
454     msi_free(postlude);
455
456     r = MSI_DatabaseOpenViewW( db, create_sql, &view );
457     msi_free(create_sql);
458
459     if (r != ERROR_SUCCESS)
460         return r;
461
462     r = MSI_ViewExecute(view, NULL);
463     MSI_ViewClose(view);
464     msiobj_release(&view->hdr);
465
466     return r;
467 }
468
469 static LPWSTR msi_build_insertsql_prelude(LPWSTR table)
470 {
471     LPWSTR prelude;
472     DWORD size;
473
474     static const WCHAR insert_fmt[] = {'I','N','S','E','R','T',' ','I','N','T','O',' ','`','%','s','`',' ','(',' ',0};
475
476     size = sizeof(insert_fmt) + lstrlenW(table) - 2;
477     prelude = msi_alloc(size * sizeof(WCHAR));
478     if (!prelude)
479         return NULL;
480
481     sprintfW(prelude, insert_fmt, table);
482     return prelude;
483 }
484
485 static LPWSTR msi_build_insertsql_columns(LPWSTR *columns_data, LPWSTR *types, DWORD num_columns)
486 {
487     LPWSTR columns;
488     DWORD sql_size = 1, i;
489     WCHAR expanded[128];
490
491     static const WCHAR column_fmt[] =  {'`','%','s','`',',',' ',0};
492
493     columns = msi_alloc_zero(sql_size * sizeof(WCHAR));
494     if (!columns)
495         return NULL;
496
497     for (i = 0; i < num_columns; i++)
498     {
499         sprintfW(expanded, column_fmt, columns_data[i]);
500         sql_size += lstrlenW(expanded);
501
502         if (i == num_columns - 1)
503         {
504             sql_size -= 2;
505             expanded[lstrlenW(expanded) - 2] = '\0';
506         }
507
508         columns = msi_realloc(columns, sql_size * sizeof(WCHAR));
509         if (!columns)
510             return NULL;
511
512         lstrcatW(columns, expanded);
513     }
514
515     return columns;
516 }
517
518 static LPWSTR msi_build_insertsql_data(LPWSTR **records, LPWSTR *types, DWORD num_columns, DWORD irec)
519 {
520     LPWSTR columns;
521     DWORD sql_size = 1, i;
522     WCHAR expanded[128];
523
524     static const WCHAR str_fmt[] = {'\'','%','s','\'',',',' ',0};
525     static const WCHAR int_fmt[] = {'%','s',',',' ',0};
526     static const WCHAR empty[] = {'\'','\'',',',' ',0};
527
528     columns = msi_alloc_zero(sql_size * sizeof(WCHAR));
529     if (!columns)
530         return NULL;
531
532     for (i = 0; i < num_columns; i++)
533     {
534         switch (types[i][0])
535         {
536             case 'L': case 'l': case 'S': case 's':
537                 sprintfW(expanded, str_fmt, records[irec][i]);
538                 break;
539             case 'I': case 'i':
540                 if (*records[0][i])
541                     sprintfW(expanded, int_fmt, records[irec][i]);
542                 else
543                     lstrcpyW(expanded, empty);
544                 break;
545             default:
546                 return NULL;
547         }
548
549         if (i == num_columns - 1)
550             expanded[lstrlenW(expanded) - 2] = '\0';
551
552         sql_size += lstrlenW(expanded);
553         columns = msi_realloc(columns, sql_size * sizeof(WCHAR));
554         if (!columns)
555             return NULL;
556
557         lstrcatW(columns, expanded);
558     }
559
560     return columns;
561 }
562
563 static UINT msi_add_records_to_table(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types,
564                                      LPWSTR *labels, LPWSTR **records,
565                                      int num_columns, int num_records)
566 {
567     MSIQUERY *view;
568     LPWSTR insert_sql;
569     DWORD size, i;
570     UINT r = ERROR_SUCCESS;
571
572     static const WCHAR mid[] = {' ',')',' ','V','A','L','U','E','S',' ','(',' ',0};
573     static const WCHAR end[] = {' ',')',0};
574
575     LPWSTR prelude = msi_build_insertsql_prelude(labels[0]);
576     LPWSTR columns_sql = msi_build_insertsql_columns(columns, types, num_columns);
577     
578     for (i = 0; i < num_records; i++)
579     {
580         LPWSTR data = msi_build_insertsql_data(records, types, num_columns, i);
581
582         size = lstrlenW(prelude) + lstrlenW(columns_sql) + sizeof(mid) + lstrlenW(data) + sizeof(end) - 1; 
583         insert_sql = msi_alloc(size * sizeof(WCHAR));
584         if (!insert_sql)
585             return ERROR_OUTOFMEMORY;
586     
587         lstrcpyW(insert_sql, prelude);
588         lstrcatW(insert_sql, columns_sql);
589         lstrcatW(insert_sql, mid);
590         lstrcatW(insert_sql, data);
591         lstrcatW(insert_sql, end);
592
593         msi_free(data);
594
595         r = MSI_DatabaseOpenViewW( db, insert_sql, &view );
596         msi_free(insert_sql);
597
598         if (r != ERROR_SUCCESS)
599             goto done;
600
601         r = MSI_ViewExecute(view, NULL);
602         MSI_ViewClose(view);
603         msiobj_release(&view->hdr);
604     }
605
606 done:
607     msi_free(prelude);
608     msi_free(columns_sql);
609
610     return r;
611 }
612
613 UINT MSI_DatabaseImport(MSIDATABASE *db, LPCWSTR folder, LPCWSTR file)
614 {
615     UINT r;
616     DWORD len, i;
617     DWORD num_columns, num_records = 0;
618     LPWSTR *columns, *types, *labels;
619     LPWSTR path, ptr, data;
620     LPWSTR **records;
621
622     static const WCHAR backslash[] = {'\\',0};
623
624     TRACE("%p %s %s\n", db, debugstr_w(folder), debugstr_w(file) );
625
626     if( folder == NULL || file == NULL )
627         return ERROR_INVALID_PARAMETER;
628
629     len = lstrlenW(folder) + lstrlenW(backslash) + lstrlenW(file) + 1;
630     path = msi_alloc( len * sizeof(WCHAR) );
631     if (!path)
632         return ERROR_OUTOFMEMORY;
633
634     lstrcpyW( path, folder );
635     lstrcatW( path, backslash );
636     lstrcatW( path, file );
637
638     data = msi_read_text_archive( path );
639
640     ptr = data;
641     msi_parse_line( &ptr, &columns, &num_columns );
642     msi_parse_line( &ptr, &types, NULL );
643     msi_parse_line( &ptr, &labels, NULL );
644
645     records = msi_alloc(sizeof(LPWSTR *));
646     if (!records)
647         return ERROR_OUTOFMEMORY;
648
649     /* read in the table records */
650     while (*ptr)
651     {
652         msi_parse_line( &ptr, &records[num_records], NULL );
653
654         num_records++;
655         records = msi_realloc(records, (num_records + 1) * sizeof(LPWSTR *));
656         if (!records)
657             return ERROR_OUTOFMEMORY;
658     }
659
660     r = msi_add_table_to_db( db, columns, types, labels, num_columns );
661     if (r != ERROR_SUCCESS)
662         goto done;
663
664     r = msi_add_records_to_table( db, columns, types, labels, records, num_columns, num_records );
665
666 done:
667     msi_free(path);
668     msi_free(data);
669     msi_free(columns);
670     msi_free(types);
671     msi_free(labels);
672
673     for (i = 0; i < num_records; i++)
674         msi_free(records[i]);
675
676     msi_free(records);
677
678     return r;
679 }
680
681 UINT WINAPI MsiDatabaseImportW(MSIHANDLE handle, LPCWSTR szFolder, LPCWSTR szFilename)
682 {
683     MSIDATABASE *db;
684     UINT r;
685
686     TRACE("%lx %s %s\n",handle,debugstr_w(szFolder), debugstr_w(szFilename));
687
688     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
689     if( !db )
690         return ERROR_INVALID_HANDLE;
691     r = MSI_DatabaseImport( db, szFolder, szFilename );
692     msiobj_release( &db->hdr );
693     return r;
694 }
695
696 UINT WINAPI MsiDatabaseImportA( MSIHANDLE handle,
697                LPCSTR szFolder, LPCSTR szFilename )
698 {
699     LPWSTR path = NULL, file = NULL;
700     UINT r = ERROR_OUTOFMEMORY;
701
702     TRACE("%lx %s %s\n", handle, debugstr_a(szFolder), debugstr_a(szFilename));
703
704     if( szFolder )
705     {
706         path = strdupAtoW( szFolder );
707         if( !path )
708             goto end;
709     }
710
711     if( szFilename )
712     {
713         file = strdupAtoW( szFilename );
714         if( !file )
715             goto end;
716     }
717
718     r = MsiDatabaseImportW( handle, path, file );
719
720 end:
721     msi_free( path );
722     msi_free( file );
723
724     return r;
725 }
726
727 static UINT msi_export_record( HANDLE handle, MSIRECORD *row, UINT start )
728 {
729     UINT i, count, len, r = ERROR_SUCCESS;
730     const char *sep;
731     char *buffer;
732     DWORD sz;
733
734     len = 0x100;
735     buffer = msi_alloc( len );
736     if ( !buffer )
737         return ERROR_OUTOFMEMORY;
738
739     count = MSI_RecordGetFieldCount( row );
740     for ( i=start; i<=count; i++ )
741     {
742         sz = len;
743         r = MSI_RecordGetStringA( row, i, buffer, &sz );
744         if (r == ERROR_MORE_DATA)
745         {
746             char *p = msi_realloc( buffer, sz + 1 );
747             if (!p)
748                 break;
749             len = sz + 1;
750             buffer = p;
751         }
752         sz = len;
753         r = MSI_RecordGetStringA( row, i, buffer, &sz );
754         if (r != ERROR_SUCCESS)
755             break;
756
757         if (!WriteFile( handle, buffer, sz, &sz, NULL ))
758         {
759             r = ERROR_FUNCTION_FAILED;
760             break;
761         }
762
763         sep = (i < count) ? "\t" : "\r\n";
764         if (!WriteFile( handle, sep, strlen(sep), &sz, NULL ))
765         {
766             r = ERROR_FUNCTION_FAILED;
767             break;
768         }
769     }
770     msi_free( buffer );
771     return r;
772 }
773
774 static UINT msi_export_row( MSIRECORD *row, void *arg )
775 {
776     return msi_export_record( arg, row, 1 );
777 }
778
779 UINT MSI_DatabaseExport( MSIDATABASE *db, LPCWSTR table,
780                LPCWSTR folder, LPCWSTR file )
781 {
782     static const WCHAR query[] = {
783         's','e','l','e','c','t',' ','*',' ','f','r','o','m',' ','%','s',0 };
784     static const WCHAR szbs[] = { '\\', 0 };
785     MSIRECORD *rec = NULL;
786     MSIQUERY *view = NULL;
787     LPWSTR filename;
788     HANDLE handle;
789     UINT len, r;
790
791     TRACE("%p %s %s %s\n", db, debugstr_w(table),
792           debugstr_w(folder), debugstr_w(file) );
793
794     if( folder == NULL || file == NULL )
795         return ERROR_INVALID_PARAMETER;
796
797     len = lstrlenW(folder) + lstrlenW(file) + 2;
798     filename = msi_alloc(len * sizeof (WCHAR));
799     if (!filename)
800         return ERROR_OUTOFMEMORY;
801
802     lstrcpyW( filename, folder );
803     lstrcatW( filename, szbs );
804     lstrcatW( filename, file );
805
806     handle = CreateFileW( filename, GENERIC_READ | GENERIC_WRITE, 0,
807                           NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
808     msi_free( filename );
809     if (handle == INVALID_HANDLE_VALUE)
810         return ERROR_FUNCTION_FAILED;
811
812     r = MSI_OpenQuery( db, &view, query, table );
813     if (r == ERROR_SUCCESS)
814     {
815         /* write out row 1, the column names */
816         r = MSI_ViewGetColumnInfo(view, MSICOLINFO_NAMES, &rec);
817         if (r == ERROR_SUCCESS)
818         {
819             msi_export_record( handle, rec, 1 );
820             msiobj_release( &rec->hdr );
821         }
822
823         /* write out row 2, the column types */
824         r = MSI_ViewGetColumnInfo(view, MSICOLINFO_TYPES, &rec);
825         if (r == ERROR_SUCCESS)
826         {
827             msi_export_record( handle, rec, 1 );
828             msiobj_release( &rec->hdr );
829         }
830
831         /* write out row 3, the table name + keys */
832         r = MSI_DatabaseGetPrimaryKeys( db, table, &rec );
833         if (r == ERROR_SUCCESS)
834         {
835             MSI_RecordSetStringW( rec, 0, table );
836             msi_export_record( handle, rec, 0 );
837             msiobj_release( &rec->hdr );
838         }
839
840         /* write out row 4 onwards, the data */
841         r = MSI_IterateRecords( view, 0, msi_export_row, handle );
842         msiobj_release( &view->hdr );
843     }
844
845     CloseHandle( handle );
846
847     return r;
848 }
849
850 /***********************************************************************
851  * MsiExportDatabaseW        [MSI.@]
852  *
853  * Writes a file containing the table data as tab separated ASCII.
854  *
855  * The format is as follows:
856  *
857  * row1 : colname1 <tab> colname2 <tab> .... colnameN <cr> <lf>
858  * row2 : coltype1 <tab> coltype2 <tab> .... coltypeN <cr> <lf>
859  * row3 : tablename <tab> key1 <tab> key2 <tab> ... keyM <cr> <lf>
860  *
861  * Followed by the data, starting at row 1 with one row per line
862  *
863  * row4 : data <tab> data <tab> data <tab> ... data <cr> <lf>
864  */
865 UINT WINAPI MsiDatabaseExportW( MSIHANDLE handle, LPCWSTR szTable,
866                LPCWSTR szFolder, LPCWSTR szFilename )
867 {
868     MSIDATABASE *db;
869     UINT r;
870
871     TRACE("%lx %s %s %s\n", handle, debugstr_w(szTable),
872           debugstr_w(szFolder), debugstr_w(szFilename));
873
874     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
875     if( !db )
876         return ERROR_INVALID_HANDLE;
877     r = MSI_DatabaseExport( db, szTable, szFolder, szFilename );
878     msiobj_release( &db->hdr );
879     return r;
880 }
881
882 UINT WINAPI MsiDatabaseExportA( MSIHANDLE handle, LPCSTR szTable,
883                LPCSTR szFolder, LPCSTR szFilename )
884 {
885     LPWSTR path = NULL, file = NULL, table = NULL;
886     UINT r = ERROR_OUTOFMEMORY;
887
888     TRACE("%lx %s %s %s\n", handle, debugstr_a(szTable),
889           debugstr_a(szFolder), debugstr_a(szFilename));
890
891     if( szTable )
892     {
893         table = strdupAtoW( szTable );
894         if( !table )
895             goto end;
896     }
897
898     if( szFolder )
899     {
900         path = strdupAtoW( szFolder );
901         if( !path )
902             goto end;
903     }
904
905     if( szFilename )
906     {
907         file = strdupAtoW( szFilename );
908         if( !file )
909             goto end;
910     }
911
912     r = MsiDatabaseExportW( handle, table, path, file );
913
914 end:
915     msi_free( table );
916     msi_free( path );
917     msi_free( file );
918
919     return r;
920 }
921
922 MSIDBSTATE WINAPI MsiGetDatabaseState( MSIHANDLE handle )
923 {
924     MSIDBSTATE ret = MSIDBSTATE_READ;
925     MSIDATABASE *db;
926
927     TRACE("%ld\n", handle);
928
929     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
930     if (!db)
931         return MSIDBSTATE_ERROR;
932     if (db->mode != MSIDBOPEN_READONLY )
933         ret = MSIDBSTATE_WRITE;
934     msiobj_release( &db->hdr );
935
936     return ret;
937 }