1 /* 2 * Implementation of the Microsoft Installer (msi.dll) 3 * 4 * Copyright 2008 James Hawkins 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 <fdi.h> 24 25 WINE_DEFAULT_DEBUG_CHANNEL(msi); 26 27 /* from msvcrt/fcntl.h */ 28 #define _O_RDONLY 0 29 #define _O_WRONLY 1 30 #define _O_RDWR 2 31 #define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR) 32 #define _O_APPEND 0x0008 33 #define _O_RANDOM 0x0010 34 #define _O_SEQUENTIAL 0x0020 35 #define _O_TEMPORARY 0x0040 36 #define _O_NOINHERIT 0x0080 37 #define _O_CREAT 0x0100 38 #define _O_TRUNC 0x0200 39 #define _O_EXCL 0x0400 40 #define _O_SHORT_LIVED 0x1000 41 #define _O_TEXT 0x4000 42 #define _O_BINARY 0x8000 43 44 static BOOL source_matches_volume(MSIMEDIAINFO *mi, LPCWSTR source_root) 45 { 46 WCHAR volume_name[MAX_PATH + 1], root[MAX_PATH + 1]; 47 const WCHAR *p; 48 int len, len2; 49 50 strcpyW(root, source_root); 51 PathStripToRootW(root); 52 PathAddBackslashW(root); 53 54 if (!GetVolumeInformationW(root, volume_name, MAX_PATH + 1, NULL, NULL, NULL, NULL, 0)) 55 { 56 WARN("failed to get volume information for %s (%u)\n", debugstr_w(root), GetLastError()); 57 return FALSE; 58 } 59 60 len = strlenW( volume_name ); 61 len2 = strlenW( mi->volume_label ); 62 if (len2 > len) return FALSE; 63 p = volume_name + len - len2; 64 65 return !strcmpiW( mi->volume_label, p ); 66 } 67 68 static UINT msi_change_media(MSIPACKAGE *package, MSIMEDIAINFO *mi) 69 { 70 LPWSTR error, error_dialog; 71 LPWSTR source_dir; 72 UINT r = ERROR_SUCCESS; 73 74 static const WCHAR error_prop[] = {'E','r','r','o','r','D','i','a','l','o','g',0}; 75 76 if ((package->ui_level & INSTALLUILEVEL_MASK) == INSTALLUILEVEL_NONE && 77 !gUIHandlerA && !gUIHandlerW && !gUIHandlerRecord) return ERROR_SUCCESS; 78 79 error = msi_build_error_string(package, 1302, 1, mi->disk_prompt); 80 error_dialog = msi_dup_property(package->db, error_prop); 81 source_dir = msi_dup_property(package->db, szSourceDir); 82 83 while (r == ERROR_SUCCESS && !source_matches_volume(mi, source_dir)) 84 { 85 r = msi_spawn_error_dialog(package, error_dialog, error); 86 87 if (gUIHandlerW) 88 { 89 gUIHandlerW(gUIContext, MB_RETRYCANCEL | INSTALLMESSAGE_ERROR, error); 90 } 91 else if (gUIHandlerA) 92 { 93 char *msg = strdupWtoA(error); 94 gUIHandlerA(gUIContext, MB_RETRYCANCEL | INSTALLMESSAGE_ERROR, msg); 95 msi_free(msg); 96 } 97 else if (gUIHandlerRecord) 98 { 99 MSIHANDLE rec = MsiCreateRecord(1); 100 MsiRecordSetStringW(rec, 0, error); 101 gUIHandlerRecord(gUIContext, MB_RETRYCANCEL | INSTALLMESSAGE_ERROR, rec); 102 MsiCloseHandle(rec); 103 } 104 } 105 106 msi_free(error); 107 msi_free(error_dialog); 108 msi_free(source_dir); 109 110 return r; 111 } 112 113 static MSICABINETSTREAM *msi_get_cabinet_stream( MSIPACKAGE *package, UINT disk_id ) 114 { 115 MSICABINETSTREAM *cab; 116 117 LIST_FOR_EACH_ENTRY( cab, &package->cabinet_streams, MSICABINETSTREAM, entry ) 118 { 119 if (cab->disk_id == disk_id) return cab; 120 } 121 return NULL; 122 } 123 124 static void * CDECL cabinet_alloc(ULONG cb) 125 { 126 return msi_alloc(cb); 127 } 128 129 static void CDECL cabinet_free(void *pv) 130 { 131 msi_free(pv); 132 } 133 134 static INT_PTR CDECL cabinet_open(char *pszFile, int oflag, int pmode) 135 { 136 DWORD dwAccess = 0; 137 DWORD dwShareMode = 0; 138 DWORD dwCreateDisposition = OPEN_EXISTING; 139 140 switch (oflag & _O_ACCMODE) 141 { 142 case _O_RDONLY: 143 dwAccess = GENERIC_READ; 144 dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE; 145 break; 146 case _O_WRONLY: 147 dwAccess = GENERIC_WRITE; 148 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; 149 break; 150 case _O_RDWR: 151 dwAccess = GENERIC_READ | GENERIC_WRITE; 152 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; 153 break; 154 } 155 156 if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL)) 157 dwCreateDisposition = CREATE_NEW; 158 else if (oflag & _O_CREAT) 159 dwCreateDisposition = CREATE_ALWAYS; 160 161 return (INT_PTR)CreateFileA(pszFile, dwAccess, dwShareMode, NULL, 162 dwCreateDisposition, 0, NULL); 163 } 164 165 static UINT CDECL cabinet_read(INT_PTR hf, void *pv, UINT cb) 166 { 167 HANDLE handle = (HANDLE)hf; 168 DWORD read; 169 170 if (ReadFile(handle, pv, cb, &read, NULL)) 171 return read; 172 173 return 0; 174 } 175 176 static UINT CDECL cabinet_write(INT_PTR hf, void *pv, UINT cb) 177 { 178 HANDLE handle = (HANDLE)hf; 179 DWORD written; 180 181 if (WriteFile(handle, pv, cb, &written, NULL)) 182 return written; 183 184 return 0; 185 } 186 187 static int CDECL cabinet_close(INT_PTR hf) 188 { 189 HANDLE handle = (HANDLE)hf; 190 return CloseHandle(handle) ? 0 : -1; 191 } 192 193 static LONG CDECL cabinet_seek(INT_PTR hf, LONG dist, int seektype) 194 { 195 HANDLE handle = (HANDLE)hf; 196 /* flags are compatible and so are passed straight through */ 197 return SetFilePointer(handle, dist, NULL, seektype); 198 } 199 200 struct package_disk 201 { 202 MSIPACKAGE *package; 203 UINT id; 204 }; 205 206 static struct package_disk package_disk; 207 208 static INT_PTR CDECL cabinet_open_stream( char *pszFile, int oflag, int pmode ) 209 { 210 MSICABINETSTREAM *cab; 211 IStream *stream; 212 213 if (!(cab = msi_get_cabinet_stream( package_disk.package, package_disk.id ))) 214 { 215 WARN("failed to get cabinet stream\n"); 216 return -1; 217 } 218 if (cab->storage == package_disk.package->db->storage) 219 { 220 UINT r = msi_get_stream( package_disk.package->db, cab->stream + 1, &stream ); 221 if (r != ERROR_SUCCESS) 222 { 223 WARN("failed to get stream %u\n", r); 224 return -1; 225 } 226 } 227 else /* patch storage */ 228 { 229 HRESULT hr; 230 WCHAR *encoded; 231 232 if (!(encoded = encode_streamname( FALSE, cab->stream + 1 ))) 233 { 234 WARN("failed to encode stream name\n"); 235 return -1; 236 } 237 hr = IStorage_OpenStream( cab->storage, encoded, NULL, STGM_READ|STGM_SHARE_EXCLUSIVE, 0, &stream ); 238 msi_free( encoded ); 239 if (FAILED(hr)) 240 { 241 WARN("failed to open stream 0x%08x\n", hr); 242 return -1; 243 } 244 } 245 return (INT_PTR)stream; 246 } 247 248 static UINT CDECL cabinet_read_stream( INT_PTR hf, void *pv, UINT cb ) 249 { 250 IStream *stm = (IStream *)hf; 251 DWORD read; 252 HRESULT hr; 253 254 hr = IStream_Read( stm, pv, cb, &read ); 255 if (hr == S_OK || hr == S_FALSE) 256 return read; 257 258 return 0; 259 } 260 261 static int CDECL cabinet_close_stream( INT_PTR hf ) 262 { 263 IStream *stm = (IStream *)hf; 264 IStream_Release( stm ); 265 return 0; 266 } 267 268 static LONG CDECL cabinet_seek_stream( INT_PTR hf, LONG dist, int seektype ) 269 { 270 IStream *stm = (IStream *)hf; 271 LARGE_INTEGER move; 272 ULARGE_INTEGER newpos; 273 HRESULT hr; 274 275 move.QuadPart = dist; 276 hr = IStream_Seek( stm, move, seektype, &newpos ); 277 if (SUCCEEDED(hr)) 278 { 279 if (newpos.QuadPart <= MAXLONG) return newpos.QuadPart; 280 ERR("Too big!\n"); 281 } 282 return -1; 283 } 284 285 static UINT CDECL msi_media_get_disk_info(MSIPACKAGE *package, MSIMEDIAINFO *mi) 286 { 287 MSIRECORD *row; 288 289 static const WCHAR query[] = { 290 'S','E','L','E','C','T',' ','*',' ', 'F','R','O','M',' ', 291 '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ', 292 '`','D','i','s','k','I','d','`',' ','=',' ','%','i',0}; 293 294 row = MSI_QueryGetRecord(package->db, query, mi->disk_id); 295 if (!row) 296 { 297 TRACE("Unable to query row\n"); 298 return ERROR_FUNCTION_FAILED; 299 } 300 301 mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3)); 302 mi->cabinet = strdupW(MSI_RecordGetString(row, 4)); 303 mi->volume_label = strdupW(MSI_RecordGetString(row, 5)); 304 305 msiobj_release(&row->hdr); 306 return ERROR_SUCCESS; 307 } 308 309 static INT_PTR cabinet_partial_file(FDINOTIFICATIONTYPE fdint, 310 PFDINOTIFICATION pfdin) 311 { 312 MSICABDATA *data = pfdin->pv; 313 data->mi->is_continuous = FALSE; 314 return 0; 315 } 316 317 static WCHAR *get_cabinet_filename(MSIMEDIAINFO *mi) 318 { 319 int len; 320 WCHAR *ret; 321 322 len = strlenW(mi->sourcedir) + strlenW(mi->cabinet) + 1; 323 if (!(ret = msi_alloc(len * sizeof(WCHAR)))) return NULL; 324 strcpyW(ret, mi->sourcedir); 325 strcatW(ret, mi->cabinet); 326 return ret; 327 } 328 329 static INT_PTR cabinet_next_cabinet(FDINOTIFICATIONTYPE fdint, 330 PFDINOTIFICATION pfdin) 331 { 332 MSICABDATA *data = pfdin->pv; 333 MSIMEDIAINFO *mi = data->mi; 334 LPWSTR cabinet_file = NULL, cab = strdupAtoW(pfdin->psz1); 335 INT_PTR res = -1; 336 UINT rc; 337 338 msi_free(mi->disk_prompt); 339 msi_free(mi->cabinet); 340 msi_free(mi->volume_label); 341 mi->disk_prompt = NULL; 342 mi->cabinet = NULL; 343 mi->volume_label = NULL; 344 345 mi->disk_id++; 346 mi->is_continuous = TRUE; 347 348 rc = msi_media_get_disk_info(data->package, mi); 349 if (rc != ERROR_SUCCESS) 350 { 351 ERR("Failed to get next cabinet information: %d\n", rc); 352 goto done; 353 } 354 355 if (strcmpiW( mi->cabinet, cab )) 356 { 357 char *next_cab; 358 ULONG length; 359 360 WARN("Continuous cabinet %s does not match the next cabinet %s in the media table => use latter one\n", debugstr_w(cab), debugstr_w(mi->cabinet)); 361 362 /* Use cabinet name from the media table */ 363 next_cab = strdupWtoA(mi->cabinet); 364 /* Modify path to cabinet file with full filename (psz3 points to a 256 bytes buffer that can be modified contrary to psz1 and psz2) */ 365 length = strlen(pfdin->psz3) + 1 + strlen(next_cab) + 1; 366 if (length > 256) 367 { 368 WARN("Cannot update next cabinet filename with a string size %u > 256\n", length); 369 msi_free(next_cab); 370 goto done; 371 } 372 else 373 { 374 strcat(pfdin->psz3, "\\"); 375 strcat(pfdin->psz3, next_cab); 376 } 377 /* Path psz3 and cabinet psz1 are concatenated by FDI so just reset psz1 */ 378 *pfdin->psz1 = 0; 379 msi_free(next_cab); 380 } 381 382 if (!(cabinet_file = get_cabinet_filename(mi))) 383 goto done; 384 385 TRACE("Searching for %s\n", debugstr_w(cabinet_file)); 386 387 res = 0; 388 if (GetFileAttributesW(cabinet_file) == INVALID_FILE_ATTRIBUTES) 389 { 390 if (msi_change_media(data->package, mi) != ERROR_SUCCESS) 391 res = -1; 392 } 393 394 done: 395 msi_free(cab); 396 msi_free(cabinet_file); 397 return res; 398 } 399 400 static INT_PTR cabinet_next_cabinet_stream( FDINOTIFICATIONTYPE fdint, 401 PFDINOTIFICATION pfdin ) 402 { 403 MSICABDATA *data = pfdin->pv; 404 MSIMEDIAINFO *mi = data->mi; 405 UINT rc; 406 407 msi_free( mi->disk_prompt ); 408 msi_free( mi->cabinet ); 409 msi_free( mi->volume_label ); 410 mi->disk_prompt = NULL; 411 mi->cabinet = NULL; 412 mi->volume_label = NULL; 413 414 mi->disk_id++; 415 mi->is_continuous = TRUE; 416 417 rc = msi_media_get_disk_info( data->package, mi ); 418 if (rc != ERROR_SUCCESS) 419 { 420 ERR("Failed to get next cabinet information: %u\n", rc); 421 return -1; 422 } 423 package_disk.id = mi->disk_id; 424 425 TRACE("next cabinet is %s disk id %u\n", debugstr_w(mi->cabinet), mi->disk_id); 426 return 0; 427 } 428 429 static INT_PTR cabinet_copy_file(FDINOTIFICATIONTYPE fdint, 430 PFDINOTIFICATION pfdin) 431 { 432 MSICABDATA *data = pfdin->pv; 433 HANDLE handle = 0; 434 LPWSTR path = NULL; 435 DWORD attrs; 436 437 data->curfile = strdupAtoW(pfdin->psz1); 438 if (!data->cb(data->package, data->curfile, MSICABEXTRACT_BEGINEXTRACT, &path, 439 &attrs, data->user)) 440 { 441 /* We're not extracting this file, so free the filename. */ 442 msi_free(data->curfile); 443 data->curfile = NULL; 444 goto done; 445 } 446 447 TRACE("extracting %s -> %s\n", debugstr_w(data->curfile), debugstr_w(path)); 448 449 attrs = attrs & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM); 450 if (!attrs) attrs = FILE_ATTRIBUTE_NORMAL; 451 452 handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, 453 NULL, CREATE_ALWAYS, attrs, NULL); 454 if (handle == INVALID_HANDLE_VALUE) 455 { 456 DWORD err = GetLastError(); 457 DWORD attrs2 = GetFileAttributesW(path); 458 459 if (attrs2 == INVALID_FILE_ATTRIBUTES) 460 { 461 ERR("failed to create %s (error %d)\n", debugstr_w(path), err); 462 goto done; 463 } 464 else if (err == ERROR_ACCESS_DENIED && (attrs2 & FILE_ATTRIBUTE_READONLY)) 465 { 466 TRACE("removing read-only attribute on %s\n", debugstr_w(path)); 467 SetFileAttributesW( path, attrs2 & ~FILE_ATTRIBUTE_READONLY ); 468 handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs, NULL); 469 470 if (handle != INVALID_HANDLE_VALUE) goto done; 471 err = GetLastError(); 472 } 473 if (err == ERROR_SHARING_VIOLATION || err == ERROR_USER_MAPPED_FILE) 474 { 475 WCHAR *tmpfileW, *tmppathW, *p; 476 DWORD len; 477 478 TRACE("file in use, scheduling rename operation\n"); 479 480 if (!(tmppathW = strdupW( path ))) return ERROR_OUTOFMEMORY; 481 if ((p = strrchrW(tmppathW, '\\'))) *p = 0; 482 len = strlenW( tmppathW ) + 16; 483 if (!(tmpfileW = msi_alloc(len * sizeof(WCHAR)))) 484 { 485 msi_free( tmppathW ); 486 return ERROR_OUTOFMEMORY; 487 } 488 if (!GetTempFileNameW(tmppathW, szMsi, 0, tmpfileW)) tmpfileW[0] = 0; 489 msi_free( tmppathW ); 490 491 handle = CreateFileW(tmpfileW, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, attrs, NULL); 492 493 if (handle != INVALID_HANDLE_VALUE && 494 MoveFileExW(path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT) && 495 MoveFileExW(tmpfileW, path, MOVEFILE_DELAY_UNTIL_REBOOT)) 496 { 497 data->package->need_reboot_at_end = 1; 498 } 499 else 500 { 501 WARN("failed to schedule rename operation %s (error %d)\n", debugstr_w(path), GetLastError()); 502 DeleteFileW( tmpfileW ); 503 } 504 msi_free(tmpfileW); 505 } 506 else 507 WARN("failed to create %s (error %d)\n", debugstr_w(path), err); 508 } 509 510 done: 511 msi_free(path); 512 513 return (INT_PTR)handle; 514 } 515 516 static INT_PTR cabinet_close_file_info(FDINOTIFICATIONTYPE fdint, 517 PFDINOTIFICATION pfdin) 518 { 519 MSICABDATA *data = pfdin->pv; 520 FILETIME ft; 521 FILETIME ftLocal; 522 HANDLE handle = (HANDLE)pfdin->hf; 523 524 data->mi->is_continuous = FALSE; 525 526 if (!DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft)) 527 return -1; 528 if (!LocalFileTimeToFileTime(&ft, &ftLocal)) 529 return -1; 530 if (!SetFileTime(handle, &ftLocal, 0, &ftLocal)) 531 return -1; 532 533 CloseHandle(handle); 534 535 data->cb(data->package, data->curfile, MSICABEXTRACT_FILEEXTRACTED, NULL, NULL, 536 data->user); 537 538 msi_free(data->curfile); 539 data->curfile = NULL; 540 541 return 1; 542 } 543 544 static INT_PTR CDECL cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin) 545 { 546 switch (fdint) 547 { 548 case fdintPARTIAL_FILE: 549 return cabinet_partial_file(fdint, pfdin); 550 551 case fdintNEXT_CABINET: 552 return cabinet_next_cabinet(fdint, pfdin); 553 554 case fdintCOPY_FILE: 555 return cabinet_copy_file(fdint, pfdin); 556 557 case fdintCLOSE_FILE_INFO: 558 return cabinet_close_file_info(fdint, pfdin); 559 560 default: 561 return 0; 562 } 563 } 564 565 static INT_PTR CDECL cabinet_notify_stream( FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin ) 566 { 567 switch (fdint) 568 { 569 case fdintPARTIAL_FILE: 570 return cabinet_partial_file( fdint, pfdin ); 571 572 case fdintNEXT_CABINET: 573 return cabinet_next_cabinet_stream( fdint, pfdin ); 574 575 case fdintCOPY_FILE: 576 return cabinet_copy_file( fdint, pfdin ); 577 578 case fdintCLOSE_FILE_INFO: 579 return cabinet_close_file_info( fdint, pfdin ); 580 581 case fdintCABINET_INFO: 582 return 0; 583 584 default: 585 ERR("Unexpected notification %d\n", fdint); 586 return 0; 587 } 588 } 589 590 static BOOL extract_cabinet( MSIPACKAGE* package, MSIMEDIAINFO *mi, LPVOID data ) 591 { 592 LPSTR cabinet, cab_path = NULL; 593 HFDI hfdi; 594 ERF erf; 595 BOOL ret = FALSE; 596 597 TRACE("extracting %s disk id %u\n", debugstr_w(mi->cabinet), mi->disk_id); 598 599 hfdi = FDICreate( cabinet_alloc, cabinet_free, cabinet_open, cabinet_read, 600 cabinet_write, cabinet_close, cabinet_seek, 0, &erf ); 601 if (!hfdi) 602 { 603 ERR("FDICreate failed\n"); 604 return FALSE; 605 } 606 607 cabinet = strdupWtoA( mi->cabinet ); 608 if (!cabinet) 609 goto done; 610 611 cab_path = strdupWtoA( mi->sourcedir ); 612 if (!cab_path) 613 goto done; 614 615 ret = FDICopy( hfdi, cabinet, cab_path, 0, cabinet_notify, NULL, data ); 616 if (!ret) 617 ERR("FDICopy failed\n"); 618 619 done: 620 FDIDestroy( hfdi ); 621 msi_free(cabinet ); 622 msi_free( cab_path ); 623 624 if (ret) 625 mi->is_extracted = TRUE; 626 627 return ret; 628 } 629 630 static BOOL extract_cabinet_stream( MSIPACKAGE *package, MSIMEDIAINFO *mi, LPVOID data ) 631 { 632 static char filename[] = {'<','S','T','R','E','A','M','>',0}; 633 HFDI hfdi; 634 ERF erf; 635 BOOL ret = FALSE; 636 637 TRACE("extracting %s disk id %u\n", debugstr_w(mi->cabinet), mi->disk_id); 638 639 hfdi = FDICreate( cabinet_alloc, cabinet_free, cabinet_open_stream, cabinet_read_stream, 640 cabinet_write, cabinet_close_stream, cabinet_seek_stream, 0, &erf ); 641 if (!hfdi) 642 { 643 ERR("FDICreate failed\n"); 644 return FALSE; 645 } 646 647 package_disk.package = package; 648 package_disk.id = mi->disk_id; 649 650 ret = FDICopy( hfdi, filename, NULL, 0, cabinet_notify_stream, NULL, data ); 651 if (!ret) ERR("FDICopy failed\n"); 652 653 FDIDestroy( hfdi ); 654 if (ret) mi->is_extracted = TRUE; 655 return ret; 656 } 657 658 /*********************************************************************** 659 * msi_cabextract 660 * 661 * Extract files from a cabinet file or stream. 662 */ 663 BOOL msi_cabextract(MSIPACKAGE* package, MSIMEDIAINFO *mi, LPVOID data) 664 { 665 if (mi->cabinet[0] == '#') 666 { 667 return extract_cabinet_stream( package, mi, data ); 668 } 669 return extract_cabinet( package, mi, data ); 670 } 671 672 void msi_free_media_info(MSIMEDIAINFO *mi) 673 { 674 msi_free(mi->disk_prompt); 675 msi_free(mi->cabinet); 676 msi_free(mi->volume_label); 677 msi_free(mi); 678 } 679 680 static UINT get_drive_type(const WCHAR *path) 681 { 682 WCHAR root[MAX_PATH + 1]; 683 684 strcpyW(root, path); 685 PathStripToRootW(root); 686 PathAddBackslashW(root); 687 688 return GetDriveTypeW(root); 689 } 690 691 UINT msi_load_media_info(MSIPACKAGE *package, UINT Sequence, MSIMEDIAINFO *mi) 692 { 693 static const WCHAR query[] = { 694 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','`','M','e','d','i','a','`',' ', 695 'W','H','E','R','E',' ','`','L','a','s','t','S','e','q','u','e','n','c','e','`',' ', 696 '>','=',' ','%','i',' ','O','R','D','E','R',' ','B','Y',' ','`','D','i','s','k','I','d','`',0}; 697 MSIRECORD *row; 698 LPWSTR source_dir, source; 699 DWORD options; 700 701 if (Sequence <= mi->last_sequence) /* already loaded */ 702 return ERROR_SUCCESS; 703 704 row = MSI_QueryGetRecord(package->db, query, Sequence); 705 if (!row) 706 { 707 TRACE("Unable to query row\n"); 708 return ERROR_FUNCTION_FAILED; 709 } 710 711 mi->is_extracted = FALSE; 712 mi->disk_id = MSI_RecordGetInteger(row, 1); 713 mi->last_sequence = MSI_RecordGetInteger(row, 2); 714 msi_free(mi->disk_prompt); 715 mi->disk_prompt = strdupW(MSI_RecordGetString(row, 3)); 716 msi_free(mi->cabinet); 717 mi->cabinet = strdupW(MSI_RecordGetString(row, 4)); 718 msi_free(mi->volume_label); 719 mi->volume_label = strdupW(MSI_RecordGetString(row, 5)); 720 msiobj_release(&row->hdr); 721 722 msi_set_sourcedir_props(package, FALSE); 723 source_dir = msi_dup_property(package->db, szSourceDir); 724 lstrcpyW(mi->sourcedir, source_dir); 725 PathAddBackslashW(mi->sourcedir); 726 mi->type = get_drive_type(source_dir); 727 728 options = MSICODE_PRODUCT; 729 if (mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE) 730 { 731 source = source_dir; 732 options |= MSISOURCETYPE_MEDIA; 733 } 734 else if (package->BaseURL && UrlIsW(package->BaseURL, URLIS_URL)) 735 { 736 source = package->BaseURL; 737 options |= MSISOURCETYPE_URL; 738 } 739 else 740 { 741 source = mi->sourcedir; 742 options |= MSISOURCETYPE_NETWORK; 743 } 744 745 msi_package_add_media_disk(package, package->Context, 746 MSICODE_PRODUCT, mi->disk_id, 747 mi->volume_label, mi->disk_prompt); 748 749 msi_package_add_info(package, package->Context, 750 options, INSTALLPROPERTY_LASTUSEDSOURCEW, source); 751 752 msi_free(source_dir); 753 TRACE("sequence %u -> cabinet %s disk id %u\n", Sequence, debugstr_w(mi->cabinet), mi->disk_id); 754 return ERROR_SUCCESS; 755 } 756 757 /* FIXME: search URL sources as well */ 758 static UINT find_published_source(MSIPACKAGE *package, MSIMEDIAINFO *mi) 759 { 760 WCHAR source[MAX_PATH]; 761 WCHAR volume[MAX_PATH]; 762 WCHAR prompt[MAX_PATH]; 763 DWORD volumesz, promptsz; 764 DWORD index, size, id; 765 WCHAR last_type[2]; 766 UINT r; 767 768 size = 2; 769 r = MsiSourceListGetInfoW(package->ProductCode, NULL, 770 package->Context, MSICODE_PRODUCT, 771 INSTALLPROPERTY_LASTUSEDTYPEW, last_type, &size); 772 if (r != ERROR_SUCCESS) 773 return r; 774 775 size = MAX_PATH; 776 r = MsiSourceListGetInfoW(package->ProductCode, NULL, 777 package->Context, MSICODE_PRODUCT, 778 INSTALLPROPERTY_LASTUSEDSOURCEW, source, &size); 779 if (r != ERROR_SUCCESS) 780 return r; 781 782 if (last_type[0] == 'n') 783 { 784 WCHAR cabinet_file[MAX_PATH]; 785 BOOL check_all = FALSE; 786 787 while(TRUE) 788 { 789 index = 0; 790 volumesz = MAX_PATH; 791 while (MsiSourceListEnumSourcesW(package->ProductCode, NULL, 792 package->Context, 793 MSISOURCETYPE_NETWORK, index++, 794 volume, &volumesz) == ERROR_SUCCESS) 795 { 796 if (check_all || !strncmpiW(source, volume, strlenW(source))) 797 { 798 lstrcpyW(cabinet_file, volume); 799 PathAddBackslashW(cabinet_file); 800 lstrcatW(cabinet_file, mi->cabinet); 801 802 if (GetFileAttributesW(cabinet_file) == INVALID_FILE_ATTRIBUTES) 803 { 804 volumesz = MAX_PATH; 805 if(!check_all) 806 break; 807 continue; 808 } 809 810 lstrcpyW(mi->sourcedir, volume); 811 PathAddBackslashW(mi->sourcedir); 812 TRACE("Found network source %s\n", debugstr_w(mi->sourcedir)); 813 return ERROR_SUCCESS; 814 } 815 } 816 817 if (!check_all) 818 check_all = TRUE; 819 else 820 break; 821 } 822 } 823 824 index = 0; 825 volumesz = MAX_PATH; 826 promptsz = MAX_PATH; 827 while (MsiSourceListEnumMediaDisksW(package->ProductCode, NULL, 828 package->Context, 829 MSICODE_PRODUCT, index++, &id, 830 volume, &volumesz, prompt, &promptsz) == ERROR_SUCCESS) 831 { 832 mi->disk_id = id; 833 msi_free( mi->volume_label ); 834 if (!(mi->volume_label = msi_alloc( ++volumesz * sizeof(WCHAR) ))) return ERROR_OUTOFMEMORY; 835 strcpyW( mi->volume_label, volume ); 836 837 msi_free( mi->disk_prompt ); 838 if (!(mi->disk_prompt = msi_alloc( ++promptsz * sizeof(WCHAR) ))) return ERROR_OUTOFMEMORY; 839 strcpyW( mi->disk_prompt, prompt ); 840 841 if (source_matches_volume(mi, source)) 842 { 843 /* FIXME: what about SourceDir */ 844 lstrcpyW(mi->sourcedir, source); 845 PathAddBackslashW(mi->sourcedir); 846 TRACE("Found disk source %s\n", debugstr_w(mi->sourcedir)); 847 return ERROR_SUCCESS; 848 } 849 } 850 851 return ERROR_FUNCTION_FAILED; 852 } 853 854 UINT ready_media( MSIPACKAGE *package, BOOL compressed, MSIMEDIAINFO *mi ) 855 { 856 UINT rc; 857 WCHAR *cabinet_file = NULL; 858 859 /* media info for continuous cabinet is already loaded */ 860 if (mi->is_continuous) return ERROR_SUCCESS; 861 862 if (mi->cabinet) 863 { 864 /* cabinet is internal, no checks needed */ 865 if (mi->cabinet[0] == '#') return ERROR_SUCCESS; 866 867 if (!(cabinet_file = get_cabinet_filename( mi ))) return ERROR_OUTOFMEMORY; 868 869 /* package should be downloaded */ 870 if (compressed && GetFileAttributesW( cabinet_file ) == INVALID_FILE_ATTRIBUTES && 871 package->BaseURL && UrlIsW( package->BaseURL, URLIS_URL )) 872 { 873 WCHAR temppath[MAX_PATH], *p; 874 875 if ((rc = msi_download_file( cabinet_file, temppath )) != ERROR_SUCCESS) 876 { 877 ERR("failed to download %s (%u)\n", debugstr_w(cabinet_file), rc); 878 msi_free( cabinet_file ); 879 return rc; 880 } 881 if ((p = strrchrW( temppath, '\\' ))) *p = 0; 882 strcpyW( mi->sourcedir, temppath ); 883 PathAddBackslashW( mi->sourcedir ); 884 msi_free( mi->cabinet ); 885 mi->cabinet = strdupW( p + 1 ); 886 msi_free( cabinet_file ); 887 return ERROR_SUCCESS; 888 } 889 } 890 /* check volume matches, change media if not */ 891 if (mi->volume_label && mi->disk_id > 1) 892 { 893 WCHAR *source = msi_dup_property( package->db, szSourceDir ); 894 BOOL match = source_matches_volume( mi, source ); 895 msi_free( source ); 896 897 if (!match && (mi->type == DRIVE_CDROM || mi->type == DRIVE_REMOVABLE)) 898 { 899 if ((rc = msi_change_media( package, mi )) != ERROR_SUCCESS) 900 { 901 msi_free( cabinet_file ); 902 return rc; 903 } 904 } 905 } 906 if (mi->cabinet) 907 { 908 if (compressed && GetFileAttributesW( cabinet_file ) == INVALID_FILE_ATTRIBUTES) 909 { 910 if ((rc = find_published_source( package, mi )) != ERROR_SUCCESS) 911 { 912 ERR("cabinet not found: %s\n", debugstr_w(cabinet_file)); 913 msi_free( cabinet_file ); 914 return ERROR_INSTALL_FAILURE; 915 } 916 } 917 } 918 msi_free( cabinet_file ); 919 return ERROR_SUCCESS; 920 } 921 922 UINT msi_add_cabinet_stream( MSIPACKAGE *package, UINT disk_id, IStorage *storage, const WCHAR *name ) 923 { 924 MSICABINETSTREAM *cab, *item; 925 926 TRACE("%p, %u, %p, %s\n", package, disk_id, storage, debugstr_w(name)); 927 928 LIST_FOR_EACH_ENTRY( item, &package->cabinet_streams, MSICABINETSTREAM, entry ) 929 { 930 if (item->disk_id == disk_id) 931 { 932 TRACE("duplicate disk id %u\n", disk_id); 933 return ERROR_FUNCTION_FAILED; 934 } 935 } 936 if (!(cab = msi_alloc( sizeof(*cab) ))) return ERROR_OUTOFMEMORY; 937 if (!(cab->stream = msi_alloc( (strlenW( name ) + 1) * sizeof(WCHAR ) ))) 938 { 939 msi_free( cab ); 940 return ERROR_OUTOFMEMORY; 941 } 942 strcpyW( cab->stream, name ); 943 cab->disk_id = disk_id; 944 cab->storage = storage; 945 IStorage_AddRef( storage ); 946 list_add_tail( &package->cabinet_streams, &cab->entry ); 947 948 return ERROR_SUCCESS; 949 } 950