shell32/tests: Make sure all created files can be removed.
[wine] / dlls / msi / helpers.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Aric Stewart 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 /*
22  * Here are helper functions formally in action.c that are used by a variety of
23  * actions and functions.
24  */
25
26 #include <stdarg.h>
27
28 #include "windef.h"
29 #include "wine/debug.h"
30 #include "msipriv.h"
31 #include "winuser.h"
32 #include "wine/unicode.h"
33 #include "msidefs.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(msi);
36
37 static const WCHAR cszTargetDir[] = {'T','A','R','G','E','T','D','I','R',0};
38 static const WCHAR cszDatabase[]={'D','A','T','A','B','A','S','E',0};
39
40 const WCHAR cszSourceDir[] = {'S','o','u','r','c','e','D','i','r',0};
41 const WCHAR cszSOURCEDIR[] = {'S','O','U','R','C','E','D','I','R',0};
42 const WCHAR cszRootDrive[] = {'R','O','O','T','D','R','I','V','E',0};
43 const WCHAR cszbs[]={'\\',0};
44 const WCHAR szLocalSid[] = {'S','-','1','-','5','-','1','8',0};
45
46 LPWSTR build_icon_path(MSIPACKAGE *package, LPCWSTR icon_name )
47 {
48     LPWSTR SystemFolder, dest, FilePath;
49
50     static const WCHAR szInstaller[] = 
51         {'M','i','c','r','o','s','o','f','t','\\',
52          'I','n','s','t','a','l','l','e','r','\\',0};
53     static const WCHAR szFolder[] =
54         {'A','p','p','D','a','t','a','F','o','l','d','e','r',0};
55
56     SystemFolder = msi_dup_property( package, szFolder );
57
58     dest = build_directory_name(3, SystemFolder, szInstaller, package->ProductCode);
59
60     create_full_pathW(dest);
61
62     FilePath = build_directory_name(2, dest, icon_name);
63
64     msi_free(SystemFolder);
65     msi_free(dest);
66     return FilePath;
67 }
68
69 LPWSTR msi_dup_record_field( MSIRECORD *rec, INT field )
70 {
71     DWORD sz = 0;
72     LPWSTR str;
73     UINT r;
74
75     if (MSI_RecordIsNull( rec, field ))
76         return NULL;
77
78     r = MSI_RecordGetStringW( rec, field, NULL, &sz );
79     if (r != ERROR_SUCCESS)
80         return NULL;
81
82     sz ++;
83     str = msi_alloc( sz * sizeof (WCHAR) );
84     if (!str)
85         return str;
86     str[0] = 0;
87     r = MSI_RecordGetStringW( rec, field, str, &sz );
88     if (r != ERROR_SUCCESS)
89     {
90         ERR("failed to get string!\n");
91         msi_free( str );
92         return NULL;
93     }
94     return str;
95 }
96
97 MSICOMPONENT* get_loaded_component( MSIPACKAGE* package, LPCWSTR Component )
98 {
99     MSICOMPONENT *comp;
100
101     LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
102     {
103         if (lstrcmpW(Component,comp->Component)==0)
104             return comp;
105     }
106     return NULL;
107 }
108
109 MSIFEATURE* get_loaded_feature(MSIPACKAGE* package, LPCWSTR Feature )
110 {
111     MSIFEATURE *feature;
112
113     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
114     {
115         if (lstrcmpW( Feature, feature->Feature )==0)
116             return feature;
117     }
118     return NULL;
119 }
120
121 MSIFILE* get_loaded_file( MSIPACKAGE* package, LPCWSTR key )
122 {
123     MSIFILE *file;
124
125     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
126     {
127         if (lstrcmpW( key, file->File )==0)
128             return file;
129     }
130     return NULL;
131 }
132
133 int track_tempfile( MSIPACKAGE *package, LPCWSTR path )
134 {
135     MSITEMPFILE *temp;
136
137     TRACE("%s\n", debugstr_w(path));
138
139     LIST_FOR_EACH_ENTRY( temp, &package->tempfiles, MSITEMPFILE, entry )
140         if (!lstrcmpW( path, temp->Path ))
141             return 0;
142
143     temp = msi_alloc_zero( sizeof (MSITEMPFILE) );
144     if (!temp)
145         return -1;
146
147     list_add_head( &package->tempfiles, &temp->entry );
148     temp->Path = strdupW( path );
149
150     return 0;
151 }
152
153 MSIFOLDER *get_loaded_folder( MSIPACKAGE *package, LPCWSTR dir )
154 {
155     MSIFOLDER *folder;
156
157     LIST_FOR_EACH_ENTRY( folder, &package->folders, MSIFOLDER, entry )
158     {
159         if (lstrcmpW( dir, folder->Directory )==0)
160             return folder;
161     }
162     return NULL;
163 }
164
165 void msi_reset_folders( MSIPACKAGE *package, BOOL source )
166 {
167     MSIFOLDER *folder;
168
169     LIST_FOR_EACH_ENTRY( folder, &package->folders, MSIFOLDER, entry )
170     {
171         if ( source )
172         {
173             msi_free( folder->ResolvedSource );
174             folder->ResolvedSource = NULL;
175         }
176         else
177         {
178             msi_free( folder->ResolvedTarget );
179             folder->ResolvedTarget = NULL;
180         }
181     }
182 }
183
184 static LPWSTR get_source_root( MSIPACKAGE *package )
185 {
186     LPWSTR path, p;
187
188     path = msi_dup_property( package, cszSourceDir );
189     if (path)
190         return path;
191
192     path = msi_dup_property( package, cszDatabase );
193     if (path)
194     {
195         p = strrchrW(path,'\\');
196         if (p)
197             *(p+1) = 0;
198     }
199     return path;
200 }
201
202 /*
203  * clean_spaces_from_path()
204  *
205  * removes spaces from the beginning and end of path segments
206  * removes multiple \\ characters
207  */
208 static void clean_spaces_from_path( LPWSTR p )
209 {
210     LPWSTR q = p;
211     int n, len = 0;
212
213     while (1)
214     {
215         /* copy until the end of the string or a space */
216         while (*p != ' ' && (*q = *p))
217         {
218             p++, len++;
219             /* reduce many backslashes to one */
220             if (*p != '\\' || *q != '\\')
221                 q++;
222         }
223
224         /* quit at the end of the string */
225         if (!*p)
226             break;
227
228         /* count the number of spaces */
229         n = 0;
230         while (p[n] == ' ')
231             n++;
232
233         /* if it's leading or trailing space, skip it */
234         if ( len == 0 || p[-1] == '\\' || p[n] == '\\' )
235             p += n;
236         else  /* copy n spaces */
237             while (n && (*q++ = *p++)) n--;
238     }
239 }
240
241 LPWSTR resolve_file_source(MSIPACKAGE *package, MSIFILE *file)
242 {
243     LPWSTR p, path;
244
245     TRACE("Working to resolve source of file %s\n", debugstr_w(file->File));
246
247     if (file->IsCompressed)
248         return NULL;
249
250     p = resolve_folder(package, file->Component->Directory,
251                        TRUE, FALSE, TRUE, NULL);
252     path = build_directory_name(2, p, file->ShortName);
253
254     if (file->LongName &&
255         GetFileAttributesW(path) == INVALID_FILE_ATTRIBUTES)
256     {
257         msi_free(path);
258         path = build_directory_name(2, p, file->LongName);
259     }
260
261     msi_free(p);
262
263     TRACE("file %s source resolves to %s\n", debugstr_w(file->File),
264           debugstr_w(path));
265
266     return path;
267 }
268
269 LPWSTR resolve_folder(MSIPACKAGE *package, LPCWSTR name, BOOL source, 
270                       BOOL set_prop, BOOL load_prop, MSIFOLDER **folder)
271 {
272     MSIFOLDER *f;
273     LPWSTR p, path = NULL, parent;
274
275     TRACE("Working to resolve %s\n",debugstr_w(name));
276
277     if (!name)
278         return NULL;
279
280     if (!lstrcmpW(name,cszSourceDir))
281         name = cszTargetDir;
282
283     f = get_loaded_folder( package, name );
284     if (!f)
285         return NULL;
286
287     /* special resolving for Target and Source root dir */
288     if (!strcmpW(name,cszTargetDir))
289     {
290         if (!f->ResolvedTarget && !f->Property)
291         {
292             LPWSTR check_path;
293             check_path = msi_dup_property( package, cszTargetDir );
294             if (!check_path)
295             {
296                 check_path = msi_dup_property( package, cszRootDrive );
297                 if (set_prop)
298                     MSI_SetPropertyW(package,cszTargetDir,check_path);
299             }
300
301             /* correct misbuilt target dir */
302             path = build_directory_name(2, check_path, NULL);
303             clean_spaces_from_path( path );
304             if (strcmpiW(path,check_path)!=0)
305                 MSI_SetPropertyW(package,cszTargetDir,path);
306             msi_free(check_path);
307
308             f->ResolvedTarget = path;
309         }
310
311         if (!f->ResolvedSource)
312             f->ResolvedSource = get_source_root( package );
313     }
314
315     if (folder)
316         *folder = f;
317
318     if (!source && f->ResolvedTarget)
319     {
320         path = strdupW( f->ResolvedTarget );
321         TRACE("   already resolved to %s\n",debugstr_w(path));
322         return path;
323     }
324
325     if (source && f->ResolvedSource)
326     {
327         path = strdupW( f->ResolvedSource );
328         TRACE("   (source)already resolved to %s\n",debugstr_w(path));
329         return path;
330     }
331
332     if (!source && f->Property)
333     {
334         path = build_directory_name( 2, f->Property, NULL );
335
336         TRACE("   internally set to %s\n",debugstr_w(path));
337         if (set_prop)
338             MSI_SetPropertyW( package, name, path );
339         return path;
340     }
341
342     if (!source && load_prop && (path = msi_dup_property( package, name )))
343     {
344         f->ResolvedTarget = strdupW( path );
345         TRACE("   property set to %s\n", debugstr_w(path));
346         return path;
347     }
348
349     if (!f->Parent)
350         return path;
351
352     parent = f->Parent;
353
354     TRACE(" ! Parent is %s\n", debugstr_w(parent));
355
356     p = resolve_folder(package, parent, source, set_prop, load_prop, NULL);
357     if (!source)
358     {
359         TRACE("   TargetDefault = %s\n", debugstr_w(f->TargetDefault));
360
361         path = build_directory_name( 3, p, f->TargetDefault, NULL );
362         clean_spaces_from_path( path );
363         f->ResolvedTarget = strdupW( path );
364         TRACE("target -> %s\n", debugstr_w(path));
365         if (set_prop)
366             MSI_SetPropertyW(package,name,path);
367     }
368     else
369     {
370         path = NULL;
371
372         if (package->WordCount & msidbSumInfoSourceTypeCompressed)
373             path = get_source_root( package );
374         else if (package->WordCount & msidbSumInfoSourceTypeSFN)
375             path = build_directory_name( 3, p, f->SourceShortPath, NULL );
376         else
377             path = build_directory_name( 3, p, f->SourceLongPath, NULL );
378
379         TRACE("source -> %s\n", debugstr_w(path));
380         f->ResolvedSource = strdupW( path );
381     }
382     msi_free(p);
383
384     return path;
385 }
386
387 /* wrapper to resist a need for a full rewrite right now */
388 DWORD deformat_string(MSIPACKAGE *package, LPCWSTR ptr, WCHAR** data )
389 {
390     if (ptr)
391     {
392         MSIRECORD *rec = MSI_CreateRecord(1);
393         DWORD size = 0;
394
395         MSI_RecordSetStringW(rec,0,ptr);
396         MSI_FormatRecordW(package,rec,NULL,&size);
397
398         size++;
399         *data = msi_alloc(size*sizeof(WCHAR));
400         if (size > 1)
401             MSI_FormatRecordW(package,rec,*data,&size);
402         else
403             *data[0] = 0;
404
405         msiobj_release( &rec->hdr );
406         return sizeof(WCHAR)*size;
407     }
408
409     *data = NULL;
410     return 0;
411 }
412
413 UINT schedule_action(MSIPACKAGE *package, UINT script, LPCWSTR action)
414 {
415     UINT count;
416     LPWSTR *newbuf = NULL;
417     if (script >= TOTAL_SCRIPTS)
418     {
419         FIXME("Unknown script requested %i\n",script);
420         return ERROR_FUNCTION_FAILED;
421     }
422     TRACE("Scheduling Action %s in script %i\n",debugstr_w(action), script);
423     
424     count = package->script->ActionCount[script];
425     package->script->ActionCount[script]++;
426     if (count != 0)
427         newbuf = msi_realloc( package->script->Actions[script],
428                         package->script->ActionCount[script]* sizeof(LPWSTR));
429     else
430         newbuf = msi_alloc( sizeof(LPWSTR));
431
432     newbuf[count] = strdupW(action);
433     package->script->Actions[script] = newbuf;
434
435    return ERROR_SUCCESS;
436 }
437
438 void msi_free_action_script(MSIPACKAGE *package, UINT script)
439 {
440     UINT i;
441     for (i = 0; i < package->script->ActionCount[script]; i++)
442         msi_free(package->script->Actions[script][i]);
443
444     msi_free(package->script->Actions[script]);
445     package->script->Actions[script] = NULL;
446     package->script->ActionCount[script] = 0;
447 }
448
449 static void remove_tracked_tempfiles(MSIPACKAGE* package)
450 {
451     struct list *item, *cursor;
452
453     LIST_FOR_EACH_SAFE( item, cursor, &package->tempfiles )
454     {
455         MSITEMPFILE *temp = LIST_ENTRY( item, MSITEMPFILE, entry );
456
457         list_remove( &temp->entry );
458         TRACE("deleting temp file %s\n", debugstr_w( temp->Path ));
459         if (!DeleteFileW( temp->Path ))
460             ERR("failed to delete %s\n", debugstr_w( temp->Path ));
461         msi_free( temp->Path );
462         msi_free( temp );
463     }
464 }
465
466 static void free_feature( MSIFEATURE *feature )
467 {
468     struct list *item, *cursor;
469
470     LIST_FOR_EACH_SAFE( item, cursor, &feature->Children )
471     {
472         FeatureList *fl = LIST_ENTRY( item, FeatureList, entry );
473         list_remove( &fl->entry );
474         msi_free( fl );
475     }
476
477     LIST_FOR_EACH_SAFE( item, cursor, &feature->Components )
478     {
479         ComponentList *cl = LIST_ENTRY( item, ComponentList, entry );
480         list_remove( &cl->entry );
481         msi_free( cl );
482     }
483     msi_free( feature->Feature );
484     msi_free( feature->Feature_Parent );
485     msi_free( feature->Directory );
486     msi_free( feature->Description );
487     msi_free( feature->Title );
488     msi_free( feature );
489 }
490
491 static void free_extension( MSIEXTENSION *ext )
492 {
493     struct list *item, *cursor;
494
495     LIST_FOR_EACH_SAFE( item, cursor, &ext->verbs )
496     {
497         MSIVERB *verb = LIST_ENTRY( item, MSIVERB, entry );
498
499         list_remove( &verb->entry );
500         msi_free( verb->Verb );
501         msi_free( verb->Command );
502         msi_free( verb->Argument );
503         msi_free( verb );
504     }
505
506     msi_free( ext->Extension );
507     msi_free( ext->ProgIDText );
508     msi_free( ext );
509 }
510
511 /* Called when the package is being closed */
512 void ACTION_free_package_structures( MSIPACKAGE* package)
513 {
514     INT i;
515     struct list *item, *cursor;
516
517     TRACE("Freeing package action data\n");
518
519     remove_tracked_tempfiles(package);
520
521     LIST_FOR_EACH_SAFE( item, cursor, &package->features )
522     {
523         MSIFEATURE *feature = LIST_ENTRY( item, MSIFEATURE, entry );
524         list_remove( &feature->entry );
525         free_feature( feature );
526     }
527
528     LIST_FOR_EACH_SAFE( item, cursor, &package->folders )
529     {
530         MSIFOLDER *folder = LIST_ENTRY( item, MSIFOLDER, entry );
531
532         list_remove( &folder->entry );
533         msi_free( folder->Parent );
534         msi_free( folder->Directory );
535         msi_free( folder->TargetDefault );
536         msi_free( folder->SourceLongPath );
537         msi_free( folder->SourceShortPath );
538         msi_free( folder->ResolvedTarget );
539         msi_free( folder->ResolvedSource );
540         msi_free( folder->Property );
541         msi_free( folder );
542     }
543
544     LIST_FOR_EACH_SAFE( item, cursor, &package->components )
545     {
546         MSICOMPONENT *comp = LIST_ENTRY( item, MSICOMPONENT, entry );
547
548         list_remove( &comp->entry );
549         msi_free( comp->Component );
550         msi_free( comp->ComponentId );
551         msi_free( comp->Directory );
552         msi_free( comp->Condition );
553         msi_free( comp->KeyPath );
554         msi_free( comp->FullKeypath );
555         msi_free( comp );
556     }
557
558     LIST_FOR_EACH_SAFE( item, cursor, &package->files )
559     {
560         MSIFILE *file = LIST_ENTRY( item, MSIFILE, entry );
561
562         list_remove( &file->entry );
563         msi_free( file->File );
564         msi_free( file->FileName );
565         msi_free( file->ShortName );
566         msi_free( file->LongName );
567         msi_free( file->Version );
568         msi_free( file->Language );
569         msi_free( file->TargetPath );
570         msi_free( file );
571     }
572
573     /* clean up extension, progid, class and verb structures */
574     LIST_FOR_EACH_SAFE( item, cursor, &package->classes )
575     {
576         MSICLASS *cls = LIST_ENTRY( item, MSICLASS, entry );
577
578         list_remove( &cls->entry );
579         msi_free( cls->clsid );
580         msi_free( cls->Context );
581         msi_free( cls->Description );
582         msi_free( cls->FileTypeMask );
583         msi_free( cls->IconPath );
584         msi_free( cls->DefInprocHandler );
585         msi_free( cls->DefInprocHandler32 );
586         msi_free( cls->Argument );
587         msi_free( cls->ProgIDText );
588         msi_free( cls );
589     }
590
591     LIST_FOR_EACH_SAFE( item, cursor, &package->extensions )
592     {
593         MSIEXTENSION *ext = LIST_ENTRY( item, MSIEXTENSION, entry );
594
595         list_remove( &ext->entry );
596         free_extension( ext );
597     }
598
599     LIST_FOR_EACH_SAFE( item, cursor, &package->progids )
600     {
601         MSIPROGID *progid = LIST_ENTRY( item, MSIPROGID, entry );
602
603         list_remove( &progid->entry );
604         msi_free( progid->ProgID );
605         msi_free( progid->Description );
606         msi_free( progid->IconPath );
607         msi_free( progid );
608     }
609
610     LIST_FOR_EACH_SAFE( item, cursor, &package->mimes )
611     {
612         MSIMIME *mt = LIST_ENTRY( item, MSIMIME, entry );
613
614         list_remove( &mt->entry );
615         msi_free( mt->clsid );
616         msi_free( mt->ContentType );
617         msi_free( mt );
618     }
619
620     LIST_FOR_EACH_SAFE( item, cursor, &package->appids )
621     {
622         MSIAPPID *appid = LIST_ENTRY( item, MSIAPPID, entry );
623
624         list_remove( &appid->entry );
625         msi_free( appid->AppID );
626         msi_free( appid->RemoteServerName );
627         msi_free( appid->LocalServer );
628         msi_free( appid->ServiceParameters );
629         msi_free( appid->DllSurrogate );
630         msi_free( appid );
631     }
632
633     LIST_FOR_EACH_SAFE( item, cursor, &package->sourcelist_info )
634     {
635         MSISOURCELISTINFO *info = LIST_ENTRY( item, MSISOURCELISTINFO, entry );
636
637         list_remove( &info->entry );
638         msi_free( info->value );
639         msi_free( info );
640     }
641
642     LIST_FOR_EACH_SAFE( item, cursor, &package->sourcelist_media )
643     {
644         MSIMEDIADISK *info = LIST_ENTRY( item, MSIMEDIADISK, entry );
645
646         list_remove( &info->entry );
647         msi_free( info->volume_label );
648         msi_free( info->disk_prompt );
649         msi_free( info );
650     }
651
652     if (package->script)
653     {
654         for (i = 0; i < TOTAL_SCRIPTS; i++)
655             msi_free_action_script(package, i);
656
657         for (i = 0; i < package->script->UniqueActionsCount; i++)
658             msi_free(package->script->UniqueActions[i]);
659
660         msi_free(package->script->UniqueActions);
661         msi_free(package->script);
662     }
663
664     if (package->patch)
665     {
666         msi_free(package->patch->patchcode);
667         msi_free(package->patch->transforms);
668         msi_free(package->patch);
669     }
670
671     msi_free(package->BaseURL);
672     msi_free(package->PackagePath);
673     msi_free(package->ProductCode);
674     msi_free(package->ActionFormat);
675     msi_free(package->LastAction);
676
677     /* cleanup control event subscriptions */
678     ControlEvent_CleanupSubscriptions(package);
679 }
680
681 /*
682  *  build_directory_name()
683  *
684  *  This function is to save messing round with directory names
685  *  It handles adding backslashes between path segments, 
686  *   and can add \ at the end of the directory name if told to.
687  *
688  *  It takes a variable number of arguments.
689  *  It always allocates a new string for the result, so make sure
690  *   to free the return value when finished with it.
691  *
692  *  The first arg is the number of path segments that follow.
693  *  The arguments following count are a list of path segments.
694  *  A path segment may be NULL.
695  *
696  *  Path segments will be added with a \ separating them.
697  *  A \ will not be added after the last segment, however if the
698  *    last segment is NULL, then the last character will be a \
699  * 
700  */
701 LPWSTR build_directory_name(DWORD count, ...)
702 {
703     DWORD sz = 1, i;
704     LPWSTR dir;
705     va_list va;
706
707     va_start(va,count);
708     for(i=0; i<count; i++)
709     {
710         LPCWSTR str = va_arg(va,LPCWSTR);
711         if (str)
712             sz += strlenW(str) + 1;
713     }
714     va_end(va);
715
716     dir = msi_alloc(sz*sizeof(WCHAR));
717     dir[0]=0;
718
719     va_start(va,count);
720     for(i=0; i<count; i++)
721     {
722         LPCWSTR str = va_arg(va,LPCWSTR);
723         if (!str)
724             continue;
725         strcatW(dir, str);
726         if( ((i+1)!=count) && dir[strlenW(dir)-1]!='\\')
727             strcatW(dir, cszbs);
728     }
729     return dir;
730 }
731
732 /***********************************************************************
733  *            create_full_pathW
734  *
735  * Recursively create all directories in the path.
736  *
737  * shamelessly stolen from setupapi/queue.c
738  */
739 BOOL create_full_pathW(const WCHAR *path)
740 {
741     BOOL ret = TRUE;
742     int len;
743     WCHAR *new_path;
744
745     new_path = msi_alloc( (strlenW(path) + 1) * sizeof(WCHAR));
746
747     strcpyW(new_path, path);
748
749     while((len = strlenW(new_path)) && new_path[len - 1] == '\\')
750     new_path[len - 1] = 0;
751
752     while(!CreateDirectoryW(new_path, NULL))
753     {
754         WCHAR *slash;
755         DWORD last_error = GetLastError();
756         if(last_error == ERROR_ALREADY_EXISTS)
757             break;
758
759         if(last_error != ERROR_PATH_NOT_FOUND)
760         {
761             ret = FALSE;
762             break;
763         }
764
765         if(!(slash = strrchrW(new_path, '\\')))
766         {
767             ret = FALSE;
768             break;
769         }
770
771         len = slash - new_path;
772         new_path[len] = 0;
773         if(!create_full_pathW(new_path))
774         {
775             ret = FALSE;
776             break;
777         }
778         new_path[len] = '\\';
779     }
780
781     msi_free(new_path);
782     return ret;
783 }
784
785 void ui_progress(MSIPACKAGE *package, int a, int b, int c, int d )
786 {
787     MSIRECORD * row;
788
789     row = MSI_CreateRecord(4);
790     MSI_RecordSetInteger(row,1,a);
791     MSI_RecordSetInteger(row,2,b);
792     MSI_RecordSetInteger(row,3,c);
793     MSI_RecordSetInteger(row,4,d);
794     MSI_ProcessMessage(package, INSTALLMESSAGE_PROGRESS, row);
795     msiobj_release(&row->hdr);
796
797     msi_dialog_check_messages(NULL);
798 }
799
800 void ui_actiondata(MSIPACKAGE *package, LPCWSTR action, MSIRECORD * record)
801 {
802     static const WCHAR Query_t[] = 
803         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
804          '`','A','c','t','i','o', 'n','T','e','x','t','`',' ',
805          'W','H','E','R','E',' ', '`','A','c','t','i','o','n','`',' ','=', 
806          ' ','\'','%','s','\'',0};
807     WCHAR message[1024];
808     MSIRECORD * row = 0;
809     DWORD size;
810
811     if (!package->LastAction || strcmpW(package->LastAction,action))
812     {
813         row = MSI_QueryGetRecord(package->db, Query_t, action);
814         if (!row)
815             return;
816
817         if (MSI_RecordIsNull(row,3))
818         {
819             msiobj_release(&row->hdr);
820             return;
821         }
822
823         /* update the cached actionformat */
824         msi_free(package->ActionFormat);
825         package->ActionFormat = msi_dup_record_field(row,3);
826
827         msi_free(package->LastAction);
828         package->LastAction = strdupW(action);
829
830         msiobj_release(&row->hdr);
831     }
832
833     MSI_RecordSetStringW(record,0,package->ActionFormat);
834     size = 1024;
835     MSI_FormatRecordW(package,record,message,&size);
836
837     row = MSI_CreateRecord(1);
838     MSI_RecordSetStringW(row,1,message);
839  
840     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, row);
841
842     msiobj_release(&row->hdr);
843 }
844
845 BOOL ACTION_VerifyComponentForAction( const MSICOMPONENT* comp, INSTALLSTATE check )
846 {
847     if (!comp)
848         return FALSE;
849
850     if (comp->ActionRequest == check)
851         return TRUE;
852     else
853         return FALSE;
854 }
855
856 BOOL ACTION_VerifyFeatureForAction( const MSIFEATURE* feature, INSTALLSTATE check )
857 {
858     if (!feature)
859         return FALSE;
860
861     if (feature->ActionRequest == check)
862         return TRUE;
863     else
864         return FALSE;
865 }
866
867 void reduce_to_longfilename(WCHAR* filename)
868 {
869     LPWSTR p = strchrW(filename,'|');
870     if (p)
871         memmove(filename, p+1, (strlenW(p+1)+1)*sizeof(WCHAR));
872 }
873
874 LPWSTR create_component_advertise_string(MSIPACKAGE* package, 
875                 MSICOMPONENT* component, LPCWSTR feature)
876 {
877     static const WCHAR fmt[] = {'%','s','%','s','%','c','%','s',0};
878     WCHAR productid_85[21], component_85[21];
879     LPWSTR output = NULL;
880     DWORD sz = 0;
881     GUID clsid;
882
883     /* > is used if there is a component GUID and < if not.  */
884
885     productid_85[0] = 0;
886     component_85[0] = 0;
887
888     CLSIDFromString(package->ProductCode, &clsid);
889     encode_base85_guid(&clsid, productid_85);
890
891     if (component)
892     {
893         CLSIDFromString(component->ComponentId, &clsid);
894         encode_base85_guid(&clsid, component_85);
895     }
896
897     TRACE("prod=%s feat=%s comp=%s\n", debugstr_w(productid_85),
898           debugstr_w(feature), debugstr_w(component_85));
899  
900     sz = 20 + lstrlenW(feature) + 20 + 3;
901
902     output = msi_alloc_zero(sz*sizeof(WCHAR));
903
904     sprintfW(output, fmt, productid_85, feature,
905              component?'>':'<', component_85);
906     
907     return output;
908 }
909
910 /* update component state based on a feature change */
911 void ACTION_UpdateComponentStates(MSIPACKAGE *package, LPCWSTR szFeature)
912 {
913     INSTALLSTATE newstate;
914     MSIFEATURE *feature;
915     ComponentList *cl;
916
917     feature = get_loaded_feature(package,szFeature);
918     if (!feature)
919         return;
920
921     newstate = feature->ActionRequest;
922
923     if (newstate == INSTALLSTATE_ABSENT)
924         newstate = INSTALLSTATE_UNKNOWN;
925
926     LIST_FOR_EACH_ENTRY( cl, &feature->Components, ComponentList, entry )
927     {
928         MSICOMPONENT* component = cl->component;
929     
930         TRACE("MODIFYING(%i): Component %s (Installed %i, Action %i, Request %i)\n",
931             newstate, debugstr_w(component->Component), component->Installed, 
932             component->Action, component->ActionRequest);
933         
934         if (!component->Enabled)
935             continue;
936  
937         if (newstate == INSTALLSTATE_LOCAL)
938             msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
939         else 
940         {
941             ComponentList *clist;
942             MSIFEATURE *f;
943
944             component->hasLocalFeature = FALSE;
945
946             msi_component_set_state(package, component, newstate);
947
948             /*if any other feature wants is local we need to set it local*/
949             LIST_FOR_EACH_ENTRY( f, &package->features, MSIFEATURE, entry )
950             {
951                 if ( f->ActionRequest != INSTALLSTATE_LOCAL &&
952                      f->ActionRequest != INSTALLSTATE_SOURCE )
953                 {
954                     continue;
955                 }
956
957                 LIST_FOR_EACH_ENTRY( clist, &f->Components, ComponentList, entry )
958                 {
959                     if ( clist->component == component &&
960                          (f->ActionRequest == INSTALLSTATE_LOCAL ||
961                           f->ActionRequest == INSTALLSTATE_SOURCE) )
962                     {
963                         TRACE("Saved by %s\n", debugstr_w(f->Feature));
964                         component->hasLocalFeature = TRUE;
965
966                         if (component->Attributes & msidbComponentAttributesOptional)
967                         {
968                             if (f->Attributes & msidbFeatureAttributesFavorSource)
969                                 msi_component_set_state(package, component, INSTALLSTATE_SOURCE);
970                             else
971                                 msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
972                         }
973                         else if (component->Attributes & msidbComponentAttributesSourceOnly)
974                             msi_component_set_state(package, component, INSTALLSTATE_SOURCE);
975                         else
976                             msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
977                     }
978                 }
979             }
980         }
981         TRACE("Result (%i): Component %s (Installed %i, Action %i, Request %i)\n",
982             newstate, debugstr_w(component->Component), component->Installed, 
983             component->Action, component->ActionRequest);
984     } 
985 }
986
987 UINT register_unique_action(MSIPACKAGE *package, LPCWSTR action)
988 {
989     UINT count;
990     LPWSTR *newbuf = NULL;
991
992     if (!package->script)
993         return FALSE;
994
995     TRACE("Registering Action %s as having fun\n",debugstr_w(action));
996     
997     count = package->script->UniqueActionsCount;
998     package->script->UniqueActionsCount++;
999     if (count != 0)
1000         newbuf = msi_realloc( package->script->UniqueActions,
1001                         package->script->UniqueActionsCount* sizeof(LPWSTR));
1002     else
1003         newbuf = msi_alloc( sizeof(LPWSTR));
1004
1005     newbuf[count] = strdupW(action);
1006     package->script->UniqueActions = newbuf;
1007
1008     return ERROR_SUCCESS;
1009 }
1010
1011 BOOL check_unique_action(const MSIPACKAGE *package, LPCWSTR action)
1012 {
1013     UINT i;
1014
1015     if (!package->script)
1016         return FALSE;
1017
1018     for (i = 0; i < package->script->UniqueActionsCount; i++)
1019         if (!strcmpW(package->script->UniqueActions[i],action))
1020             return TRUE;
1021
1022     return FALSE;
1023 }
1024
1025 WCHAR* generate_error_string(MSIPACKAGE *package, UINT error, DWORD count, ... )
1026 {
1027     static const WCHAR query[] = {'S','E','L','E','C','T',' ','`','M','e','s','s','a','g','e','`',' ','F','R','O','M',' ','`','E','r','r','o','r','`',' ','W','H','E','R','E',' ','`','E','r','r','o','r','`',' ','=',' ','%','i',0};
1028
1029     MSIRECORD *rec;
1030     MSIRECORD *row;
1031     DWORD size = 0;
1032     DWORD i;
1033     va_list va;
1034     LPCWSTR str;
1035     LPWSTR data;
1036
1037     row = MSI_QueryGetRecord(package->db, query, error);
1038     if (!row)
1039         return 0;
1040
1041     rec = MSI_CreateRecord(count+2);
1042
1043     str = MSI_RecordGetString(row,1);
1044     MSI_RecordSetStringW(rec,0,str);
1045     msiobj_release( &row->hdr );
1046     MSI_RecordSetInteger(rec,1,error);
1047
1048     va_start(va,count);
1049     for (i = 0; i < count; i++)
1050     {
1051         str = va_arg(va,LPCWSTR);
1052         MSI_RecordSetStringW(rec,(i+2),str);
1053     }
1054     va_end(va);
1055
1056     MSI_FormatRecordW(package,rec,NULL,&size);
1057
1058     size++;
1059     data = msi_alloc(size*sizeof(WCHAR));
1060     if (size > 1)
1061         MSI_FormatRecordW(package,rec,data,&size);
1062     else
1063         data[0] = 0;
1064     msiobj_release( &rec->hdr );
1065     return data;
1066 }