wined3d: Add device resources from resource_init().
[wine] / dlls / msi / helpers.c
index a2bfb0d..5cfb4af 100644 (file)
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
 /*
- * Here are helper functions formally in action.c that are used by a variaty of
+ * Here are helper functions formally in action.c that are used by a variety of
  * actions and functions.
  */
 
 #include <stdarg.h>
 
 #include "windef.h"
-#include "winbase.h"
-#include "winerror.h"
 #include "wine/debug.h"
 #include "msipriv.h"
 #include "winuser.h"
 #include "wine/unicode.h"
-#include "action.h"
+#include "msidefs.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(msi);
 
@@ -40,49 +38,10 @@ static const WCHAR cszTargetDir[] = {'T','A','R','G','E','T','D','I','R',0};
 static const WCHAR cszDatabase[]={'D','A','T','A','B','A','S','E',0};
 
 const WCHAR cszSourceDir[] = {'S','o','u','r','c','e','D','i','r',0};
+const WCHAR cszSOURCEDIR[] = {'S','O','U','R','C','E','D','I','R',0};
 const WCHAR cszRootDrive[] = {'R','O','O','T','D','R','I','V','E',0};
 const WCHAR cszbs[]={'\\',0};
-
-DWORD build_version_dword(LPCWSTR version_string)
-{
-    SHORT major,minor;
-    WORD build;
-    DWORD rc = 0x00000000;
-    LPCWSTR ptr1;
-
-    ptr1 = version_string;
-
-    if (!ptr1)
-        return rc;
-    else
-        major = atoiW(ptr1);
-
-
-    if(ptr1)
-        ptr1 = strchrW(ptr1,'.');
-    if (ptr1)
-    {
-        ptr1++;
-        minor = atoiW(ptr1);
-    }
-    else
-        minor = 0;
-
-    if (ptr1)
-        ptr1 = strchrW(ptr1,'.');
-
-    if (ptr1)
-    {
-        ptr1++;
-        build = atoiW(ptr1);
-    }
-    else
-        build = 0;
-
-    rc = MAKELONG(build,MAKEWORD(minor,major));
-    TRACE("%s -> 0x%lx\n",debugstr_w(version_string),rc);
-    return rc;
-}
+const WCHAR szLocalSid[] = {'S','-','1','-','5','-','1','8',0};
 
 LPWSTR build_icon_path(MSIPACKAGE *package, LPCWSTR icon_name )
 {
@@ -107,28 +66,30 @@ LPWSTR build_icon_path(MSIPACKAGE *package, LPCWSTR icon_name )
     return FilePath;
 }
 
-LPWSTR msi_dup_record_field( MSIRECORD *row, INT index )
-{
-    return strdupW( MSI_RecordGetString(row,index) );
-}
-
-LPWSTR msi_dup_property(MSIPACKAGE *package, LPCWSTR prop)
+LPWSTR msi_dup_record_field( MSIRECORD *rec, INT field )
 {
     DWORD sz = 0;
     LPWSTR str;
     UINT r;
 
-    r = MSI_GetPropertyW(package, prop, NULL, &sz);
-    if (r != ERROR_SUCCESS && r != ERROR_MORE_DATA)
+    if (MSI_RecordIsNull( rec, field ))
+        return NULL;
+
+    r = MSI_RecordGetStringW( rec, field, NULL, &sz );
+    if (r != ERROR_SUCCESS)
         return NULL;
 
-    sz++;
-    str = msi_alloc(sz*sizeof(WCHAR));
-    r = MSI_GetPropertyW(package, prop, str, &sz);
+    sz ++;
+    str = msi_alloc( sz * sizeof (WCHAR) );
+    if (!str)
+        return str;
+    str[0] = 0;
+    r = MSI_RecordGetStringW( rec, field, str, &sz );
     if (r != ERROR_SUCCESS)
     {
-        msi_free(str);
-        str = NULL;
+        ERR("failed to get string!\n");
+        msi_free( str );
+        return NULL;
     }
     return str;
 }
@@ -169,39 +130,30 @@ MSIFILE* get_loaded_file( MSIPACKAGE* package, LPCWSTR key )
     return NULL;
 }
 
