diff options
author | robertshield@chromium.org <robertshield@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-14 19:47:56 +0000 |
---|---|---|
committer | robertshield@chromium.org <robertshield@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-14 19:47:56 +0000 |
commit | a3bb1a6c553b17964e4e8ee9041b7dff7191364e (patch) | |
tree | 855480c56d0e96e29911001d76521ed6c6c3ab66 | |
parent | 83c86b6790ca8f58c910d7f68ae257a34b9a3206 (diff) | |
download | chromium_src-a3bb1a6c553b17964e4e8ee9041b7dff7191364e.zip chromium_src-a3bb1a6c553b17964e4e8ee9041b7dff7191364e.tar.gz chromium_src-a3bb1a6c553b17964e4e8ee9041b7dff7191364e.tar.bz2 |
Refactor out of install.cc the set of functions that operate on a work item list for better testability.
Write preliminary test framework for testing these functions.
BUG=61609
TEST=setup_unittests.exe
Review URL: http://codereview.chromium.org/6153003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@71473 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/chrome_installer.gypi | 11 | ||||
-rw-r--r-- | chrome/installer/setup/chrome_frame_ready_mode.cc | 1 | ||||
-rw-r--r-- | chrome/installer/setup/install.cc | 694 | ||||
-rw-r--r-- | chrome/installer/setup/install.h | 33 | ||||
-rw-r--r-- | chrome/installer/setup/install_worker.cc | 872 | ||||
-rw-r--r-- | chrome/installer/setup/install_worker.h | 98 | ||||
-rw-r--r-- | chrome/installer/setup/install_worker_unittest.cc | 205 | ||||
-rw-r--r-- | chrome/installer/setup/uninstall.cc | 1 | ||||
-rw-r--r-- | chrome/installer/util/installation_state.cc | 16 | ||||
-rw-r--r-- | chrome/installer/util/installation_state.h | 14 | ||||
-rw-r--r-- | chrome/installer/util/installer_state.cc | 1 | ||||
-rw-r--r-- | chrome/installer/util/installer_state.h | 8 | ||||
-rw-r--r-- | chrome/installer/util/work_item_list.h | 64 |
13 files changed, 1255 insertions, 763 deletions
diff --git a/chrome/chrome_installer.gypi b/chrome/chrome_installer.gypi index 1433ec5..9db6089 100644 --- a/chrome/chrome_installer.gypi +++ b/chrome/chrome_installer.gypi @@ -208,6 +208,8 @@ 'installer/setup/chrome_frame_ready_mode.h', 'installer/setup/install.cc', 'installer/setup/install.h', + 'installer/setup/install_worker.cc', + 'installer/setup/install_worker.h', 'installer/setup/setup_main.cc', 'installer/setup/setup.ico', 'installer/setup/setup.rc', @@ -311,13 +313,22 @@ '<(DEPTH)/base/base.gyp:base_i18n', '<(DEPTH)/base/base.gyp:test_support_base', '<(DEPTH)/build/temp_gyp/googleurl.gyp:googleurl', + '<(DEPTH)/testing/gmock.gyp:gmock', '<(DEPTH)/testing/gtest.gyp:gtest', ], 'include_dirs': [ '<(DEPTH)', ], + # TODO(robertshield): Move the items marked with "Move to lib" + # below into a separate lib and then link both setup.exe and + # setup_unittests.exe against that. 'sources': [ + 'installer/setup/install_worker.cc', # Move to lib + 'installer/setup/install_worker.h', # Move to lib + 'installer/setup/install_worker_unittest.cc', 'installer/setup/run_all_unittests.cc', + 'installer/setup/setup_constants.cc', # Move to lib + 'installer/setup/setup_constants.h', # Move to lib 'installer/setup/setup_util.cc', 'installer/setup/setup_util_unittest.cc', ], diff --git a/chrome/installer/setup/chrome_frame_ready_mode.cc b/chrome/installer/setup/chrome_frame_ready_mode.cc index 3523237..6e980bb 100644 --- a/chrome/installer/setup/chrome_frame_ready_mode.cc +++ b/chrome/installer/setup/chrome_frame_ready_mode.cc @@ -13,6 +13,7 @@ #include "base/utf_string_conversions.h" #include "base/win/registry.h" #include "chrome/installer/setup/install.h" +#include "chrome/installer/setup/install_worker.h" #include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/google_update_constants.h" #include "chrome/installer/util/helper.h" diff --git a/chrome/installer/setup/install.cc b/chrome/installer/setup/install.cc index dc545a7..0df4e69 100644 --- a/chrome/installer/setup/install.cc +++ b/chrome/installer/setup/install.cc @@ -18,6 +18,7 @@ #include "base/utf_string_conversions.h" #include "base/win/registry.h" #include "chrome/installer/setup/setup_constants.h" +#include "chrome/installer/setup/install_worker.h" #include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/channel_info.h" #include "chrome/installer/util/chrome_frame_distribution.h" @@ -92,35 +93,6 @@ void AddInstallerCopyTasks(const FilePath& setup_path, } } -void AppendUninstallCommandLineFlags(CommandLine* uninstall_cmd, - const Product& product) { - DCHECK(uninstall_cmd); - - uninstall_cmd->AppendSwitch(installer::switches::kUninstall); - - // Append the product-specific uninstall flags. - product.distribution()->AppendUninstallCommandLineFlags(uninstall_cmd); - if (product.IsMsi()) { - uninstall_cmd->AppendSwitch(installer::switches::kMsi); - // See comment in uninstall.cc where we check for the kDeleteProfile switch. - if (product.is_chrome_frame()) { - uninstall_cmd->AppendSwitch(installer::switches::kDeleteProfile); - } - } - if (product.system_level()) - uninstall_cmd->AppendSwitch(installer::switches::kSystemLevel); - - // Propagate switches obtained from preferences as well. - const MasterPreferences& prefs = MasterPreferences::ForCurrentProcess(); - if (prefs.is_multi_install()) { - uninstall_cmd->AppendSwitch(installer::switches::kMultiInstall); - } - bool value = false; - if (prefs.GetBool(installer::master_preferences::kVerboseLogging, - &value) && value) - uninstall_cmd->AppendSwitch(installer::switches::kVerboseLogging); -} - // Adds work items that make registry adjustments for Google Update. When a // product is installed (including overinstall), Google Update will write the // channel ("ap") value into either Chrome or Chrome Frame's ClientState key. @@ -395,204 +367,6 @@ bool CreateOrUpdateChromeShortcuts(const FilePath& setup_path, return ret; } -// Local helper to call AddRegisterComDllWorkItems for all DLLs in a set of -// products managed by a given package. -void AddRegisterComDllWorkItemsForPackage(const Package& package, - const Version* old_version, - const Version& new_version, - WorkItemList* work_item_list) { - // First collect the list of DLLs to be registered from each product. - const Products& products = package.products(); - Products::const_iterator product_iter(products.begin()); - std::vector<FilePath> com_dll_list; - for (; product_iter != products.end(); ++product_iter) { - BrowserDistribution* dist = product_iter->get()->distribution(); - std::vector<FilePath> dist_dll_list(dist->GetComDllList()); - com_dll_list.insert(com_dll_list.end(), dist_dll_list.begin(), - dist_dll_list.end()); - } - - // Then, if we got some, attempt to unregister the DLLs from the old - // version directory and then re-register them in the new one. - // Note that if we are migrating the install directory then we will not - // successfully unregister the old DLLs. - // TODO(robertshield): See whether we need to fix the migration case. - // TODO(robertshield): If we ever remove a DLL from a product, this will - // not unregister it on update. We should build the unregistration list from - // saved state instead of assuming it is the same as the registration list. - if (!com_dll_list.empty()) { - if (old_version) { - FilePath old_dll_path( - package.path().Append(UTF8ToWide(old_version->GetString()))); - - installer::AddRegisterComDllWorkItems(old_dll_path, - com_dll_list, - package.system_level(), - false, // Unregister - true, // May fail - work_item_list); - } - - FilePath dll_path( - package.path().Append(UTF8ToWide(new_version.GetString()))); - installer::AddRegisterComDllWorkItems(dll_path, - com_dll_list, - package.system_level(), - true, // Register - false, // Must succeed. - work_item_list); - } -} - -// After a successful copying of all the files, this function is called to -// do a few post install tasks: -// - Handle the case of in-use-update by updating "opv" (old version) key or -// deleting it if not required. -// - Register any new dlls and unregister old dlls. -// - If this is an MSI install, ensures that the MSI marker is set, and sets -// it if not. -// If these operations are successful, the function returns true, otherwise -// false. -bool AppendPostInstallTasks(bool multi_install, - const FilePath& setup_path, - const FilePath& new_chrome_exe, - const Version* current_version, - const Version& new_version, - const Package& package, - WorkItemList* post_install_task_list) { - DCHECK(post_install_task_list); - HKEY root = package.system_level() ? HKEY_LOCAL_MACHINE : - HKEY_CURRENT_USER; - const Products& products = package.products(); - - - // Append work items that will only be executed if this was an update. - // We update the 'opv' key with the current version that is active and 'cmd' - // key with the rename command to run. - { - scoped_ptr<WorkItemList> in_use_update_work_items( - WorkItem::CreateConditionalWorkItemList( - new ConditionRunIfFileExists(new_chrome_exe))); - in_use_update_work_items->set_log_message("InUseUpdateWorkItemList"); - - FilePath installer_path(package.GetInstallerDirectory(new_version) - .Append(setup_path.BaseName())); - - CommandLine rename(installer_path); - rename.AppendSwitch(installer::switches::kRenameChromeExe); - if (package.system_level()) - rename.AppendSwitch(installer::switches::kSystemLevel); - - if (InstallUtil::IsChromeSxSProcess()) - rename.AppendSwitch(installer::switches::kChromeSxS); - - if (multi_install) - rename.AppendSwitch(installer::switches::kMultiInstall); - - std::wstring version_key; - for (size_t i = 0; i < products.size(); ++i) { - BrowserDistribution* dist = products[i]->distribution(); - version_key = dist->GetVersionKey(); - - if (current_version != NULL) { - in_use_update_work_items->AddSetRegValueWorkItem(root, version_key, - google_update::kRegOldVersionField, - UTF8ToWide(current_version->GetString()), true); - } - - // Adding this registry entry for all products is overkill. - // However, as it stands, we don't have a way to know which distribution - // will check the key and run the command, so we add it for all. - // After the first run, the subsequent runs should just be noops. - // (see Upgrade::SwapNewChromeExeIfPresent). - in_use_update_work_items->AddSetRegValueWorkItem( - root, - version_key, - google_update::kRegRenameCmdField, - rename.command_line_string(), - true); - } - - if (multi_install) { - PackageProperties* props = package.properties(); - if (props->ReceivesUpdates() && current_version != NULL) { - in_use_update_work_items->AddSetRegValueWorkItem( - root, - props->GetVersionKey(), - google_update::kRegOldVersionField, - UTF8ToWide(current_version->GetString()), - true); - // TODO(tommi): We should move the rename command here. We also need to - // update Upgrade::SwapNewChromeExeIfPresent. - } - } - - post_install_task_list->AddWorkItem(in_use_update_work_items.release()); - } - - - // Append work items that will be executed if this was NOT an in-use update. - { - scoped_ptr<WorkItemList> regular_update_work_items( - WorkItem::CreateConditionalWorkItemList( - new Not(new ConditionRunIfFileExists(new_chrome_exe)))); - regular_update_work_items->set_log_message( - "RegularUpdateWorkItemList"); - - // Since this was not an in-use-update, delete 'opv' and 'cmd' keys. - for (size_t i = 0; i < products.size(); ++i) { - BrowserDistribution* dist = products[i]->distribution(); - std::wstring version_key(dist->GetVersionKey()); - regular_update_work_items->AddDeleteRegValueWorkItem(root, version_key, - google_update::kRegOldVersionField, - REG_SZ); - regular_update_work_items->AddDeleteRegValueWorkItem(root, version_key, - google_update::kRegRenameCmdField, - REG_SZ); - } - - post_install_task_list->AddWorkItem(regular_update_work_items.release()); - } - - AddRegisterComDllWorkItemsForPackage(package, current_version, new_version, - post_install_task_list); - - for (size_t i = 0; i < products.size(); ++i) { - const Product* product = products[i]; - // If we're told that we're an MSI install, make sure to set the marker - // in the client state key so that future updates do the right thing. - if (product->IsMsi()) { - AddSetMsiMarkerWorkItem(*product, true, post_install_task_list); - - // We want MSI installs to take over the Add/Remove Programs shortcut. - // Make a best-effort attempt to delete any shortcuts left over from - // previous non-MSI installations for the same type of install (system or - // per user). - AddDeleteUninstallShortcutsForMSIWorkItems(*product, - post_install_task_list); - } - } - - return true; -} - -// This method tells if we are running on 64 bit platform so that we can copy -// one extra exe. If the API call to determine 64 bit fails, we play it safe -// and return true anyway so that the executable can be copied. -bool Is64bit() { - typedef BOOL (WINAPI *WOW_FUNC)(HANDLE, PBOOL); - BOOL is64 = FALSE; - - HANDLE handle = GetCurrentProcess(); - HMODULE module = GetModuleHandle(L"kernel32.dll"); - WOW_FUNC p = reinterpret_cast<WOW_FUNC>(GetProcAddress(module, - "IsWow64Process")); - if ((p != NULL) && (!(p)(handle, &is64) || (is64 != FALSE))) { - return true; - } - - return false; -} void RegisterChromeOnMachine(const Product& product, bool make_chrome_default) { @@ -620,44 +394,6 @@ void RegisterChromeOnMachine(const Product& product, } } -// Create Version key for a product (if not already present) and sets the new -// product version as the last step. -void AddVersionKeyWorkItems(HKEY root, - const Product& product, - const Version& new_version, - WorkItemList* list) { - // Create Version key for each distribution (if not already present) and set - // the new product version as the last step. - std::wstring version_key(product.distribution()->GetVersionKey()); - list->AddCreateRegKeyWorkItem(root, version_key); - - std::wstring product_name(product.distribution()->GetAppShortCutName()); - list->AddSetRegValueWorkItem(root, version_key, google_update::kRegNameField, - product_name, true); // overwrite name also - list->AddSetRegValueWorkItem(root, version_key, - google_update::kRegOopcrashesField, - static_cast<DWORD>(1), - false); // set during first install - list->AddSetRegValueWorkItem(root, version_key, - google_update::kRegVersionField, - UTF8ToWide(new_version.GetString()), - true); // overwrite version -} - -void AddProductSpecificWorkItems(bool install, - const FilePath& setup_path, - const Version& new_version, - const Package& package, - WorkItemList* list) { - const Products& products = package.products(); - for (size_t i = 0; i < products.size(); ++i) { - const Product& p = *products[i]; - if (p.is_chrome_frame()) { - AddChromeFrameWorkItems(install, setup_path, new_version, p, list); - } - } -} - // This function installs a new version of Chrome to the specified location. // // setup_path: Path to the executable (setup.exe) as it will be copied @@ -669,7 +405,7 @@ void AddProductSpecificWorkItems(bool install, // temp_dir: the path of working directory used during installation. This path // does not need to exist. // new_version: new Chrome version that needs to be installed -// oldest_installed_version: returns the oldest active version (if any) +// current_version: returns the current active version (if any) // // This function makes best effort to do installation in a transactional // manner. If failed it tries to rollback all changes on the file system @@ -691,119 +427,28 @@ installer::InstallStatus InstallNewVersion( const Package& package) { DCHECK(current_version); - const Products& products = package.products(); - DCHECK(products.size()); - - if (FindProduct(products, BrowserDistribution::CHROME_FRAME)) { - // Make sure that we don't end up deleting installed files on next reboot. - if (!RemoveFromMovesPendingReboot(package.path().value().c_str())) { - LOG(ERROR) << "Error accessing pending moves value."; - } - } - current_version->reset(package.GetCurrentVersion()); - scoped_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList()); - // A temp directory that work items need and the actual install directory. - install_list->AddCreateDirWorkItem(temp_dir); - install_list->AddCreateDirWorkItem(package.path()); - // Delete any new_chrome.exe if present (we will end up creating a new one - // if required) and then copy chrome.exe + AddInstallWorkItems(original_state, + installer_state, + multi_install, + setup_path, + archive_path, + src_path, + temp_dir, + new_version, + current_version, + package, + install_list.get()); + FilePath new_chrome_exe( package.path().Append(installer::kChromeNewExe)); - install_list->AddDeleteTreeWorkItem(new_chrome_exe); - install_list->AddCopyTreeWorkItem( - src_path.Append(installer::kChromeExe).value(), - package.path().Append(installer::kChromeExe).value(), - temp_dir.value(), WorkItem::NEW_NAME_IF_IN_USE, new_chrome_exe.value()); - - // Extra executable for 64 bit systems. - if (Is64bit()) { - install_list->AddCopyTreeWorkItem( - src_path.Append(installer::kWowHelperExe).value(), - package.path().Append(installer::kWowHelperExe).value(), - temp_dir.value(), WorkItem::ALWAYS); - } - - // If it is system level install copy the version folder (since we want to - // take the permissions of %ProgramFiles% folder) otherwise just move it. - if (package.system_level()) { - install_list->AddCopyTreeWorkItem( - src_path.Append(UTF8ToWide(new_version.GetString())).value(), - package.path().Append(UTF8ToWide(new_version.GetString())).value(), - temp_dir.value(), WorkItem::ALWAYS); - } else { - install_list->AddMoveTreeWorkItem( - src_path.Append(UTF8ToWide(new_version.GetString())).value(), - package.path().Append(UTF8ToWide(new_version.GetString())).value(), - temp_dir.value()); - } - - // Copy the default Dictionaries only if the folder doesn't exist already. - install_list->AddCopyTreeWorkItem( - src_path.Append(installer::kDictionaries).value(), - package.path().Append(installer::kDictionaries).value(), - temp_dir.value(), WorkItem::IF_NOT_PRESENT); - - // Delete any old_chrome.exe if present. - install_list->AddDeleteTreeWorkItem( - package.path().Append(installer::kChromeOldExe)); - - // Copy installer in install directory and - // add shortcut in Control Panel->Add/Remove Programs. - AddInstallerCopyTasks(setup_path, archive_path, temp_dir, new_version, - install_list.get(), package); - - HKEY root = package.system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; - - for (size_t i = 0; i < products.size(); ++i) { - const Product* product = products[i]; - - AddUninstallShortcutWorkItems(setup_path, new_version, install_list.get(), - *product); - - AddVersionKeyWorkItems(root, *product, new_version, install_list.get()); - } - - if (multi_install) { - PackageProperties* props = package.properties(); - if (props->ReceivesUpdates()) { - std::wstring version_key(props->GetVersionKey()); - install_list->AddCreateRegKeyWorkItem(root, version_key); - install_list->AddSetRegValueWorkItem(root, version_key, - google_update::kRegVersionField, - UTF8ToWide(new_version.GetString()), - true); // overwrite version - install_list->AddSetRegValueWorkItem(root, version_key, - google_update::kRegNameField, - ASCIIToWide(installer::PackageProperties::kPackageProductName), - true); // overwrite name also - } - } - - // Add any remaining work items that involve special settings for - // each product. - AddProductSpecificWorkItems(true, setup_path, new_version, package, - install_list.get()); - - AddGoogleUpdateWorkItems(original_state, installer_state, package, - install_list.get()); - - // Append the tasks that run after the installation. - AppendPostInstallTasks(multi_install, - setup_path, - new_chrome_exe, - current_version->get(), - new_version, - package, - install_list.get()); - if (!install_list->Do()) { installer::InstallStatus result = file_util::PathExists(new_chrome_exe) && current_version->get() && - new_version.Equals(**current_version) ? + new_version.Equals(*current_version->get()) ? installer::SAME_VERSION_REPAIR_FAILED : installer::INSTALL_FAILED; LOG(ERROR) << "Install failed, rolling back... result: " << result; @@ -853,11 +498,24 @@ installer::InstallStatus InstallOrUpdateProduct( FilePath src_path(install_temp_path); src_path = src_path.Append(kInstallSourceDir).Append(kInstallSourceChromeDir); + // TODO(robertshield): Removing the pending on-reboot moves should be done + // elsewhere. + const Products& products = install.products(); + DCHECK(products.size()); + if (FindProduct(products, BrowserDistribution::CHROME_FRAME)) { + // Make sure that we don't end up deleting installed files on next reboot. + if (!RemoveFromMovesPendingReboot(install.path().value().c_str())) { + LOG(ERROR) << "Error accessing pending moves value."; + } + } + scoped_ptr<Version> existing_version; installer::InstallStatus result = InstallNewVersion(original_state, installer_state, prefs.is_multi_install(), setup_path, archive_path, src_path, install_temp_path, new_version, &existing_version, install); + // TODO(robertshield): Everything below this line should instead be captured + // by WorkItems. if (!InstallUtil::GetInstallReturnCode(result)) { if (result == installer::FIRST_INSTALL_SUCCESS && !prefs_path.empty()) CopyPreferenceFileForFirstRun(install, prefs_path); @@ -910,300 +568,4 @@ installer::InstallStatus InstallOrUpdateProduct( return result; } -void AddRegisterComDllWorkItems(const FilePath& dll_folder, - const std::vector<FilePath>& dll_list, - bool system_level, - bool do_register, - bool ignore_failures, - WorkItemList* work_item_list) { - DCHECK(work_item_list); - if (dll_list.empty()) { - VLOG(1) << "No COM DLLs to register"; - } else { - std::vector<FilePath>::const_iterator dll_iter(dll_list.begin()); - for (; dll_iter != dll_list.end(); ++dll_iter) { - FilePath dll_path = dll_folder.Append(*dll_iter); - WorkItem* work_item = work_item_list->AddSelfRegWorkItem( - dll_path.value(), do_register, !system_level); - DCHECK(work_item); - work_item->set_ignore_failure(ignore_failures); - } - } -} - -void AddSetMsiMarkerWorkItem(const Product& product, - bool set, - WorkItemList* work_item_list) { - DCHECK(work_item_list); - BrowserDistribution* dist = product.distribution(); - HKEY reg_root = product.system_level() ? HKEY_LOCAL_MACHINE : - HKEY_CURRENT_USER; - DWORD msi_value = set ? 1 : 0; - WorkItem* set_msi_work_item = work_item_list->AddSetRegValueWorkItem( - reg_root, dist->GetStateKey(), google_update::kRegMSIField, - msi_value, true); - DCHECK(set_msi_work_item); - set_msi_work_item->set_ignore_failure(true); - set_msi_work_item->set_log_message("Could not write MSI marker!"); -} - -void AddUninstallShortcutWorkItems(const FilePath& setup_path, - const Version& new_version, - WorkItemList* install_list, - const Product& product) { - HKEY reg_root = product.system_level() ? HKEY_LOCAL_MACHINE : - HKEY_CURRENT_USER; - BrowserDistribution* browser_dist = product.distribution(); - DCHECK(browser_dist); - - // When we are installed via an MSI, we need to store our uninstall strings - // in the Google Update client state key. We do this even for non-MSI - // managed installs to avoid breaking the edge case whereby an MSI-managed - // install is updated by a non-msi installer (which would confuse the MSI - // machinery if these strings were not also updated). - // Do not quote the command line for the MSI invocation. - FilePath install_path(product.package().path()); - FilePath installer_path( - product.package().GetInstallerDirectory(new_version)); - installer_path = installer_path.Append(setup_path.BaseName()); - - CommandLine uninstall_arguments(CommandLine::NO_PROGRAM); - AppendUninstallCommandLineFlags(&uninstall_arguments, product); - - if (product.is_chrome()) { - // The Chrome uninstallation command serves as the master uninstall - // command for Chrome + all other products (i.e. Chrome Frame) that do - // not have an uninstall entry in the Add/Remove Programs dialog. - const Products& products = product.package().products(); - for (size_t i = 0; i < products.size(); ++i) { - const Product& p = *products[i]; - if (!p.is_chrome() && !p.ShouldCreateUninstallEntry()) { - p.distribution()->AppendUninstallCommandLineFlags(&uninstall_arguments); - } - } - } - - std::wstring update_state_key(browser_dist->GetStateKey()); - install_list->AddCreateRegKeyWorkItem(reg_root, update_state_key); - install_list->AddSetRegValueWorkItem(reg_root, update_state_key, - installer::kUninstallStringField, installer_path.value(), true); - install_list->AddSetRegValueWorkItem(reg_root, update_state_key, - installer::kUninstallArgumentsField, - uninstall_arguments.command_line_string(), true); - - if (product.ShouldCreateUninstallEntry()) { - // We need to quote the command line for the Add/Remove Programs dialog. - CommandLine quoted_uninstall_cmd(installer_path); - DCHECK_EQ(quoted_uninstall_cmd.command_line_string()[0], '"'); - quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false); - - std::wstring uninstall_reg = browser_dist->GetUninstallRegPath(); - install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg); - install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - installer::kUninstallDisplayNameField, - browser_dist->GetAppShortCutName(), true); - install_list->AddSetRegValueWorkItem(reg_root, - uninstall_reg, installer::kUninstallStringField, - quoted_uninstall_cmd.command_line_string(), true); - install_list->AddSetRegValueWorkItem(reg_root, - uninstall_reg, - L"InstallLocation", - install_path.value(), - true); - - // DisplayIcon, NoModify and NoRepair - FilePath chrome_icon(install_path.Append(installer::kChromeExe)); - ShellUtil::GetChromeIcon(product.distribution(), chrome_icon.value()); - install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"DisplayIcon", chrome_icon.value(), - true); - install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"NoModify", static_cast<DWORD>(1), - true); - install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"NoRepair", static_cast<DWORD>(1), - true); - - install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"Publisher", - browser_dist->GetPublisherName(), - true); - install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"Version", - UTF8ToWide(new_version.GetString()), - true); - install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"DisplayVersion", - UTF8ToWide(new_version.GetString()), - true); - time_t rawtime = time(NULL); - struct tm timeinfo = {0}; - localtime_s(&timeinfo, &rawtime); - wchar_t buffer[9]; - if (wcsftime(buffer, 9, L"%Y%m%d", &timeinfo) == 8) { - install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, - L"InstallDate", - buffer, false); - } - } -} - -void AddChromeFrameWorkItems(bool install, - const FilePath& setup_path, - const Version& new_version, - const Product& product, - WorkItemList* list) { - DCHECK(product.is_chrome_frame()); - if (!product.package().multi_install()) { - VLOG(1) << "Not adding GCF specific work items for single install."; - return; - } - - const MasterPreferences& prefs = MasterPreferences::ForCurrentProcess(); - - BrowserDistribution* cf = BrowserDistribution::GetSpecificDistribution( - BrowserDistribution::CHROME_FRAME, prefs); - std::wstring version_key(cf->GetVersionKey()); - - // TODO(tommi): This assumes we know exactly how ShouldCreateUninstallEntry - // is implemented. Since there is logic in ChromeFrameDistribution for how - // to determine when this is enabled, this is how we have to figure out if - // this feature is enabled right now, but it's a hack and we need a cleaner - // way to figure this out. - // Note that we cannot just check the master preferences for - // kChromeFrameReadyMode, since there are other things that need to be correct - // in the environment in order to enable this feature. - bool ready_mode = !product.distribution()->ShouldCreateUninstallEntry(); - - HKEY root = product.package().system_level() ? HKEY_LOCAL_MACHINE : - HKEY_CURRENT_USER; - bool update_chrome_uninstall_command = false; - if (ready_mode) { - // If GCF is being installed in ready mode, we write an entry to the - // multi-install state key. If the value already exists, we will not - // overwrite it since the user might have opted out. - list->AddCreateRegKeyWorkItem(root, - product.package().properties()->GetStateKey()); - list->AddSetRegValueWorkItem(root, - product.package().properties()->GetStateKey(), - installer::kChromeFrameReadyModeField, - static_cast<int64>(install ? 1 : 0), // The value we want to set. - install ? false : true); // Overwrite existing value. - if (install) { - FilePath installer_path(product.package() - .GetInstallerDirectory(new_version).Append(setup_path.BaseName())); - - CommandLine basic_cl(installer_path); - basic_cl.AppendSwitch(installer::switches::kChromeFrame); - basic_cl.AppendSwitch(installer::switches::kMultiInstall); - - if (product.package().system_level()) - basic_cl.AppendSwitch(installer::switches::kSystemLevel); - - if (InstallUtil::IsChromeSxSProcess()) - basic_cl.AppendSwitch(installer::switches::kChromeSxS); - - CommandLine temp_opt_out(basic_cl); - temp_opt_out.AppendSwitch( - installer::switches::kChromeFrameReadyModeTempOptOut); - - CommandLine end_temp_opt_out(basic_cl); - end_temp_opt_out.AppendSwitch( - installer::switches::kChromeFrameReadyModeEndTempOptOut); - - CommandLine opt_out(installer_path); - AppendUninstallCommandLineFlags(&opt_out, product); - // Force Uninstall silences the prompt to reboot to complete uninstall. - opt_out.AppendSwitch(installer::switches::kForceUninstall); - - CommandLine opt_in(basic_cl); - opt_in.AppendSwitch( - installer::switches::kChromeFrameReadyModeOptIn); - - list->AddSetRegValueWorkItem(root, version_key, - google_update::kRegCFTempOptOutCmdField, - temp_opt_out.command_line_string(), true); - list->AddSetRegValueWorkItem(root, version_key, - google_update::kRegCFEndTempOptOutCmdField, - end_temp_opt_out.command_line_string(), - true); - list->AddSetRegValueWorkItem(root, version_key, - google_update::kRegCFOptOutCmdField, - opt_out.command_line_string(), true); - list->AddSetRegValueWorkItem(root, version_key, - google_update::kRegCFOptInCmdField, - opt_in.command_line_string(), true); - } else { - // If Chrome is not also being uninstalled, we need to update its command - // line so that it doesn't include uninstalling Chrome Frame now. - update_chrome_uninstall_command = - (installer::FindProduct(product.package().products(), - BrowserDistribution::CHROME_BROWSER) == NULL); - } - } else { - // It doesn't matter here if we're installing or uninstalling Chrome Frame. - // If ready mode isn't specified on the command line for installs, we need - // to delete the ready mode flag from the registry if it exists - this - // constitutes an opt-in for the user. If we're uninstalling CF and ready - // mode isn't specified on the command line, that means that CF wasn't - // installed with ready mode enabled (the --ready-mode switch should be set - // in the registry) so deleting the value should have no effect. - // In both cases (install/uninstall), we need to make sure that Chrome's - // uninstallation command line does not include the --chrome-frame switch - // so that uninstalling Chrome will no longer uninstall Chrome Frame. - - if (RegKey(root, product.package().properties()->GetStateKey().c_str(), - KEY_QUERY_VALUE).Valid()) { - list->AddDeleteRegValueWorkItem(root, - product.package().properties()->GetStateKey(), - installer::kChromeFrameReadyModeField, REG_QWORD); - } - - const Product* chrome = installer::FindProduct(product.package().products(), - BrowserDistribution::CHROME_BROWSER); - if (chrome) { - // Chrome is already a part of this installation run, so we can assume - // that the uninstallation arguments will be updated correctly. - } else { - // Chrome is not a part of this installation run, so we have to explicitly - // check if Chrome is installed, and if so, update its uninstallation - // command lines. - BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution( - BrowserDistribution::CHROME_BROWSER, - MasterPreferences::ForCurrentProcess()); - update_chrome_uninstall_command = - IsInstalledAsMulti(product.system_level(), dist); - } - } - - if (!ready_mode || !install) { - list->AddDeleteRegValueWorkItem(root, version_key, - google_update::kRegCFTempOptOutCmdField, - REG_SZ); - list->AddDeleteRegValueWorkItem(root, version_key, - google_update::kRegCFEndTempOptOutCmdField, - REG_SZ); - list->AddDeleteRegValueWorkItem(root, version_key, - google_update::kRegCFOptOutCmdField, - REG_SZ); - list->AddDeleteRegValueWorkItem(root, version_key, - google_update::kRegCFOptInCmdField, REG_SZ); - } - - if (update_chrome_uninstall_command) { - // Chrome is not a part of this installation run, so we have to explicitly - // check if Chrome is installed, and if so, update its uninstallation - // command lines. - BrowserDistribution* chrome_dist = - BrowserDistribution::GetSpecificDistribution( - BrowserDistribution::CHROME_BROWSER, prefs); - const Package& pack = product.package(); - scoped_refptr<Package> package(new Package(pack.multi_install(), - pack.system_level(), pack.path(), pack.properties())); - scoped_refptr<Product> chrome_product(new Product(chrome_dist, package)); - AddUninstallShortcutWorkItems(setup_path, new_version, list, - *chrome_product.get()); - } -} - } // namespace installer diff --git a/chrome/installer/setup/install.h b/chrome/installer/setup/install.h index 7e8d465..c3ff033 100644 --- a/chrome/installer/setup/install.h +++ b/chrome/installer/setup/install.h @@ -51,40 +51,7 @@ InstallStatus InstallOrUpdateProduct( const installer::MasterPreferences& prefs, const Version& new_version, const Package& package); -// Appends registration or unregistration work items to |work_item_list| for the -// COM DLLs whose file names are given in |dll_files| and which reside in the -// path |dll_folder|. -// |system_level| specifies whether to call the system or user level DLL -// registration entry points. -// |do_register| says whether to register or unregister. -// |may_fail| states whether this is best effort or not. If |may_fail| is true -// then |work_item_list| will still succeed if the registration fails and -// no registration rollback will be performed. -void AddRegisterComDllWorkItems(const FilePath& dll_folder, - const std::vector<FilePath>& dll_files, - bool system_level, - bool do_register, - bool ignore_failures, - WorkItemList* work_item_list); -void AddSetMsiMarkerWorkItem(const Product& product, - bool set, - WorkItemList* work_item_list); - -// This method adds work items to create (or update) Chrome uninstall entry in -// either the Control Panel->Add/Remove Programs list or in the Omaha client -// state key if running under an MSI installer. -void AddUninstallShortcutWorkItems(const FilePath& setup_path, - const Version& new_version, - WorkItemList* install_list, - const Product& product); - -// Called for either installation or uninstallation. This method updates the -// registry according to Chrome Frame specific options for the current -// installation. This includes handling of the ready-mode option. -void AddChromeFrameWorkItems(bool install, const FilePath& setup_path, - const Version& new_version, const Product& product, - WorkItemList* list); } // namespace installer diff --git a/chrome/installer/setup/install_worker.cc b/chrome/installer/setup/install_worker.cc new file mode 100644 index 0000000..e945bdb --- /dev/null +++ b/chrome/installer/setup/install_worker.cc @@ -0,0 +1,872 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file contains the definitions of the installer functions that build +// the WorkItemList used to install the application. + +#include "chrome/installer/setup/install_worker.h" + +#include <shlobj.h> +#include <time.h> +#include <vector> + +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/version.h" +#include "base/win/registry.h" +#include "chrome/installer/setup/install.h" +#include "chrome/installer/setup/setup_constants.h" +#include "chrome/installer/util/chrome_frame_distribution.h" +#include "chrome/installer/util/conditional_work_item_list.h" +#include "chrome/installer/util/create_reg_key_work_item.h" +#include "chrome/installer/util/google_update_constants.h" +#include "chrome/installer/util/helper.h" +#include "chrome/installer/util/installation_state.h" +#include "chrome/installer/util/installer_state.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/master_preferences.h" +#include "chrome/installer/util/master_preferences_constants.h" +#include "chrome/installer/util/package.h" +#include "chrome/installer/util/package_properties.h" +#include "chrome/installer/util/product.h" +#include "chrome/installer/util/set_reg_value_work_item.h" +#include "chrome/installer/util/shell_util.h" +#include "chrome/installer/util/util_constants.h" +#include "chrome/installer/util/work_item_list.h" + +using base::win::RegKey; + +namespace { + +// This method tells if we are running on 64 bit platform so that we can copy +// one extra exe. If the API call to determine 64 bit fails, we play it safe +// and return true anyway so that the executable can be copied. +bool Is64bit() { + typedef BOOL (WINAPI* WOW_FUNC)(HANDLE, BOOL*); + BOOL is_64 = FALSE; + + HMODULE module = GetModuleHandle(L"kernel32.dll"); + WOW_FUNC is_wow64 = reinterpret_cast<WOW_FUNC>( + GetProcAddress(module, "IsWow64Process")); + return (is_wow64 != NULL) && + (!(is_wow64)(GetCurrentProcess(), &is_64) || (is_64 != FALSE)); +} + +} // namespace + +namespace installer { + +// Local helper to call AddRegisterComDllWorkItems for all DLLs in a set of +// products managed by a given package. +void AddRegisterComDllWorkItemsForPackage(const Package& package, + const Version* old_version, + const Version& new_version, + WorkItemList* work_item_list) { + // First collect the list of DLLs to be registered from each product. + const Products& products = package.products(); + Products::const_iterator product_iter(products.begin()); + std::vector<FilePath> com_dll_list; + for (; product_iter != products.end(); ++product_iter) { + BrowserDistribution* dist = product_iter->get()->distribution(); + std::vector<FilePath> dist_dll_list(dist->GetComDllList()); + com_dll_list.insert(com_dll_list.end(), dist_dll_list.begin(), + dist_dll_list.end()); + } + + // Then, if we got some, attempt to unregister the DLLs from the old + // version directory and then re-register them in the new one. + // Note that if we are migrating the install directory then we will not + // successfully unregister the old DLLs. + // TODO(robertshield): See whether we need to fix the migration case. + // TODO(robertshield): If we ever remove a DLL from a product, this will + // not unregister it on update. We should build the unregistration list from + // saved state instead of assuming it is the same as the registration list. + if (!com_dll_list.empty()) { + if (old_version) { + FilePath old_dll_path( + package.path().Append(UTF8ToWide(old_version->GetString()))); + + installer::AddRegisterComDllWorkItems(old_dll_path, + com_dll_list, + package.system_level(), + false, // Unregister + true, // May fail + work_item_list); + } + + FilePath dll_path( + package.path().Append(UTF8ToWide(new_version.GetString()))); + installer::AddRegisterComDllWorkItems(dll_path, + com_dll_list, + package.system_level(), + true, // Register + false, // Must succeed. + work_item_list); + } +} + +void AddInstallerCopyTasks(const FilePath& setup_path, + const FilePath& archive_path, + const FilePath& temp_path, + const Version& new_version, + WorkItemList* install_list, + const Package& package) { + DCHECK(install_list); + FilePath installer_dir(package.GetInstallerDirectory(new_version)); + install_list->AddCreateDirWorkItem(installer_dir); + + FilePath exe_dst(installer_dir.Append(setup_path.BaseName())); + FilePath archive_dst(installer_dir.Append(archive_path.BaseName())); + + install_list->AddCopyTreeWorkItem(setup_path.value(), exe_dst.value(), + temp_path.value(), WorkItem::ALWAYS); + if (package.system_level()) { + install_list->AddCopyTreeWorkItem(archive_path.value(), archive_dst.value(), + temp_path.value(), WorkItem::ALWAYS); + } else { + install_list->AddMoveTreeWorkItem(archive_path.value(), archive_dst.value(), + temp_path.value()); + } +} + +// This method adds work items to create (or update) Chrome uninstall entry in +// either the Control Panel->Add/Remove Programs list or in the Omaha client +// state key if running under an MSI installer. +void AddUninstallShortcutWorkItems(const FilePath& setup_path, + const Version& new_version, + WorkItemList* install_list, + const Product& product) { + HKEY reg_root = product.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + BrowserDistribution* browser_dist = product.distribution(); + DCHECK(browser_dist); + + // When we are installed via an MSI, we need to store our uninstall strings + // in the Google Update client state key. We do this even for non-MSI + // managed installs to avoid breaking the edge case whereby an MSI-managed + // install is updated by a non-msi installer (which would confuse the MSI + // machinery if these strings were not also updated). + // Do not quote the command line for the MSI invocation. + FilePath install_path(product.package().path()); + FilePath installer_path( + product.package().GetInstallerDirectory(new_version)); + installer_path = installer_path.Append(setup_path.BaseName()); + + CommandLine uninstall_arguments(CommandLine::NO_PROGRAM); + AppendUninstallCommandLineFlags(&uninstall_arguments, product); + + if (product.is_chrome()) { + // The Chrome uninstallation command serves as the master uninstall + // command for Chrome + all other products (i.e. Chrome Frame) that do + // not have an uninstall entry in the Add/Remove Programs dialog. + const Products& products = product.package().products(); + for (size_t i = 0; i < products.size(); ++i) { + const Product& p = *products[i]; + if (!p.is_chrome() && !p.ShouldCreateUninstallEntry()) { + p.distribution()->AppendUninstallCommandLineFlags(&uninstall_arguments); + } + } + } + + std::wstring update_state_key(browser_dist->GetStateKey()); + install_list->AddCreateRegKeyWorkItem(reg_root, update_state_key); + install_list->AddSetRegValueWorkItem(reg_root, update_state_key, + installer::kUninstallStringField, installer_path.value(), true); + install_list->AddSetRegValueWorkItem(reg_root, update_state_key, + installer::kUninstallArgumentsField, + uninstall_arguments.command_line_string(), true); + + if (product.ShouldCreateUninstallEntry()) { + // We need to quote the command line for the Add/Remove Programs dialog. + CommandLine quoted_uninstall_cmd(installer_path); + DCHECK_EQ(quoted_uninstall_cmd.command_line_string()[0], '"'); + quoted_uninstall_cmd.AppendArguments(uninstall_arguments, false); + + std::wstring uninstall_reg = browser_dist->GetUninstallRegPath(); + install_list->AddCreateRegKeyWorkItem(reg_root, uninstall_reg); + install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, + installer::kUninstallDisplayNameField, + browser_dist->GetAppShortCutName(), true); + install_list->AddSetRegValueWorkItem(reg_root, + uninstall_reg, installer::kUninstallStringField, + quoted_uninstall_cmd.command_line_string(), true); + install_list->AddSetRegValueWorkItem(reg_root, + uninstall_reg, + L"InstallLocation", + install_path.value(), + true); + + // DisplayIcon, NoModify and NoRepair + FilePath chrome_icon(install_path.Append(installer::kChromeExe)); + ShellUtil::GetChromeIcon(product.distribution(), chrome_icon.value()); + install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, + L"DisplayIcon", chrome_icon.value(), + true); + install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, + L"NoModify", static_cast<DWORD>(1), + true); + install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, + L"NoRepair", static_cast<DWORD>(1), + true); + + install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, + L"Publisher", + browser_dist->GetPublisherName(), + true); + install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, + L"Version", + UTF8ToWide(new_version.GetString()), + true); + install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, + L"DisplayVersion", + UTF8ToWide(new_version.GetString()), + true); + time_t rawtime = time(NULL); + struct tm timeinfo = {0}; + localtime_s(&timeinfo, &rawtime); + wchar_t buffer[9]; + if (wcsftime(buffer, 9, L"%Y%m%d", &timeinfo) == 8) { + install_list->AddSetRegValueWorkItem(reg_root, uninstall_reg, + L"InstallDate", + buffer, false); + } + } +} + +// Create Version key for a product (if not already present) and sets the new +// product version as the last step. +void AddVersionKeyWorkItems(HKEY root, + const Product& product, + const Version& new_version, + WorkItemList* list) { + // Create Version key for each distribution (if not already present) and set + // the new product version as the last step. + std::wstring version_key(product.distribution()->GetVersionKey()); + list->AddCreateRegKeyWorkItem(root, version_key); + + std::wstring product_name(product.distribution()->GetAppShortCutName()); + list->AddSetRegValueWorkItem(root, version_key, google_update::kRegNameField, + product_name, true); // overwrite name also + list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegOopcrashesField, + static_cast<DWORD>(1), + false); // set during first install + list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegVersionField, + UTF8ToWide(new_version.GetString()), + true); // overwrite version +} + +void AddProductSpecificWorkItems(bool install, + const FilePath& setup_path, + const Version& new_version, + const Package& package, + WorkItemList* list) { + const Products& products = package.products(); + for (size_t i = 0; i < products.size(); ++i) { + const Product& p = *products[i]; + if (p.is_chrome_frame()) { + AddChromeFrameWorkItems(install, setup_path, new_version, p, list); + } + } +} + +// Adds work items that make registry adjustments for Google Update. When a +// product is installed (including overinstall), Google Update will write the +// channel ("ap") value into either Chrome or Chrome Frame's ClientState key. +// In the multi-install case, this value is used as the basis upon which the +// package's channel value is built (by adding the ordered list of installed +// products and their options). +void AddGoogleUpdateWorkItems(const InstallationState& original_state, + const InstallerState& installer_state, + const Package& package, + WorkItemList* install_list) { + // Is a multi-install product being installed or over-installed? + if (installer_state.operation() != InstallerState::MULTI_INSTALL) + return; + + const HKEY reg_root = package.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + const std::wstring key_path = installer_state.state_key(); + ChannelInfo channel_info; + + // Update the "ap" value for the product being installed/updated. + // It is completely acceptable for there to be no "ap" value or even no + // ClientState key. Note that we check the registry rather than + // original_state since on a fresh install the "ap" value will be present + // sans "pv" value. + channel_info.Initialize(RegKey(reg_root, key_path.c_str(), KEY_QUERY_VALUE)); + + // This is a multi-install product. + bool modified = channel_info.SetMultiInstall(true); + + // Add the appropriate modifiers for all products and their options. + Products::const_iterator scan = package.products().begin(); + const Products::const_iterator end = package.products().end(); + for (; scan != end; ++scan) { + modified |= scan->get()->distribution()->SetChannelFlags(true, + &channel_info); + } + + // Write the results if needed. + if (modified) { + install_list->AddSetRegValueWorkItem(reg_root, key_path, + google_update::kRegApField, + channel_info.value(), true); + } + + // Synchronize the other products and the package with this one. + std::wstring other_key; + std::vector<std::wstring> keys; + + keys.reserve(package.products().size()); + other_key = package.properties()->GetStateKey(); + if (other_key != key_path) + keys.push_back(other_key); + scan = package.products().begin(); + for (; scan != end; ++scan) { + other_key = scan->get()->distribution()->GetStateKey(); + if (other_key != key_path) + keys.push_back(other_key); + } + + RegKey key; + ChannelInfo other_info; + std::vector<std::wstring>::const_iterator kscan = keys.begin(); + std::vector<std::wstring>::const_iterator kend = keys.end(); + for (; kscan != kend; ++kscan) { + // Handle the case where the ClientState key doesn't exist by creating it. + // This takes care of the multi-installer's package key, which is not + // created by Google Update for us. + if (!key.Open(reg_root, kscan->c_str(), KEY_QUERY_VALUE) || + !other_info.Initialize(key)) { + other_info.set_value(std::wstring()); + } + if (!other_info.Equals(channel_info)) { + if (!key.Valid()) + install_list->AddCreateRegKeyWorkItem(reg_root, *kscan); + install_list->AddSetRegValueWorkItem(reg_root, *kscan, + google_update::kRegApField, + channel_info.value(), true); + } + } + // TODO(grt): check for other keys/values we should put in the package's + // ClientState and/or Clients key. +} + +// This is called when an MSI installation is run. It may be that a user is +// attempting to install the MSI on top of a non-MSI managed installation. +// If so, try and remove any existing uninstallation shortcuts, as we want the +// uninstall to be managed entirely by the MSI machinery (accessible via the +// Add/Remove programs dialog). +void AddDeleteUninstallShortcutsForMSIWorkItems(const Product& product, + WorkItemList* work_item_list) { + DCHECK(product.IsMsi()) << "This must only be called for MSI installations!"; + + // First attempt to delete the old installation's ARP dialog entry. + HKEY reg_root = product.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + base::win::RegKey root_key(reg_root, L"", KEY_ALL_ACCESS); + std::wstring uninstall_reg(product.distribution()->GetUninstallRegPath()); + + WorkItem* delete_reg_key = work_item_list->AddDeleteRegKeyWorkItem( + reg_root, uninstall_reg); + delete_reg_key->set_ignore_failure(true); + + // Then attempt to delete the old installation's start menu shortcut. + FilePath uninstall_link; + if (product.system_level()) { + PathService::Get(base::DIR_COMMON_START_MENU, &uninstall_link); + } else { + PathService::Get(base::DIR_START_MENU, &uninstall_link); + } + + if (uninstall_link.empty()) { + LOG(ERROR) << "Failed to get location for shortcut."; + } else { + uninstall_link = uninstall_link.Append( + product.distribution()->GetAppShortCutName()); + uninstall_link = uninstall_link.Append( + product.distribution()->GetUninstallLinkName() + L".lnk"); + VLOG(1) << "Deleting old uninstall shortcut (if present): " + << uninstall_link.value(); + WorkItem* delete_link = work_item_list->AddDeleteTreeWorkItem( + uninstall_link); + delete_link->set_ignore_failure(true); + delete_link->set_log_message( + "Failed to delete old uninstall shortcut."); + } +} + +// After a successful copying of all the files, this function is called to +// do a few post install tasks: +// - Handle the case of in-use-update by updating "opv" (old version) key or +// deleting it if not required. +// - Register any new dlls and unregister old dlls. +// - If this is an MSI install, ensures that the MSI marker is set, and sets +// it if not. +// If these operations are successful, the function returns true, otherwise +// false. +bool AppendPostInstallTasks(bool multi_install, + const FilePath& setup_path, + const FilePath& new_chrome_exe, + const Version* current_version, + const Version& new_version, + const Package& package, + WorkItemList* post_install_task_list) { + DCHECK(post_install_task_list); + HKEY root = package.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + const Products& products = package.products(); + + + // Append work items that will only be executed if this was an update. + // We update the 'opv' key with the current version that is active and 'cmd' + // key with the rename command to run. + { + scoped_ptr<WorkItemList> in_use_update_work_items( + WorkItem::CreateConditionalWorkItemList( + new ConditionRunIfFileExists(new_chrome_exe))); + in_use_update_work_items->set_log_message("InUseUpdateWorkItemList"); + + FilePath installer_path(package.GetInstallerDirectory(new_version) + .Append(setup_path.BaseName())); + + CommandLine rename(installer_path); + rename.AppendSwitch(installer::switches::kRenameChromeExe); + if (package.system_level()) + rename.AppendSwitch(installer::switches::kSystemLevel); + + if (InstallUtil::IsChromeSxSProcess()) + rename.AppendSwitch(installer::switches::kChromeSxS); + + if (multi_install) + rename.AppendSwitch(installer::switches::kMultiInstall); + + std::wstring version_key; + for (size_t i = 0; i < products.size(); ++i) { + BrowserDistribution* dist = products[i]->distribution(); + version_key = dist->GetVersionKey(); + + if (current_version != NULL) { + in_use_update_work_items->AddSetRegValueWorkItem(root, version_key, + google_update::kRegOldVersionField, + UTF8ToWide(current_version->GetString()), true); + } + + // Adding this registry entry for all products is overkill. + // However, as it stands, we don't have a way to know which distribution + // will check the key and run the command, so we add it for all. + // After the first run, the subsequent runs should just be noops. + // (see Upgrade::SwapNewChromeExeIfPresent). + in_use_update_work_items->AddSetRegValueWorkItem( + root, + version_key, + google_update::kRegRenameCmdField, + rename.command_line_string(), + true); + } + + if (multi_install) { + PackageProperties* props = package.properties(); + if (props->ReceivesUpdates() && current_version != NULL) { + in_use_update_work_items->AddSetRegValueWorkItem( + root, + props->GetVersionKey(), + google_update::kRegOldVersionField, + UTF8ToWide(current_version->GetString()), + true); + // TODO(tommi): We should move the rename command here. We also need to + // update Upgrade::SwapNewChromeExeIfPresent. + } + } + + post_install_task_list->AddWorkItem(in_use_update_work_items.release()); + } + + // Append work items that will be executed if this was NOT an in-use update. + { + scoped_ptr<WorkItemList> regular_update_work_items( + WorkItem::CreateConditionalWorkItemList( + new Not(new ConditionRunIfFileExists(new_chrome_exe)))); + regular_update_work_items->set_log_message( + "RegularUpdateWorkItemList"); + + // Since this was not an in-use-update, delete 'opv' and 'cmd' keys. + for (size_t i = 0; i < products.size(); ++i) { + BrowserDistribution* dist = products[i]->distribution(); + std::wstring version_key(dist->GetVersionKey()); + regular_update_work_items->AddDeleteRegValueWorkItem(root, version_key, + google_update::kRegOldVersionField, + true); + regular_update_work_items->AddDeleteRegValueWorkItem(root, version_key, + google_update::kRegRenameCmdField, + true); + } + + post_install_task_list->AddWorkItem(regular_update_work_items.release()); + } + + AddRegisterComDllWorkItemsForPackage(package, current_version, new_version, + post_install_task_list); + + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + // If we're told that we're an MSI install, make sure to set the marker + // in the client state key so that future updates do the right thing. + if (product->IsMsi()) { + AddSetMsiMarkerWorkItem(*product, true, post_install_task_list); + + // We want MSI installs to take over the Add/Remove Programs shortcut. + // Make a best-effort attempt to delete any shortcuts left over from + // previous non-MSI installations for the same type of install (system or + // per user). + AddDeleteUninstallShortcutsForMSIWorkItems(*product, + post_install_task_list); + } + } + + return true; +} + +void AddInstallWorkItems(const InstallationState& original_state, + const InstallerState& installer_state, + bool multi_install, + const FilePath& setup_path, + const FilePath& archive_path, + const FilePath& src_path, + const FilePath& temp_dir, + const Version& new_version, + scoped_ptr<Version>* current_version, + const Package& package, + WorkItemList* install_list) { + DCHECK(install_list); + + // A temp directory that work items need and the actual install directory. + install_list->AddCreateDirWorkItem(temp_dir); + install_list->AddCreateDirWorkItem(package.path()); + + // Delete any new_chrome.exe if present (we will end up creating a new one + // if required) and then copy chrome.exe + FilePath new_chrome_exe( + package.path().Append(installer::kChromeNewExe)); + + install_list->AddDeleteTreeWorkItem(new_chrome_exe); + install_list->AddCopyTreeWorkItem( + src_path.Append(installer::kChromeExe).value(), + package.path().Append(installer::kChromeExe).value(), + temp_dir.value(), WorkItem::NEW_NAME_IF_IN_USE, new_chrome_exe.value()); + + // Extra executable for 64 bit systems. + if (Is64bit()) { + install_list->AddCopyTreeWorkItem( + src_path.Append(installer::kWowHelperExe).value(), + package.path().Append(installer::kWowHelperExe).value(), + temp_dir.value(), WorkItem::ALWAYS); + } + + // If it is system level install copy the version folder (since we want to + // take the permissions of %ProgramFiles% folder) otherwise just move it. + if (package.system_level()) { + install_list->AddCopyTreeWorkItem( + src_path.Append(UTF8ToWide(new_version.GetString())).value(), + package.path().Append(UTF8ToWide(new_version.GetString())).value(), + temp_dir.value(), WorkItem::ALWAYS); + } else { + install_list->AddMoveTreeWorkItem( + src_path.Append(UTF8ToWide(new_version.GetString())).value(), + package.path().Append(UTF8ToWide(new_version.GetString())).value(), + temp_dir.value()); + } + + // Copy the default Dictionaries only if the folder doesn't exist already. + install_list->AddCopyTreeWorkItem( + src_path.Append(installer::kDictionaries).value(), + package.path().Append(installer::kDictionaries).value(), + temp_dir.value(), WorkItem::IF_NOT_PRESENT); + + // Delete any old_chrome.exe if present. + install_list->AddDeleteTreeWorkItem( + package.path().Append(installer::kChromeOldExe)); + + // Copy installer in install directory and + // add shortcut in Control Panel->Add/Remove Programs. + AddInstallerCopyTasks(setup_path, archive_path, temp_dir, new_version, + install_list, package); + + HKEY root = package.system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; + + const Products& products = package.products(); + for (size_t i = 0; i < products.size(); ++i) { + const Product* product = products[i]; + + AddUninstallShortcutWorkItems(setup_path, new_version, install_list, + *product); + + AddVersionKeyWorkItems(root, *product, new_version, install_list); + } + + if (multi_install) { + PackageProperties* props = package.properties(); + if (props->ReceivesUpdates()) { + std::wstring version_key(props->GetVersionKey()); + install_list->AddCreateRegKeyWorkItem(root, version_key); + install_list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegVersionField, + UTF8ToWide(new_version.GetString()), + true); // overwrite version + install_list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegNameField, + ASCIIToWide(installer::PackageProperties::kPackageProductName), + true); // overwrite name also + } + } + + // Add any remaining work items that involve special settings for + // each product. + AddProductSpecificWorkItems(true, setup_path, new_version, package, + install_list); + + AddGoogleUpdateWorkItems(original_state, installer_state, package, + install_list); + + // Append the tasks that run after the installation. + AppendPostInstallTasks(multi_install, + setup_path, + new_chrome_exe, + current_version->get(), + new_version, + package, + install_list); +} + +void AddRegisterComDllWorkItems(const FilePath& dll_folder, + const std::vector<FilePath>& dll_list, + bool system_level, + bool do_register, + bool ignore_failures, + WorkItemList* work_item_list) { + DCHECK(work_item_list); + if (dll_list.empty()) { + VLOG(1) << "No COM DLLs to register"; + } else { + std::vector<FilePath>::const_iterator dll_iter(dll_list.begin()); + for (; dll_iter != dll_list.end(); ++dll_iter) { + FilePath dll_path = dll_folder.Append(*dll_iter); + WorkItem* work_item = work_item_list->AddSelfRegWorkItem( + dll_path.value(), do_register, !system_level); + DCHECK(work_item); + work_item->set_ignore_failure(ignore_failures); + } + } +} + +void AddSetMsiMarkerWorkItem(const Product& product, + bool set, + WorkItemList* work_item_list) { + DCHECK(work_item_list); + BrowserDistribution* dist = product.distribution(); + HKEY reg_root = product.system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + DWORD msi_value = set ? 1 : 0; + WorkItem* set_msi_work_item = work_item_list->AddSetRegValueWorkItem( + reg_root, dist->GetStateKey(), google_update::kRegMSIField, + msi_value, true); + DCHECK(set_msi_work_item); + set_msi_work_item->set_ignore_failure(true); + set_msi_work_item->set_log_message("Could not write MSI marker!"); +} + +void AddChromeFrameWorkItems(bool install, + const FilePath& setup_path, + const Version& new_version, + const Product& product, + WorkItemList* list) { + DCHECK(product.is_chrome_frame()); + if (!product.package().multi_install()) { + VLOG(1) << "Not adding GCF specific work items for single install."; + return; + } + + const MasterPreferences& prefs = MasterPreferences::ForCurrentProcess(); + + BrowserDistribution* cf = BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::CHROME_FRAME, prefs); + std::wstring version_key(cf->GetVersionKey()); + + // TODO(tommi): This assumes we know exactly how ShouldCreateUninstallEntry + // is implemented. Since there is logic in ChromeFrameDistribution for how + // to determine when this is enabled, this is how we have to figure out if + // this feature is enabled right now, but it's a hack and we need a cleaner + // way to figure this out. + // Note that we cannot just check the master preferences for + // kChromeFrameReadyMode, since there are other things that need to be correct + // in the environment in order to enable this feature. + bool ready_mode = !product.distribution()->ShouldCreateUninstallEntry(); + + HKEY root = product.package().system_level() ? HKEY_LOCAL_MACHINE : + HKEY_CURRENT_USER; + bool update_chrome_uninstall_command = false; + if (ready_mode) { + // If GCF is being installed in ready mode, we write an entry to the + // multi-install state key. If the value already exists, we will not + // overwrite it since the user might have opted out. + list->AddCreateRegKeyWorkItem(root, + product.package().properties()->GetStateKey()); + list->AddSetRegValueWorkItem(root, + product.package().properties()->GetStateKey(), + installer::kChromeFrameReadyModeField, + static_cast<int64>(install ? 1 : 0), // The value we want to set. + install ? false : true); // Overwrite existing value. + if (install) { + FilePath installer_path(product.package() + .GetInstallerDirectory(new_version).Append(setup_path.BaseName())); + + CommandLine basic_cl(installer_path); + basic_cl.AppendSwitch(installer::switches::kChromeFrame); + basic_cl.AppendSwitch(installer::switches::kMultiInstall); + + if (product.package().system_level()) + basic_cl.AppendSwitch(installer::switches::kSystemLevel); + + if (InstallUtil::IsChromeSxSProcess()) + basic_cl.AppendSwitch(installer::switches::kChromeSxS); + + CommandLine temp_opt_out(basic_cl); + temp_opt_out.AppendSwitch( + installer::switches::kChromeFrameReadyModeTempOptOut); + + CommandLine end_temp_opt_out(basic_cl); + end_temp_opt_out.AppendSwitch( + installer::switches::kChromeFrameReadyModeEndTempOptOut); + + CommandLine opt_out(installer_path); + AppendUninstallCommandLineFlags(&opt_out, product); + // Force Uninstall silences the prompt to reboot to complete uninstall. + opt_out.AppendSwitch(installer::switches::kForceUninstall); + + CommandLine opt_in(basic_cl); + opt_in.AppendSwitch( + installer::switches::kChromeFrameReadyModeOptIn); + + list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegCFTempOptOutCmdField, + temp_opt_out.command_line_string(), true); + list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegCFEndTempOptOutCmdField, + end_temp_opt_out.command_line_string(), + true); + list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegCFOptOutCmdField, + opt_out.command_line_string(), true); + list->AddSetRegValueWorkItem(root, version_key, + google_update::kRegCFOptInCmdField, + opt_in.command_line_string(), true); + } else { + // If Chrome is not also being uninstalled, we need to update its command + // line so that it doesn't include uninstalling Chrome Frame now. + update_chrome_uninstall_command = + (installer::FindProduct(product.package().products(), + BrowserDistribution::CHROME_BROWSER) == NULL); + } + } else { + // It doesn't matter here if we're installing or uninstalling Chrome Frame. + // If ready mode isn't specified on the command line for installs, we need + // to delete the ready mode flag from the registry if it exists - this + // constitutes an opt-in for the user. If we're uninstalling CF and ready + // mode isn't specified on the command line, that means that CF wasn't + // installed with ready mode enabled (the --ready-mode switch should be set + // in the registry) so deleting the value should have no effect. + // In both cases (install/uninstall), we need to make sure that Chrome's + // uninstallation command line does not include the --chrome-frame switch + // so that uninstalling Chrome will no longer uninstall Chrome Frame. + + if (RegKey(root, product.package().properties()->GetStateKey().c_str(), + KEY_QUERY_VALUE).Valid()) { + list->AddDeleteRegValueWorkItem(root, + product.package().properties()->GetStateKey(), + installer::kChromeFrameReadyModeField, REG_QWORD); + } + + const Product* chrome = installer::FindProduct(product.package().products(), + BrowserDistribution::CHROME_BROWSER); + if (chrome) { + // Chrome is already a part of this installation run, so we can assume + // that the uninstallation arguments will be updated correctly. + } else { + // Chrome is not a part of this installation run, so we have to explicitly + // check if Chrome is installed, and if so, update its uninstallation + // command lines. + BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::CHROME_BROWSER, + MasterPreferences::ForCurrentProcess()); + update_chrome_uninstall_command = + IsInstalledAsMulti(product.system_level(), dist); + } + } + + if (!ready_mode || !install) { + list->AddDeleteRegValueWorkItem(root, version_key, + google_update::kRegCFTempOptOutCmdField, + REG_SZ); + list->AddDeleteRegValueWorkItem(root, version_key, + google_update::kRegCFEndTempOptOutCmdField, + REG_SZ); + list->AddDeleteRegValueWorkItem(root, version_key, + google_update::kRegCFOptOutCmdField, + REG_SZ); + list->AddDeleteRegValueWorkItem(root, version_key, + google_update::kRegCFOptInCmdField, REG_SZ); + } + + if (update_chrome_uninstall_command) { + // Chrome is not a part of this installation run, so we have to explicitly + // check if Chrome is installed, and if so, update its uninstallation + // command lines. + BrowserDistribution* chrome_dist = + BrowserDistribution::GetSpecificDistribution( + BrowserDistribution::CHROME_BROWSER, prefs); + const Package& pack = product.package(); + scoped_refptr<Package> package(new Package(pack.multi_install(), + pack.system_level(), pack.path(), pack.properties())); + scoped_refptr<Product> chrome_product(new Product(chrome_dist, package)); + AddUninstallShortcutWorkItems(setup_path, new_version, list, + *chrome_product.get()); + } +} + +void AppendUninstallCommandLineFlags(CommandLine* uninstall_cmd, + const Product& product) { + DCHECK(uninstall_cmd); + + uninstall_cmd->AppendSwitch(installer::switches::kUninstall); + + // Append the product-specific uninstall flags. + product.distribution()->AppendUninstallCommandLineFlags(uninstall_cmd); + if (product.IsMsi()) { + uninstall_cmd->AppendSwitch(installer::switches::kMsi); + // See comment in uninstall.cc where we check for the kDeleteProfile switch. + if (product.is_chrome_frame()) { + uninstall_cmd->AppendSwitch(installer::switches::kDeleteProfile); + } + } + if (product.system_level()) + uninstall_cmd->AppendSwitch(installer::switches::kSystemLevel); + + // Propagate switches obtained from preferences as well. + const MasterPreferences& prefs = MasterPreferences::ForCurrentProcess(); + if (prefs.is_multi_install()) { + uninstall_cmd->AppendSwitch(installer::switches::kMultiInstall); + } + bool value = false; + if (prefs.GetBool(installer::master_preferences::kVerboseLogging, + &value) && value) + uninstall_cmd->AppendSwitch(installer::switches::kVerboseLogging); +} + +} // namespace installer diff --git a/chrome/installer/setup/install_worker.h b/chrome/installer/setup/install_worker.h new file mode 100644 index 0000000..167ed9b --- /dev/null +++ b/chrome/installer/setup/install_worker.h @@ -0,0 +1,98 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file contains the declarations of the installer functions that build +// the WorkItemList used to install the application. + +#ifndef CHROME_INSTALLER_SETUP_INSTALL_WORKER_H_ +#define CHROME_INSTALLER_SETUP_INSTALL_WORKER_H_ +#pragma once + +#include <vector> + +#include "base/scoped_ptr.h" + +class CommandLine; +class FilePath; +class Version; +class WorkItemList; + +namespace installer { + +class InstallationState; +class InstallerState; +class Package; +class Product; + +// Builds the complete WorkItemList used to build the set of installation steps +// needed to lay down one or more installed products. +// +// setup_path: Path to the executable (setup.exe) as it will be copied +// to Chrome install folder after install is complete +// archive_path: Path to the archive (chrome.7z) as it will be copied +// to Chrome install folder after install is complete +// src_path: the path that contains a complete and unpacked Chrome package +// to be installed. +// temp_dir: the path of working directory used during installation. This path +// does not need to exist. +void AddInstallWorkItems(const InstallationState& original_state, + const InstallerState& installer_state, + bool multi_install, + const FilePath& setup_path, + const FilePath& archive_path, + const FilePath& src_path, + const FilePath& temp_dir, + const Version& new_version, + scoped_ptr<Version>* current_version, + const Package& package, + WorkItemList* install_list); + +// Appends registration or unregistration work items to |work_item_list| for the +// COM DLLs whose file names are given in |dll_files| and which reside in the +// path |dll_folder|. +// |system_level| specifies whether to call the system or user level DLL +// registration entry points. +// |do_register| says whether to register or unregister. +// |may_fail| states whether this is best effort or not. If |may_fail| is true +// then |work_item_list| will still succeed if the registration fails and +// no registration rollback will be performed. +void AddRegisterComDllWorkItems(const FilePath& dll_folder, + const std::vector<FilePath>& dll_files, + bool system_level, + bool do_register, + bool ignore_failures, + WorkItemList* work_item_list); + +void AddSetMsiMarkerWorkItem(const Product& product, + bool set, + WorkItemList* work_item_list); + +// Called for either installation or uninstallation. This method updates the +// registry according to Chrome Frame specific options for the current +// installation. This includes handling of the ready-mode option. +void AddChromeFrameWorkItems(bool install, const FilePath& setup_path, + const Version& new_version, const Product& product, + WorkItemList* list); + + +// This method adds work items to create (or update) Chrome uninstall entry in +// either the Control Panel->Add/Remove Programs list or in the Omaha client +// state key if running under an MSI installer. +void AddUninstallShortcutWorkItems(const FilePath& setup_path, + const Version& new_version, + WorkItemList* install_list, + const Product& product); + +void AddUninstallShortcutWorkItems(const FilePath& setup_path, + const Version& new_version, + WorkItemList* install_list, + const Product& product); + +// Utility method currently shared between install.cc and install_worker.cc +void AppendUninstallCommandLineFlags(CommandLine* uninstall_cmd, + const Product& product); + +} // namespace installer + +#endif // CHROME_INSTALLER_SETUP_INSTALL_WORKER_H_ diff --git a/chrome/installer/setup/install_worker_unittest.cc b/chrome/installer/setup/install_worker_unittest.cc new file mode 100644 index 0000000..51f066e --- /dev/null +++ b/chrome/installer/setup/install_worker_unittest.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/installer/setup/install_worker.h" + +#include "base/win/registry.h" +#include "base/version.h" +#include "chrome/installer/util/installation_state.h" +#include "chrome/installer/util/installer_state.h" +#include "chrome/installer/util/package.h" +#include "chrome/installer/util/package_properties.h" +#include "chrome/installer/util/work_item_list.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" + +using installer::ChromiumPackageProperties; +using installer::InstallationState; +using installer::InstallerState; +using installer::Package; +using installer::PackageProperties; +using installer::PackagePropertiesImpl; +using installer::ProductState; + +using ::testing::_; +using ::testing::AtLeast; + +// Mock classes to help with testing +//------------------------------------------------------------------------------ + +class MockWorkItemList : public WorkItemList { + public: + MockWorkItemList() {} + + MOCK_METHOD5(AddCopyTreeWorkItem, WorkItem*(const std::wstring&, + const std::wstring&, + const std::wstring&, + CopyOverWriteOption, + const std::wstring&)); + MOCK_METHOD1(AddCreateDirWorkItem, WorkItem* (const FilePath&)); + MOCK_METHOD2(AddCreateRegKeyWorkItem, WorkItem* (HKEY, const std::wstring&)); + MOCK_METHOD2(AddDeleteRegKeyWorkItem, WorkItem* (HKEY, const std::wstring&)); + MOCK_METHOD4(AddDeleteRegValueWorkItem, WorkItem* (HKEY, + const std::wstring&, + const std::wstring&, + bool)); + MOCK_METHOD2(AddDeleteTreeWorkItem, WorkItem* (const FilePath&, + const std::vector<FilePath>&)); + MOCK_METHOD1(AddDeleteTreeWorkItem, WorkItem* (const FilePath&)); + MOCK_METHOD3(AddMoveTreeWorkItem, WorkItem* (const std::wstring&, + const std::wstring&, + const std::wstring&)); + MOCK_METHOD5(AddSetRegValueWorkItem, WorkItem*(HKEY, + const std::wstring&, + const std::wstring&, + const std::wstring&, + bool)); + MOCK_METHOD5(AddSetRegValueWorkItem, WorkItem* (HKEY, + const std::wstring&, + const std::wstring&, + DWORD, + bool)); + MOCK_METHOD3(AddSelfRegWorkItem, WorkItem* (const std::wstring&, + bool, + bool)); +}; + +// Okay, so this isn't really a mock as such, but it does add setter methods +// to make it easier to build custom InstallationStates. +class MockInstallationState : public InstallationState { + public: + // Included for testing. + void SetMultiPackageState(bool system_install, + const ProductState& package_state) { + ProductState& target = + (system_install ? system_products_ : user_products_) + [MULTI_PACKAGE_INDEX]; + target.CopyFrom(package_state); + } + + // Included for testing. + void SetProductState(bool system_install, + BrowserDistribution::Type type, + const ProductState& product_state) { + ProductState& target = (system_install ? system_products_ : + user_products_)[IndexFromDistType(type)]; + target.CopyFrom(product_state); + } +}; + +class MockInstallerState : public InstallerState { + public: + void set_system_install(bool system_install) { + system_install_ = system_install; + } + + void set_operation(Operation operation) { operation_ = operation; } + + void set_state_key(const std::wstring& state_key) { + state_key_ = state_key; + } +}; + +// The test fixture +//------------------------------------------------------------------------------ + +class InstallWorkerTest : public testing::Test { + public: + virtual void SetUp() { + current_version_.reset(Version::GetVersionFromString("1.0.0.0")); + new_version_.reset(Version::GetVersionFromString("42.0.0.0")); + + // Don't bother ensuring that these paths exist. Since we're just + // building the work item lists and not running them, they shouldn't + // actually be touched. + archive_path_ = FilePath(L"C:\\UnlikelyPath\\Temp\\chrome_123\\chrome.7z"); + // TODO(robertshield): Take this from the BrowserDistribution once that + // no longer depends on MasterPreferences. + installation_path_ = FilePath(L"C:\\Program Files\\Google\\Chrome\\"); + src_path_ = + FilePath(L"C:\\UnlikelyPath\\Temp\\chrome_123\\source\\Chrome-bin"); + setup_path_ = FilePath(L"C:\\UnlikelyPath\\Temp\\CR_123.tmp\\setup.exe"); + temp_dir_ = FilePath(L"C:\\UnlikelyPath\\Temp\\chrome_123"); + } + + virtual void TearDown() { + + } + + MockInstallationState* BuildChromeSingleSystemInstallationState() { + scoped_ptr<MockInstallationState> installation_state( + new MockInstallationState()); + + ProductState product_state; + product_state.set_version(current_version_->Clone()); + // Do not call SetMultiPackageState since this is a single install. + installation_state->SetProductState(true, + BrowserDistribution::CHROME_BROWSER, + product_state); + + return installation_state.release(); + } + + MockInstallerState* BuildChromeSingleSystemInstallerState() { + scoped_ptr<MockInstallerState> installer_state(new MockInstallerState()); + + installer_state->set_system_install(true); + installer_state->set_operation(InstallerState::SINGLE_INSTALL_OR_UPDATE); + // Hope this next one isn't checked for now. + installer_state->set_state_key(L"PROBABLY_INVALID_REG_PATH"); + + return installer_state.release(); + } + + protected: + scoped_ptr<Version> current_version_; + scoped_ptr<Version> new_version_; + FilePath archive_path_; + FilePath installation_path_; + FilePath setup_path_; + FilePath src_path_; + FilePath temp_dir_; +}; + +// Tests +//------------------------------------------------------------------------------ + +TEST_F(InstallWorkerTest, TestInstallChromeSingleSystem) { + MockWorkItemList work_item_list; + + scoped_ptr<InstallationState> installation_state( + BuildChromeSingleSystemInstallationState()); + + scoped_ptr<InstallerState> installer_state( + BuildChromeSingleSystemInstallerState()); + + // This MUST outlive the package, since the package doesn't assume ownership + // of it. Note: This feels like an implementation bug: + // The PackageProperties <-> Package relationship is 1:1 and nothing else + // uses the PackageProperties. I have the feeling that PackageProperties, and + // perhaps Package itself should not exist at all. + scoped_ptr<PackageProperties> package_properties( + new ChromiumPackageProperties()); + + scoped_refptr<Package> package( + new Package(false, true, installation_path_, package_properties.get())); + + // Set up some expectations. + // TODO(robertshield): Set up some real expectations. + EXPECT_CALL(work_item_list, AddCopyTreeWorkItem(_,_,_,_,_)) + .Times(AtLeast(1)); + + AddInstallWorkItems(*installation_state.get(), + *installer_state.get(), + false, + setup_path_, + archive_path_, + src_path_, + temp_dir_, + *new_version_.get(), + ¤t_version_, + *package.get(), + &work_item_list); +} diff --git a/chrome/installer/setup/uninstall.cc b/chrome/installer/setup/uninstall.cc index f07e703..a886e3b 100644 --- a/chrome/installer/setup/uninstall.cc +++ b/chrome/installer/setup/uninstall.cc @@ -18,6 +18,7 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths_internal.h" #include "chrome/installer/setup/install.h" +#include "chrome/installer/setup/install_worker.h" #include "chrome/installer/setup/setup_constants.h" #include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/channel_info.h" diff --git a/chrome/installer/util/installation_state.cc b/chrome/installer/util/installation_state.cc index 5db624e..85023bf 100644 --- a/chrome/installer/util/installation_state.cc +++ b/chrome/installer/util/installation_state.cc @@ -108,14 +108,6 @@ const ProductState* InstallationState::GetMultiPackageState( return product_state.version_.get() == NULL ? NULL : &product_state; } -// Included for testing. -void InstallationState::SetMultiPackageState(bool system_install, - const ProductState& package_state) { - ProductState& target = - (system_install ? system_products_ : user_products_)[MULTI_PACKAGE_INDEX]; - target.CopyFrom(package_state); -} - const ProductState* InstallationState::GetProductState( bool system_install, BrowserDistribution::Type type) const { @@ -124,12 +116,4 @@ const ProductState* InstallationState::GetProductState( return product_state.version_.get() == NULL ? NULL : &product_state; } -// Included for testing. -void InstallationState::SetProductState(bool system_install, - BrowserDistribution::Type type, const ProductState& product_state) { - ProductState& target = (system_install ? system_products_ : - user_products_)[IndexFromDistType(type)]; - target.CopyFrom(product_state); -} - } // namespace installer diff --git a/chrome/installer/util/installation_state.h b/chrome/installer/util/installation_state.h index 29a4006..3657c62 100644 --- a/chrome/installer/util/installation_state.h +++ b/chrome/installer/util/installation_state.h @@ -55,22 +55,15 @@ class InstallationState { // Returns the state of the multi-installer package or NULL if no // multi-install products are installed. + // Caller does NOT assume ownership of returned pointer. const ProductState* GetMultiPackageState(bool system_install) const; - // Sets the state of the multi-installer package; intended for tests. - void SetMultiPackageState(bool system_install, - const ProductState& package_state); - // Returns the state of a product or NULL if not installed. + // Caller does NOT assume ownership of returned pointer. const ProductState* GetProductState(bool system_install, BrowserDistribution::Type type) const; - // Sets the state of a product; indended for tests. - void SetProductState(bool system_install, - BrowserDistribution::Type type, - const ProductState& product_state); - - private: + protected: enum { CHROME_BROWSER_INDEX, CHROME_FRAME_INDEX, @@ -89,6 +82,7 @@ class InstallationState { ProductState user_products_[NUM_PRODUCTS]; ProductState system_products_[NUM_PRODUCTS]; + private: DISALLOW_COPY_AND_ASSIGN(InstallationState); }; // class InstallationState diff --git a/chrome/installer/util/installer_state.cc b/chrome/installer/util/installer_state.cc index fd396da..69d2841 100644 --- a/chrome/installer/util/installer_state.cc +++ b/chrome/installer/util/installer_state.cc @@ -85,7 +85,6 @@ void InstallerState::Initialize(const MasterPreferences& prefs, } if (operand != NULL) { - install_operand_ = operand->GetType(); state_key_ = operand->GetStateKey(); } } diff --git a/chrome/installer/util/installer_state.h b/chrome/installer/util/installer_state.h index 214ae45..0bc2a5f 100644 --- a/chrome/installer/util/installer_state.h +++ b/chrome/installer/util/installer_state.h @@ -38,22 +38,18 @@ class InstallerState { Operation operation() const { return operation_; } - BrowserDistribution::Type GetInstallOperand() const { - return install_operand_; - } - // The ClientState key by which we interact with Google Update. const std::wstring& state_key() const { return state_key_; } - private: + protected: bool IsMultiInstallUpdate(const MasterPreferences& prefs, const InstallationState& machine_state); Operation operation_; - BrowserDistribution::Type install_operand_; std::wstring state_key_; bool system_install_; + private: DISALLOW_COPY_AND_ASSIGN(InstallerState); }; // class InstallerState diff --git a/chrome/installer/util/work_item_list.h b/chrome/installer/util/work_item_list.h index 3474c34..5792d0f 100644 --- a/chrome/installer/util/work_item_list.h +++ b/chrome/installer/util/work_item_list.h @@ -35,31 +35,32 @@ class WorkItemList : public WorkItem { // Add a WorkItem to the list. // A WorkItem can only be added to the list before the list's DO() is called. // Once a WorkItem is added to the list. The list owns the WorkItem. - void AddWorkItem(WorkItem* work_item); + virtual void AddWorkItem(WorkItem* work_item); // Add a CopyTreeWorkItem to the list of work items. - WorkItem* AddCopyTreeWorkItem(const std::wstring& source_path, - const std::wstring& dest_path, - const std::wstring& temp_dir, - CopyOverWriteOption overwrite_option, - const std::wstring& alternative_path = L""); + virtual WorkItem* AddCopyTreeWorkItem( + const std::wstring& source_path, + const std::wstring& dest_path, + const std::wstring& temp_dir, + CopyOverWriteOption overwrite_option, + const std::wstring& alternative_path = L""); // Add a CreateDirWorkItem that creates a directory at the given path. - WorkItem* AddCreateDirWorkItem(const FilePath& path); + virtual WorkItem* AddCreateDirWorkItem(const FilePath& path); // Add a CreateRegKeyWorkItem that creates a registry key at the given // path. - WorkItem* AddCreateRegKeyWorkItem(HKEY predefined_root, - const std::wstring& path); + virtual WorkItem* AddCreateRegKeyWorkItem(HKEY predefined_root, + const std::wstring& path); // Add a DeleteRegKeyWorkItem that deletes a registry key from the given // path. - WorkItem* AddDeleteRegKeyWorkItem(HKEY predefined_root, - const std::wstring& path); + virtual WorkItem* AddDeleteRegKeyWorkItem(HKEY predefined_root, + const std::wstring& path); // Add a DeleteRegValueWorkItem that deletes registry value of type REG_SZ // or REG_DWORD. - WorkItem* AddDeleteRegValueWorkItem(HKEY predefined_root, + virtual WorkItem* AddDeleteRegValueWorkItem(HKEY predefined_root, const std::wstring& key_path, const std::wstring& value_name, DWORD type); @@ -67,32 +68,33 @@ class WorkItemList : public WorkItem { // Add a DeleteTreeWorkItem that recursively deletes a file system // hierarchy at the given root path. A key file can be optionally specified // by key_path. - WorkItem* AddDeleteTreeWorkItem(const FilePath& root_path, - const std::vector<FilePath>& key_paths); + virtual WorkItem* AddDeleteTreeWorkItem( + const FilePath& root_path, + const std::vector<FilePath>& key_paths); // Same as above but without support for key files. - WorkItem* AddDeleteTreeWorkItem(const FilePath& root_path); + virtual WorkItem* AddDeleteTreeWorkItem(const FilePath& root_path); // Add a MoveTreeWorkItem to the list of work items. - WorkItem* AddMoveTreeWorkItem(const std::wstring& source_path, - const std::wstring& dest_path, - const std::wstring& temp_dir); + virtual WorkItem* AddMoveTreeWorkItem(const std::wstring& source_path, + const std::wstring& dest_path, + const std::wstring& temp_dir); // Add a SetRegValueWorkItem that sets a registry value with REG_SZ type // at the key with specified path. - WorkItem* AddSetRegValueWorkItem(HKEY predefined_root, - const std::wstring& key_path, - const std::wstring& value_name, - const std::wstring& value_data, - bool overwrite); + virtual WorkItem* AddSetRegValueWorkItem(HKEY predefined_root, + const std::wstring& key_path, + const std::wstring& value_name, + const std::wstring& value_data, + bool overwrite); // Add a SetRegValueWorkItem that sets a registry value with REG_DWORD type // at the key with specified path. - WorkItem* AddSetRegValueWorkItem(HKEY predefined_root, - const std::wstring& key_path, - const std::wstring& value_name, - DWORD value_data, - bool overwrite); + virtual WorkItem* AddSetRegValueWorkItem(HKEY predefined_root, + const std::wstring& key_path, + const std::wstring& value_name, + DWORD value_data, + bool overwrite); // Add a SetRegValueWorkItem that sets a registry value with REG_QWORD type // at the key with specified path. @@ -105,9 +107,9 @@ class WorkItemList : public WorkItem { // Add a SelfRegWorkItem that registers or unregisters a DLL at the // specified path. If user_level_registration is true, then alternate // registration and unregistration entry point names will be used. - WorkItem* AddSelfRegWorkItem(const std::wstring& dll_path, - bool do_register, - bool user_level_registration); + virtual WorkItem* AddSelfRegWorkItem(const std::wstring& dll_path, + bool do_register, + bool user_level_registration); protected: friend class WorkItem; |