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