| /* | 
 |  * Implementation of the Microsoft Installer (msi.dll) | 
 |  * | 
 |  * Copyright 2005 Aric Stewart for CodeWeavers | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  * | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 |  * Lesser General Public License for more details. | 
 |  * | 
 |  * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
 |  */ | 
 |  | 
 | #include <stdarg.h> | 
 | #include <stdio.h> | 
 |  | 
 | #include "windef.h" | 
 | #include "winbase.h" | 
 | #include "winerror.h" | 
 | #include "winreg.h" | 
 | #include "msi.h" | 
 | #include "msipriv.h" | 
 |  | 
 | #include "wine/debug.h" | 
 | #include "wine/unicode.h" | 
 |  | 
 | WINE_DEFAULT_DEBUG_CHANNEL(msi); | 
 |  | 
 | typedef UINT (*EVENTHANDLER)(MSIPACKAGE*,LPCWSTR,msi_dialog *); | 
 |  | 
 | struct control_events | 
 | { | 
 |     const WCHAR *event; | 
 |     EVENTHANDLER handler; | 
 | }; | 
 |  | 
 | struct subscriber { | 
 |     struct list entry; | 
 |     msi_dialog *dialog; | 
 |     LPWSTR event; | 
 |     LPWSTR control; | 
 |     LPWSTR attribute; | 
 | }; | 
 |  | 
 | static UINT ControlEvent_HandleControlEvent(MSIPACKAGE *, LPCWSTR, LPCWSTR, msi_dialog*); | 
 |  | 
 | /* | 
 |  * Create a dialog box and run it if it's modal | 
 |  */ | 
 | static UINT event_do_dialog( MSIPACKAGE *package, LPCWSTR name, msi_dialog *parent, BOOL destroy_modeless ) | 
 | { | 
 |     msi_dialog *dialog; | 
 |     UINT r; | 
 |  | 
 |     /* create a new dialog */ | 
 |     dialog = msi_dialog_create( package, name, parent, | 
 |                                 ControlEvent_HandleControlEvent ); | 
 |     if( dialog ) | 
 |     { | 
 |         /* kill the current modeless dialog */ | 
 |         if( destroy_modeless && package->dialog ) | 
 |         { | 
 |             msi_dialog_destroy( package->dialog ); | 
 |             package->dialog = NULL; | 
 |         } | 
 |  | 
 |         /* modeless dialogs return an error message */ | 
 |         r = msi_dialog_run_message_loop( dialog ); | 
 |         if( r == ERROR_SUCCESS ) | 
 |             msi_dialog_destroy( dialog ); | 
 |         else | 
 |             package->dialog = dialog; | 
 |     } | 
 |     else | 
 |         r = ERROR_FUNCTION_FAILED; | 
 |  | 
 |     return r; | 
 | } | 
 |  | 
 |  | 
 | /* | 
 |  * End a modal dialog box | 
 |  */ | 
 | static UINT ControlEvent_EndDialog(MSIPACKAGE* package, LPCWSTR argument,  | 
 |                                    msi_dialog* dialog) | 
 | { | 
 |     static const WCHAR szExit[] = {'E','x','i','t',0}; | 
 |     static const WCHAR szRetry[] = {'R','e','t','r','y',0}; | 
 |     static const WCHAR szIgnore[] = {'I','g','n','o','r','e',0}; | 
 |     static const WCHAR szReturn[] = {'R','e','t','u','r','n',0}; | 
 |  | 
 |     if (!strcmpW( argument, szExit )) | 
 |         package->CurrentInstallState = ERROR_INSTALL_USEREXIT; | 
 |     else if (!strcmpW( argument, szRetry )) | 
 |         package->CurrentInstallState = ERROR_INSTALL_SUSPEND; | 
 |     else if (!strcmpW( argument, szIgnore )) | 
 |         package->CurrentInstallState = ERROR_SUCCESS; | 
 |     else if (!strcmpW( argument, szReturn )) | 
 |     { | 
 |         msi_dialog *parent = msi_dialog_get_parent(dialog); | 
 |         msi_free(package->next_dialog); | 
 |         package->next_dialog = (parent) ? strdupW(msi_dialog_get_name(parent)) : NULL; | 
 |         package->CurrentInstallState = ERROR_SUCCESS; | 
 |     } | 
 |     else | 
 |     { | 
 |         ERR("Unknown argument string %s\n",debugstr_w(argument)); | 
 |         package->CurrentInstallState = ERROR_FUNCTION_FAILED; | 
 |     } | 
 |  | 
 |     ControlEvent_CleanupDialogSubscriptions(package, msi_dialog_get_name( dialog )); | 
 |     msi_dialog_end_dialog( dialog ); | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | /* | 
 |  * transition from one modal dialog to another modal dialog | 
 |  */ | 
 | static UINT ControlEvent_NewDialog(MSIPACKAGE* package, LPCWSTR argument,  | 
 |                                    msi_dialog *dialog) | 
 | { | 
 |     /* store the name of the next dialog, and signal this one to end */ | 
 |     package->next_dialog = strdupW(argument); | 
 |     ControlEvent_CleanupSubscriptions(package); | 
 |     msi_dialog_end_dialog( dialog ); | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | /* | 
 |  * Create a new child dialog of an existing modal dialog | 
 |  */ | 
 | static UINT ControlEvent_SpawnDialog(MSIPACKAGE* package, LPCWSTR argument,  | 
 |                               msi_dialog *dialog) | 
 | { | 
 |     /* don't destroy a modeless dialogs that might be our parent */ | 
 |     event_do_dialog( package, argument, dialog, FALSE ); | 
 |     if( package->CurrentInstallState != ERROR_SUCCESS ) | 
 |         msi_dialog_end_dialog( dialog ); | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | /* | 
 |  * Creates a dialog that remains up for a period of time | 
 |  * based on a condition | 
 |  */ | 
 | static UINT ControlEvent_SpawnWaitDialog(MSIPACKAGE* package, LPCWSTR argument,  | 
 |                                   msi_dialog* dialog) | 
 | { | 
 |     FIXME("Doing Nothing\n"); | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | static UINT ControlEvent_DoAction(MSIPACKAGE* package, LPCWSTR argument,  | 
 |                                   msi_dialog* dialog) | 
 | { | 
 |     ACTION_PerformAction(package, argument, -1); | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | static UINT ControlEvent_AddLocal( MSIPACKAGE *package, LPCWSTR argument, msi_dialog *dialog ) | 
 | { | 
 |     MSIFEATURE *feature; | 
 |  | 
 |     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) | 
 |     { | 
 |         if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) | 
 |         { | 
 |             if (feature->ActionRequest != INSTALLSTATE_LOCAL) | 
 |                 msi_set_property( package->db, szPreselected, szOne ); | 
 |             MSI_SetFeatureStateW( package, feature->Feature, INSTALLSTATE_LOCAL ); | 
 |         } | 
 |     } | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | static UINT ControlEvent_Remove( MSIPACKAGE *package, LPCWSTR argument, msi_dialog *dialog ) | 
 | { | 
 |     MSIFEATURE *feature; | 
 |  | 
 |     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) | 
 |     { | 
 |         if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) | 
 |         { | 
 |             if (feature->ActionRequest != INSTALLSTATE_ABSENT) | 
 |                 msi_set_property( package->db, szPreselected, szOne ); | 
 |             MSI_SetFeatureStateW( package, feature->Feature, INSTALLSTATE_ABSENT ); | 
 |         } | 
 |     } | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | static UINT ControlEvent_AddSource( MSIPACKAGE *package, LPCWSTR argument, msi_dialog *dialog ) | 
 | { | 
 |     MSIFEATURE *feature; | 
 |  | 
 |     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry ) | 
 |     { | 
 |         if (!strcmpW( argument, feature->Feature ) || !strcmpW( argument, szAll )) | 
 |         { | 
 |             if (feature->ActionRequest != INSTALLSTATE_SOURCE) | 
 |                 msi_set_property( package->db, szPreselected, szOne ); | 
 |             MSI_SetFeatureStateW( package, feature->Feature, INSTALLSTATE_SOURCE ); | 
 |         } | 
 |     } | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | static UINT ControlEvent_SetTargetPath(MSIPACKAGE* package, LPCWSTR argument,  | 
 |                                    msi_dialog* dialog) | 
 | { | 
 |     static const WCHAR szSelectionPath[] = {'S','e','l','e','c','t','i','o','n','P','a','t','h',0}; | 
 |     LPWSTR path = msi_dup_property( package->db, argument ); | 
 |     MSIRECORD *rec = MSI_CreateRecord( 1 ); | 
 |     UINT r = ERROR_SUCCESS; | 
 |  | 
 |     MSI_RecordSetStringW( rec, 1, path ); | 
 |     ControlEvent_FireSubscribedEvent( package, szSelectionPath, rec ); | 
 |     if (path) | 
 |     { | 
 |         /* failure to set the path halts the executing of control events */ | 
 |         r = MSI_SetTargetPathW(package, argument, path); | 
 |         msi_free(path); | 
 |     } | 
 |     msi_free(&rec->hdr); | 
 |     return r; | 
 | } | 
 |  | 
 | static UINT ControlEvent_Reset(MSIPACKAGE* package, LPCWSTR argument,  | 
 |                                    msi_dialog* dialog) | 
 | { | 
 |     msi_dialog_reset(dialog); | 
 |     return ERROR_SUCCESS; | 
 | } | 
 |  | 
 | /* | 
 |  * Subscribed events | 
 |  */ | 
 | static void free_subscriber( struct subscriber *sub ) | 
 | { | 
 |     msi_free(sub->event); | 
 |     msi_free(sub->control); | 
 |     msi_free(sub->attribute); | 
 |     msi_free(sub); | 
 | } | 
 |  | 
 | VOID ControlEvent_SubscribeToEvent( MSIPACKAGE *package, msi_dialog *dialog, | 
 |                                     LPCWSTR event, LPCWSTR control, LPCWSTR attribute ) | 
 | { | 
 |     struct subscriber *sub; | 
 |  | 
 |     TRACE("event %s control %s attribute %s\n", debugstr_w(event), debugstr_w(control), debugstr_w(attribute)); | 
 |  | 
 |     LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry ) | 
 |     { | 
 |         if (!strcmpiW( sub->event, event ) && | 
 |             !strcmpiW( sub->control, control ) && | 
 |             !strcmpiW( sub->attribute, attribute )) | 
 |         { | 
 |             TRACE("already subscribed\n"); | 
 |             return; | 
 |         }; | 
 |     } | 
 |     if (!(sub = msi_alloc( sizeof(*sub) ))) return; | 
 |     sub->dialog = dialog; | 
 |     sub->event = strdupW(event); | 
 |     sub->control = strdupW(control); | 
 |     sub->attribute = strdupW(attribute); | 
 |     list_add_tail( &package->subscriptions, &sub->entry ); | 
 | } | 
 |  | 
 | VOID ControlEvent_FireSubscribedEvent( MSIPACKAGE *package, LPCWSTR event, MSIRECORD *rec ) | 
 | { | 
 |     struct subscriber *sub; | 
 |  | 
 |     TRACE("Firing event %s\n", debugstr_w(event)); | 
 |  | 
 |     LIST_FOR_EACH_ENTRY( sub, &package->subscriptions, struct subscriber, entry ) | 
 |     { | 
 |         if (strcmpiW( sub->event, event )) continue; | 
 |         msi_dialog_handle_event( sub->dialog, sub->control, sub->attribute, rec ); | 
 |     } | 
 | } | 
 |  | 
 | VOID ControlEvent_CleanupDialogSubscriptions(MSIPACKAGE *package, LPWSTR dialog) | 
 | { | 
 |     struct list *i, *t; | 
 |     struct subscriber *sub; | 
 |  | 
 |     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions ) | 
 |     { | 
 |         sub = LIST_ENTRY( i, struct subscriber, entry ); | 
 |  | 
 |         if (strcmpW( msi_dialog_get_name( sub->dialog ), dialog )) | 
 |             continue; | 
 |  | 
 |         list_remove( &sub->entry ); | 
 |         free_subscriber( sub ); | 
 |     } | 
 | } | 
 |  | 
 | VOID ControlEvent_CleanupSubscriptions(MSIPACKAGE *package) | 
 | { | 
 |     struct list *i, *t; | 
 |     struct subscriber *sub; | 
 |  | 
 |     LIST_FOR_EACH_SAFE( i, t, &package->subscriptions ) | 
 |     { | 
 |         sub = LIST_ENTRY( i, struct subscriber, entry ); | 
 |  | 
 |         list_remove( &sub->entry ); | 
 |         free_subscriber( sub ); | 
 |     } | 
 | } | 
 |  | 
 | /* | 
 |  * ACTION_DialogBox() | 
 |  * | 
 |  * Return ERROR_SUCCESS if dialog is process and ERROR_FUNCTION_FAILED | 
 |  * if the given parameter is not a dialog box | 
 |  */ | 
 | UINT ACTION_DialogBox( MSIPACKAGE* package, LPCWSTR szDialogName ) | 
 | { | 
 |     UINT r = ERROR_SUCCESS; | 
 |  | 
 |     if( package->next_dialog ) | 
 |         ERR("Already a next dialog... ignoring it\n"); | 
 |     package->next_dialog = NULL; | 
 |  | 
 |     /* | 
 |      * Dialogs are chained by filling in the next_dialog member | 
 |      *  of the package structure, then terminating the current dialog. | 
 |      *  The code below sees the next_dialog member set, and runs the | 
 |      *  next dialog. | 
 |      * We fall out of the loop below if we come across a modeless | 
 |      *  dialog, as it returns ERROR_IO_PENDING when we try to run | 
 |      *  its message loop. | 
 |      */ | 
 |     r = event_do_dialog( package, szDialogName, NULL, TRUE ); | 
 |     while( r == ERROR_SUCCESS && package->next_dialog ) | 
 |     { | 
 |         LPWSTR name = package->next_dialog; | 
 |  | 
 |         package->next_dialog = NULL; | 
 |         r = event_do_dialog( package, name, NULL, TRUE ); | 
 |         msi_free( name ); | 
 |     } | 
 |  | 
 |     if( r == ERROR_IO_PENDING ) | 
 |         r = ERROR_SUCCESS; | 
 |  | 
 |     return r; | 
 | } | 
 |  | 
 | static UINT ControlEvent_SetInstallLevel(MSIPACKAGE* package, LPCWSTR argument, | 
 |                                           msi_dialog* dialog) | 
 | { | 
 |     int iInstallLevel = atolW(argument); | 
 |  | 
 |     TRACE("Setting install level: %i\n", iInstallLevel); | 
 |  | 
 |     return MSI_SetInstallLevel( package, iInstallLevel ); | 
 | } | 
 |  | 
 | static UINT ControlEvent_DirectoryListUp(MSIPACKAGE *package, LPCWSTR argument, | 
 |                                          msi_dialog *dialog) | 
 | { | 
 |     return msi_dialog_directorylist_up( dialog ); | 
 | } | 
 |  | 
 | static UINT ControlEvent_ReinstallMode(MSIPACKAGE *package, LPCWSTR argument, | 
 |                                        msi_dialog *dialog) | 
 | { | 
 |     return msi_set_property( package->db, szReinstallMode, argument ); | 
 | } | 
 |  | 
 | static UINT ControlEvent_Reinstall( MSIPACKAGE *package, LPCWSTR argument, | 
 |                                     msi_dialog *dialog ) | 
 | { | 
 |     return msi_set_property( package->db, szReinstall, argument ); | 
 | } | 
 |  | 
 | static UINT ControlEvent_ValidateProductID(MSIPACKAGE *package, LPCWSTR argument, | 
 |                                            msi_dialog *dialog) | 
 | { | 
 |     LPWSTR key, template; | 
 |     UINT ret = ERROR_SUCCESS; | 
 |  | 
 |     template = msi_dup_property( package->db, szPIDTemplate ); | 
 |     key = msi_dup_property( package->db, szPIDKEY ); | 
 |  | 
 |     if (key && template) | 
 |     { | 
 |         FIXME( "partial stub: template %s key %s\n", debugstr_w(template), debugstr_w(key) ); | 
 |         ret = msi_set_property( package->db, szProductID, key ); | 
 |     } | 
 |     msi_free( template ); | 
 |     msi_free( key ); | 
 |     return ret; | 
 | } | 
 |  | 
 | static const WCHAR end_dialogW[] = {'E','n','d','D','i','a','l','o','g',0}; | 
 | static const WCHAR new_dialogW[] = {'N','e','w','D','i','a','l','o','g',0}; | 
 | static const WCHAR spawn_dialogW[] = {'S','p','a','w','n','D','i','a','l','o','g',0}; | 
 | static const WCHAR spawn_wait_dialogW[] = {'S','p','a','w','n','W','a','i','t','D','i','a','l','o','g',0}; | 
 | static const WCHAR do_actionW[] = {'D','o','A','c','t','i','o','n',0}; | 
 | static const WCHAR add_localW[] = {'A','d','d','L','o','c','a','l',0}; | 
 | static const WCHAR removeW[] = {'R','e','m','o','v','e',0}; | 
 | static const WCHAR add_sourceW[] = {'A','d','d','S','o','u','r','c','e',0}; | 
 | static const WCHAR set_target_pathW[] = {'S','e','t','T','a','r','g','e','t','P','a','t','h',0}; | 
 | static const WCHAR resetW[] = {'R','e','s','e','t',0}; | 
 | static const WCHAR set_install_levelW[] = {'S','e','t','I','n','s','t','a','l','l','L','e','v','e','l',0}; | 
 | static const WCHAR directory_list_upW[] = {'D','i','r','e','c','t','o','r','y','L','i','s','t','U','p',0}; | 
 | static const WCHAR selection_browseW[] = {'S','e','l','e','c','t','i','o','n','B','r','o','w','s','e',0}; | 
 | static const WCHAR reinstall_modeW[] = {'R','e','i','n','s','t','a','l','l','M','o','d','e',0}; | 
 | static const WCHAR reinstallW[] = {'R','e','i','n','s','t','a','l','l',0}; | 
 | static const WCHAR validate_product_idW[] = {'V','a','l','i','d','a','t','e','P','r','o','d','u','c','t','I','D',0}; | 
 |  | 
 | static const struct control_events control_events[] = | 
 | { | 
 |     { end_dialogW, ControlEvent_EndDialog }, | 
 |     { new_dialogW, ControlEvent_NewDialog }, | 
 |     { spawn_dialogW, ControlEvent_SpawnDialog }, | 
 |     { spawn_wait_dialogW, ControlEvent_SpawnWaitDialog }, | 
 |     { do_actionW, ControlEvent_DoAction }, | 
 |     { add_localW, ControlEvent_AddLocal }, | 
 |     { removeW, ControlEvent_Remove }, | 
 |     { add_sourceW, ControlEvent_AddSource }, | 
 |     { set_target_pathW, ControlEvent_SetTargetPath }, | 
 |     { resetW, ControlEvent_Reset }, | 
 |     { set_install_levelW, ControlEvent_SetInstallLevel }, | 
 |     { directory_list_upW, ControlEvent_DirectoryListUp }, | 
 |     { selection_browseW, ControlEvent_SpawnDialog }, | 
 |     { reinstall_modeW, ControlEvent_ReinstallMode }, | 
 |     { reinstallW, ControlEvent_Reinstall }, | 
 |     { validate_product_idW, ControlEvent_ValidateProductID }, | 
 |     { NULL, NULL } | 
 | }; | 
 |  | 
 | UINT ControlEvent_HandleControlEvent( MSIPACKAGE *package, LPCWSTR event, | 
 |                                       LPCWSTR argument, msi_dialog *dialog ) | 
 | { | 
 |     unsigned int i; | 
 |  | 
 |     TRACE("handling control event %s\n", debugstr_w(event)); | 
 |  | 
 |     if (!event) return ERROR_SUCCESS; | 
 |  | 
 |     for (i = 0; control_events[i].event; i++) | 
 |     { | 
 |         if (!strcmpW( control_events[i].event, event )) | 
 |             return control_events[i].handler( package, argument, dialog ); | 
 |     } | 
 |     FIXME("unhandled control event %s arg(%s)\n", debugstr_w(event), debugstr_w(argument)); | 
 |     return ERROR_SUCCESS; | 
 | } |