xref: /reactos/dll/win32/msi/database.c (revision ba3f0743)
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 "msipriv.h"
22 
23 #include <stdio.h>
24 
25 WINE_DEFAULT_DEBUG_CHANNEL(msi);
26 
27 /*
28  *  .MSI  file format
29  *
30  *  An .msi file is a structured storage file.
31  *  It contains a number of streams.
32  *  A stream for each table in the database.
33  *  Two streams for the string table in the database.
34  *  Any binary data in a table is a reference to a stream.
35  */
36 
37 #define IS_INTMSIDBOPEN(x)      (((ULONG_PTR)(x) >> 16) == 0)
38 
39 struct row_export_info
40 {
41     HANDLE handle;
42     LPCWSTR folder;
43     LPCWSTR table;
44 };
45 
46 static void free_transforms( MSIDATABASE *db )
47 {
48     while( !list_empty( &db->transforms ) )
49     {
50         MSITRANSFORM *t = LIST_ENTRY( list_head( &db->transforms ), MSITRANSFORM, entry );
51         list_remove( &t->entry );
52         IStorage_Release( t->stg );
53         msi_free( t );
54     }
55 }
56 
57 static void free_streams( MSIDATABASE *db )
58 {
59     UINT i;
60     for (i = 0; i < db->num_streams; i++)
61     {
62         if (db->streams[i].stream) IStream_Release( db->streams[i].stream );
63     }
64     msi_free( db->streams );
65 }
66 
67 void append_storage_to_db( MSIDATABASE *db, IStorage *stg )
68 {
69     MSITRANSFORM *t;
70 
71     t = msi_alloc( sizeof *t );
72     t->stg = stg;
73     IStorage_AddRef( stg );
74     list_add_head( &db->transforms, &t->entry );
75 }
76 
77 static VOID MSI_CloseDatabase( MSIOBJECTHDR *arg )
78 {
79     MSIDATABASE *db = (MSIDATABASE *) arg;
80 
81     msi_free(db->path);
82     free_streams( db );
83     free_cached_tables( db );
84     free_transforms( db );
85     if (db->strings) msi_destroy_stringtable( db->strings );
86     IStorage_Release( db->storage );
87     if (db->deletefile)
88     {
89         DeleteFileW( db->deletefile );
90         msi_free( db->deletefile );
91     }
92     msi_free( db->tempfolder );
93 }
94 
95 static HRESULT db_initialize( IStorage *stg, const GUID *clsid )
96 {
97     static const WCHAR szTables[]  = { '_','T','a','b','l','e','s',0 };
98     HRESULT hr;
99 
100     hr = IStorage_SetClass( stg, clsid );
101     if (FAILED( hr ))
102     {
103         WARN("failed to set class id 0x%08x\n", hr);
104         return hr;
105     }
106 
107     /* create the _Tables stream */
108     hr = write_stream_data( stg, szTables, NULL, 0, TRUE );
109     if (FAILED( hr ))
110     {
111         WARN("failed to create _Tables stream 0x%08x\n", hr);
112         return hr;
113     }
114 
115     hr = msi_init_string_table( stg );
116     if (FAILED( hr ))
117     {
118         WARN("failed to initialize string table 0x%08x\n", hr);
119         return hr;
120     }
121 
122     hr = IStorage_Commit( stg, 0 );
123     if (FAILED( hr ))
124     {
125         WARN("failed to commit changes 0x%08x\n", hr);
126         return hr;
127     }
128 
129     return S_OK;
130 }
131 
132 UINT MSI_OpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIDATABASE **pdb)
133 {
134     IStorage *stg = NULL;
135     HRESULT r;
136     MSIDATABASE *db = NULL;
137     UINT ret = ERROR_FUNCTION_FAILED;
138     LPCWSTR szMode, save_path;
139     STATSTG stat;
140     BOOL created = FALSE, patch = FALSE;
141     WCHAR path[MAX_PATH];
142 
143     TRACE("%s %s\n",debugstr_w(szDBPath),debugstr_w(szPersist) );
144 
145     if( !pdb )
146         return ERROR_INVALID_PARAMETER;
147 
148     if (szPersist - MSIDBOPEN_PATCHFILE <= MSIDBOPEN_CREATEDIRECT)
149     {
150         TRACE("Database is a patch\n");
151         szPersist -= MSIDBOPEN_PATCHFILE;
152         patch = TRUE;
153     }
154 
155     save_path = szDBPath;
156     szMode = szPersist;
157     if( !IS_INTMSIDBOPEN(szPersist) )
158     {
159         if (!CopyFileW( szDBPath, szPersist, FALSE ))
160             return ERROR_OPEN_FAILED;
161 
162         szDBPath = szPersist;
163         szPersist = MSIDBOPEN_TRANSACT;
164         created = TRUE;
165     }
166 
167     if( szPersist == MSIDBOPEN_READONLY )
168     {
169         r = StgOpenStorage( szDBPath, NULL,
170               STGM_DIRECT|STGM_READ|STGM_SHARE_DENY_WRITE, NULL, 0, &stg);
171     }
172     else if( szPersist == MSIDBOPEN_CREATE )
173     {
174         r = StgCreateDocfile( szDBPath,
175               STGM_CREATE|STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg );
176 
177         if( SUCCEEDED(r) )
178             r = db_initialize( stg, patch ? &CLSID_MsiPatch : &CLSID_MsiDatabase );
179         created = TRUE;
180     }
181     else if( szPersist == MSIDBOPEN_CREATEDIRECT )
182     {
183         r = StgCreateDocfile( szDBPath,
184               STGM_CREATE|STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 0, &stg );
185 
186         if( SUCCEEDED(r) )
187             r = db_initialize( stg, patch ? &CLSID_MsiPatch : &CLSID_MsiDatabase );
188         created = TRUE;
189     }
190     else if( szPersist == MSIDBOPEN_TRANSACT )
191     {
192         r = StgOpenStorage( szDBPath, NULL,
193               STGM_TRANSACTED|STGM_READWRITE|STGM_SHARE_DENY_WRITE, NULL, 0, &stg);
194     }
195     else if( szPersist == MSIDBOPEN_DIRECT )
196     {
197         r = StgOpenStorage( szDBPath, NULL,
198               STGM_DIRECT|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, 0, &stg);
199     }
200     else
201     {
202         ERR("unknown flag %p\n",szPersist);
203         return ERROR_INVALID_PARAMETER;
204     }
205 
206     if( FAILED( r ) || !stg )
207     {
208         WARN("open failed r = %08x for %s\n", r, debugstr_w(szDBPath));
209         return ERROR_FUNCTION_FAILED;
210     }
211 
212     r = IStorage_Stat( stg, &stat, STATFLAG_NONAME );
213     if( FAILED( r ) )
214     {
215         FIXME("Failed to stat storage\n");
216         goto end;
217     }
218 
219     if ( !IsEqualGUID( &stat.clsid, &CLSID_MsiDatabase ) &&
220          !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) &&
221          !IsEqualGUID( &stat.clsid, &CLSID_MsiTransform ) )
222     {
223         ERR("storage GUID is not a MSI database GUID %s\n",
224              debugstr_guid(&stat.clsid) );
225         goto end;
226     }
227 
228     if ( patch && !IsEqualGUID( &stat.clsid, &CLSID_MsiPatch ) )
229     {
230         ERR("storage GUID is not the MSI patch GUID %s\n",
231              debugstr_guid(&stat.clsid) );
232         ret = ERROR_OPEN_FAILED;
233         goto end;
234     }
235 
236     db = alloc_msiobject( MSIHANDLETYPE_DATABASE, sizeof (MSIDATABASE),
237                               MSI_CloseDatabase );
238     if( !db )
239     {
240         FIXME("Failed to allocate a handle\n");
241         goto end;
242     }
243 
244     if (!strchrW( save_path, '\\' ))
245     {
246         GetCurrentDirectoryW( MAX_PATH, path );
247         lstrcatW( path, szBackSlash );
248         lstrcatW( path, save_path );
249     }
250     else
251         lstrcpyW( path, save_path );
252 
253     db->path = strdupW( path );
254     db->media_transform_offset = MSI_INITIAL_MEDIA_TRANSFORM_OFFSET;
255     db->media_transform_disk_id = MSI_INITIAL_MEDIA_TRANSFORM_DISKID;
256 
257     if( TRACE_ON( msi ) )
258         enum_stream_names( stg );
259 
260     db->storage = stg;
261     db->mode = szMode;
262     if (created)
263         db->deletefile = strdupW( szDBPath );
264     list_init( &db->tables );
265     list_init( &db->transforms );
266 
267     db->strings = msi_load_string_table( stg, &db->bytes_per_strref );
268     if( !db->strings )
269         goto end;
270 
271     ret = ERROR_SUCCESS;
272 
273     msiobj_addref( &db->hdr );
274     IStorage_AddRef( stg );
275     *pdb = db;
276 
277 end:
278     if( db )
279         msiobj_release( &db->hdr );
280     if( stg )
281         IStorage_Release( stg );
282 
283     return ret;
284 }
285 
286 UINT WINAPI MsiOpenDatabaseW(LPCWSTR szDBPath, LPCWSTR szPersist, MSIHANDLE *phDB)
287 {
288     MSIDATABASE *db;
289     UINT ret;
290 
291     TRACE("%s %s %p\n",debugstr_w(szDBPath),debugstr_w(szPersist), phDB);
292 
293     ret = MSI_OpenDatabaseW( szDBPath, szPersist, &db );
294     if( ret == ERROR_SUCCESS )
295     {
296         *phDB = alloc_msihandle( &db->hdr );
297         if (! *phDB)
298             ret = ERROR_NOT_ENOUGH_MEMORY;
299         msiobj_release( &db->hdr );
300     }
301 
302     return ret;
303 }
304 
305 UINT WINAPI MsiOpenDatabaseA(LPCSTR szDBPath, LPCSTR szPersist, MSIHANDLE *phDB)
306 {
307     HRESULT r = ERROR_FUNCTION_FAILED;
308     LPWSTR szwDBPath = NULL, szwPersist = NULL;
309 
310     TRACE("%s %s %p\n", debugstr_a(szDBPath), debugstr_a(szPersist), phDB);
311 
312     if( szDBPath )
313     {
314         szwDBPath = strdupAtoW( szDBPath );
315         if( !szwDBPath )
316             goto end;
317     }
318 
319     if( !IS_INTMSIDBOPEN(szPersist) )
320     {
321         szwPersist = strdupAtoW( szPersist );
322         if( !szwPersist )
323             goto end;
324     }
325     else
326         szwPersist = (LPWSTR)(DWORD_PTR)szPersist;
327 
328     r = MsiOpenDatabaseW( szwDBPath, szwPersist, phDB );
329 
330 end:
331     if( !IS_INTMSIDBOPEN(szPersist) )
332         msi_free( szwPersist );
333     msi_free( szwDBPath );
334 
335     return r;
336 }
337 
338 static LPWSTR msi_read_text_archive(LPCWSTR path, DWORD *len)
339 {
340     HANDLE file;
341     LPSTR data = NULL;
342     LPWSTR wdata = NULL;
343     DWORD read, size = 0;
344 
345     file = CreateFileW( path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL );
346     if (file == INVALID_HANDLE_VALUE)
347         return NULL;
348 
349     size = GetFileSize( file, NULL );
350     if (!(data = msi_alloc( size ))) goto done;
351 
352     if (!ReadFile( file, data, size, &read, NULL ) || read != size) goto done;
353 
354     while (!data[size - 1]) size--;
355     *len = MultiByteToWideChar( CP_ACP, 0, data, size, NULL, 0 );
356     if ((wdata = msi_alloc( (*len + 1) * sizeof(WCHAR) )))
357     {
358         MultiByteToWideChar( CP_ACP, 0, data, size, wdata, *len );
359         wdata[*len] = 0;
360     }
361 
362 done:
363     CloseHandle( file );
364     msi_free( data );
365     return wdata;
366 }
367 
368 static void msi_parse_line(LPWSTR *line, LPWSTR **entries, DWORD *num_entries, DWORD *len)
369 {
370     LPWSTR ptr = *line, save;
371     DWORD i, count = 1, chars_left = *len;
372 
373     *entries = NULL;
374 
375     /* stay on this line */
376     while (chars_left && *ptr != '\n')
377     {
378         /* entries are separated by tabs */
379         if (*ptr == '\t')
380             count++;
381 
382         ptr++;
383         chars_left--;
384     }
385 
386     *entries = msi_alloc(count * sizeof(LPWSTR));
387     if (!*entries)
388         return;
389 
390     /* store pointers into the data */
391     chars_left = *len;
392     for (i = 0, ptr = *line; i < count; i++)
393     {
394         while (chars_left && *ptr == '\r')
395         {
396             ptr++;
397             chars_left--;
398         }
399         save = ptr;
400 
401         while (chars_left && *ptr != '\t' && *ptr != '\n' && *ptr != '\r')
402         {
403             if (!*ptr) *ptr = '\n'; /* convert embedded nulls to \n */
404             if (ptr > *line && *ptr == '\x19' && *(ptr - 1) == '\x11')
405             {
406                 *ptr = '\n';
407                 *(ptr - 1) = '\r';
408             }
409             ptr++;
410             chars_left--;
411         }
412 
413         /* NULL-separate the data */
414         if (*ptr == '\n' || *ptr == '\r')
415         {
416             while (chars_left && (*ptr == '\n' || *ptr == '\r'))
417             {
418                 *(ptr++) = 0;
419                 chars_left--;
420             }
421         }
422         else if (*ptr)
423         {
424             *(ptr++) = 0;
425             chars_left--;
426         }
427         (*entries)[i] = save;
428     }
429 
430     /* move to the next line if there's more, else EOF */
431     *line = ptr;
432     *len = chars_left;
433     if (num_entries)
434         *num_entries = count;
435 }
436 
437 static LPWSTR msi_build_createsql_prelude(LPWSTR table)
438 {
439     LPWSTR prelude;
440     DWORD size;
441 
442     static const WCHAR create_fmt[] = {'C','R','E','A','T','E',' ','T','A','B','L','E',' ','`','%','s','`',' ','(',' ',0};
443 
444     size = sizeof(create_fmt)/sizeof(create_fmt[0]) + lstrlenW(table) - 2;
445     prelude = msi_alloc(size * sizeof(WCHAR));
446     if (!prelude)
447         return NULL;
448 
449     sprintfW(prelude, create_fmt, table);
450     return prelude;
451 }
452 
453 static LPWSTR msi_build_createsql_columns(LPWSTR *columns_data, LPWSTR *types, DWORD num_columns)
454 {
455     LPWSTR columns, p;
456     LPCWSTR type;
457     DWORD sql_size = 1, i, len;
458     WCHAR expanded[128], *ptr;
459     WCHAR size[10], comma[2], extra[30];
460 
461     static const WCHAR column_fmt[] = {'`','%','s','`',' ','%','s','%','s','%','s','%','s',' ',0};
462     static const WCHAR size_fmt[] = {'(','%','s',')',0};
463     static const WCHAR type_char[] = {'C','H','A','R',0};
464     static const WCHAR type_int[] = {'I','N','T',0};
465     static const WCHAR type_long[] = {'L','O','N','G',0};
466     static const WCHAR type_object[] = {'O','B','J','E','C','T',0};
467     static const WCHAR type_notnull[] = {' ','N','O','T',' ','N','U','L','L',0};
468     static const WCHAR localizable[] = {' ','L','O','C','A','L','I','Z','A','B','L','E',0};
469 
470     columns = msi_alloc_zero(sql_size * sizeof(WCHAR));
471     if (!columns)
472         return NULL;
473 
474     for (i = 0; i < num_columns; i++)
475     {
476         type = NULL;
477         comma[1] = size[0] = extra[0] = '\0';
478 
479         if (i == num_columns - 1)
480             comma[0] = '\0';
481         else
482             comma[0] = ',';
483 
484         ptr = &types[i][1];
485         len = atolW(ptr);
486         extra[0] = '\0';
487 
488         switch (types[i][0])
489         {
490             case 'l':
491                 lstrcpyW(extra, type_notnull);
492                 /* fall through */
493             case 'L':
494                 lstrcatW(extra, localizable);
495                 type = type_char;
496                 sprintfW(size, size_fmt, ptr);
497                 break;
498             case 's':
499                 lstrcpyW(extra, type_notnull);
500                 /* fall through */
501             case 'S':
502                 type = type_char;
503                 sprintfW(size, size_fmt, ptr);
504                 break;
505             case 'i':
506                 lstrcpyW(extra, type_notnull);
507                 /* fall through */
508             case 'I':
509                 if (len <= 2)
510                     type = type_int;
511                 else if (len == 4)
512                     type = type_long;
513                 else
514                 {
515                     WARN("invalid int width %u\n", len);
516                     msi_free(columns);
517                     return NULL;
518                 }
519                 break;
520             case 'v':
521                 lstrcpyW(extra, type_notnull);
522                 /* fall through */
523             case 'V':
524                 type = type_object;
525                 break;
526             default:
527                 ERR("Unknown type: %c\n", types[i][0]);
528                 msi_free(columns);
529                 return NULL;
530         }
531 
532         sprintfW(expanded, column_fmt, columns_data[i], type, size, extra, comma);
533         sql_size += lstrlenW(expanded);
534 
535         p = msi_realloc(columns, sql_size * sizeof(WCHAR));
536         if (!p)
537         {
538             msi_free(columns);
539             return NULL;
540         }
541         columns = p;
542 
543         lstrcatW(columns, expanded);
544     }
545 
546     return columns;
547 }
548 
549 static LPWSTR msi_build_createsql_postlude(LPWSTR *primary_keys, DWORD num_keys)
550 {
551     LPWSTR postlude, keys, ptr;
552     DWORD size, i;
553 
554     static const WCHAR key_fmt[] = {'`','%','s','`',',',' ',0};
555     static const WCHAR postlude_fmt[] = {'P','R','I','M','A','R','Y',' ','K','E','Y',' ','%','s',')',0};
556 
557     for (i = 0, size = 1; i < num_keys; i++)
558         size += lstrlenW(key_fmt) + lstrlenW(primary_keys[i]) - 2;
559 
560     keys = msi_alloc(size * sizeof(WCHAR));
561     if (!keys)
562         return NULL;
563 
564     for (i = 0, ptr = keys; i < num_keys; i++)
565     {
566         ptr += sprintfW(ptr, key_fmt, primary_keys[i]);
567     }
568 
569     /* remove final ', ' */
570     *(ptr - 2) = '\0';
571 
572     size = lstrlenW(postlude_fmt) + size - 1;
573     postlude = msi_alloc(size * sizeof(WCHAR));
574     if (!postlude)
575         goto done;
576 
577     sprintfW(postlude, postlude_fmt, keys);
578 
579 done:
580     msi_free(keys);
581     return postlude;
582 }
583 
584 static UINT msi_add_table_to_db(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types, LPWSTR *labels, DWORD num_labels, DWORD num_columns)
585 {
586     UINT r = ERROR_OUTOFMEMORY;
587     DWORD size;
588     MSIQUERY *view;
589     LPWSTR create_sql = NULL;
590     LPWSTR prelude, columns_sql, postlude;
591 
592     prelude = msi_build_createsql_prelude(labels[0]);
593     columns_sql = msi_build_createsql_columns(columns, types, num_columns);
594     postlude = msi_build_createsql_postlude(labels + 1, num_labels - 1); /* skip over table name */
595 
596     if (!prelude || !columns_sql || !postlude)
597         goto done;
598 
599     size = lstrlenW(prelude) + lstrlenW(columns_sql) + lstrlenW(postlude) + 1;
600     create_sql = msi_alloc(size * sizeof(WCHAR));
601     if (!create_sql)
602         goto done;
603 
604     lstrcpyW(create_sql, prelude);
605     lstrcatW(create_sql, columns_sql);
606     lstrcatW(create_sql, postlude);
607 
608     r = MSI_DatabaseOpenViewW( db, create_sql, &view );
609     if (r != ERROR_SUCCESS)
610         goto done;
611 
612     r = MSI_ViewExecute(view, NULL);
613     MSI_ViewClose(view);
614     msiobj_release(&view->hdr);
615 
616 done:
617     msi_free(prelude);
618     msi_free(columns_sql);
619     msi_free(postlude);
620     msi_free(create_sql);
621     return r;
622 }
623 
624 static LPWSTR msi_import_stream_filename(LPCWSTR path, LPCWSTR name)
625 {
626     DWORD len;
627     LPWSTR fullname, ptr;
628 
629     len = lstrlenW(path) + lstrlenW(name) + 1;
630     fullname = msi_alloc(len*sizeof(WCHAR));
631     if (!fullname)
632        return NULL;
633 
634     lstrcpyW( fullname, path );
635 
636     /* chop off extension from path */
637     ptr = strrchrW(fullname, '.');
638     if (!ptr)
639     {
640         msi_free (fullname);
641         return NULL;
642     }
643     *ptr++ = '\\';
644     lstrcpyW( ptr, name );
645     return fullname;
646 }
647 
648 static UINT construct_record(DWORD num_columns, LPWSTR *types,
649                              LPWSTR *data, LPWSTR path, MSIRECORD **rec)
650 {
651     UINT i;
652 
653     *rec = MSI_CreateRecord(num_columns);
654     if (!*rec)
655         return ERROR_OUTOFMEMORY;
656 
657     for (i = 0; i < num_columns; i++)
658     {
659         switch (types[i][0])
660         {
661             case 'L': case 'l': case 'S': case 's':
662                 MSI_RecordSetStringW(*rec, i + 1, data[i]);
663                 break;
664             case 'I': case 'i':
665                 if (*data[i])
666                     MSI_RecordSetInteger(*rec, i + 1, atoiW(data[i]));
667                 break;
668             case 'V': case 'v':
669                 if (*data[i])
670                 {
671                     UINT r;
672                     LPWSTR file = msi_import_stream_filename(path, data[i]);
673                     if (!file)
674                         return ERROR_FUNCTION_FAILED;
675 
676                     r = MSI_RecordSetStreamFromFileW(*rec, i + 1, file);
677                     msi_free (file);
678                     if (r != ERROR_SUCCESS)
679                         return ERROR_FUNCTION_FAILED;
680                 }
681                 break;
682             default:
683                 ERR("Unhandled column type: %c\n", types[i][0]);
684                 msiobj_release(&(*rec)->hdr);
685                 return ERROR_FUNCTION_FAILED;
686         }
687     }
688 
689     return ERROR_SUCCESS;
690 }
691 
692 static UINT msi_add_records_to_table(MSIDATABASE *db, LPWSTR *columns, LPWSTR *types,
693                                      LPWSTR *labels, LPWSTR **records,
694                                      int num_columns, int num_records,
695                                      LPWSTR path)
696 {
697     UINT r;
698     int i;
699     MSIQUERY *view;
700     MSIRECORD *rec;
701 
702     static const WCHAR select[] = {
703         'S','E','L','E','C','T',' ','*',' ',
704         'F','R','O','M',' ','`','%','s','`',0
705     };
706 
707     r = MSI_OpenQuery(db, &view, select, labels[0]);
708     if (r != ERROR_SUCCESS)
709         return r;
710 
711     while (MSI_ViewFetch(view, &rec) != ERROR_NO_MORE_ITEMS)
712     {
713         r = MSI_ViewModify(view, MSIMODIFY_DELETE, rec);
714         msiobj_release(&rec->hdr);
715         if (r != ERROR_SUCCESS)
716             goto done;
717     }
718 
719     for (i = 0; i < num_records; i++)
720     {
721         r = construct_record(num_columns, types, records[i], path, &rec);
722         if (r != ERROR_SUCCESS)
723             goto done;
724 
725         r = MSI_ViewModify(view, MSIMODIFY_INSERT, rec);
726         if (r != ERROR_SUCCESS)
727         {
728             msiobj_release(&rec->hdr);
729             goto done;
730         }
731 
732         msiobj_release(&rec->hdr);
733     }
734 
735 done:
736     msiobj_release(&view->hdr);
737     return r;
738 }
739 
740 static UINT MSI_DatabaseImport(MSIDATABASE *db, LPCWSTR folder, LPCWSTR file)
741 {
742     UINT r;
743     DWORD len, i;
744     DWORD num_labels, num_types;
745     DWORD num_columns, num_records = 0;
746     LPWSTR *columns, *types, *labels;
747     LPWSTR path, ptr, data;
748     LPWSTR **records = NULL;
749     LPWSTR **temp_records;
750 
751     static const WCHAR suminfo[] =
752         {'_','S','u','m','m','a','r','y','I','n','f','o','r','m','a','t','i','o','n',0};
753     static const WCHAR forcecodepage[] =
754         {'_','F','o','r','c','e','C','o','d','e','p','a','g','e',0};
755 
756     TRACE("%p %s %s\n", db, debugstr_w(folder), debugstr_w(file) );
757 
758     if( folder == NULL || file == NULL )
759         return ERROR_INVALID_PARAMETER;
760 
761     len = lstrlenW(folder) + lstrlenW(szBackSlash) + lstrlenW(file) + 1;
762     path = msi_alloc( len * sizeof(WCHAR) );
763     if (!path)
764         return ERROR_OUTOFMEMORY;
765 
766     lstrcpyW( path, folder );
767     lstrcatW( path, szBackSlash );
768     lstrcatW( path, file );
769 
770     data = msi_read_text_archive( path, &len );
771     if (!data)
772     {
773         msi_free(path);
774         return ERROR_FUNCTION_FAILED;
775     }
776 
777     ptr = data;
778     msi_parse_line( &ptr, &columns, &num_columns, &len );
779     msi_parse_line( &ptr, &types, &num_types, &len );
780     msi_parse_line( &ptr, &labels, &num_labels, &len );
781 
782     if (num_columns == 1 && !columns[0][0] && num_labels == 1 && !labels[0][0] &&
783         num_types == 2 && !strcmpW( types[1], forcecodepage ))
784     {
785         r = msi_set_string_table_codepage( db->strings, atoiW( types[0] ) );
786         goto done;
787     }
788 
789     if (num_columns != num_types)
790     {
791         r = ERROR_FUNCTION_FAILED;
792         goto done;
793     }
794 
795     records = msi_alloc(sizeof(LPWSTR *));
796     if (!records)
797     {
798         r = ERROR_OUTOFMEMORY;
799         goto done;
800     }
801 
802     /* read in the table records */
803     while (len)
804     {
805         msi_parse_line( &ptr, &records[num_records], NULL, &len );
806 
807         num_records++;
808         temp_records = msi_realloc(records, (num_records + 1) * sizeof(LPWSTR *));
809         if (!temp_records)
810         {
811             r = ERROR_OUTOFMEMORY;
812             goto done;
813         }
814         records = temp_records;
815     }
816 
817     if (!strcmpW(labels[0], suminfo))
818     {
819         r = msi_add_suminfo( db, records, num_records, num_columns );
820         if (r != ERROR_SUCCESS)
821         {
822             r = ERROR_FUNCTION_FAILED;
823             goto done;
824         }
825     }
826     else
827     {
828         if (!TABLE_Exists(db, labels[0]))
829         {
830             r = msi_add_table_to_db( db, columns, types, labels, num_labels, num_columns );
831             if (r != ERROR_SUCCESS)
832             {
833                 r = ERROR_FUNCTION_FAILED;
834                 goto done;
835             }
836         }
837 
838         r = msi_add_records_to_table( db, columns, types, labels, records, num_columns, num_records, path );
839     }
840 
841 done:
842     msi_free(path);
843     msi_free(data);
844     msi_free(columns);
845     msi_free(types);
846     msi_free(labels);
847 
848     for (i = 0; i < num_records; i++)
849         msi_free(records[i]);
850 
851     msi_free(records);
852 
853     return r;
854 }
855 
856 UINT WINAPI MsiDatabaseImportW(MSIHANDLE handle, LPCWSTR szFolder, LPCWSTR szFilename)
857 {
858     MSIDATABASE *db;
859     UINT r;
860 
861     TRACE("%x %s %s\n",handle,debugstr_w(szFolder), debugstr_w(szFilename));
862 
863     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
864     if( !db )
865     {
866         IWineMsiRemoteDatabase *remote_database;
867 
868         remote_database = (IWineMsiRemoteDatabase *)msi_get_remote( handle );
869         if ( !remote_database )
870             return ERROR_INVALID_HANDLE;
871 
872         IWineMsiRemoteDatabase_Release( remote_database );
873         WARN("MsiDatabaseImport not allowed during a custom action!\n");
874 
875         return ERROR_SUCCESS;
876     }
877 
878     r = MSI_DatabaseImport( db, szFolder, szFilename );
879     msiobj_release( &db->hdr );
880     return r;
881 }
882 
883 UINT WINAPI MsiDatabaseImportA( MSIHANDLE handle,
884                LPCSTR szFolder, LPCSTR szFilename )
885 {
886     LPWSTR path = NULL, file = NULL;
887     UINT r = ERROR_OUTOFMEMORY;
888 
889     TRACE("%x %s %s\n", handle, debugstr_a(szFolder), debugstr_a(szFilename));
890 
891     if( szFolder )
892     {
893         path = strdupAtoW( szFolder );
894         if( !path )
895             goto end;
896     }
897 
898     if( szFilename )
899     {
900         file = strdupAtoW( szFilename );
901         if( !file )
902             goto end;
903     }
904 
905     r = MsiDatabaseImportW( handle, path, file );
906 
907 end:
908     msi_free( path );
909     msi_free( file );
910 
911     return r;
912 }
913 
914 static UINT msi_export_field( HANDLE handle, MSIRECORD *row, UINT field )
915 {
916     char *buffer;
917     BOOL bret;
918     DWORD sz;
919     UINT r;
920 
921     sz = 0x100;
922     buffer = msi_alloc( sz );
923     if (!buffer)
924         return ERROR_OUTOFMEMORY;
925 
926     r = MSI_RecordGetStringA( row, field, buffer, &sz );
927     if (r == ERROR_MORE_DATA)
928     {
929         char *p;
930 
931         sz++; /* leave room for NULL terminator */
932         p = msi_realloc( buffer, sz );
933         if (!p)
934         {
935             msi_free( buffer );
936             return ERROR_OUTOFMEMORY;
937         }
938         buffer = p;
939 
940         r = MSI_RecordGetStringA( row, field, buffer, &sz );
941         if (r != ERROR_SUCCESS)
942         {
943             msi_free( buffer );
944             return r;
945         }
946     }
947     else if (r != ERROR_SUCCESS)
948         return r;
949 
950     bret = WriteFile( handle, buffer, sz, &sz, NULL );
951     msi_free( buffer );
952     if (!bret)
953         return ERROR_FUNCTION_FAILED;
954 
955     return r;
956 }
957 
958 static UINT msi_export_stream( LPCWSTR folder, LPCWSTR table, MSIRECORD *row, UINT field,
959                                UINT start )
960 {
961     static const WCHAR fmt_file[] = { '%','s','/','%','s','/','%','s',0 };
962     static const WCHAR fmt_folder[] = { '%','s','/','%','s',0 };
963     WCHAR stream_name[256], stream_filename[MAX_PATH];
964     DWORD sz, read_size, write_size;
965     char buffer[1024];
966     HANDLE file;
967     UINT r;
968 
969     /* get the name of the file */
970     sz = sizeof(stream_name)/sizeof(WCHAR);
971     r = MSI_RecordGetStringW( row, start, stream_name, &sz );
972     if (r != ERROR_SUCCESS)
973         return r;
974 
975     /* if the destination folder does not exist then create it (folder name = table name) */
976     snprintfW( stream_filename, sizeof(stream_filename)/sizeof(WCHAR), fmt_folder, folder, table );
977     if (GetFileAttributesW( stream_filename ) == INVALID_FILE_ATTRIBUTES)
978     {
979         if (!CreateDirectoryW( stream_filename, NULL ))
980             return ERROR_PATH_NOT_FOUND;
981     }
982 
983     /* actually create the file */
984     snprintfW( stream_filename, sizeof(stream_filename)/sizeof(WCHAR), fmt_file, folder, table, stream_name );
985     file = CreateFileW( stream_filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
986                         NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
987     if (file == INVALID_HANDLE_VALUE)
988         return ERROR_FILE_NOT_FOUND;
989 
990     /* copy the stream to the file */
991     read_size = sizeof(buffer);
992     while (read_size == sizeof(buffer))
993     {
994         r = MSI_RecordReadStream( row, field, buffer, &read_size );
995         if (r != ERROR_SUCCESS)
996         {
997             CloseHandle( file );
998             return r;
999         }
1000         if (!WriteFile( file, buffer, read_size, &write_size, NULL ) || read_size != write_size)
1001         {
1002             CloseHandle( file );
1003             return ERROR_WRITE_FAULT;
1004         }
1005     }
1006     CloseHandle( file );
1007     return r;
1008 }
1009 
1010 static UINT msi_export_record( struct row_export_info *row_export_info, MSIRECORD *row, UINT start )
1011 {
1012     HANDLE handle = row_export_info->handle;
1013     UINT i, count, r = ERROR_SUCCESS;
1014     const char *sep;
1015     DWORD sz;
1016 
1017     count = MSI_RecordGetFieldCount( row );
1018     for (i = start; i <= count; i++)
1019     {
1020         r = msi_export_field( handle, row, i );
1021         if (r == ERROR_INVALID_PARAMETER)
1022         {
1023             r = msi_export_stream( row_export_info->folder, row_export_info->table, row, i, start );
1024             if (r != ERROR_SUCCESS)
1025                 return r;
1026 
1027             /* exporting a binary stream, repeat the "Name" field */
1028             r = msi_export_field( handle, row, start );
1029             if (r != ERROR_SUCCESS)
1030                 return r;
1031         }
1032         else if (r != ERROR_SUCCESS)
1033             return r;
1034 
1035         sep = (i < count) ? "\t" : "\r\n";
1036         if (!WriteFile( handle, sep, strlen(sep), &sz, NULL ))
1037             return ERROR_FUNCTION_FAILED;
1038     }
1039     return r;
1040 }
1041 
1042 static UINT msi_export_row( MSIRECORD *row, void *arg )
1043 {
1044     return msi_export_record( arg, row, 1 );
1045 }
1046 
1047 static UINT msi_export_forcecodepage( HANDLE handle, UINT codepage )
1048 {
1049     static const char fmt[] = "\r\n\r\n%u\t_ForceCodepage\r\n";
1050     char data[sizeof(fmt) + 10];
1051     DWORD sz;
1052 
1053     sprintf( data, fmt, codepage );
1054 
1055     sz = lstrlenA(data) + 1;
1056     if (!WriteFile(handle, data, sz, &sz, NULL))
1057         return ERROR_FUNCTION_FAILED;
1058 
1059     return ERROR_SUCCESS;
1060 }
1061 
1062 static UINT msi_export_summaryinformation( MSIDATABASE *db, HANDLE handle )
1063 {
1064     static const char header[] = "PropertyId\tValue\r\n"
1065                                  "i2\tl255\r\n"
1066                                  "_SummaryInformation\tPropertyId\r\n";
1067     DWORD sz;
1068 
1069     sz = lstrlenA(header);
1070     if (!WriteFile(handle, header, sz, &sz, NULL))
1071         return ERROR_WRITE_FAULT;
1072 
1073     return msi_export_suminfo( db, handle );
1074 }
1075 
1076 static UINT MSI_DatabaseExport( MSIDATABASE *db, LPCWSTR table,
1077                LPCWSTR folder, LPCWSTR file )
1078 {
1079     static const WCHAR summaryinformation[] = {
1080         '_','S','u','m','m','a','r','y','I','n','f','o','r','m','a','t','i','o','n',0 };
1081     static const WCHAR query[] = {
1082         's','e','l','e','c','t',' ','*',' ','f','r','o','m',' ','%','s',0 };
1083     static const WCHAR forcecodepage[] = {
1084         '_','F','o','r','c','e','C','o','d','e','p','a','g','e',0 };
1085     MSIRECORD *rec = NULL;
1086     MSIQUERY *view = NULL;
1087     LPWSTR filename;
1088     HANDLE handle;
1089     UINT len, r;
1090 
1091     TRACE("%p %s %s %s\n", db, debugstr_w(table),
1092           debugstr_w(folder), debugstr_w(file) );
1093 
1094     if( folder == NULL || file == NULL )
1095         return ERROR_INVALID_PARAMETER;
1096 
1097     len = lstrlenW(folder) + lstrlenW(file) + 2;
1098     filename = msi_alloc(len * sizeof (WCHAR));
1099     if (!filename)
1100         return ERROR_OUTOFMEMORY;
1101 
1102     lstrcpyW( filename, folder );
1103     lstrcatW( filename, szBackSlash );
1104     lstrcatW( filename, file );
1105 
1106     handle = CreateFileW( filename, GENERIC_READ | GENERIC_WRITE, 0,
1107                           NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
1108     msi_free( filename );
1109     if (handle == INVALID_HANDLE_VALUE)
1110         return ERROR_FUNCTION_FAILED;
1111 
1112     if (!strcmpW( table, forcecodepage ))
1113     {
1114         UINT codepage = msi_get_string_table_codepage( db->strings );
1115         r = msi_export_forcecodepage( handle, codepage );
1116         goto done;
1117     }
1118 
1119     if (!strcmpW( table, summaryinformation ))
1120     {
1121         r = msi_export_summaryinformation( db, handle );
1122         goto done;
1123     }
1124 
1125     r = MSI_OpenQuery( db, &view, query, table );
1126     if (r == ERROR_SUCCESS)
1127     {
1128         struct row_export_info row_export_info = { handle, folder, table };
1129 
1130         /* write out row 1, the column names */
1131         r = MSI_ViewGetColumnInfo(view, MSICOLINFO_NAMES, &rec);
1132         if (r == ERROR_SUCCESS)
1133         {
1134             msi_export_record( &row_export_info, rec, 1 );
1135             msiobj_release( &rec->hdr );
1136         }
1137 
1138         /* write out row 2, the column types */
1139         r = MSI_ViewGetColumnInfo(view, MSICOLINFO_TYPES, &rec);
1140         if (r == ERROR_SUCCESS)
1141         {
1142             msi_export_record( &row_export_info, rec, 1 );
1143             msiobj_release( &rec->hdr );
1144         }
1145 
1146         /* write out row 3, the table name + keys */
1147         r = MSI_DatabaseGetPrimaryKeys( db, table, &rec );
1148         if (r == ERROR_SUCCESS)
1149         {
1150             MSI_RecordSetStringW( rec, 0, table );
1151             msi_export_record( &row_export_info, rec, 0 );
1152             msiobj_release( &rec->hdr );
1153         }
1154 
1155         /* write out row 4 onwards, the data */
1156         r = MSI_IterateRecords( view, 0, msi_export_row, &row_export_info );
1157         msiobj_release( &view->hdr );
1158     }
1159 
1160 done:
1161     CloseHandle( handle );
1162     return r;
1163 }
1164 
1165 /***********************************************************************
1166  * MsiExportDatabaseW        [MSI.@]
1167  *
1168  * Writes a file containing the table data as tab separated ASCII.
1169  *
1170  * The format is as follows:
1171  *
1172  * row1 : colname1 <tab> colname2 <tab> .... colnameN <cr> <lf>
1173  * row2 : coltype1 <tab> coltype2 <tab> .... coltypeN <cr> <lf>
1174  * row3 : tablename <tab> key1 <tab> key2 <tab> ... keyM <cr> <lf>
1175  *
1176  * Followed by the data, starting at row 1 with one row per line
1177  *
1178  * row4 : data <tab> data <tab> data <tab> ... data <cr> <lf>
1179  */
1180 UINT WINAPI MsiDatabaseExportW( MSIHANDLE handle, LPCWSTR szTable,
1181                LPCWSTR szFolder, LPCWSTR szFilename )
1182 {
1183     MSIDATABASE *db;
1184     UINT r;
1185 
1186     TRACE("%x %s %s %s\n", handle, debugstr_w(szTable),
1187           debugstr_w(szFolder), debugstr_w(szFilename));
1188 
1189     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
1190     if( !db )
1191     {
1192         IWineMsiRemoteDatabase *remote_database;
1193 
1194         remote_database = (IWineMsiRemoteDatabase *)msi_get_remote( handle );
1195         if ( !remote_database )
1196             return ERROR_INVALID_HANDLE;
1197 
1198         IWineMsiRemoteDatabase_Release( remote_database );
1199         WARN("MsiDatabaseExport not allowed during a custom action!\n");
1200 
1201         return ERROR_SUCCESS;
1202     }
1203 
1204     r = MSI_DatabaseExport( db, szTable, szFolder, szFilename );
1205     msiobj_release( &db->hdr );
1206     return r;
1207 }
1208 
1209 UINT WINAPI MsiDatabaseExportA( MSIHANDLE handle, LPCSTR szTable,
1210                LPCSTR szFolder, LPCSTR szFilename )
1211 {
1212     LPWSTR path = NULL, file = NULL, table = NULL;
1213     UINT r = ERROR_OUTOFMEMORY;
1214 
1215     TRACE("%x %s %s %s\n", handle, debugstr_a(szTable),
1216           debugstr_a(szFolder), debugstr_a(szFilename));
1217 
1218     if( szTable )
1219     {
1220         table = strdupAtoW( szTable );
1221         if( !table )
1222             goto end;
1223     }
1224 
1225     if( szFolder )
1226     {
1227         path = strdupAtoW( szFolder );
1228         if( !path )
1229             goto end;
1230     }
1231 
1232     if( szFilename )
1233     {
1234         file = strdupAtoW( szFilename );
1235         if( !file )
1236             goto end;
1237     }
1238 
1239     r = MsiDatabaseExportW( handle, table, path, file );
1240 
1241 end:
1242     msi_free( table );
1243     msi_free( path );
1244     msi_free( file );
1245 
1246     return r;
1247 }
1248 
1249 UINT WINAPI MsiDatabaseMergeA(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge,
1250                               LPCSTR szTableName)
1251 {
1252     UINT r;
1253     LPWSTR table;
1254 
1255     TRACE("(%d, %d, %s)\n", hDatabase, hDatabaseMerge,
1256           debugstr_a(szTableName));
1257 
1258     table = strdupAtoW(szTableName);
1259     r = MsiDatabaseMergeW(hDatabase, hDatabaseMerge, table);
1260 
1261     msi_free(table);
1262     return r;
1263 }
1264 
1265 typedef struct _tagMERGETABLE
1266 {
1267     struct list entry;
1268     struct list rows;
1269     LPWSTR name;
1270     DWORD numconflicts;
1271     LPWSTR *columns;
1272     DWORD numcolumns;
1273     LPWSTR *types;
1274     DWORD numtypes;
1275     LPWSTR *labels;
1276     DWORD numlabels;
1277 } MERGETABLE;
1278 
1279 typedef struct _tagMERGEROW
1280 {
1281     struct list entry;
1282     MSIRECORD *data;
1283 } MERGEROW;
1284 
1285 typedef struct _tagMERGEDATA
1286 {
1287     MSIDATABASE *db;
1288     MSIDATABASE *merge;
1289     MERGETABLE *curtable;
1290     MSIQUERY *curview;
1291     struct list *tabledata;
1292 } MERGEDATA;
1293 
1294 static BOOL merge_type_match(LPCWSTR type1, LPCWSTR type2)
1295 {
1296     if (((type1[0] == 'l') || (type1[0] == 's')) &&
1297         ((type2[0] == 'l') || (type2[0] == 's')))
1298         return TRUE;
1299 
1300     if (((type1[0] == 'L') || (type1[0] == 'S')) &&
1301         ((type2[0] == 'L') || (type2[0] == 'S')))
1302         return TRUE;
1303 
1304     return !strcmpW( type1, type2 );
1305 }
1306 
1307 static UINT merge_verify_colnames(MSIQUERY *dbview, MSIQUERY *mergeview)
1308 {
1309     MSIRECORD *dbrec, *mergerec;
1310     UINT r, i, count;
1311 
1312     r = MSI_ViewGetColumnInfo(dbview, MSICOLINFO_NAMES, &dbrec);
1313     if (r != ERROR_SUCCESS)
1314         return r;
1315 
1316     r = MSI_ViewGetColumnInfo(mergeview, MSICOLINFO_NAMES, &mergerec);
1317     if (r != ERROR_SUCCESS)
1318     {
1319         msiobj_release(&dbrec->hdr);
1320         return r;
1321     }
1322 
1323     count = MSI_RecordGetFieldCount(dbrec);
1324     for (i = 1; i <= count; i++)
1325     {
1326         if (!MSI_RecordGetString(mergerec, i))
1327             break;
1328 
1329         if (strcmpW( MSI_RecordGetString( dbrec, i ), MSI_RecordGetString( mergerec, i ) ))
1330         {
1331             r = ERROR_DATATYPE_MISMATCH;
1332             goto done;
1333         }
1334     }
1335 
1336     msiobj_release(&dbrec->hdr);
1337     msiobj_release(&mergerec->hdr);
1338     dbrec = mergerec = NULL;
1339 
1340     r = MSI_ViewGetColumnInfo(dbview, MSICOLINFO_TYPES, &dbrec);
1341     if (r != ERROR_SUCCESS)
1342         return r;
1343 
1344     r = MSI_ViewGetColumnInfo(mergeview, MSICOLINFO_TYPES, &mergerec);
1345     if (r != ERROR_SUCCESS)
1346     {
1347         msiobj_release(&dbrec->hdr);
1348         return r;
1349     }
1350 
1351     count = MSI_RecordGetFieldCount(dbrec);
1352     for (i = 1; i <= count; i++)
1353     {
1354         if (!MSI_RecordGetString(mergerec, i))
1355             break;
1356 
1357         if (!merge_type_match(MSI_RecordGetString(dbrec, i),
1358                      MSI_RecordGetString(mergerec, i)))
1359         {
1360             r = ERROR_DATATYPE_MISMATCH;
1361             break;
1362         }
1363     }
1364 
1365 done:
1366     msiobj_release(&dbrec->hdr);
1367     msiobj_release(&mergerec->hdr);
1368 
1369     return r;
1370 }
1371 
1372 static UINT merge_verify_primary_keys(MSIDATABASE *db, MSIDATABASE *mergedb,
1373                                       LPCWSTR table)
1374 {
1375     MSIRECORD *dbrec, *mergerec = NULL;
1376     UINT r, i, count;
1377 
1378     r = MSI_DatabaseGetPrimaryKeys(db, table, &dbrec);
1379     if (r != ERROR_SUCCESS)
1380         return r;
1381 
1382     r = MSI_DatabaseGetPrimaryKeys(mergedb, table, &mergerec);
1383     if (r != ERROR_SUCCESS)
1384         goto done;
1385 
1386     count = MSI_RecordGetFieldCount(dbrec);
1387     if (count != MSI_RecordGetFieldCount(mergerec))
1388     {
1389         r = ERROR_DATATYPE_MISMATCH;
1390         goto done;
1391     }
1392 
1393     for (i = 1; i <= count; i++)
1394     {
1395         if (strcmpW( MSI_RecordGetString( dbrec, i ), MSI_RecordGetString( mergerec, i ) ))
1396         {
1397             r = ERROR_DATATYPE_MISMATCH;
1398             goto done;
1399         }
1400     }
1401 
1402 done:
1403     msiobj_release(&dbrec->hdr);
1404     msiobj_release(&mergerec->hdr);
1405 
1406     return r;
1407 }
1408 
1409 static LPWSTR get_key_value(MSIQUERY *view, LPCWSTR key, MSIRECORD *rec)
1410 {
1411     MSIRECORD *colnames;
1412     LPWSTR str, val;
1413     UINT r, i = 0, sz = 0;
1414     int cmp;
1415 
1416     r = MSI_ViewGetColumnInfo(view, MSICOLINFO_NAMES, &colnames);
1417     if (r != ERROR_SUCCESS)
1418         return NULL;
1419 
1420     do
1421     {
1422         str = msi_dup_record_field(colnames, ++i);
1423         cmp = strcmpW( key, str );
1424         msi_free(str);
1425     } while (cmp);
1426 
1427     msiobj_release(&colnames->hdr);
1428 
1429     r = MSI_RecordGetStringW(rec, i, NULL, &sz);
1430     if (r != ERROR_SUCCESS)
1431         return NULL;
1432     sz++;
1433 
1434     if (MSI_RecordGetString(rec, i))  /* check record field is a string */
1435     {
1436         /* quote string record fields */
1437         const WCHAR szQuote[] = {'\'', 0};
1438         sz += 2;
1439         val = msi_alloc(sz*sizeof(WCHAR));
1440         if (!val)
1441             return NULL;
1442 
1443         lstrcpyW(val, szQuote);
1444         r = MSI_RecordGetStringW(rec, i, val+1, &sz);
1445         lstrcpyW(val+1+sz, szQuote);
1446     }
1447     else
1448     {
1449         /* do not quote integer record fields */
1450         val = msi_alloc(sz*sizeof(WCHAR));
1451         if (!val)
1452             return NULL;
1453 
1454         r = MSI_RecordGetStringW(rec, i, val, &sz);
1455     }
1456 
1457     if (r != ERROR_SUCCESS)
1458     {
1459         ERR("failed to get string!\n");
1460         msi_free(val);
1461         return NULL;
1462     }
1463 
1464     return val;
1465 }
1466 
1467 static LPWSTR create_diff_row_query(MSIDATABASE *merge, MSIQUERY *view,
1468                                     LPWSTR table, MSIRECORD *rec)
1469 {
1470     LPWSTR query = NULL, clause = NULL, val;
1471     LPCWSTR setptr, key;
1472     DWORD size, oldsize;
1473     MSIRECORD *keys;
1474     UINT r, i, count;
1475 
1476     static const WCHAR keyset[] = {
1477         '`','%','s','`',' ','=',' ','%','s',' ','A','N','D',' ',0};
1478     static const WCHAR lastkeyset[] = {
1479         '`','%','s','`',' ','=',' ','%','s',' ',0};
1480     static const WCHAR fmt[] = {'S','E','L','E','C','T',' ','*',' ',
1481         'F','R','O','M',' ','`','%','s','`',' ',
1482         'W','H','E','R','E',' ','%','s',0};
1483 
1484     r = MSI_DatabaseGetPrimaryKeys(merge, table, &keys);
1485     if (r != ERROR_SUCCESS)
1486         return NULL;
1487 
1488     clause = msi_alloc_zero(sizeof(WCHAR));
1489     if (!clause)
1490         goto done;
1491 
1492     size = 1;
1493     count = MSI_RecordGetFieldCount(keys);
1494     for (i = 1; i <= count; i++)
1495     {
1496         key = MSI_RecordGetString(keys, i);
1497         val = get_key_value(view, key, rec);
1498 
1499         if (i == count)
1500             setptr = lastkeyset;
1501         else
1502             setptr = keyset;
1503 
1504         oldsize = size;
1505         size += lstrlenW(setptr) + lstrlenW(key) + lstrlenW(val) - 4;
1506         clause = msi_realloc(clause, size * sizeof (WCHAR));
1507         if (!clause)
1508         {
1509             msi_free(val);
1510             goto done;
1511         }
1512 
1513         sprintfW(clause + oldsize - 1, setptr, key, val);
1514         msi_free(val);
1515     }
1516 
1517     size = lstrlenW(fmt) + lstrlenW(table) + lstrlenW(clause) + 1;
1518     query = msi_alloc(size * sizeof(WCHAR));
1519     if (!query)
1520         goto done;
1521 
1522     sprintfW(query, fmt, table, clause);
1523 
1524 done:
1525     msi_free(clause);
1526     msiobj_release(&keys->hdr);
1527     return query;
1528 }
1529 
1530 static UINT merge_diff_row(MSIRECORD *rec, LPVOID param)
1531 {
1532     MERGEDATA *data = param;
1533     MERGETABLE *table = data->curtable;
1534     MERGEROW *mergerow;
1535     MSIQUERY *dbview = NULL;
1536     MSIRECORD *row = NULL;
1537     LPWSTR query = NULL;
1538     UINT r = ERROR_SUCCESS;
1539 
1540     if (TABLE_Exists(data->db, table->name))
1541     {
1542         query = create_diff_row_query(data->merge, data->curview, table->name, rec);
1543         if (!query)
1544             return ERROR_OUTOFMEMORY;
1545 
1546         r = MSI_DatabaseOpenViewW(data->db, query, &dbview);
1547         if (r != ERROR_SUCCESS)
1548             goto done;
1549 
1550         r = MSI_ViewExecute(dbview, NULL);
1551         if (r != ERROR_SUCCESS)
1552             goto done;
1553 
1554         r = MSI_ViewFetch(dbview, &row);
1555         if (r == ERROR_SUCCESS && !MSI_RecordsAreEqual(rec, row))
1556         {
1557             table->numconflicts++;
1558             goto done;
1559         }
1560         else if (r != ERROR_NO_MORE_ITEMS)
1561             goto done;
1562 
1563         r = ERROR_SUCCESS;
1564     }
1565 
1566     mergerow = msi_alloc(sizeof(MERGEROW));
1567     if (!mergerow)
1568     {
1569         r = ERROR_OUTOFMEMORY;
1570         goto done;
1571     }
1572 
1573     mergerow->data = MSI_CloneRecord(rec);
1574     if (!mergerow->data)
1575     {
1576         r = ERROR_OUTOFMEMORY;
1577         msi_free(mergerow);
1578         goto done;
1579     }
1580 
1581     list_add_tail(&table->rows, &mergerow->entry);
1582 
1583 done:
1584     msi_free(query);
1585     msiobj_release(&row->hdr);
1586     msiobj_release(&dbview->hdr);
1587     return r;
1588 }
1589 
1590 static UINT msi_get_table_labels(MSIDATABASE *db, LPCWSTR table, LPWSTR **labels, DWORD *numlabels)
1591 {
1592     UINT r, i, count;
1593     MSIRECORD *prec = NULL;
1594 
1595     r = MSI_DatabaseGetPrimaryKeys(db, table, &prec);
1596     if (r != ERROR_SUCCESS)
1597         return r;
1598 
1599     count = MSI_RecordGetFieldCount(prec);
1600     *numlabels = count + 1;
1601     *labels = msi_alloc((*numlabels)*sizeof(LPWSTR));
1602     if (!*labels)
1603     {
1604         r = ERROR_OUTOFMEMORY;
1605         goto end;
1606     }
1607 
1608     (*labels)[0] = strdupW(table);
1609     for (i=1; i<=count; i++ )
1610     {
1611         (*labels)[i] = strdupW(MSI_RecordGetString(prec, i));
1612     }
1613 
1614 end:
1615     msiobj_release( &prec->hdr );
1616     return r;
1617 }
1618 
1619 static UINT msi_get_query_columns(MSIQUERY *query, LPWSTR **columns, DWORD *numcolumns)
1620 {
1621     UINT r, i, count;
1622     MSIRECORD *prec = NULL;
1623 
1624     r = MSI_ViewGetColumnInfo(query, MSICOLINFO_NAMES, &prec);
1625     if (r != ERROR_SUCCESS)
1626         return r;
1627 
1628     count = MSI_RecordGetFieldCount(prec);
1629     *columns = msi_alloc(count*sizeof(LPWSTR));
1630     if (!*columns)
1631     {
1632         r = ERROR_OUTOFMEMORY;
1633         goto end;
1634     }
1635 
1636     for (i=1; i<=count; i++ )
1637     {
1638         (*columns)[i-1] = strdupW(MSI_RecordGetString(prec, i));
1639     }
1640 
1641     *numcolumns = count;
1642 
1643 end:
1644     msiobj_release( &prec->hdr );
1645     return r;
1646 }
1647 
1648 static UINT msi_get_query_types(MSIQUERY *query, LPWSTR **types, DWORD *numtypes)
1649 {
1650     UINT r, i, count;
1651     MSIRECORD *prec = NULL;
1652 
1653     r = MSI_ViewGetColumnInfo(query, MSICOLINFO_TYPES, &prec);
1654     if (r != ERROR_SUCCESS)
1655         return r;
1656 
1657     count = MSI_RecordGetFieldCount(prec);
1658     *types = msi_alloc(count*sizeof(LPWSTR));
1659     if (!*types)
1660     {
1661         r = ERROR_OUTOFMEMORY;
1662         goto end;
1663     }
1664 
1665     *numtypes = count;
1666     for (i=1; i<=count; i++ )
1667     {
1668         (*types)[i-1] = strdupW(MSI_RecordGetString(prec, i));
1669     }
1670 
1671 end:
1672     msiobj_release( &prec->hdr );
1673     return r;
1674 }
1675 
1676 static void merge_free_rows(MERGETABLE *table)
1677 {
1678     struct list *item, *cursor;
1679 
1680     LIST_FOR_EACH_SAFE(item, cursor, &table->rows)
1681     {
1682         MERGEROW *row = LIST_ENTRY(item, MERGEROW, entry);
1683 
1684         list_remove(&row->entry);
1685         msiobj_release(&row->data->hdr);
1686         msi_free(row);
1687     }
1688 }
1689 
1690 static void free_merge_table(MERGETABLE *table)
1691 {
1692     UINT i;
1693 
1694     if (table->labels != NULL)
1695     {
1696         for (i = 0; i < table->numlabels; i++)
1697             msi_free(table->labels[i]);
1698 
1699         msi_free(table->labels);
1700     }
1701 
1702     if (table->columns != NULL)
1703     {
1704         for (i = 0; i < table->numcolumns; i++)
1705             msi_free(table->columns[i]);
1706 
1707         msi_free(table->columns);
1708     }
1709 
1710     if (table->types != NULL)
1711     {
1712         for (i = 0; i < table->numtypes; i++)
1713             msi_free(table->types[i]);
1714 
1715         msi_free(table->types);
1716     }
1717 
1718     msi_free(table->name);
1719     merge_free_rows(table);
1720 
1721     msi_free(table);
1722 }
1723 
1724 static UINT msi_get_merge_table (MSIDATABASE *db, LPCWSTR name, MERGETABLE **ptable)
1725 {
1726     UINT r;
1727     MERGETABLE *table;
1728     MSIQUERY *mergeview = NULL;
1729 
1730     static const WCHAR query[] = {'S','E','L','E','C','T',' ','*',' ',
1731         'F','R','O','M',' ','`','%','s','`',0};
1732 
1733     table = msi_alloc_zero(sizeof(MERGETABLE));
1734     if (!table)
1735     {
1736        *ptable = NULL;
1737        return ERROR_OUTOFMEMORY;
1738     }
1739 
1740     r = msi_get_table_labels(db, name, &table->labels, &table->numlabels);
1741     if (r != ERROR_SUCCESS)
1742         goto err;
1743 
1744     r = MSI_OpenQuery(db, &mergeview, query, name);
1745     if (r != ERROR_SUCCESS)
1746         goto err;
1747 
1748     r = msi_get_query_columns(mergeview, &table->columns, &table->numcolumns);
1749     if (r != ERROR_SUCCESS)
1750         goto err;
1751 
1752     r = msi_get_query_types(mergeview, &table->types, &table->numtypes);
1753     if (r != ERROR_SUCCESS)
1754         goto err;
1755 
1756     list_init(&table->rows);
1757 
1758     table->name = strdupW(name);
1759     table->numconflicts = 0;
1760 
1761     msiobj_release(&mergeview->hdr);
1762     *ptable = table;
1763     return ERROR_SUCCESS;
1764 
1765 err:
1766     msiobj_release(&mergeview->hdr);
1767     free_merge_table(table);
1768     *ptable = NULL;
1769     return r;
1770 }
1771 
1772 static UINT merge_diff_tables(MSIRECORD *rec, LPVOID param)
1773 {
1774     MERGEDATA *data = param;
1775     MERGETABLE *table;
1776     MSIQUERY *dbview = NULL;
1777     MSIQUERY *mergeview = NULL;
1778     LPCWSTR name;
1779     UINT r;
1780 
1781     static const WCHAR query[] = {'S','E','L','E','C','T',' ','*',' ',
1782         'F','R','O','M',' ','`','%','s','`',0};
1783 
1784     name = MSI_RecordGetString(rec, 1);
1785 
1786     r = MSI_OpenQuery(data->merge, &mergeview, query, name);
1787     if (r != ERROR_SUCCESS)
1788         goto done;
1789 
1790     if (TABLE_Exists(data->db, name))
1791     {
1792         r = MSI_OpenQuery(data->db, &dbview, query, name);
1793         if (r != ERROR_SUCCESS)
1794             goto done;
1795 
1796         r = merge_verify_colnames(dbview, mergeview);
1797         if (r != ERROR_SUCCESS)
1798             goto done;
1799 
1800         r = merge_verify_primary_keys(data->db, data->merge, name);
1801         if (r != ERROR_SUCCESS)
1802             goto done;
1803     }
1804 
1805     r = msi_get_merge_table(data->merge, name, &table);
1806     if (r != ERROR_SUCCESS)
1807         goto done;
1808 
1809     data->curtable = table;
1810     data->curview = mergeview;
1811     r = MSI_IterateRecords(mergeview, NULL, merge_diff_row, data);
1812     if (r != ERROR_SUCCESS)
1813     {
1814         free_merge_table(table);
1815         goto done;
1816     }
1817 
1818     list_add_tail(data->tabledata, &table->entry);
1819 
1820 done:
1821     msiobj_release(&dbview->hdr);
1822     msiobj_release(&mergeview->hdr);
1823     return r;
1824 }
1825 
1826 static UINT gather_merge_data(MSIDATABASE *db, MSIDATABASE *merge,
1827                               struct list *tabledata)
1828 {
1829     static const WCHAR query[] = {
1830         'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
1831         '`','_','T','a','b','l','e','s','`',0};
1832     MSIQUERY *view;
1833     MERGEDATA data;
1834     UINT r;
1835 
1836     r = MSI_DatabaseOpenViewW(merge, query, &view);
1837     if (r != ERROR_SUCCESS)
1838         return r;
1839 
1840     data.db = db;
1841     data.merge = merge;
1842     data.tabledata = tabledata;
1843     r = MSI_IterateRecords(view, NULL, merge_diff_tables, &data);
1844     msiobj_release(&view->hdr);
1845     return r;
1846 }
1847 
1848 static UINT merge_table(MSIDATABASE *db, MERGETABLE *table)
1849 {
1850     UINT r;
1851     MERGEROW *row;
1852     MSIVIEW *tv;
1853 
1854     if (!TABLE_Exists(db, table->name))
1855     {
1856         r = msi_add_table_to_db(db, table->columns, table->types,
1857                table->labels, table->numlabels, table->numcolumns);
1858         if (r != ERROR_SUCCESS)
1859            return ERROR_FUNCTION_FAILED;
1860     }
1861 
1862     LIST_FOR_EACH_ENTRY(row, &table->rows, MERGEROW, entry)
1863     {
1864         r = TABLE_CreateView(db, table->name, &tv);
1865         if (r != ERROR_SUCCESS)
1866             return r;
1867 
1868         r = tv->ops->insert_row(tv, row->data, -1, FALSE);
1869         tv->ops->delete(tv);
1870 
1871         if (r != ERROR_SUCCESS)
1872             return r;
1873     }
1874 
1875     return ERROR_SUCCESS;
1876 }
1877 
1878 static UINT update_merge_errors(MSIDATABASE *db, LPCWSTR error,
1879                                 LPWSTR table, DWORD numconflicts)
1880 {
1881     UINT r;
1882     MSIQUERY *view;
1883 
1884     static const WCHAR create[] = {
1885         'C','R','E','A','T','E',' ','T','A','B','L','E',' ',
1886         '`','%','s','`',' ','(','`','T','a','b','l','e','`',' ',
1887         'C','H','A','R','(','2','5','5',')',' ','N','O','T',' ',
1888         'N','U','L','L',',',' ','`','N','u','m','R','o','w','M','e','r','g','e',
1889         'C','o','n','f','l','i','c','t','s','`',' ','S','H','O','R','T',' ',
1890         'N','O','T',' ','N','U','L','L',' ','P','R','I','M','A','R','Y',' ',
1891         'K','E','Y',' ','`','T','a','b','l','e','`',')',0};
1892     static const WCHAR insert[] = {
1893         'I','N','S','E','R','T',' ','I','N','T','O',' ',
1894         '`','%','s','`',' ','(','`','T','a','b','l','e','`',',',' ',
1895         '`','N','u','m','R','o','w','M','e','r','g','e',
1896         'C','o','n','f','l','i','c','t','s','`',')',' ','V','A','L','U','E','S',
1897         ' ','(','\'','%','s','\'',',',' ','%','d',')',0};
1898 
1899     if (!TABLE_Exists(db, error))
1900     {
1901         r = MSI_OpenQuery(db, &view, create, error);
1902         if (r != ERROR_SUCCESS)
1903             return r;
1904 
1905         r = MSI_ViewExecute(view, NULL);
1906         msiobj_release(&view->hdr);
1907         if (r != ERROR_SUCCESS)
1908             return r;
1909     }
1910 
1911     r = MSI_OpenQuery(db, &view, insert, error, table, numconflicts);
1912     if (r != ERROR_SUCCESS)
1913         return r;
1914 
1915     r = MSI_ViewExecute(view, NULL);
1916     msiobj_release(&view->hdr);
1917     return r;
1918 }
1919 
1920 UINT WINAPI MsiDatabaseMergeW(MSIHANDLE hDatabase, MSIHANDLE hDatabaseMerge,
1921                               LPCWSTR szTableName)
1922 {
1923     struct list tabledata = LIST_INIT(tabledata);
1924     struct list *item, *cursor;
1925     MSIDATABASE *db, *merge;
1926     MERGETABLE *table;
1927     BOOL conflicts;
1928     UINT r;
1929 
1930     TRACE("(%d, %d, %s)\n", hDatabase, hDatabaseMerge,
1931           debugstr_w(szTableName));
1932 
1933     if (szTableName && !*szTableName)
1934         return ERROR_INVALID_TABLE;
1935 
1936     db = msihandle2msiinfo(hDatabase, MSIHANDLETYPE_DATABASE);
1937     merge = msihandle2msiinfo(hDatabaseMerge, MSIHANDLETYPE_DATABASE);
1938     if (!db || !merge)
1939     {
1940         r = ERROR_INVALID_HANDLE;
1941         goto done;
1942     }
1943 
1944     r = gather_merge_data(db, merge, &tabledata);
1945     if (r != ERROR_SUCCESS)
1946         goto done;
1947 
1948     conflicts = FALSE;
1949     LIST_FOR_EACH_ENTRY(table, &tabledata, MERGETABLE, entry)
1950     {
1951         if (table->numconflicts)
1952         {
1953             conflicts = TRUE;
1954 
1955             r = update_merge_errors(db, szTableName, table->name,
1956                                     table->numconflicts);
1957             if (r != ERROR_SUCCESS)
1958                 break;
1959         }
1960         else
1961         {
1962             r = merge_table(db, table);
1963             if (r != ERROR_SUCCESS)
1964                 break;
1965         }
1966     }
1967 
1968     LIST_FOR_EACH_SAFE(item, cursor, &tabledata)
1969     {
1970         MERGETABLE *table = LIST_ENTRY(item, MERGETABLE, entry);
1971         list_remove(&table->entry);
1972         free_merge_table(table);
1973     }
1974 
1975     if (conflicts)
1976         r = ERROR_FUNCTION_FAILED;
1977 
1978 done:
1979     msiobj_release(&db->hdr);
1980     msiobj_release(&merge->hdr);
1981     return r;
1982 }
1983 
1984 MSIDBSTATE WINAPI MsiGetDatabaseState( MSIHANDLE handle )
1985 {
1986     MSIDBSTATE ret = MSIDBSTATE_READ;
1987     MSIDATABASE *db;
1988 
1989     TRACE("%d\n", handle);
1990 
1991     db = msihandle2msiinfo( handle, MSIHANDLETYPE_DATABASE );
1992     if( !db )
1993     {
1994         WARN("MsiGetDatabaseState not allowed during a custom action!\n");
1995         return MSIDBSTATE_ERROR;
1996     }
1997 
1998     if (db->mode != MSIDBOPEN_READONLY )
1999         ret = MSIDBSTATE_WRITE;
2000     msiobj_release( &db->hdr );
2001 
2002     return ret;
2003 }
2004 
2005 typedef struct _msi_remote_database_impl {
2006     IWineMsiRemoteDatabase IWineMsiRemoteDatabase_iface;
2007     MSIHANDLE database;
2008     LONG refs;
2009 } msi_remote_database_impl;
2010 
2011 static inline msi_remote_database_impl *impl_from_IWineMsiRemoteDatabase( IWineMsiRemoteDatabase *iface )
2012 {
2013     return CONTAINING_RECORD(iface, msi_remote_database_impl, IWineMsiRemoteDatabase_iface);
2014 }
2015 
2016 static HRESULT WINAPI mrd_QueryInterface( IWineMsiRemoteDatabase *iface,
2017                                           REFIID riid,LPVOID *ppobj)
2018 {
2019     if( IsEqualCLSID( riid, &IID_IUnknown ) ||
2020         IsEqualCLSID( riid, &IID_IWineMsiRemoteDatabase ) )
2021     {
2022         IWineMsiRemoteDatabase_AddRef( iface );
2023         *ppobj = iface;
2024         return S_OK;
2025     }
2026 
2027     return E_NOINTERFACE;
2028 }
2029 
2030 static ULONG WINAPI mrd_AddRef( IWineMsiRemoteDatabase *iface )
2031 {
2032     msi_remote_database_impl* This = impl_from_IWineMsiRemoteDatabase( iface );
2033 
2034     return InterlockedIncrement( &This->refs );
2035 }
2036 
2037 static ULONG WINAPI mrd_Release( IWineMsiRemoteDatabase *iface )
2038 {
2039     msi_remote_database_impl* This = impl_from_IWineMsiRemoteDatabase( iface );
2040     ULONG r;
2041 
2042     r = InterlockedDecrement( &This->refs );
2043     if (r == 0)
2044     {
2045         MsiCloseHandle( This->database );
2046         msi_free( This );
2047     }
2048     return r;
2049 }
2050 
2051 static HRESULT WINAPI mrd_IsTablePersistent( IWineMsiRemoteDatabase *iface,
2052                                              LPCWSTR table, MSICONDITION *persistent )
2053 {
2054     msi_remote_database_impl *This = impl_from_IWineMsiRemoteDatabase( iface );
2055     *persistent = MsiDatabaseIsTablePersistentW(This->database, table);
2056     return S_OK;
2057 }
2058 
2059 static HRESULT WINAPI mrd_GetPrimaryKeys( IWineMsiRemoteDatabase *iface,
2060                                           LPCWSTR table, MSIHANDLE *keys )
2061 {
2062     msi_remote_database_impl *This = impl_from_IWineMsiRemoteDatabase( iface );
2063     UINT r = MsiDatabaseGetPrimaryKeysW(This->database, table, keys);
2064     return HRESULT_FROM_WIN32(r);
2065 }
2066 
2067 static HRESULT WINAPI mrd_GetSummaryInformation( IWineMsiRemoteDatabase *iface,
2068                                                 UINT updatecount, MSIHANDLE *suminfo )
2069 {
2070     msi_remote_database_impl *This = impl_from_IWineMsiRemoteDatabase( iface );
2071     UINT r = MsiGetSummaryInformationW(This->database, NULL, updatecount, suminfo);
2072     return HRESULT_FROM_WIN32(r);
2073 }
2074 
2075 static HRESULT WINAPI mrd_OpenView( IWineMsiRemoteDatabase *iface,
2076                                     LPCWSTR query, MSIHANDLE *view )
2077 {
2078     msi_remote_database_impl *This = impl_from_IWineMsiRemoteDatabase( iface );
2079     UINT r = MsiDatabaseOpenViewW(This->database, query, view);
2080     return HRESULT_FROM_WIN32(r);
2081 }
2082 
2083 static HRESULT WINAPI mrd_SetMsiHandle( IWineMsiRemoteDatabase *iface, MSIHANDLE handle )
2084 {
2085     msi_remote_database_impl* This = impl_from_IWineMsiRemoteDatabase( iface );
2086     This->database = handle;
2087     return S_OK;
2088 }
2089 
2090 static const IWineMsiRemoteDatabaseVtbl msi_remote_database_vtbl =
2091 {
2092     mrd_QueryInterface,
2093     mrd_AddRef,
2094     mrd_Release,
2095     mrd_IsTablePersistent,
2096     mrd_GetPrimaryKeys,
2097     mrd_GetSummaryInformation,
2098     mrd_OpenView,
2099     mrd_SetMsiHandle,
2100 };
2101 
2102 HRESULT create_msi_remote_database( IUnknown *pOuter, LPVOID *ppObj )
2103 {
2104     msi_remote_database_impl *This;
2105 
2106     This = msi_alloc( sizeof *This );
2107     if (!This)
2108         return E_OUTOFMEMORY;
2109 
2110     This->IWineMsiRemoteDatabase_iface.lpVtbl = &msi_remote_database_vtbl;
2111     This->database = 0;
2112     This->refs = 1;
2113 
2114     *ppObj = &This->IWineMsiRemoteDatabase_iface;
2115 
2116     return S_OK;
2117 }
2118