-int track_tempfile( MSIPACKAGE *package, LPCWSTR name, LPCWSTR path )
+int track_tempfile( MSIPACKAGE *package, LPCWSTR path )
 {
     MSITEMPFILE *temp;
 
+    TRACE("%s\n", debugstr_w(path));
+
     LIST_FOR_EACH_ENTRY( temp, &package->tempfiles, MSITEMPFILE, entry )
-    {
-        if (lstrcmpW( name, temp->File )==0)
-        {
-            TRACE("tempfile %s already exists with path %s\n",
-                debugstr_w(temp->File), debugstr_w(temp->Path));
-            return -1;
-        }
-    }
+        if (!lstrcmpW( path, temp->Path ))
+            return 0;
 
     temp = msi_alloc_zero( sizeof (MSITEMPFILE) );
     if (!temp)
         return -1;
 
     list_add_head( &package->tempfiles, &temp->entry );
-
-    temp->File = strdupW( name );
     temp->Path = strdupW( path );
 
-    TRACE("adding tempfile %s with path %s\n",
-           debugstr_w(temp->File), debugstr_w(temp->Path));
-
     return 0;
 }
 
 MSIFOLDER *get_loaded_folder( MSIPACKAGE *package, LPCWSTR dir )
 {
     MSIFOLDER *folder;
-    
+
     LIST_FOR_EACH_ENTRY( folder, &package->folders, MSIFOLDER, entry )
     {
         if (lstrcmpW( dir, folder->Directory )==0)
@@ -210,97 +162,225 @@ MSIFOLDER *get_loaded_folder( MSIPACKAGE *package, LPCWSTR dir )
     return NULL;
 }
 
-LPWSTR resolve_folder(MSIPACKAGE *package, LPCWSTR name, BOOL source, 
-                      BOOL set_prop, MSIFOLDER **folder)
+void msi_reset_folders( MSIPACKAGE *package, BOOL source )
 {
-    MSIFOLDER *f;
-    LPWSTR p, path = NULL;
-
-    TRACE("Working to resolve %s\n",debugstr_w(name));
-
-    if (!name)
-        return NULL;
+    MSIFOLDER *folder;
 
-    /* source directories appear to always be at the root */
-    if (source)
+    LIST_FOR_EACH_ENTRY( folder, &package->folders, MSIFOLDER, entry )
     {
-        path = msi_dup_property( package, cszSourceDir );
-        if (!path)
+        if ( source )
         {
-            path = msi_dup_property( package, cszDatabase );
-            if (path)
-            {
-                p = strrchrW(path,'\\');
-                if (p)
-                    *(p+1) = 0;
-            }
+            msi_free( folder->ResolvedSource );
+            folder->ResolvedSource = NULL;
+        }
+        else
+        {
+            msi_free( folder->ResolvedTarget );
+            folder->ResolvedTarget = NULL;
         }
-        if (folder)
-            *folder = get_loaded_folder( package, name );
+    }
+}
+
+static LPWSTR get_source_root( MSIPACKAGE *package )
+{
+    LPWSTR path, p;
+
+    path = msi_dup_property( package, cszSourceDir );
+    if (path)
         return path;
+
+    path = msi_dup_property( package, cszDatabase );
+    if (path)
+    {
+        p = strrchrW(path,'\\');
+        if (p)
+            *(p+1) = 0;
     }
+    return path;
+}
 
-    /* special resolving for Target and Source root dir */
-    if (strcmpW(name,cszTargetDir)==0 || strcmpW(name,cszSourceDir)==0)
+/*
+ * clean_spaces_from_path()
+ *
+ * removes spaces from the beginning and end of path segments
+ * removes multiple \\ characters
+ */
+static void clean_spaces_from_path( LPWSTR p )
+{
+    LPWSTR q = p;
+    int n, len = 0;
+
+    while (1)
     {
-        LPWSTR check_path;
-        check_path = msi_dup_property( package, cszTargetDir );
-        if (!check_path)
+        /* copy until the end of the string or a space */
+        while (*p != ' ' && (*q = *p))
         {
-            check_path = msi_dup_property( package, cszRootDrive );
-            if (set_prop)
-                MSI_SetPropertyW(package,cszTargetDir,check_path);
+            p++, len++;
+            /* reduce many backslashes to one */
+            if (*p != '\\' || *q != '\\')
+                q++;
         }
 
-        /* correct misbuilt target dir */
-        path = build_directory_name(2, check_path, NULL);
-        if (strcmpiW(path,check_path)!=0)
-            MSI_SetPropertyW(package,cszTargetDir,path);
-        msi_free(check_path);
-        if (folder)
-            *folder = get_loaded_folder( package, name );
-        return path;
+        /* quit at the end of the string */
+        if (!*p)
+            break;
+
+        /* count the number of spaces */
+        n = 0;
+        while (p[n] == ' ')
+            n++;
+
+        /* if it's leading or trailing space, skip it */
+        if ( len == 0 || p[-1] == '\\' || p[n] == '\\' )
+            p += n;
+        else  /* copy n spaces */
+            while (n && (*q++ = *p++)) n--;
     }
+}
+
+LPWSTR resolve_file_source(MSIPACKAGE *package, MSIFILE *file)
+{
+    LPWSTR p, path;
+
+    TRACE("Working to resolve source of file %s\n", debugstr_w(file->File));
+
+    if (file->IsCompressed)
+        return NULL;
+
+    p = resolve_folder(package, file->Component->Directory,
+                       TRUE, FALSE, TRUE, NULL);
+    path = build_directory_name(2, p, file->ShortName);
+
+    if (file->LongName &&
+        GetFileAttributesW(path) == INVALID_FILE_ATTRIBUTES)
+    {
+        msi_free(path);
+        path = build_directory_name(2, p, file->LongName);
+    }
+
+    msi_free(p);
+
+    TRACE("file %s source resolves to %s\n", debugstr_w(file->File),
+          debugstr_w(path));
+
+    return path;
+}
+
+LPWSTR resolve_folder(MSIPACKAGE *package, LPCWSTR name, BOOL source, 
+                      BOOL set_prop, BOOL load_prop, MSIFOLDER **folder)
+{
+    MSIFOLDER *f;
+    LPWSTR p, path = NULL, parent;
+
+    TRACE("Working to resolve %s\n",debugstr_w(name));
+
+    if (!name)
+        return NULL;
+
+    if (!lstrcmpW(name,cszSourceDir))
+        name = cszTargetDir;
 
     f = get_loaded_folder( package, name );
     if (!f)
         return NULL;
 
+    /* special resolving for Target and Source root dir */
+    if (!strcmpW(name,cszTargetDir))
+    {
+        if (!f->ResolvedTarget && !f->Property)
+        {
+            LPWSTR check_path;
+            check_path = msi_dup_property( package, cszTargetDir );
+            if (!check_path)
+            {
+                check_path = msi_dup_property( package, cszRootDrive );
+                if (set_prop)
+                    MSI_SetPropertyW(package,cszTargetDir,check_path);
+            }
+
+            /* correct misbuilt target dir */
+            path = build_directory_name(2, check_path, NULL);
+            clean_spaces_from_path( path );
+            if (strcmpiW(path,check_path)!=0)
+                MSI_SetPropertyW(package,cszTargetDir,path);
+            msi_free(check_path);
+
+            f->ResolvedTarget = path;
+        }
+
+        if (!f->ResolvedSource)
+            f->ResolvedSource = get_source_root( package );
+    }
+
     if (folder)
         *folder = f;
 
-    if (f->ResolvedTarget)
+    if (!source && f->ResolvedTarget)
     {
         path = strdupW( f->ResolvedTarget );
         TRACE("   already resolved to %s\n",debugstr_w(path));
         return path;
     }
-    else if (f->Property)
+
+    if (source && f->ResolvedSource)
+    {
+        path = strdupW( f->ResolvedSource );
+        TRACE("   (source)already resolved to %s\n",debugstr_w(path));
+        return path;
+    }
+
+    if (!source && f->Property)
     {
         path = build_directory_name( 2, f->Property, NULL );
-                    
+
         TRACE("   internally set to %s\n",debugstr_w(path));
         if (set_prop)
             MSI_SetPropertyW( package, name, path );
         return path;
     }
 
-    if (f->Parent)
+    if (!source && load_prop && (path = msi_dup_property( package, name )))
     {
-        LPWSTR parent = f->Parent->Directory;
+        f->ResolvedTarget = strdupW( path );
+        TRACE("   property set to %s\n", debugstr_w(path));
+        return path;
+    }
+
+    if (!f->Parent)
+        return path;
 
-        TRACE(" ! Parent is %s\n", debugstr_w(parent));
+    parent = f->Parent;
 
-        p = resolve_folder(package, parent, source, set_prop, NULL);
+    TRACE(" ! Parent is %s\n", debugstr_w(parent));
+
+    p = resolve_folder(package, parent, source, set_prop, load_prop, NULL);
+    if (!source)
+    {
         TRACE("   TargetDefault = %s\n", debugstr_w(f->TargetDefault));
 
         path = build_directory_name( 3, p, f->TargetDefault, NULL );
+        clean_spaces_from_path( path );
         f->ResolvedTarget = strdupW( path );
-        TRACE("   resolved into %s\n",debugstr_w(path));
+        TRACE("target -> %s\n", debugstr_w(path));
         if (set_prop)
             MSI_SetPropertyW(package,name,path);
-        msi_free(p);
     }
+    else
+    {
+        path = NULL;
+
+        if (package->WordCount & msidbSumInfoSourceTypeCompressed)
+            path = get_source_root( package );
+        else if (package->WordCount & msidbSumInfoSourceTypeSFN)
+            path = build_directory_name( 3, p, f->SourceShortPath, NULL );
+        else
+            path = build_directory_name( 3, p, f->SourceLongPath, NULL );
+
+        TRACE("source -> %s\n", debugstr_w(path));
+        f->ResolvedSource = strdupW( path );
+    }
+    msi_free(p);
+
     return path;
 }
 
@@ -314,18 +394,16 @@ DWORD deformat_string(MSIPACKAGE *package, LPCWSTR ptr, WCHAR** data )
 
         MSI_RecordSetStringW(rec,0,ptr);
         MSI_FormatRecordW(package,rec,NULL,&size);
-        if (size >= 0)
-        {
-            size++;
-            *data = msi_alloc(size*sizeof(WCHAR));
-            if (size > 1)
-                MSI_FormatRecordW(package,rec,*data,&size);
-            else
-                *data[0] = 0;
-            msiobj_release( &rec->hdr );
-            return sizeof(WCHAR)*size;
-        }
+
+        size++;
+        *data = msi_alloc(size*sizeof(WCHAR));
+        if (size > 1)
+            MSI_FormatRecordW(package,rec,*data,&size);
+        else
+            *data[0] = 0;
+
         msiobj_release( &rec->hdr );
+        return sizeof(WCHAR)*size;
     }
 
     *data = NULL;
