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