| // Copyright (c) 2012 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/util/installer_state.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/file_util.h" |
| #include "base/file_version_info.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_handle.h" |
| #include "chrome/installer/util/delete_tree_work_item.h" |
| #include "chrome/installer/util/helper.h" |
| #include "chrome/installer/util/install_util.h" |
| #include "chrome/installer/util/installation_state.h" |
| #include "chrome/installer/util/master_preferences.h" |
| #include "chrome/installer/util/master_preferences_constants.h" |
| #include "chrome/installer/util/product.h" |
| #include "chrome/installer/util/work_item.h" |
| #include "chrome/installer/util/work_item_list.h" |
| |
| namespace installer { |
| |
| bool InstallerState::IsMultiInstallUpdate(const MasterPreferences& prefs, |
| const InstallationState& machine_state) { |
| // First, is the package present? |
| const ProductState* package = |
| machine_state.GetProductState(level_ == SYSTEM_LEVEL, |
| BrowserDistribution::CHROME_BINARIES); |
| if (package == NULL) { |
| // The multi-install package has not been installed, so it certainly isn't |
| // being updated. |
| return false; |
| } |
| |
| BrowserDistribution::Type types[2]; |
| size_t num_types = 0; |
| if (prefs.install_chrome()) |
| types[num_types++] = BrowserDistribution::CHROME_BROWSER; |
| if (prefs.install_chrome_frame()) |
| types[num_types++] = BrowserDistribution::CHROME_FRAME; |
| |
| for (const BrowserDistribution::Type* scan = &types[0], |
| *end = &types[num_types]; scan != end; ++scan) { |
| const ProductState* product = |
| machine_state.GetProductState(level_ == SYSTEM_LEVEL, *scan); |
| if (product == NULL) { |
| VLOG(2) << "It seems that distribution type " << *scan |
| << " is being installed for the first time."; |
| return false; |
| } |
| if (!product->channel().Equals(package->channel())) { |
| VLOG(2) << "It seems that distribution type " << *scan |
| << " is being over installed."; |
| return false; |
| } |
| } |
| |
| VLOG(2) << "It seems that the package is being updated."; |
| |
| return true; |
| } |
| |
| InstallerState::InstallerState() |
| : operation_(UNINITIALIZED), |
| multi_package_distribution_(NULL), |
| level_(UNKNOWN_LEVEL), |
| package_type_(UNKNOWN_PACKAGE_TYPE), |
| state_type_(BrowserDistribution::CHROME_BROWSER), |
| root_key_(NULL), |
| msi_(false), |
| verbose_logging_(false), |
| ensure_google_update_present_(false) { |
| } |
| |
| InstallerState::InstallerState(Level level) |
| : operation_(UNINITIALIZED), |
| multi_package_distribution_(NULL), |
| level_(UNKNOWN_LEVEL), |
| package_type_(UNKNOWN_PACKAGE_TYPE), |
| state_type_(BrowserDistribution::CHROME_BROWSER), |
| root_key_(NULL), |
| msi_(false), |
| verbose_logging_(false), |
| ensure_google_update_present_(false) { |
| // Use set_level() so that root_key_ is updated properly. |
| set_level(level); |
| } |
| |
| void InstallerState::Initialize(const CommandLine& command_line, |
| const MasterPreferences& prefs, |
| const InstallationState& machine_state) { |
| bool pref_bool; |
| if (!prefs.GetBool(master_preferences::kSystemLevel, &pref_bool)) |
| pref_bool = false; |
| set_level(pref_bool ? SYSTEM_LEVEL : USER_LEVEL); |
| |
| if (!prefs.GetBool(master_preferences::kVerboseLogging, &verbose_logging_)) |
| verbose_logging_ = false; |
| |
| if (!prefs.GetBool(master_preferences::kMultiInstall, &pref_bool)) |
| pref_bool = false; |
| set_package_type(pref_bool ? MULTI_PACKAGE : SINGLE_PACKAGE); |
| |
| if (!prefs.GetBool(master_preferences::kMsi, &msi_)) |
| msi_ = false; |
| |
| ensure_google_update_present_ = |
| command_line.HasSwitch(installer::switches::kEnsureGoogleUpdatePresent); |
| |
| const bool is_uninstall = command_line.HasSwitch(switches::kUninstall); |
| |
| if (prefs.install_chrome()) { |
| Product* p = AddProductFromPreferences( |
| BrowserDistribution::CHROME_BROWSER, prefs, machine_state); |
| VLOG(1) << (is_uninstall ? "Uninstall" : "Install") |
| << " distribution: " << p->distribution()->GetAppShortCutName(); |
| } |
| if (prefs.install_chrome_frame()) { |
| Product* p = AddProductFromPreferences( |
| BrowserDistribution::CHROME_FRAME, prefs, machine_state); |
| VLOG(1) << (is_uninstall ? "Uninstall" : "Install") |
| << " distribution: " << p->distribution()->GetAppShortCutName(); |
| } |
| |
| if (prefs.install_chrome_app_launcher()) { |
| Product* p = AddProductFromPreferences( |
| BrowserDistribution::CHROME_APP_HOST, prefs, machine_state); |
| VLOG(1) << (is_uninstall ? "Uninstall" : "Install") |
| << " distribution: " << p->distribution()->GetAppShortCutName(); |
| } |
| |
| if (!is_uninstall && is_multi_install()) { |
| bool need_binaries = false; |
| if (FindProduct(BrowserDistribution::CHROME_APP_HOST)) { |
| // App Host will happily use Chrome at system level, or binaries at system |
| // level, even if app host is user level. |
| const ProductState* chrome_state = machine_state.GetProductState( |
| true, // system level |
| BrowserDistribution::CHROME_BROWSER); |
| // If Chrome is at system-level, multi- or otherwise. We'll use it. |
| if (!chrome_state) { |
| const ProductState* binaries_state = machine_state.GetProductState( |
| true, // system level |
| BrowserDistribution::CHROME_BINARIES); |
| if (!binaries_state) |
| need_binaries = true; |
| } |
| } |
| |
| // Chrome/Chrome Frame multi need Binaries at their own level. |
| if (FindProduct(BrowserDistribution::CHROME_BROWSER)) |
| need_binaries = true; |
| |
| if (FindProduct(BrowserDistribution::CHROME_FRAME)) |
| need_binaries = true; |
| |
| if (need_binaries && !FindProduct(BrowserDistribution::CHROME_BINARIES)) { |
| // Force binaries to be installed/updated. |
| Product* p = AddProductFromPreferences( |
| BrowserDistribution::CHROME_BINARIES, prefs, machine_state); |
| VLOG(1) << "Install distribution: " |
| << p->distribution()->GetAppShortCutName(); |
| } |
| } |
| |
| if (is_uninstall && prefs.is_multi_install()) { |
| if (FindProduct(BrowserDistribution::CHROME_BROWSER)) { |
| // Uninstall each product of type |type| listed below based on the |
| // presence or absence of |switch_name| in that product's uninstall |
| // command. |
| const struct { |
| BrowserDistribution::Type type; |
| const char* switch_name; |
| bool switch_expected; |
| } conditional_additions[] = { |
| // If Chrome Frame is installed in Ready Mode, remove it with Chrome. |
| { BrowserDistribution::CHROME_FRAME, |
| switches::kChromeFrameReadyMode, |
| true }, |
| // If the App Host is installed, but not the App Launcher, remove it |
| // with Chrome. Note however that for system-level Chrome uninstalls, |
| // any installed user-level App Host will remain even if there is no |
| // App Launcher present (the orphaned app_host.exe will prompt the user |
| // for further action when executed). |
| { BrowserDistribution::CHROME_APP_HOST, |
| switches::kChromeAppLauncher, |
| false }, |
| }; |
| |
| for (size_t i = 0; i < arraysize(conditional_additions); ++i) { |
| const ProductState* product_state = machine_state.GetProductState( |
| system_install(), conditional_additions[i].type); |
| if (product_state != NULL && |
| product_state->uninstall_command().HasSwitch( |
| conditional_additions[i].switch_name) == |
| conditional_additions[i].switch_expected && |
| !FindProduct(conditional_additions[i].type)) { |
| Product* p = AddProductFromPreferences( |
| conditional_additions[i].type, prefs, machine_state); |
| VLOG(1) << "Uninstall distribution: " |
| << p->distribution()->GetAppShortCutName(); |
| } |
| } |
| } |
| |
| bool keep_binaries = false; |
| // Look for a multi-install product that is not the binaries and that is not |
| // being uninstalled. If not found, binaries are uninstalled too. |
| for (size_t i = 0; i < BrowserDistribution::NUM_TYPES; ++i) { |
| BrowserDistribution::Type type = |
| static_cast<BrowserDistribution::Type>(i); |
| |
| if (type == BrowserDistribution::CHROME_BINARIES) |
| continue; |
| |
| const ProductState* product_state = |
| machine_state.GetProductState(system_install(), type); |
| if (product_state == NULL) { |
| // The product is not installed. |
| continue; |
| } |
| |
| if (!product_state->is_multi_install() && |
| type != BrowserDistribution::CHROME_BROWSER) { |
| // The product is not sharing the binaries. It is ordinarily impossible |
| // for single-install Chrome to be installed along with any |
| // multi-install product. Treat single-install Chrome the same as any |
| // multi-install product just in case the impossible happens. |
| continue; |
| } |
| |
| // The product is installed. |
| |
| if (!FindProduct(type)) { |
| // The product is not being uninstalled. |
| if (type != BrowserDistribution::CHROME_APP_HOST) { |
| keep_binaries = true; |
| break; |
| } else { |
| // If binaries/chrome are at system-level, we can discard them at |
| // user-level... |
| if (!machine_state.GetProductState( |
| true, // system-level |
| BrowserDistribution::CHROME_BROWSER) && |
| !machine_state.GetProductState( |
| true, // system-level |
| BrowserDistribution::CHROME_BINARIES)) { |
| // ... otherwise keep them. |
| keep_binaries = true; |
| break; |
| } |
| |
| } |
| } |
| |
| // The product is being uninstalled. |
| } |
| if (!keep_binaries && |
| machine_state.GetProductState(system_install(), |
| BrowserDistribution::CHROME_BINARIES)) { |
| Product* p = AddProductFromPreferences( |
| BrowserDistribution::CHROME_BINARIES, prefs, machine_state); |
| VLOG(1) << (is_uninstall ? "Uninstall" : "Install") |
| << " distribution: " << p->distribution()->GetAppShortCutName(); |
| } |
| } |
| |
| BrowserDistribution* operand = NULL; |
| |
| if (is_uninstall) { |
| operation_ = UNINSTALL; |
| } else if (!prefs.is_multi_install()) { |
| // For a single-install, the current browser dist is the operand. |
| operand = BrowserDistribution::GetDistribution(); |
| operation_ = SINGLE_INSTALL_OR_UPDATE; |
| } else if (IsMultiInstallUpdate(prefs, machine_state)) { |
| // Updates driven by Google Update take place under the multi-installer's |
| // app guid. |
| operand = multi_package_distribution_; |
| operation_ = MULTI_UPDATE; |
| } else { |
| operation_ = MULTI_INSTALL; |
| } |
| |
| // Initial, over, and un-installs will take place under one of the |
| // product app guids (Chrome, Chrome Frame, App Host, or Binaries, in order of |
| // preference). |
| if (operand == NULL) { |
| BrowserDistribution::Type operand_distribution_type = |
| BrowserDistribution::CHROME_BINARIES; |
| if (prefs.install_chrome()) |
| operand_distribution_type = BrowserDistribution::CHROME_BROWSER; |
| else if (prefs.install_chrome_frame()) |
| operand_distribution_type = BrowserDistribution::CHROME_FRAME; |
| else if (prefs.install_chrome_app_launcher()) |
| operand_distribution_type = BrowserDistribution::CHROME_APP_HOST; |
| |
| operand = BrowserDistribution::GetSpecificDistribution( |
| operand_distribution_type); |
| } |
| |
| state_key_ = operand->GetStateKey(); |
| state_type_ = operand->GetType(); |
| |
| // Parse --critical-update-version=W.X.Y.Z |
| std::string critical_version_value( |
| command_line.GetSwitchValueASCII(switches::kCriticalUpdateVersion)); |
| critical_update_version_ = Version(critical_version_value); |
| } |
| |
| void InstallerState::set_level(Level level) { |
| level_ = level; |
| switch (level) { |
| case USER_LEVEL: |
| root_key_ = HKEY_CURRENT_USER; |
| break; |
| case SYSTEM_LEVEL: |
| root_key_ = HKEY_LOCAL_MACHINE; |
| break; |
| default: |
| DCHECK(level == UNKNOWN_LEVEL); |
| level_ = UNKNOWN_LEVEL; |
| root_key_ = NULL; |
| break; |
| } |
| } |
| |
| void InstallerState::set_package_type(PackageType type) { |
| package_type_ = type; |
| switch (type) { |
| case SINGLE_PACKAGE: |
| multi_package_distribution_ = NULL; |
| break; |
| case MULTI_PACKAGE: |
| multi_package_distribution_ = |
| BrowserDistribution::GetSpecificDistribution( |
| BrowserDistribution::CHROME_BINARIES); |
| break; |
| default: |
| DCHECK(type == UNKNOWN_PACKAGE_TYPE); |
| package_type_ = UNKNOWN_PACKAGE_TYPE; |
| multi_package_distribution_ = NULL; |
| break; |
| } |
| } |
| |
| // Returns the Chrome binaries directory for multi-install or |dist|'s directory |
| // otherwise. |
| base::FilePath InstallerState::GetDefaultProductInstallPath( |
| BrowserDistribution* dist) const { |
| DCHECK(dist); |
| DCHECK(package_type_ != UNKNOWN_PACKAGE_TYPE); |
| |
| if (package_type_ == SINGLE_PACKAGE) { |
| return GetChromeInstallPath(system_install(), dist); |
| } else { |
| return GetChromeInstallPath(system_install(), |
| BrowserDistribution::GetSpecificDistribution( |
| BrowserDistribution::CHROME_BINARIES)); |
| } |
| } |
| |
| // Evaluates a product's eligibility for participation in this operation. |
| // We never expect these checks to fail, hence they all terminate the process in |
| // debug builds. See the log messages for details. |
| bool InstallerState::CanAddProduct(const Product& product, |
| const base::FilePath* product_dir) const { |
| switch (package_type_) { |
| case SINGLE_PACKAGE: |
| if (!products_.empty()) { |
| LOG(DFATAL) << "Cannot process more than one single-install product."; |
| return false; |
| } |
| break; |
| case MULTI_PACKAGE: |
| if (!product.HasOption(kOptionMultiInstall)) { |
| LOG(DFATAL) << "Cannot process a single-install product with a " |
| "multi-install state."; |
| return false; |
| } |
| if (FindProduct(product.distribution()->GetType()) != NULL) { |
| LOG(DFATAL) << "Cannot process more than one product of the same type."; |
| return false; |
| } |
| if (!target_path_.empty()) { |
| base::FilePath default_dir; |
| if (product_dir == NULL) |
| default_dir = GetDefaultProductInstallPath(product.distribution()); |
| if (!base::FilePath::CompareEqualIgnoreCase( |
| (product_dir == NULL ? default_dir : *product_dir).value(), |
| target_path_.value())) { |
| LOG(DFATAL) << "Cannot process products in different directories."; |
| return false; |
| } |
| } |
| break; |
| default: |
| DCHECK_EQ(UNKNOWN_PACKAGE_TYPE, package_type_); |
| break; |
| } |
| return true; |
| } |
| |
| // Adds |product|, installed in |product_dir| to this object's collection. If |
| // |product_dir| is NULL, the product's default install location is used. |
| // Returns NULL if |product| is incompatible with this object. Otherwise, |
| // returns a pointer to the product (ownership is held by this object). |
| Product* InstallerState::AddProductInDirectory( |
| const base::FilePath* product_dir, |
| scoped_ptr<Product>* product) { |
| DCHECK(product != NULL); |
| DCHECK(product->get() != NULL); |
| const Product& the_product = *product->get(); |
| |
| if (!CanAddProduct(the_product, product_dir)) |
| return NULL; |
| |
| if (package_type_ == UNKNOWN_PACKAGE_TYPE) { |
| set_package_type(the_product.HasOption(kOptionMultiInstall) ? |
| MULTI_PACKAGE : SINGLE_PACKAGE); |
| } |
| |
| if (target_path_.empty()) { |
| if (product_dir == NULL) |
| target_path_ = GetDefaultProductInstallPath(the_product.distribution()); |
| else |
| target_path_ = *product_dir; |
| } |
| |
| if (state_key_.empty()) |
| state_key_ = the_product.distribution()->GetStateKey(); |
| |
| products_.push_back(product->release()); |
| return products_[products_.size() - 1]; |
| } |
| |
| Product* InstallerState::AddProduct(scoped_ptr<Product>* product) { |
| return AddProductInDirectory(NULL, product); |
| } |
| |
| // Adds a product of type |distribution_type| constructed on the basis of |
| // |prefs|, setting this object's msi flag if the product is represented in |
| // |machine_state| and is msi-installed. Returns the product that was added, |
| // or NULL if |state| is incompatible with this object. Ownership is not passed |
| // to the caller. |
| Product* InstallerState::AddProductFromPreferences( |
| BrowserDistribution::Type distribution_type, |
| const MasterPreferences& prefs, |
| const InstallationState& machine_state) { |
| scoped_ptr<Product> product_ptr( |
| new Product(BrowserDistribution::GetSpecificDistribution( |
| distribution_type))); |
| product_ptr->InitializeFromPreferences(prefs); |
| |
| Product* product = AddProductInDirectory(NULL, &product_ptr); |
| |
| if (product != NULL && !msi_) { |
| const ProductState* product_state = machine_state.GetProductState( |
| system_install(), distribution_type); |
| if (product_state != NULL) |
| msi_ = product_state->is_msi(); |
| } |
| |
| return product; |
| } |
| |
| Product* InstallerState::AddProductFromState( |
| BrowserDistribution::Type type, |
| const ProductState& state) { |
| scoped_ptr<Product> product_ptr( |
| new Product(BrowserDistribution::GetSpecificDistribution(type))); |
| product_ptr->InitializeFromUninstallCommand(state.uninstall_command()); |
| |
| // Strip off <version>/Installer/setup.exe; see GetInstallerDirectory(). |
| base::FilePath product_dir = |
| state.GetSetupPath().DirName().DirName().DirName(); |
| |
| Product* product = AddProductInDirectory(&product_dir, &product_ptr); |
| |
| if (product != NULL) |
| msi_ |= state.is_msi(); |
| |
| return product; |
| } |
| |
| bool InstallerState::system_install() const { |
| DCHECK(level_ == USER_LEVEL || level_ == SYSTEM_LEVEL); |
| return level_ == SYSTEM_LEVEL; |
| } |
| |
| bool InstallerState::is_multi_install() const { |
| DCHECK(package_type_ == SINGLE_PACKAGE || package_type_ == MULTI_PACKAGE); |
| return package_type_ != SINGLE_PACKAGE; |
| } |
| |
| bool InstallerState::RemoveProduct(const Product* product) { |
| ScopedVector<Product>::iterator it = |
| std::find(products_.begin(), products_.end(), product); |
| if (it != products_.end()) { |
| products_.weak_erase(it); |
| return true; |
| } |
| return false; |
| } |
| |
| const Product* InstallerState::FindProduct( |
| BrowserDistribution::Type distribution_type) const { |
| for (Products::const_iterator scan = products_.begin(), end = products_.end(); |
| scan != end; ++scan) { |
| if ((*scan)->is_type(distribution_type)) |
| return *scan; |
| } |
| return NULL; |
| } |
| |
| Version* InstallerState::GetCurrentVersion( |
| const InstallationState& machine_state) const { |
| DCHECK(!products_.empty()); |
| scoped_ptr<Version> current_version; |
| // If we're doing a multi-install, the current version may be either an |
| // existing multi or an existing single product that is being migrated |
| // in place (i.e., Chrome). In the latter case, there is no existing |
| // CHROME_BINARIES installation so we need to search for the product. |
| BrowserDistribution::Type prod_type; |
| if (package_type_ == MULTI_PACKAGE) { |
| prod_type = BrowserDistribution::CHROME_BINARIES; |
| if (machine_state.GetProductState(level_ == SYSTEM_LEVEL, |
| prod_type) == NULL) { |
| // Search for a product on which we're operating that is installed in our |
| // target directory. |
| Products::const_iterator end = products().end(); |
| for (Products::const_iterator scan = products().begin(); scan != end; |
| ++scan) { |
| BrowserDistribution::Type product_type = |
| (*scan)->distribution()->GetType(); |
| const ProductState* state = |
| machine_state.GetProductState(level_ == SYSTEM_LEVEL, product_type); |
| if (state != NULL && target_path_.IsParent(state->GetSetupPath())) { |
| prod_type = product_type; |
| break; |
| } |
| } |
| } |
| } else { |
| prod_type = products_[0]->distribution()->GetType(); |
| } |
| const ProductState* product_state = |
| machine_state.GetProductState(level_ == SYSTEM_LEVEL, prod_type); |
| |
| if (product_state != NULL) { |
| const Version* version = NULL; |
| |
| // Be aware that there might be a pending "new_chrome.exe" already in the |
| // installation path. If so, we use old_version, which holds the version of |
| // "chrome.exe" itself. |
| if (base::PathExists(target_path().Append(kChromeNewExe))) |
| version = product_state->old_version(); |
| |
| if (version == NULL) |
| version = &product_state->version(); |
| |
| current_version.reset(new Version(*version)); |
| } |
| |
| return current_version.release(); |
| } |
| |
| Version InstallerState::DetermineCriticalVersion( |
| const Version* current_version, |
| const Version& new_version) const { |
| DCHECK(current_version == NULL || current_version->IsValid()); |
| DCHECK(new_version.IsValid()); |
| if (critical_update_version_.IsValid() && |
| (current_version == NULL || |
| (current_version->CompareTo(critical_update_version_) < 0)) && |
| new_version.CompareTo(critical_update_version_) >= 0) { |
| return critical_update_version_; |
| } |
| return Version(); |
| } |
| |
| bool InstallerState::IsChromeFrameRunning( |
| const InstallationState& machine_state) const { |
| // We check only for the current version (e.g. the version we are upgrading |
| // _from_). We don't need to check interstitial versions if any (as would |
| // occur in the case of multiple updates) since if they are in use, we are |
| // guaranteed that the current version is in use too. |
| bool in_use = false; |
| scoped_ptr<Version> current_version(GetCurrentVersion(machine_state)); |
| if (current_version != NULL) { |
| base::FilePath cf_install_path( |
| target_path().AppendASCII(current_version->GetString()) |
| .Append(kChromeFrameDll)); |
| in_use = base::PathExists(cf_install_path) && |
| IsFileInUse(cf_install_path); |
| } |
| return in_use; |
| } |
| |
| base::FilePath InstallerState::GetInstallerDirectory( |
| const Version& version) const { |
| return target_path().Append(ASCIIToWide(version.GetString())) |
| .Append(kInstallerDir); |
| } |
| |
| // static |
| bool InstallerState::IsFileInUse(const base::FilePath& file) { |
| // Call CreateFile with a share mode of 0 which should cause this to fail |
| // with ERROR_SHARING_VIOLATION if the file exists and is in-use. |
| return !base::win::ScopedHandle(CreateFile(file.value().c_str(), |
| GENERIC_WRITE, 0, NULL, |
| OPEN_EXISTING, 0, 0)).IsValid(); |
| } |
| |
| void InstallerState::GetExistingExeVersions( |
| std::set<std::string>* existing_versions) const { |
| |
| static const wchar_t* const kChromeFilenames[] = { |
| installer::kChromeExe, |
| installer::kChromeNewExe, |
| installer::kChromeOldExe, |
| }; |
| |
| for (int i = 0; i < arraysize(kChromeFilenames); ++i) { |
| base::FilePath chrome_exe(target_path().Append(kChromeFilenames[i])); |
| scoped_ptr<FileVersionInfo> file_version_info( |
| FileVersionInfo::CreateFileVersionInfo(chrome_exe)); |
| if (file_version_info) { |
| string16 version_string = file_version_info->file_version(); |
| if (!version_string.empty() && IsStringASCII(version_string)) |
| existing_versions->insert(WideToASCII(version_string)); |
| } |
| } |
| } |
| |
| void InstallerState::RemoveOldVersionDirectories( |
| const Version& new_version, |
| Version* existing_version, |
| const base::FilePath& temp_path) const { |
| Version version; |
| std::vector<base::FilePath> key_files; |
| scoped_ptr<WorkItem> item; |
| |
| std::set<std::string> existing_version_strings; |
| existing_version_strings.insert(new_version.GetString()); |
| if (existing_version) |
| existing_version_strings.insert(existing_version->GetString()); |
| |
| // Make sure not to delete any version dir that is "referenced" by an existing |
| // Chrome executable. |
| GetExistingExeVersions(&existing_version_strings); |
| |
| // Try to delete all directories that are not in the set we care to keep. |
| base::FileEnumerator version_enum(target_path(), false, |
| base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath next_version = version_enum.Next(); !next_version.empty(); |
| next_version = version_enum.Next()) { |
| base::FilePath dir_name(next_version.BaseName()); |
| version = Version(WideToASCII(dir_name.value())); |
| // Delete the version folder if it is less than the new version and not |
| // equal to the old version (if we have an old version). |
| if (version.IsValid() && |
| existing_version_strings.count(version.GetString()) == 0) { |
| // Note: temporarily log old version deletion at ERROR level to make it |
| // more likely we see this in the installer log. |
| LOG(ERROR) << "Deleting old version directory: " << next_version.value(); |
| |
| // Attempt to recursively delete the old version dir. |
| bool delete_succeeded = base::DeleteFile(next_version, true); |
| |
| // Note: temporarily log old version deletion at ERROR level to make it |
| // more likely we see this in the installer log. |
| LOG_IF(ERROR, !delete_succeeded) |
| << "Failed to delete old version directory: " << next_version.value(); |
| } |
| } |
| } |
| |
| void InstallerState::AddComDllList( |
| std::vector<base::FilePath>* com_dll_list) const { |
| std::for_each(products_.begin(), products_.end(), |
| std::bind2nd(std::mem_fun(&Product::AddComDllList), |
| com_dll_list)); |
| } |
| |
| bool InstallerState::SetChannelFlags(bool set, |
| ChannelInfo* channel_info) const { |
| bool modified = false; |
| for (Products::const_iterator scan = products_.begin(), end = products_.end(); |
| scan != end; ++scan) { |
| modified |= (*scan)->SetChannelFlags(set, channel_info); |
| } |
| return modified; |
| } |
| |
| void InstallerState::UpdateStage(installer::InstallerStage stage) const { |
| InstallUtil::UpdateInstallerStage(system_install(), state_key_, stage); |
| } |
| |
| void InstallerState::UpdateChannels() const { |
| if (operation_ != MULTI_INSTALL && operation_ != MULTI_UPDATE) { |
| VLOG(1) << "InstallerState::UpdateChannels noop: " << operation_; |
| return; |
| } |
| |
| // Update the "ap" value for the product being installed/updated. We get the |
| // current value from the registry since the InstallationState instance used |
| // by the bulk of the installer does not track changes made by UpdateStage. |
| // Create the app's ClientState key if it doesn't exist. |
| ChannelInfo channel_info; |
| base::win::RegKey state_key; |
| LONG result = state_key.Create(root_key_, state_key_.c_str(), |
| KEY_QUERY_VALUE | KEY_SET_VALUE); |
| if (result == ERROR_SUCCESS) { |
| channel_info.Initialize(state_key); |
| |
| // This is a multi-install product. |
| bool modified = channel_info.SetMultiInstall(true); |
| |
| // Add the appropriate modifiers for all products and their options. |
| modified |= SetChannelFlags(true, &channel_info); |
| |
| VLOG(1) << "ap: " << channel_info.value(); |
| |
| // Write the results if needed. |
| if (modified) |
| channel_info.Write(&state_key); |
| |
| // Remove the -stage: modifier since we don't want to propagate that to the |
| // other app_guids. |
| channel_info.SetStage(NULL); |
| |
| // Synchronize the other products and the package with this one. |
| ChannelInfo other_info; |
| for (int i = 0; i < BrowserDistribution::NUM_TYPES; ++i) { |
| BrowserDistribution::Type type = |
| static_cast<BrowserDistribution::Type>(i); |
| // Skip the app_guid we started with. |
| if (type == state_type_) |
| continue; |
| BrowserDistribution* dist = NULL; |
| // Always operate on the binaries. |
| if (i == BrowserDistribution::CHROME_BINARIES) { |
| dist = multi_package_distribution_; |
| } else { |
| const Product* product = FindProduct(type); |
| // Skip this one if it's for a product we're not operating on. |
| if (product == NULL) |
| continue; |
| dist = product->distribution(); |
| } |
| result = state_key.Create(root_key_, dist->GetStateKey().c_str(), |
| KEY_QUERY_VALUE | KEY_SET_VALUE); |
| if (result == ERROR_SUCCESS) { |
| other_info.Initialize(state_key); |
| if (!other_info.Equals(channel_info)) |
| channel_info.Write(&state_key); |
| } else { |
| LOG(ERROR) << "Failed opening key " << dist->GetStateKey() |
| << " to update app channels; result: " << result; |
| } |
| } |
| } else { |
| LOG(ERROR) << "Failed opening key " << state_key_ |
| << " to update app channels; result: " << result; |
| } |
| } |
| |
| void InstallerState::WriteInstallerResult( |
| InstallStatus status, |
| int string_resource_id, |
| const std::wstring* const launch_cmd) const { |
| DWORD installer_result = |
| (InstallUtil::GetInstallReturnCode(status) == 0) ? 0 : 1; |
| // Use a no-rollback list since this is a best-effort deal. |
| scoped_ptr<WorkItemList> install_list( |
| WorkItem::CreateNoRollbackWorkItemList()); |
| const bool system_install = this->system_install(); |
| // Write the value for all products upon which we're operating. |
| Products::const_iterator end = products().end(); |
| for (Products::const_iterator scan = products().begin(); scan != end; |
| ++scan) { |
| InstallUtil::AddInstallerResultItems( |
| system_install, (*scan)->distribution()->GetStateKey(), status, |
| string_resource_id, launch_cmd, install_list.get()); |
| } |
| // And for the binaries if this is a multi-install. |
| if (is_multi_install()) { |
| InstallUtil::AddInstallerResultItems( |
| system_install, multi_package_binaries_distribution()->GetStateKey(), |
| status, string_resource_id, launch_cmd, install_list.get()); |
| } |
| if (!install_list->Do()) |
| LOG(ERROR) << "Failed to record installer error information in registry."; |
| } |
| |
| bool InstallerState::RequiresActiveSetup() const { |
| return system_install() && FindProduct(BrowserDistribution::CHROME_BROWSER); |
| } |
| |
| } // namespace installer |