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