2 * Implementation of the Microsoft Installer (msi.dll)
4 * Copyright 2004,2005 Aric Stewart for CodeWeavers
5 * Copyright 2011 Hans Leidekker for CodeWeavers
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
29 #include "wine/debug.h"
30 #include "wine/unicode.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(msi);
35 static BOOL match_language( MSIPACKAGE *package, LANGID langid )
39 if (!package->num_langids || !langid) return TRUE;
40 for (i = 0; i < package->num_langids; i++)
42 if (package->langids[i] == langid) return TRUE;
47 static UINT check_transform_applicable( MSIPACKAGE *package, IStorage *patch )
49 LPWSTR prod_code, patch_product, template = NULL;
50 UINT ret = ERROR_FUNCTION_FAILED;
52 prod_code = msi_dup_property( package->db, szProductCode );
53 patch_product = msi_get_suminfo_product( patch );
55 TRACE("db = %s patch = %s\n", debugstr_w(prod_code), debugstr_w(patch_product));
57 if (strstrW( patch_product, prod_code ))
62 si = MSI_GetSummaryInformationW( patch, 0 );
65 ERR("no summary information!\n");
68 template = msi_suminfo_dup_string( si, PID_TEMPLATE );
71 ERR("no template property!\n");
72 msiobj_release( &si->hdr );
78 msiobj_release( &si->hdr );
81 TRACE("template: %s\n", debugstr_w(template));
82 p = strchrW( template, ';' );
83 if (p && match_language( package, atoiW( p + 1 ) ))
85 TRACE("applicable transform\n");
88 /* FIXME: check platform */
89 msiobj_release( &si->hdr );
93 msi_free( patch_product );
94 msi_free( prod_code );
99 static UINT apply_substorage_transform( MSIPACKAGE *package, MSIDATABASE *patch_db, LPCWSTR name )
101 UINT ret = ERROR_FUNCTION_FAILED;
102 IStorage *stg = NULL;
105 TRACE("%p %s\n", package, debugstr_w(name));
109 ERR("expected a colon in %s\n", debugstr_w(name));
110 return ERROR_FUNCTION_FAILED;
112 r = IStorage_OpenStorage( patch_db->storage, name, NULL, STGM_SHARE_EXCLUSIVE, NULL, 0, &stg );
115 ret = check_transform_applicable( package, stg );
116 if (ret == ERROR_SUCCESS)
117 msi_table_apply_transform( package->db, stg );
119 TRACE("substorage transform %s wasn't applicable\n", debugstr_w(name));
120 IStorage_Release( stg );
124 ERR("failed to open substorage %s\n", debugstr_w(name));
126 return ERROR_SUCCESS;
129 UINT msi_check_patch_applicable( MSIPACKAGE *package, MSISUMMARYINFO *si )
131 LPWSTR guid_list, *guids, product_code;
132 UINT i, ret = ERROR_FUNCTION_FAILED;
134 product_code = msi_dup_property( package->db, szProductCode );
137 /* FIXME: the property ProductCode should be written into the DB somewhere */
138 ERR("no product code to check\n");
139 return ERROR_SUCCESS;
141 guid_list = msi_suminfo_dup_string( si, PID_TEMPLATE );
142 guids = msi_split_string( guid_list, ';' );
143 for (i = 0; guids[i] && ret != ERROR_SUCCESS; i++)
145 if (!strcmpW( guids[i], product_code )) ret = ERROR_SUCCESS;
148 msi_free( guid_list );
149 msi_free( product_code );
153 static UINT msi_parse_patch_summary( MSISUMMARYINFO *si, MSIPATCHINFO **patch )
156 UINT r = ERROR_SUCCESS;
159 if (!(pi = msi_alloc_zero( sizeof(MSIPATCHINFO) )))
161 return ERROR_OUTOFMEMORY;
163 if (!(pi->patchcode = msi_suminfo_dup_string( si, PID_REVNUMBER )))
166 return ERROR_OUTOFMEMORY;
171 msi_free( pi->patchcode );
173 return ERROR_PATCH_PACKAGE_INVALID;
175 if (!(p = strchrW( p + 1, '}' )))
177 msi_free( pi->patchcode );
179 return ERROR_PATCH_PACKAGE_INVALID;
183 FIXME("patch obsoletes %s\n", debugstr_w(p + 1));
186 TRACE("patch code %s\n", debugstr_w(pi->patchcode));
187 if (!(pi->products = msi_suminfo_dup_string( si, PID_TEMPLATE )))
189 msi_free( pi->patchcode );
191 return ERROR_OUTOFMEMORY;
193 if (!(pi->transforms = msi_suminfo_dup_string( si, PID_LASTAUTHOR )))
195 msi_free( pi->patchcode );
196 msi_free( pi->products );
198 return ERROR_OUTOFMEMORY;
204 static UINT patch_set_media_source_prop( MSIPACKAGE *package )
206 static const WCHAR query[] = {
207 'S','E','L','E','C','T',' ','`','S','o','u','r','c','e','`',' ','F','R','O','M',' ',
208 '`','M','e','d','i','a','`',' ','W','H','E','R','E',' ','`','S','o','u','r','c','e','`',' ',
209 'I','S',' ','N','O','T',' ','N','U','L','L',0};
212 const WCHAR *property;
216 r = MSI_DatabaseOpenViewW( package->db, query, &view );
217 if (r != ERROR_SUCCESS)
220 r = MSI_ViewExecute( view, 0 );
221 if (r != ERROR_SUCCESS)
224 if (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
226 property = MSI_RecordGetString( rec, 1 );
227 patch = msi_dup_property( package->db, szPatch );
228 msi_set_property( package->db, property, patch, -1 );
230 msiobj_release( &rec->hdr );
234 msiobj_release( &view->hdr );
245 struct patch_offset_list
248 UINT count, min, max;
249 UINT offset_to_apply;
252 static struct patch_offset_list *patch_offset_list_create( void )
254 struct patch_offset_list *pos = msi_alloc( sizeof(struct patch_offset_list) );
255 list_init( &pos->files );
256 pos->count = pos->max = 0;
261 static void patch_offset_list_free( struct patch_offset_list *pos )
263 struct patch_offset *po, *po2;
265 LIST_FOR_EACH_ENTRY_SAFE( po, po2, &pos->files, struct patch_offset, entry )
267 msi_free( po->name );
273 static void patch_offset_get_patches( MSIDATABASE *db, UINT last_sequence, struct patch_offset_list *pos )
275 static const WCHAR query[] = {
276 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','P','a','t','c','h',' ',
277 'W','H','E','R','E',' ','S','e','q','u','e','n','c','e',' ','<','=',' ','?',' ',
278 'O','R','D','E','R',' ','B','Y',' ','S','e','q','u','e','n','c','e',0};
283 r = MSI_DatabaseOpenViewW( db, query, &view );
284 if (r != ERROR_SUCCESS)
287 rec = MSI_CreateRecord( 1 );
288 MSI_RecordSetInteger( rec, 1, last_sequence );
290 r = MSI_ViewExecute( view, rec );
291 msiobj_release( &rec->hdr );
292 if (r != ERROR_SUCCESS)
295 while (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
297 UINT sequence = MSI_RecordGetInteger( rec, 2 );
299 /* FIXME: we only use the max/min sequence numbers for now */
300 pos->min = min( pos->min, sequence );
301 pos->max = max( pos->max, sequence );
303 msiobj_release( &rec->hdr );
305 msiobj_release( &view->hdr );
308 static void patch_offset_get_files( MSIDATABASE *db, UINT last_sequence, struct patch_offset_list *pos )
310 static const WCHAR query[] = {
311 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','F','i','l','e',' ',
312 'W','H','E','R','E',' ','S','e','q','u','e','n','c','e',' ','<','=',' ','?',' ',
313 'O','R','D','E','R',' ','B','Y',' ','S','e','q','u','e','n','c','e',0};
318 r = MSI_DatabaseOpenViewW( db, query, &view );
319 if (r != ERROR_SUCCESS)
322 rec = MSI_CreateRecord( 1 );
323 MSI_RecordSetInteger( rec, 1, last_sequence );
325 r = MSI_ViewExecute( view, rec );
326 msiobj_release( &rec->hdr );
327 if (r != ERROR_SUCCESS)
330 while (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
332 UINT attributes = MSI_RecordGetInteger( rec, 7 );
333 if (attributes & msidbFileAttributesPatchAdded)
335 struct patch_offset *po = msi_alloc( sizeof(struct patch_offset) );
337 po->name = msi_dup_record_field( rec, 1 );
338 po->sequence = MSI_RecordGetInteger( rec, 8 );
339 pos->min = min( pos->min, po->sequence );
340 pos->max = max( pos->max, po->sequence );
341 list_add_tail( &pos->files, &po->entry );
344 msiobj_release( &rec->hdr );
346 msiobj_release( &view->hdr );
349 static UINT patch_offset_modify_db( MSIDATABASE *db, struct patch_offset_list *pos )
351 static const WCHAR query[] = {
352 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','F','i','l','e',' ',
353 'W','H','E','R','E',' ','S','e','q','u','e','n','c','e',' ','>','=',' ','?',' ',
354 'A','N','D',' ','S','e','q','u','e','n','c','e',' ','<','=',' ','?',' ',
355 'O','R','D','E','R',' ','B','Y',' ','S','e','q','u','e','n','c','e',0};
356 struct patch_offset *po;
361 r = MSI_DatabaseOpenViewW( db, query, &view );
362 if (r != ERROR_SUCCESS)
363 return ERROR_SUCCESS;
365 rec = MSI_CreateRecord( 2 );
366 MSI_RecordSetInteger( rec, 1, pos->min );
367 MSI_RecordSetInteger( rec, 2, pos->max );
369 r = MSI_ViewExecute( view, rec );
370 msiobj_release( &rec->hdr );
371 if (r != ERROR_SUCCESS)
374 LIST_FOR_EACH_ENTRY( po, &pos->files, struct patch_offset, entry )
377 while ((r_fetch = MSI_ViewFetch( view, &rec )) == ERROR_SUCCESS)
379 const WCHAR *file = MSI_RecordGetString( rec, 1 );
382 if (!strcmpiW( file, po->name ))
385 seq = MSI_RecordGetInteger( rec, 8 );
386 MSI_RecordSetInteger( rec, 8, seq + pos->offset_to_apply );
387 r = MSI_ViewModify( view, MSIMODIFY_UPDATE, rec );
388 if (r != ERROR_SUCCESS)
389 ERR("Failed to update offset for file %s\n", debugstr_w(file));
390 msiobj_release( &rec->hdr );
393 msiobj_release( &rec->hdr );
395 if (r_fetch != ERROR_SUCCESS) break;
399 msiobj_release( &view->hdr );
400 return ERROR_SUCCESS;
403 static const WCHAR patch_media_query[] = {
404 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ','`','M','e','d','i','a','`',' ',
405 'W','H','E','R','E',' ','`','S','o','u','r','c','e','`',' ','I','S',' ','N','O','T',' ','N','U','L','L',' ',
406 'A','N','D',' ','`','C','a','b','i','n','e','t','`',' ','I','S',' ','N','O','T',' ','N','U','L','L',' ',
407 'O','R','D','E','R',' ','B','Y',' ','`','D','i','s','k','I','d','`',0};
420 static UINT add_patch_media( MSIPACKAGE *package, IStorage *patch )
422 static const WCHAR delete_query[] = {
423 'D','E','L','E','T','E',' ','F','R','O','M',' ','`','M','e','d','i','a','`',' ',
424 'W','H','E','R','E',' ','`','D','i','s','k','I','d','`','=','?',0};
425 static const WCHAR insert_query[] = {
426 'I','N','S','E','R','T',' ','I','N','T','O',' ','`','M','e','d','i','a','`',' ',
427 '(','`','D','i','s','k','I','d','`',',','`','L','a','s','t','S','e','q','u','e','n','c','e','`',',',
428 '`','D','i','s','k','P','r','o','m','p','t','`',',','`','C','a','b','i','n','e','t','`',',',
429 '`','V','o','l','u','m','e','L','a','b','e','l','`',',','`','S','o','u','r','c','e','`',')',' ',
430 'V','A','L','U','E','S',' ','(','?',',','?',',','?',',','?',',','?',',','?',')',0};
434 struct list media_list;
435 struct patch_media *media, *next;
437 r = MSI_DatabaseOpenViewW( package->db, patch_media_query, &view );
438 if (r != ERROR_SUCCESS) return r;
440 r = MSI_ViewExecute( view, 0 );
441 if (r != ERROR_SUCCESS)
443 msiobj_release( &view->hdr );
444 TRACE("query failed %u\n", r);
447 list_init( &media_list );
448 while (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
450 disk_id = MSI_RecordGetInteger( rec, 1 );
451 TRACE("disk_id %u\n", disk_id);
452 if (disk_id >= MSI_INITIAL_MEDIA_TRANSFORM_DISKID)
454 msiobj_release( &rec->hdr );
457 if (!(media = msi_alloc( sizeof( *media )))) goto done;
458 media->disk_id = disk_id;
459 media->last_sequence = MSI_RecordGetInteger( rec, 2 );
460 media->prompt = msi_dup_record_field( rec, 3 );
461 media->cabinet = msi_dup_record_field( rec, 4 );
462 media->volume = msi_dup_record_field( rec, 5 );
463 media->source = msi_dup_record_field( rec, 6 );
465 list_add_tail( &media_list, &media->entry );
466 msiobj_release( &rec->hdr );
468 LIST_FOR_EACH_ENTRY( media, &media_list, struct patch_media, entry )
470 MSIQUERY *delete_view, *insert_view;
472 r = MSI_DatabaseOpenViewW( package->db, delete_query, &delete_view );
473 if (r != ERROR_SUCCESS) goto done;
475 rec = MSI_CreateRecord( 1 );
476 MSI_RecordSetInteger( rec, 1, media->disk_id );
478 r = MSI_ViewExecute( delete_view, rec );
479 msiobj_release( &delete_view->hdr );
480 msiobj_release( &rec->hdr );
481 if (r != ERROR_SUCCESS) goto done;
483 r = MSI_DatabaseOpenViewW( package->db, insert_query, &insert_view );
484 if (r != ERROR_SUCCESS) goto done;
486 disk_id = package->db->media_transform_disk_id;
487 TRACE("disk id %u\n", disk_id);
488 TRACE("last sequence %u\n", media->last_sequence);
489 TRACE("prompt %s\n", debugstr_w(media->prompt));
490 TRACE("cabinet %s\n", debugstr_w(media->cabinet));
491 TRACE("volume %s\n", debugstr_w(media->volume));
492 TRACE("source %s\n", debugstr_w(media->source));
494 rec = MSI_CreateRecord( 6 );
495 MSI_RecordSetInteger( rec, 1, disk_id );
496 MSI_RecordSetInteger( rec, 2, media->last_sequence );
497 MSI_RecordSetStringW( rec, 3, media->prompt );
498 MSI_RecordSetStringW( rec, 4, media->cabinet );
499 MSI_RecordSetStringW( rec, 5, media->volume );
500 MSI_RecordSetStringW( rec, 6, media->source );
502 r = MSI_ViewExecute( insert_view, rec );
503 msiobj_release( &insert_view->hdr );
504 msiobj_release( &rec->hdr );
505 if (r != ERROR_SUCCESS) goto done;
507 r = msi_add_cabinet_stream( package, disk_id, patch, media->cabinet );
508 if (r != ERROR_SUCCESS) WARN("failed to add cabinet stream %u\n", r);
509 package->db->media_transform_disk_id++;
513 msiobj_release( &view->hdr );
514 LIST_FOR_EACH_ENTRY_SAFE( media, next, &media_list, struct patch_media, entry )
516 list_remove( &media->entry );
517 msi_free( media->prompt );
518 msi_free( media->cabinet );
519 msi_free( media->volume );
520 msi_free( media->source );
526 static UINT set_patch_offsets( MSIDATABASE *db )
532 r = MSI_DatabaseOpenViewW( db, patch_media_query, &view );
533 if (r != ERROR_SUCCESS)
536 r = MSI_ViewExecute( view, 0 );
537 if (r != ERROR_SUCCESS)
540 while (MSI_ViewFetch( view, &rec ) == ERROR_SUCCESS)
542 UINT last_sequence = MSI_RecordGetInteger( rec, 2 );
543 struct patch_offset_list *pos;
545 /* FIXME: set/check Source field instead? */
546 if (last_sequence >= MSI_INITIAL_MEDIA_TRANSFORM_OFFSET)
548 msiobj_release( &rec->hdr );
551 pos = patch_offset_list_create();
552 patch_offset_get_files( db, last_sequence, pos );
553 patch_offset_get_patches( db, last_sequence, pos );
556 UINT offset = db->media_transform_offset - pos->min;
557 last_sequence = offset + pos->max;
559 /* FIXME: this is for the patch table, which is not yet properly transformed */
560 last_sequence += pos->min;
561 pos->offset_to_apply = offset;
562 patch_offset_modify_db( db, pos );
564 MSI_RecordSetInteger( rec, 2, last_sequence );
565 r = MSI_ViewModify( view, MSIMODIFY_UPDATE, rec );
566 if (r != ERROR_SUCCESS)
567 ERR("Failed to update Media table entry, expect breakage (%u)\n", r);
568 db->media_transform_offset = last_sequence + 1;
570 patch_offset_list_free( pos );
571 msiobj_release( &rec->hdr );
575 msiobj_release( &view->hdr );
579 static UINT msi_apply_patch_db( MSIPACKAGE *package, MSIDATABASE *patch_db, MSIPATCHINFO *patch )
581 UINT i, r = ERROR_SUCCESS;
584 /* apply substorage transforms */
585 substorage = msi_split_string( patch->transforms, ';' );
586 for (i = 0; substorage && substorage[i] && r == ERROR_SUCCESS; i++)
588 r = apply_substorage_transform( package, patch_db, substorage[i] );
589 if (r == ERROR_SUCCESS)
591 add_patch_media( package, patch_db->storage );
592 set_patch_offsets( package->db );
595 msi_free( substorage );
596 if (r != ERROR_SUCCESS)
599 patch_set_media_source_prop( package );
601 patch->state = MSIPATCHSTATE_APPLIED;
602 list_add_tail( &package->patches, &patch->entry );
603 return ERROR_SUCCESS;
606 void msi_free_patchinfo( MSIPATCHINFO *patch )
608 msi_free( patch->patchcode );
609 msi_free( patch->products );
610 msi_free( patch->transforms );
611 msi_free( patch->filename );
612 msi_free( patch->localfile );
616 static UINT msi_apply_patch_package( MSIPACKAGE *package, const WCHAR *file )
618 static const WCHAR dotmsp[] = {'.','m','s','p',0};
619 MSIDATABASE *patch_db = NULL;
620 WCHAR localfile[MAX_PATH];
622 MSIPATCHINFO *patch = NULL;
623 UINT r = ERROR_SUCCESS;
625 TRACE("%p %s\n", package, debugstr_w(file));
627 r = MSI_OpenDatabaseW( file, MSIDBOPEN_READONLY + MSIDBOPEN_PATCHFILE, &patch_db );
628 if (r != ERROR_SUCCESS)
630 ERR("failed to open patch collection %s\n", debugstr_w( file ) );
633 if (!(si = MSI_GetSummaryInformationW( patch_db->storage, 0 )))
635 msiobj_release( &patch_db->hdr );
636 return ERROR_FUNCTION_FAILED;
638 r = msi_check_patch_applicable( package, si );
639 if (r != ERROR_SUCCESS)
641 TRACE("patch not applicable\n");
645 r = msi_parse_patch_summary( si, &patch );
646 if ( r != ERROR_SUCCESS )
649 r = msi_create_empty_local_file( localfile, dotmsp );
650 if ( r != ERROR_SUCCESS )
653 r = ERROR_OUTOFMEMORY;
654 if (!(patch->filename = strdupW( file ))) goto done;
655 if (!(patch->localfile = strdupW( localfile ))) goto done;
657 r = msi_apply_patch_db( package, patch_db, patch );
658 if (r != ERROR_SUCCESS) WARN("patch failed to apply %u\n", r);
661 msiobj_release( &si->hdr );
662 msiobj_release( &patch_db->hdr );
663 if (patch && r != ERROR_SUCCESS)
665 DeleteFileW( patch->localfile );
666 msi_free_patchinfo( patch );
671 /* get the PATCH property, and apply all the patches it specifies */
672 UINT msi_apply_patches( MSIPACKAGE *package )
674 LPWSTR patch_list, *patches;
675 UINT i, r = ERROR_SUCCESS;
677 patch_list = msi_dup_property( package->db, szPatch );
679 TRACE("patches to be applied: %s\n", debugstr_w(patch_list));
681 patches = msi_split_string( patch_list, ';' );
682 for (i = 0; patches && patches[i] && r == ERROR_SUCCESS; i++)
683 r = msi_apply_patch_package( package, patches[i] );
686 msi_free( patch_list );
690 UINT msi_apply_transforms( MSIPACKAGE *package )
692 static const WCHAR szTransforms[] = {'T','R','A','N','S','F','O','R','M','S',0};
693 LPWSTR xform_list, *xforms;
694 UINT i, r = ERROR_SUCCESS;
696 xform_list = msi_dup_property( package->db, szTransforms );
697 xforms = msi_split_string( xform_list, ';' );
699 for (i = 0; xforms && xforms[i] && r == ERROR_SUCCESS; i++)
701 if (xforms[i][0] == ':')
702 r = apply_substorage_transform( package, package->db, xforms[i] );
707 if (!PathIsRelativeW( xforms[i] )) transform = xforms[i];
710 WCHAR *p = strrchrW( package->PackagePath, '\\' );
711 DWORD len = p - package->PackagePath + 1;
713 if (!(transform = msi_alloc( (len + strlenW( xforms[i] ) + 1) * sizeof(WCHAR)) ))
716 msi_free( xform_list );
717 return ERROR_OUTOFMEMORY;
719 memcpy( transform, package->PackagePath, len * sizeof(WCHAR) );
720 memcpy( transform + len, xforms[i], (strlenW( xforms[i] ) + 1) * sizeof(WCHAR) );
722 r = MSI_DatabaseApplyTransformW( package->db, transform, 0 );
723 if (transform != xforms[i]) msi_free( transform );
727 msi_free( xform_list );
731 UINT msi_apply_registered_patch( MSIPACKAGE *package, LPCWSTR patch_code )
735 WCHAR patch_file[MAX_PATH];
736 MSIDATABASE *patch_db;
737 MSIPATCHINFO *patch_info;
740 len = sizeof(patch_file) / sizeof(WCHAR);
741 r = MsiGetPatchInfoExW( patch_code, package->ProductCode, NULL, package->Context,
742 INSTALLPROPERTY_LOCALPACKAGEW, patch_file, &len );
743 if (r != ERROR_SUCCESS)
745 ERR("failed to get patch filename %u\n", r);
748 r = MSI_OpenDatabaseW( patch_file, MSIDBOPEN_READONLY + MSIDBOPEN_PATCHFILE, &patch_db );
749 if (r != ERROR_SUCCESS)
751 ERR("failed to open patch database %s\n", debugstr_w( patch_file ));
754 si = MSI_GetSummaryInformationW( patch_db->storage, 0 );
757 msiobj_release( &patch_db->hdr );
758 return ERROR_FUNCTION_FAILED;
760 r = msi_parse_patch_summary( si, &patch_info );
761 msiobj_release( &si->hdr );
762 if (r != ERROR_SUCCESS)
764 ERR("failed to parse patch summary %u\n", r);
765 msiobj_release( &patch_db->hdr );
768 patch_info->localfile = strdupW( patch_file );
769 if (!patch_info->localfile)
771 msiobj_release( &patch_db->hdr );
772 msi_free_patchinfo( patch_info );
773 return ERROR_OUTOFMEMORY;
775 r = msi_apply_patch_db( package, patch_db, patch_info );
776 msiobj_release( &patch_db->hdr );
777 if (r != ERROR_SUCCESS)
779 ERR("failed to apply patch %u\n", r);
780 msi_free_patchinfo( patch_info );