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