@@ -357,6 +435,17 @@ UINT schedule_action(MSIPACKAGE *package, UINT script, LPCWSTR action)
    return ERROR_SUCCESS;
 }
 
+void msi_free_action_script(MSIPACKAGE *package, UINT script)
+{
+    UINT i;
+    for (i = 0; i < package->script->ActionCount[script]; i++)
+        msi_free(package->script->Actions[script][i]);
+
+    msi_free(package->script->Actions[script]);
+    package->script->Actions[script] = NULL;
+    package->script->ActionCount[script] = 0;
+}
+
 static void remove_tracked_tempfiles(MSIPACKAGE* package)
 {
     struct list *item, *cursor;
@@ -367,8 +456,8 @@ static void remove_tracked_tempfiles(MSIPACKAGE* package)
 
         list_remove( &temp->entry );
         TRACE("deleting temp file %s\n", debugstr_w( temp->Path ));
-        DeleteFileW( temp->Path );
-        msi_free( temp->File );
+        if (!DeleteFileW( temp->Path ))
+            ERR("failed to delete %s\n", debugstr_w( temp->Path ));
         msi_free( temp->Path );
         msi_free( temp );
     }
@@ -378,6 +467,13 @@ static void free_feature( MSIFEATURE *feature )
 {
     struct list *item, *cursor;
 
+    LIST_FOR_EACH_SAFE( item, cursor, &feature->Children )
+    {
+        FeatureList *fl = LIST_ENTRY( item, FeatureList, entry );
+        list_remove( &fl->entry );
+        msi_free( fl );
+    }
+
     LIST_FOR_EACH_SAFE( item, cursor, &feature->Components )
     {
         ComponentList *cl = LIST_ENTRY( item, ComponentList, entry );
@@ -392,7 +488,7 @@ static void free_feature( MSIFEATURE *feature )
     msi_free( feature );
 }
 
-void free_extension( MSIEXTENSION *ext )
+static void free_extension( MSIEXTENSION *ext )
 {
     struct list *item, *cursor;
 
@@ -417,7 +513,7 @@ void ACTION_free_package_structures( MSIPACKAGE* package)
 {
     INT i;
     struct list *item, *cursor;
-    
+
     TRACE("Freeing package action data\n");
 
     remove_tracked_tempfiles(package);
@@ -434,9 +530,11 @@ void ACTION_free_package_structures( MSIPACKAGE* package)
         MSIFOLDER *folder = LIST_ENTRY( item, MSIFOLDER, entry );
 
         list_remove( &folder->entry );
+        msi_free( folder->Parent );
         msi_free( folder->Directory );
         msi_free( folder->TargetDefault );
-        msi_free( folder->SourceDefault );
+        msi_free( folder->SourceLongPath );
+        msi_free( folder->SourceShortPath );
         msi_free( folder->ResolvedTarget );
         msi_free( folder->ResolvedSource );
         msi_free( folder->Property );
@@ -446,7 +544,7 @@ void ACTION_free_package_structures( MSIPACKAGE* package)
     LIST_FOR_EACH_SAFE( item, cursor, &package->components )
     {
         MSICOMPONENT *comp = LIST_ENTRY( item, MSICOMPONENT, entry );
-        
+
         list_remove( &comp->entry );
         msi_free( comp->Component );
         msi_free( comp->ComponentId );
@@ -465,9 +563,9 @@ void ACTION_free_package_structures( MSIPACKAGE* package)
         msi_free( file->File );
         msi_free( file->FileName );
         msi_free( file->ShortName );
+        msi_free( file->LongName );
         msi_free( file->Version );
         msi_free( file->Language );
-        msi_free( file->SourcePath );
         msi_free( file->TargetPath );
         msi_free( file );
     }
@@ -532,16 +630,29 @@ void ACTION_free_package_structures( MSIPACKAGE* package)
         msi_free( appid );
     }
 
+    LIST_FOR_EACH_SAFE( item, cursor, &package->sourcelist_info )
+    {
+        MSISOURCELISTINFO *info = LIST_ENTRY( item, MSISOURCELISTINFO, entry );
+
+        list_remove( &info->entry );
+        msi_free( info->value );
+       msi_free( info );
+    }
+
+    LIST_FOR_EACH_SAFE( item, cursor, &package->sourcelist_media )
+    {
+        MSIMEDIADISK *info = LIST_ENTRY( item, MSIMEDIADISK, entry );
+
+        list_remove( &info->entry );
+        msi_free( info->volume_label );
+        msi_free( info->disk_prompt );
+       msi_free( info );
+    }
+
     if (package->script)
     {
         for (i = 0; i < TOTAL_SCRIPTS; i++)
-        {
-            int j;
-            for (j = 0; j < package->script->ActionCount[i]; j++)
-                msi_free(package->script->Actions[i][j]);
-        
-            msi_free(package->script->Actions[i]);
-        }
+            msi_free_action_script(package, i);
 
         for (i = 0; i < package->script->UniqueActionsCount; i++)
             msi_free(package->script->UniqueActions[i]);
@@ -550,6 +661,14 @@ void ACTION_free_package_structures( MSIPACKAGE* package)
         msi_free(package->script);
     }
 
+    if (package->patch)
+    {
+        msi_free(package->patch->patchcode);
+        msi_free(package->patch->transforms);
+        msi_free(package->patch);
+    }
+
+    msi_free(package->BaseURL);
     msi_free(package->PackagePath);
     msi_free(package->ProductCode);
     msi_free(package->ActionFormat);
@@ -688,8 +807,6 @@ void ui_actiondata(MSIPACKAGE *package, LPCWSTR action, MSIRECORD * record)
     WCHAR message[1024];
     MSIRECORD * row = 0;
     DWORD size;
-    static const WCHAR szActionData[] = 
-        {'A','c','t','i','o','n','D','a','t','a',0};
 
     if (!package->LastAction || strcmpW(package->LastAction,action))
     {
@@ -722,33 +839,25 @@ void ui_actiondata(MSIPACKAGE *package, LPCWSTR action, MSIRECORD * record)
  
     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, row);
 
-    ControlEvent_FireSubscribedEvent(package,szActionData, row);
-
     msiobj_release(&row->hdr);
 }
 
-BOOL ACTION_VerifyComponentForAction( MSICOMPONENT* comp, INSTALLSTATE check )
+BOOL ACTION_VerifyComponentForAction( const MSICOMPONENT* comp, INSTALLSTATE check )
 {
     if (!comp)
         return FALSE;
 
-    if (comp->Installed == check)
-        return FALSE;
-
     if (comp->ActionRequest == check)
         return TRUE;
     else
         return FALSE;
 }
 
-BOOL ACTION_VerifyFeatureForAction( MSIFEATURE* feature, INSTALLSTATE check )
+BOOL ACTION_VerifyFeatureForAction( const MSIFEATURE* feature, INSTALLSTATE check )
 {
     if (!feature)
         return FALSE;
 
-    if (feature->Installed == check)
-        return FALSE;
-
     if (feature->ActionRequest == check)
         return TRUE;
     else
@@ -762,64 +871,43 @@ void reduce_to_longfilename(WCHAR* filename)
         memmove(filename, p+1, (strlenW(p+1)+1)*sizeof(WCHAR));
 }
 
-void reduce_to_shortfilename(WCHAR* filename)
-{
-    LPWSTR p = strchrW(filename,'|');
-    if (p)
-        *p = 0;
-}
-
 LPWSTR create_component_advertise_string(MSIPACKAGE* package, 
                 MSICOMPONENT* component, LPCWSTR feature)
 {
-    GUID clsid;
-    WCHAR productid_85[21];
-    WCHAR component_85[21];
-    /*
-     * I have a fair bit of confusion as to when a < is used and when a > is
-     * used. I do not think i have it right...
-     *
-     * Ok it appears that the > is used if there is a guid for the compoenent
-     * and the < is used if not.
-     */
-    static WCHAR fmt1[] = {'%','s','%','s','<',0,0};
-    static WCHAR fmt2[] = {'%','s','%','s','>','%','s',0,0};
+    static const WCHAR fmt[] = {'%','s','%','s','%','c','%','s',0};
+    WCHAR productid_85[21], component_85[21];
     LPWSTR output = NULL;
     DWORD sz = 0;
+    GUID clsid;
 
-    memset(productid_85,0,sizeof(productid_85));
-    memset(component_85,0,sizeof(component_85));
+    /* > is used if there is a component GUID and < if not.  */
+
+    productid_85[0] = 0;
+    component_85[0] = 0;
 
     CLSIDFromString(package->ProductCode, &clsid);
-    
-    encode_base85_guid(&clsid,productid_85);
+    encode_base85_guid(&clsid, productid_85);
 
-    CLSIDFromString(component->ComponentId, &clsid);
-    encode_base85_guid(&clsid,component_85);
+    if (component)
+    {
+        CLSIDFromString(component->ComponentId, &clsid);
+        encode_base85_guid(&clsid, component_85);
+    }
 
-    TRACE("Doing something with this... %s %s %s\n", 
-            debugstr_w(productid_85), debugstr_w(feature),
-            debugstr_w(component_85));
+    TRACE("prod=%s feat=%s comp=%s\n", debugstr_w(productid_85),
+          debugstr_w(feature), debugstr_w(component_85));
  
-    sz = lstrlenW(productid_85) + lstrlenW(feature);
-    if (component)
-        sz += lstrlenW(component_85);
+    sz = 20 + lstrlenW(feature) + 20 + 3;
 
-    sz+=3;
-    sz *= sizeof(WCHAR);
-           
-    output = msi_alloc(sz);
-    memset(output,0,sz);
+    output = msi_alloc_zero(sz*sizeof(WCHAR));
 
-    if (component)
-        sprintfW(output,fmt2,productid_85,feature,component_85);
-    else
-        sprintfW(output,fmt1,productid_85,feature);
+    sprintfW(output, fmt, productid_85, feature,
+             component?'>':'<', component_85);
     
     return output;
 }
 
-/* update compoennt state based on a feature change */
+/* update component state based on a feature change */
 void ACTION_UpdateComponentStates(MSIPACKAGE *package, LPCWSTR szFeature)
 {
     INSTALLSTATE newstate;
@@ -832,6 +920,9 @@ void ACTION_UpdateComponentStates(MSIPACKAGE *package, LPCWSTR szFeature)
 
     newstate = feature->ActionRequest;
 
+    if (newstate == INSTALLSTATE_ABSENT)
+        newstate = INSTALLSTATE_UNKNOWN;
+
     LIST_FOR_EACH_ENTRY( cl, &feature->Components, ComponentList, entry )
     {
         MSICOMPONENT* component = cl->component;
@@ -844,35 +935,45 @@ void ACTION_UpdateComponentStates(MSIPACKAGE *package, LPCWSTR szFeature)
             continue;
  
         if (newstate == INSTALLSTATE_LOCAL)
-        {
-            component->ActionRequest = INSTALLSTATE_LOCAL;
-            component->Action = INSTALLSTATE_LOCAL;
-        }
+            msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
         else 
         {
             ComponentList *clist;
             MSIFEATURE *f;
 
-            component->ActionRequest = newstate;
-            component->Action = newstate;
+            component->hasLocalFeature = FALSE;
+
+            msi_component_set_state(package, component, newstate);
 
             /*if any other feature wants is local we need to set it local*/
             LIST_FOR_EACH_ENTRY( f, &package->features, MSIFEATURE, entry )
             {
-                if ( component->ActionRequest != INSTALLSTATE_LOCAL )
-                    break;
+                if ( f->ActionRequest != INSTALLSTATE_LOCAL &&
+                     f->ActionRequest != INSTALLSTATE_SOURCE )
+                {
+                    continue;
+                }
 
                 LIST_FOR_EACH_ENTRY( clist, &f->Components, ComponentList, entry )
                 {
-                    if ( clist->component == component )
+                    if ( clist->component == component &&
+                         (f->ActionRequest == INSTALLSTATE_LOCAL ||
+                          f->ActionRequest == INSTALLSTATE_SOURCE) )
                     {
-                        if (f->ActionRequest == INSTALLSTATE_LOCAL)
+                        TRACE("Saved by %s\n", debugstr_w(f->Feature));
+                        component->hasLocalFeature = TRUE;
+
+                        if (component->Attributes & msidbComponentAttributesOptional)
                         {
-                            TRACE("Saved by %s\n", debugstr_w(f->Feature));
-                            component->ActionRequest = INSTALLSTATE_LOCAL;
-                            component->Action = INSTALLSTATE_LOCAL;
+                            if (f->Attributes & msidbFeatureAttributesFavorSource)
+                                msi_component_set_state(package, component, INSTALLSTATE_SOURCE);
+                            else
+                                msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
                         }
-                        break;
+                        else if (component->Attributes & msidbComponentAttributesSourceOnly)
+                            msi_component_set_state(package, component, INSTALLSTATE_SOURCE);
+                        else
+                            msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
                     }
                 }
             }
@@ -907,9 +1008,9 @@ UINT register_unique_action(MSIPACKAGE *package, LPCWSTR action)
     return ERROR_SUCCESS;
 }
 
-BOOL check_unique_action(MSIPACKAGE *package, LPCWSTR action)
+BOOL check_unique_action(const MSIPACKAGE *package, LPCWSTR action)
 {
-    INT i;
+    UINT i;
 
     if (!package->script)
         return FALSE;
@@ -953,19 +1054,13 @@ WCHAR* generate_error_string(MSIPACKAGE *package, UINT error, DWORD count, ... )
     va_end(va);
 
     MSI_FormatRecordW(package,rec,NULL,&size);
-    if (size >= 0)
-    {
-        size++;
-        data = msi_alloc(size*sizeof(WCHAR));
-        if (size > 1)
-            MSI_FormatRecordW(package,rec,data,&size);
-        else
-            data[0] = 0;
-        msiobj_release( &rec->hdr );
-        return data;
-    }
 
+    size++;
+    data = msi_alloc(size*sizeof(WCHAR));
+    if (size > 1)
+        MSI_FormatRecordW(package,rec,data,&size);
+    else
+        data[0] = 0;
     msiobj_release( &rec->hdr );
-    data = NULL;
     return data;
 }