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