// 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 #include #include #include #include #include "base/at_exit.h" #include "base/basictypes.h" #include "base/command_line.h" #include "base/file_util.h" #include "base/file_version_info.h" #include "base/path_service.h" #include "base/process_util.h" #include "base/scoped_temp_dir.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "base/win/registry.h" #include "base/win/scoped_handle.h" #include "base/win/win_util.h" #include "base/win/windows_version.h" #include "breakpad/src/client/windows/handler/exception_handler.h" #include "chrome/common/chrome_switches.h" #include "chrome/installer/setup/chrome_frame_quick_enable.h" #include "chrome/installer/setup/chrome_frame_ready_mode.h" #include "chrome/installer/setup/install.h" #include "chrome/installer/setup/setup_constants.h" #include "chrome/installer/setup/setup_util.h" #include "chrome/installer/setup/uninstall.h" #include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/channel_info.h" #include "chrome/installer/util/delete_after_reboot_helper.h" #include "chrome/installer/util/delete_tree_work_item.h" #include "chrome/installer/util/google_update_settings.h" #include "chrome/installer/util/google_update_constants.h" #include "chrome/installer/util/helper.h" #include "chrome/installer/util/html_dialog.h" #include "chrome/installer/util/install_util.h" #include "chrome/installer/util/installation_validator.h" #include "chrome/installer/util/installer_state.h" #include "chrome/installer/util/installation_state.h" #include "chrome/installer/util/l10n_string_util.h" #include "chrome/installer/util/logging_installer.h" #include "chrome/installer/util/lzma_util.h" #include "chrome/installer/util/master_preferences.h" #include "chrome/installer/util/master_preferences_constants.h" #include "chrome/installer/util/self_cleaning_temp_dir.h" #include "chrome/installer/util/shell_util.h" #include "chrome/installer/util/util_constants.h" #include "installer_util_strings.h" // NOLINT using installer::InstallerState; using installer::InstallationState; using installer::InstallationValidator; using installer::Product; using installer::ProductState; using installer::Products; using installer::MasterPreferences; const wchar_t kChromePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\"; const wchar_t kSystemPrincipalSid[] = L"S-1-5-18"; const MINIDUMP_TYPE kLargerDumpType = static_cast( MiniDumpWithProcessThreadData | // Get PEB and TEB. MiniDumpWithUnloadedModules | // Get unloaded modules when available. MiniDumpWithIndirectlyReferencedMemory); // Get memory referenced by stack. namespace { // This method unpacks and uncompresses the given archive file. For Chrome // install we are creating a uncompressed archive that contains all the files // needed for the installer. This uncompressed archive is later compressed. // // This method first uncompresses archive specified by parameter "archive" // and assumes that it will result in an uncompressed full archive file // (chrome.7z) or uncompressed archive patch file (chrome_patch.diff). If it // is patch file, it is applied to the old archive file that should be // present on the system already. As the final step the new archive file // is unpacked in the path specified by parameter "output_directory". DWORD UnPackArchive(const FilePath& archive, const InstallerState& installer_state, const FilePath& temp_path, const FilePath& output_directory, installer::ArchiveType* archive_type) { DCHECK(archive_type); installer_state.UpdateStage(installer::UNCOMPRESSING); // First uncompress the payload. This could be a differential // update (patch.7z) or full archive (chrome.7z). If this uncompress fails // return with error. std::wstring unpacked_file; int32 ret = LzmaUtil::UnPackArchive(archive.value(), temp_path.value(), &unpacked_file); if (ret != NO_ERROR) return ret; FilePath uncompressed_archive(temp_path.Append(installer::kChromeArchive)); scoped_ptr archive_version( installer::GetMaxVersionFromArchiveDir(installer_state.target_path())); // Check if this is differential update and if it is, patch it to the // installer archive that should already be on the machine. We assume // it is a differential installer if chrome.7z is not found. if (!file_util::PathExists(uncompressed_archive)) { *archive_type = installer::INCREMENTAL_ARCHIVE_TYPE; VLOG(1) << "Differential patch found. Applying to existing archive."; if (!archive_version.get()) { LOG(ERROR) << "Can not use differential update when Chrome is not " << "installed on the system."; return installer::CHROME_NOT_INSTALLED; } FilePath existing_archive(installer_state.target_path().AppendASCII( archive_version->GetString())); existing_archive = existing_archive.Append(installer::kInstallerDir); existing_archive = existing_archive.Append(installer::kChromeArchive); if (int i = installer::ApplyDiffPatch(existing_archive, FilePath(unpacked_file), uncompressed_archive, &installer_state)) { LOG(ERROR) << "Binary patching failed with error " << i; return i; } } else { *archive_type = installer::FULL_ARCHIVE_TYPE; } installer_state.UpdateStage(installer::UNPACKING); // Unpack the uncompressed archive. return LzmaUtil::UnPackArchive(uncompressed_archive.value(), output_directory.value(), &unpacked_file); } // This function is called when --rename-chrome-exe option is specified on // setup.exe command line. This function assumes an in-use update has happened // for Chrome so there should be a file called new_chrome.exe on the file // system and a key called 'opv' in the registry. This function will move // new_chrome.exe to chrome.exe and delete 'opv' key in one atomic operation. installer::InstallStatus RenameChromeExecutables( const InstallerState& installer_state) { const FilePath &target_path = installer_state.target_path(); FilePath chrome_exe(target_path.Append(installer::kChromeExe)); FilePath chrome_new_exe(target_path.Append(installer::kChromeNewExe)); FilePath chrome_old_exe(target_path.Append(installer::kChromeOldExe)); // Create a temporary backup directory on the same volume as chrome.exe so // that moving in-use files doesn't lead to trouble. installer::SelfCleaningTempDir temp_path; if (!temp_path.Initialize(target_path.DirName(), installer::kInstallTempDir)) { PLOG(ERROR) << "Failed to create Temp directory " << target_path.DirName() .Append(installer::kInstallTempDir).value(); return installer::RENAME_FAILED; } scoped_ptr install_list(WorkItem::CreateWorkItemList()); // Move chrome.exe to old_chrome.exe, then move new_chrome.exe to chrome.exe. install_list->AddMoveTreeWorkItem(chrome_exe.value(), chrome_old_exe.value(), temp_path.path().value()); install_list->AddMoveTreeWorkItem(chrome_new_exe.value(), chrome_exe.value(), temp_path.path().value()); install_list->AddDeleteTreeWorkItem(chrome_new_exe, temp_path.path()); // old_chrome.exe is still in use in most cases, so ignore failures here. install_list->AddDeleteTreeWorkItem(chrome_old_exe, temp_path.path()) ->set_ignore_failure(true); HKEY reg_root = installer_state.root_key(); const Products& products = installer_state.products(); for (size_t i = 0; i < products.size(); ++i) { const Product* product = products[i]; BrowserDistribution* browser_dist = product->distribution(); std::wstring version_key(browser_dist->GetVersionKey()); install_list->AddDeleteRegValueWorkItem(reg_root, version_key, google_update::kRegOldVersionField); install_list->AddDeleteRegValueWorkItem(reg_root, version_key, google_update::kRegRenameCmdField); } installer::InstallStatus ret = installer::RENAME_SUCCESSFUL; if (!install_list->Do()) { LOG(ERROR) << "Renaming of executables failed. Rolling back any changes."; install_list->Rollback(); ret = installer::RENAME_FAILED; } // temp_path's dtor will take care of deleting or scheduling itself for // deletion at reboot when this scope closes. VLOG(1) << "Deleting temporary directory " << temp_path.path().value(); return ret; } // The supported multi-install modes are: // --multi-install --chrome --chrome-frame --ready-mode // - If a non-multi Chrome Frame installation is present, Chrome Frame is // removed from |installer_state|'s list of products (thereby preserving // the existing SxS install). // - If a multi Chrome Frame installation is present, its options are // preserved (i.e., the --ready-mode command-line option is ignored). // --multi-install --chrome-frame // - If a non-multi Chrome Frame installation is present, fail. // - If a Chrome installation on a different channel is present, fail. // - If --ready-mode and no Chrome installation is present, fail. // - If a Chrome installation is present, add it to the set of products to // install. bool CheckMultiInstallConditions(const InstallationState& original_state, InstallerState* installer_state, installer::InstallStatus* status) { const Products& products = installer_state->products(); DCHECK(products.size()); bool is_first_install = true; const bool system_level = installer_state->system_install(); if (installer_state->is_multi_install()) { const Product* chrome = installer_state->FindProduct(BrowserDistribution::CHROME_BROWSER); const Product* chrome_frame = installer_state->FindProduct(BrowserDistribution::CHROME_FRAME); const ProductState* cf_state = original_state.GetProductState(system_level, BrowserDistribution::CHROME_FRAME); if (chrome != NULL) { if (chrome_frame != NULL && chrome_frame->HasOption(installer::kOptionReadyMode)) { // We're being asked to install Chrome with Chrome Frame in ready-mode. // This is an optimistic operation: if a SxS install of Chrome Frame // is already present, don't touch it; if a multi-install of Chrome // Frame is present, preserve its settings (ready-mode, CEEE). if (cf_state != NULL) { installer_state->RemoveProduct(chrome_frame); chrome_frame = NULL; if (cf_state->is_multi_install()) { chrome_frame = installer_state->AddProductFromState( BrowserDistribution::CHROME_FRAME, *cf_state); VLOG(1) << "Upgrading existing multi-install Chrome Frame rather " "than installing in ready-mode."; } else { VLOG(1) << "Skipping upgrade of single-install Chrome Frame rather " "than installing in ready-mode."; } } else { VLOG(1) << "Performing initial install of Chrome Frame ready-mode."; } } } else if (chrome_frame != NULL) { // We're being asked to install or update Chrome Frame alone. const ProductState* chrome_state = original_state.GetProductState(system_level, BrowserDistribution::CHROME_BROWSER); if (chrome_state != NULL) { // Chrome Frame may not yet be installed if this is a first install, so // use InstallationState's GetNonVersionedProductState() which will lets // us access the ap value from the partially constructed product state. // There will be no value if we're not being managed by Google Update. const ProductState* cf_non_versioned_state = original_state.GetNonVersionedProductState( system_level, BrowserDistribution::CHROME_FRAME); DCHECK(cf_non_versioned_state); const installer::ChannelInfo& cf_channel( cf_non_versioned_state->channel()); // Fail if Chrome is already installed but is on a different update // channel. if (!cf_channel.EqualsBaseOf(chrome_state->channel())) { LOG(ERROR) << "Cannot install Chrome Frame because existing Chrome " "install is on a different update channel."; *status = installer::CONFLICTING_CHANNEL_EXISTS; InstallUtil::WriteInstallerResult(system_level, installer_state->state_key(), *status, IDS_INSTALL_CONFLICTING_CHANNEL_EXISTS_BASE, NULL); return false; } // Otherwise, add Chrome to the set of products (making it multi-install // in the process) so that it is updated, too. scoped_ptr multi_chrome(new Product( BrowserDistribution::GetSpecificDistribution( BrowserDistribution::CHROME_BROWSER))); multi_chrome->SetOption(installer::kOptionMultiInstall, true); chrome = installer_state->AddProduct(&multi_chrome); VLOG(1) << "Upgrading existing multi-install Chrome browser along with " << chrome_frame->distribution()->GetApplicationName(); } else if (chrome_frame->HasOption(installer::kOptionReadyMode)) { // Chrome Frame with ready-mode is to be installed, yet Chrome is // neither installed nor being installed. Fail. LOG(ERROR) << "Cannot install Chrome Frame in ready mode without " "Chrome."; *status = installer::READY_MODE_REQUIRES_CHROME; InstallUtil::WriteInstallerResult(system_level, installer_state->state_key(), *status, IDS_INSTALL_READY_MODE_REQUIRES_CHROME_BASE, NULL); return false; } } // Fail if we're installing Chrome Frame when a single-install of it is // already installed. // TODO(grt): Add support for migration of Chrome Frame from single- to // multi-install. if (chrome_frame != NULL && cf_state != NULL && !cf_state->is_multi_install()) { LOG(ERROR) << "Cannot migrate existing Chrome Frame installation to " "multi-install."; *status = installer::NON_MULTI_INSTALLATION_EXISTS; InstallUtil::WriteInstallerResult(system_level, installer_state->state_key(), *status, IDS_INSTALL_NON_MULTI_INSTALLATION_EXISTS_BASE, NULL); return false; } } else if (DCHECK_IS_ON()) { // It isn't possible to stuff two products into a single-install // InstallerState. Abort the process here in debug builds just in case // someone finds a way. DCHECK_EQ(1U, products.size()); } return true; } // In multi-install, adds all products to |installer_state| that are // multi-installed and must be updated along with the products already present // in |installer_state|. void AddExistingMultiInstalls(const InstallationState& original_state, InstallerState* installer_state) { if (installer_state->is_multi_install()) { BrowserDistribution::Type product_checks[] = { BrowserDistribution::CHROME_BROWSER, BrowserDistribution::CHROME_FRAME }; for (size_t i = 0; i < arraysize(product_checks); ++i) { BrowserDistribution::Type type = product_checks[i]; if (!installer_state->FindProduct(type)) { const ProductState* state = original_state.GetProductState(installer_state->system_install(), type); if ((state != NULL) && state->is_multi_install()) { installer_state->AddProductFromState(type, *state); VLOG(1) << "Product already installed and must be included: " << BrowserDistribution::GetSpecificDistribution( type)->GetApplicationName(); } } } } } // Checks for compatibility between the current state of the system and the // desired operation. Also applies policy that mutates the desired operation; // specifically, the |installer_state| object. // Also blocks simultaneous user-level and system-level installs. In the case // of trying to install user-level Chrome when system-level exists, the // existing system-level Chrome is launched. // When the pre-install conditions are not satisfied, the result is written to // the registry (via WriteInstallerResult), |status| is set appropriately, and // false is returned. bool CheckPreInstallConditions(const InstallationState& original_state, InstallerState* installer_state, installer::InstallStatus* status) { const Products& products = installer_state->products(); DCHECK(products.size()); if (!CheckMultiInstallConditions(original_state, installer_state, status)) return false; bool is_first_install = true; const bool system_level = installer_state->system_install(); for (size_t i = 0; i < products.size(); ++i) { const Product* product = products[i]; BrowserDistribution* browser_dist = product->distribution(); // Check for an existing installation of the product. const ProductState* product_state = original_state.GetProductState(system_level, browser_dist->GetType()); if (product_state != NULL) { is_first_install = false; // Block downgrades from multi-install to single-install. if (!installer_state->is_multi_install() && product_state->is_multi_install()) { LOG(ERROR) << "Multi-install " << browser_dist->GetApplicationName() << " exists; aborting single install."; *status = installer::MULTI_INSTALLATION_EXISTS; InstallUtil::WriteInstallerResult(system_level, installer_state->state_key(), *status, IDS_INSTALL_MULTI_INSTALLATION_EXISTS_BASE, NULL); return false; } } // Check to avoid simultaneous per-user and per-machine installs. const ProductState* other_state = original_state.GetProductState(!system_level, browser_dist->GetType()); if (other_state != NULL) { LOG(ERROR) << "Already installed version " << other_state->version().GetString() << " conflicts with the current install mode."; if (!system_level && is_first_install && product->is_chrome()) { // This is user-level install and there is a system-level chrome // installation. Instruct Google Update to launch the existing one. // There should be no error dialog. FilePath chrome_exe(installer::GetChromeInstallPath(!system_level, browser_dist)); if (chrome_exe.empty()) { // If we failed to construct install path. Give up. *status = installer::OS_ERROR; InstallUtil::WriteInstallerResult(system_level, installer_state->state_key(), *status, IDS_INSTALL_OS_ERROR_BASE, NULL); } else { *status = installer::EXISTING_VERSION_LAUNCHED; chrome_exe = chrome_exe.Append(installer::kChromeExe); CommandLine cmd(chrome_exe); cmd.AppendSwitch(switches::kFirstRun); InstallUtil::WriteInstallerResult(system_level, installer_state->state_key(), *status, 0, NULL); VLOG(1) << "Launching existing system-level chrome instead."; base::LaunchApp(cmd, false, false, NULL); } return false; } // If the following compile assert fires it means that the InstallStatus // enumeration changed which will break the contract between the old // chrome installed and the new setup.exe that is trying to upgrade. COMPILE_ASSERT(installer::CONFLICTING_CHANNEL_EXISTS == 39, dont_change_enum); // This is an update, not an install. Omaha should know the difference // and not show a dialog. *status = system_level ? installer::USER_LEVEL_INSTALL_EXISTS : installer::SYSTEM_LEVEL_INSTALL_EXISTS; int str_id = system_level ? IDS_INSTALL_USER_LEVEL_EXISTS_BASE : IDS_INSTALL_SYSTEM_LEVEL_EXISTS_BASE; InstallUtil::WriteInstallerResult(system_level, installer_state->state_key(), *status, str_id, NULL); return false; } } // See what products are already installed in multi mode. When we do multi // installs, we must upgrade all installations since they share the binaries. AddExistingMultiInstalls(original_state, installer_state); // If no previous installation of Chrome, make sure installation directory // either does not exist or can be deleted (i.e. is not locked by some other // process). if (is_first_install) { if (file_util::PathExists(installer_state->target_path()) && !file_util::Delete(installer_state->target_path(), true)) { LOG(ERROR) << "Installation directory " << installer_state->target_path().value() << " exists and can not be deleted."; *status = installer::INSTALL_DIR_IN_USE; int str_id = IDS_INSTALL_DIR_IN_USE_BASE; InstallUtil::WriteInstallerResult(system_level, installer_state->state_key(), *status, str_id, NULL); return false; } } return true; } installer::InstallStatus InstallProductsHelper( const InstallationState& original_state, const CommandLine& cmd_line, const MasterPreferences& prefs, const InstallerState& installer_state, installer::ArchiveType* archive_type) { DCHECK(archive_type); const bool system_install = installer_state.system_install(); installer::InstallStatus install_status = installer::UNKNOWN_STATUS; // For install the default location for chrome.packed.7z is in current // folder, so get that value first. FilePath archive(cmd_line.GetProgram().DirName().Append( installer::kChromeCompressedArchive)); // If --install-archive is given, get the user specified value if (cmd_line.HasSwitch(installer::switches::kInstallArchive)) { archive = cmd_line.GetSwitchValuePath( installer::switches::kInstallArchive); } VLOG(1) << "Archive found to install Chrome " << archive.value(); const Products& products = installer_state.products(); // Create a temp folder where we will unpack Chrome archive. If it fails, // then we are doomed, so return immediately and no cleanup is required. installer::SelfCleaningTempDir temp_path; if (!temp_path.Initialize(installer_state.target_path().DirName(), installer::kInstallTempDir)) { PLOG(ERROR) << "Could not create temporary path."; InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), installer::TEMP_DIR_FAILED, IDS_INSTALL_TEMP_DIR_FAILED_BASE, NULL); return installer::TEMP_DIR_FAILED; } VLOG(1) << "created path " << temp_path.path().value(); FilePath unpack_path(temp_path.path().Append(installer::kInstallSourceDir)); if (UnPackArchive(archive, installer_state, temp_path.path(), unpack_path, archive_type)) { install_status = installer::UNCOMPRESSION_FAILED; InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), install_status, IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, NULL); } else { VLOG(1) << "unpacked to " << unpack_path.value(); FilePath src_path(unpack_path.Append(installer::kInstallSourceChromeDir)); scoped_ptr installer_version(installer::GetMaxVersionFromArchiveDir(src_path)); if (!installer_version.get()) { LOG(ERROR) << "Did not find any valid version in installer."; install_status = installer::INVALID_ARCHIVE; InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), install_status, IDS_INSTALL_INVALID_ARCHIVE_BASE, NULL); } else { // TODO(tommi): Move towards having only a single version that is common // to all products. Only the package should have a version since it // represents all the binaries. When a single product is upgraded, all // currently installed product for the shared binary installation, should // (or rather must) be upgraded. VLOG(1) << "version to install: " << installer_version->GetString(); bool higher_version_installed = false; for (size_t i = 0; i < installer_state.products().size(); ++i) { const Product* product = installer_state.products()[i]; const ProductState* product_state = original_state.GetProductState(system_install, product->distribution()->GetType()); if (product_state != NULL && (product_state->version().CompareTo(*installer_version) > 0)) { LOG(ERROR) << "Higher version is already installed."; higher_version_installed = true; install_status = installer::HIGHER_VERSION_EXISTS; if (product->is_chrome()) { // TODO(robertshield): We should take the installer result text // strings from the Product. InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), install_status, IDS_INSTALL_HIGHER_VERSION_BASE, NULL); } else { InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), install_status, IDS_INSTALL_HIGHER_VERSION_CF_BASE, NULL); } } } if (!higher_version_installed) { // We want to keep uncompressed archive (chrome.7z) that we get after // uncompressing and binary patching. Get the location for this file. FilePath archive_to_copy( temp_path.path().Append(installer::kChromeArchive)); FilePath prefs_source_path(cmd_line.GetSwitchValueNative( installer::switches::kInstallerData)); install_status = installer::InstallOrUpdateProduct(original_state, installer_state, cmd_line.GetProgram(), archive_to_copy, temp_path.path(), prefs_source_path, prefs, *installer_version); int install_msg_base = IDS_INSTALL_FAILED_BASE; std::wstring chrome_exe; if (install_status == installer::SAME_VERSION_REPAIR_FAILED) { if (installer_state.FindProduct(BrowserDistribution::CHROME_FRAME)) { install_msg_base = IDS_SAME_VERSION_REPAIR_FAILED_CF_BASE; } else { install_msg_base = IDS_SAME_VERSION_REPAIR_FAILED_BASE; } } else if (install_status != installer::INSTALL_FAILED) { if (installer_state.target_path().empty()) { // If we failed to construct install path, it means the OS call to // get %ProgramFiles% or %AppData% failed. Report this as failure. install_msg_base = IDS_INSTALL_OS_ERROR_BASE; install_status = installer::OS_ERROR; } else { chrome_exe = installer_state.target_path() .Append(installer::kChromeExe).value(); chrome_exe = L"\"" + chrome_exe + L"\""; install_msg_base = 0; } } // Only do Chrome-specific stuff (like launching the browser) if // Chrome was specifically requested (rather than being upgraded as // part of a multi-install). const Product* chrome_install = prefs.install_chrome() ? installer_state.FindProduct(BrowserDistribution::CHROME_BROWSER) : NULL; bool value = false; if (chrome_install) { prefs.GetBool( installer::master_preferences::kDoNotRegisterForUpdateLaunch, &value); } else { value = true; // Never register. } bool write_chrome_launch_string = (!value) && (install_status != installer::IN_USE_UPDATED); InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), install_status, install_msg_base, write_chrome_launch_string ? &chrome_exe : NULL); if (install_status == installer::FIRST_INSTALL_SUCCESS) { VLOG(1) << "First install successful."; if (chrome_install) { // We never want to launch Chrome in system level install mode. bool do_not_launch_chrome = false; prefs.GetBool( installer::master_preferences::kDoNotLaunchChrome, &do_not_launch_chrome); if (!system_install && !do_not_launch_chrome) chrome_install->LaunchChrome(installer_state.target_path()); } } else if ((install_status == installer::NEW_VERSION_UPDATED) || (install_status == installer::IN_USE_UPDATED)) { for (size_t i = 0; i < products.size(); ++i) { installer::RemoveLegacyRegistryKeys( products[i]->distribution()); } } } } // There might be an experiment (for upgrade usually) that needs to happen. // An experiment's outcome can include chrome's uninstallation. If that is // the case we would not do that directly at this point but in another // instance of setup.exe // // There is another way to reach this same function if this is a system // level install. See HandleNonInstallCmdLineOptions(). for (size_t i = 0; i < products.size(); ++i) { const Product* product = products[i]; product->distribution()->LaunchUserExperiment(install_status, *installer_version, *product, system_install); } } // Delete the master profile file if present. Note that we do not care about // rollback here and we schedule for deletion on reboot if the delete fails. // As such, we do not use DeleteTreeWorkItem. if (cmd_line.HasSwitch(installer::switches::kInstallerData)) { std::wstring prefs_path(cmd_line.GetSwitchValueNative( installer::switches::kInstallerData)); if (!file_util::Delete(prefs_path, true)) { LOG(ERROR) << "Failed deleting master preferences file " << prefs_path << ", scheduling for deletion after reboot."; ScheduleFileSystemEntityForDeletion(prefs_path.c_str()); } } // temp_path's dtor will take care of deleting or scheduling itself for // deletion at reboot when this scope closes. VLOG(1) << "Deleting temporary directory " << temp_path.path().value(); return install_status; } installer::InstallStatus InstallProducts( const InstallationState& original_state, const CommandLine& cmd_line, const MasterPreferences& prefs, InstallerState* installer_state) { DCHECK(installer_state); const bool system_install = installer_state->system_install(); installer::InstallStatus install_status = installer::UNKNOWN_STATUS; installer::ArchiveType archive_type = installer::UNKNOWN_ARCHIVE_TYPE; bool incremental_install = false; installer_state->UpdateStage(installer::PRECONDITIONS); // The stage provides more fine-grained information than -multifail, so remove // the -multifail suffix from the Google Update "ap" value. BrowserDistribution::GetSpecificDistribution(installer_state->state_type()) ->UpdateInstallStatus(system_install, archive_type, install_status); if (CheckPreInstallConditions(original_state, installer_state, &install_status)) { install_status = InstallProductsHelper( original_state, cmd_line, prefs, *installer_state, &archive_type); } const Products& products = installer_state->products(); for (size_t i = 0; i < products.size(); ++i) { const Product* product = products[i]; product->distribution()->UpdateInstallStatus( system_install, archive_type, install_status); } if (installer_state->is_multi_install()) { installer_state->multi_package_binaries_distribution()->UpdateInstallStatus( system_install, archive_type, install_status); } installer_state->UpdateStage(installer::NO_STAGE); return install_status; } installer::InstallStatus UninstallProduct( const InstallationState& original_state, const InstallerState& installer_state, const CommandLine& cmd_line, const Product& product) { bool force = cmd_line.HasSwitch(installer::switches::kForceUninstall); const ProductState* product_state = original_state.GetProductState(installer_state.system_install(), product.distribution()->GetType()); if (product_state != NULL) { VLOG(1) << "version on the system: " << product_state->version().GetString(); } else if (!force) { LOG(ERROR) << "No Chrome installation found for uninstall."; return installer::CHROME_NOT_INSTALLED; } bool remove_all = !cmd_line.HasSwitch( installer::switches::kDoNotRemoveSharedItems); return installer::UninstallProduct(original_state, installer_state, cmd_line.GetProgram(), product, remove_all, force, cmd_line); } installer::InstallStatus ShowEULADialog(const std::wstring& inner_frame) { VLOG(1) << "About to show EULA"; std::wstring eula_path = installer::GetLocalizedEulaResource(); if (eula_path.empty()) { LOG(ERROR) << "No EULA path available"; return installer::EULA_REJECTED; } // Newer versions of the caller pass an inner frame parameter that must // be given to the html page being launched. if (!inner_frame.empty()) { eula_path += L"?innerframe="; eula_path += inner_frame; } installer::EulaHTMLDialog dlg(eula_path); installer::EulaHTMLDialog::Outcome outcome = dlg.ShowModal(); if (installer::EulaHTMLDialog::REJECTED == outcome) { LOG(ERROR) << "EULA rejected or EULA failure"; return installer::EULA_REJECTED; } if (installer::EulaHTMLDialog::ACCEPTED_OPT_IN == outcome) { VLOG(1) << "EULA accepted (opt-in)"; return installer::EULA_ACCEPTED_OPT_IN; } VLOG(1) << "EULA accepted (no opt-in)"; return installer::EULA_ACCEPTED; } // This method processes any command line options that make setup.exe do // various tasks other than installation (renaming chrome.exe, showing eula // among others). This function returns true if any such command line option // has been found and processed (so setup.exe should exit at that point). bool HandleNonInstallCmdLineOptions(const InstallationState& original_state, const CommandLine& cmd_line, InstallerState* installer_state, int* exit_code) { bool handled = true; // TODO(tommi): Split these checks up into functions and use a data driven // map of switch->function. if (cmd_line.HasSwitch(installer::switches::kUpdateSetupExe)) { installer::InstallStatus status = installer::SETUP_PATCH_FAILED; // If --update-setup-exe command line option is given, we apply the given // patch to current exe, and store the resulting binary in the path // specified by --new-setup-exe. But we need to first unpack the file // given in --update-setup-exe. ScopedTempDir temp_path; if (!temp_path.CreateUniqueTempDir()) { PLOG(ERROR) << "Could not create temporary path."; } else { std::wstring setup_patch = cmd_line.GetSwitchValueNative( installer::switches::kUpdateSetupExe); VLOG(1) << "Opening archive " << setup_patch; std::wstring uncompressed_patch; if (LzmaUtil::UnPackArchive(setup_patch, temp_path.path().value(), &uncompressed_patch) == NO_ERROR) { FilePath old_setup_exe = cmd_line.GetProgram(); FilePath new_setup_exe = cmd_line.GetSwitchValuePath( installer::switches::kNewSetupExe); if (!installer::ApplyDiffPatch(old_setup_exe, FilePath(uncompressed_patch), new_setup_exe, installer_state)) status = installer::NEW_VERSION_UPDATED; } if (!temp_path.Delete()) { // PLOG would be nice, but Delete() doesn't leave a meaningful value in // the Windows last-error code. LOG(WARNING) << "Scheduling temporary path " << temp_path.path().value() << " for deletion at reboot."; ScheduleDirectoryForDeletion(temp_path.path().value().c_str()); } } *exit_code = InstallUtil::GetInstallReturnCode(status); if (*exit_code) { LOG(WARNING) << "setup.exe patching failed."; InstallUtil::WriteInstallerResult(installer_state->system_install(), installer_state->state_key(), status, IDS_SETUP_PATCH_FAILED_BASE, NULL); } } else if (cmd_line.HasSwitch(installer::switches::kShowEula)) { // Check if we need to show the EULA. If it is passed as a command line // then the dialog is shown and regardless of the outcome setup exits here. std::wstring inner_frame = cmd_line.GetSwitchValueNative(installer::switches::kShowEula); *exit_code = ShowEULADialog(inner_frame); if (installer::EULA_REJECTED != *exit_code) GoogleUpdateSettings::SetEULAConsent(*installer_state, true); } else if (cmd_line.HasSwitch( installer::switches::kRegisterChromeBrowser)) { const Product* chrome_install = installer_state->FindProduct(BrowserDistribution::CHROME_BROWSER); if (chrome_install) { // If --register-chrome-browser option is specified, register all // Chrome protocol/file associations as well as register it as a valid // browser for Start Menu->Internet shortcut. This option should only // be used when setup.exe is launched with admin rights. We do not // make any user specific changes in this option. std::wstring chrome_exe(cmd_line.GetSwitchValueNative( installer::switches::kRegisterChromeBrowser)); std::wstring suffix; if (cmd_line.HasSwitch( installer::switches::kRegisterChromeBrowserSuffix)) { suffix = cmd_line.GetSwitchValueNative( installer::switches::kRegisterChromeBrowserSuffix); } *exit_code = ShellUtil::RegisterChromeBrowser( chrome_install->distribution(), chrome_exe, suffix, false); } else { LOG(DFATAL) << "Can't register browser - Chrome distribution not found"; *exit_code = installer::UNKNOWN_STATUS; } } else if (cmd_line.HasSwitch(installer::switches::kRenameChromeExe)) { // If --rename-chrome-exe is specified, we want to rename the executables // and exit. *exit_code = RenameChromeExecutables(*installer_state); } else if (cmd_line.HasSwitch( installer::switches::kRemoveChromeRegistration)) { // This is almost reverse of --register-chrome-browser option above. // Here we delete Chrome browser registration. This option should only // be used when setup.exe is launched with admin rights. We do not // make any user specific changes in this option. std::wstring suffix; if (cmd_line.HasSwitch( installer::switches::kRegisterChromeBrowserSuffix)) { suffix = cmd_line.GetSwitchValueNative( installer::switches::kRegisterChromeBrowserSuffix); } installer::InstallStatus tmp = installer::UNKNOWN_STATUS; const Product* chrome_install = installer_state->FindProduct(BrowserDistribution::CHROME_BROWSER); DCHECK(chrome_install); if (chrome_install) { installer::DeleteChromeRegistrationKeys(chrome_install->distribution(), HKEY_LOCAL_MACHINE, suffix, tmp); } *exit_code = tmp; } else if (cmd_line.HasSwitch(installer::switches::kInactiveUserToast)) { // Launch the inactive user toast experiment. int flavor = -1; base::StringToInt(cmd_line.GetSwitchValueNative( installer::switches::kInactiveUserToast), &flavor); DCHECK_NE(-1, flavor); if (flavor == -1) { *exit_code = installer::UNKNOWN_STATUS; } else { const Products& products = installer_state->products(); for (size_t i = 0; i < products.size(); ++i) { const Product* product = products[i]; BrowserDistribution* browser_dist = product->distribution(); browser_dist->InactiveUserToastExperiment(flavor, *product, installer_state->target_path()); } } } else if (cmd_line.HasSwitch(installer::switches::kSystemLevelToast)) { const Products& products = installer_state->products(); for (size_t i = 0; i < products.size(); ++i) { const Product* product = products[i]; BrowserDistribution* browser_dist = product->distribution(); // We started as system-level and have been re-launched as user level // to continue with the toast experiment. scoped_ptr installed_version( InstallUtil::GetChromeVersion(browser_dist, installer_state->system_install())); browser_dist->LaunchUserExperiment(installer::REENTRY_SYS_UPDATE, *installed_version, *product, true); } } else if (cmd_line.HasSwitch( installer::switches::kChromeFrameReadyModeOptIn)) { *exit_code = InstallUtil::GetInstallReturnCode( installer::ChromeFrameReadyModeOptIn(original_state, *installer_state)); } else if (cmd_line.HasSwitch( installer::switches::kChromeFrameReadyModeTempOptOut)) { *exit_code = InstallUtil::GetInstallReturnCode( installer::ChromeFrameReadyModeTempOptOut(original_state, *installer_state)); } else if (cmd_line.HasSwitch( installer::switches::kChromeFrameReadyModeEndTempOptOut)) { *exit_code = InstallUtil::GetInstallReturnCode( installer::ChromeFrameReadyModeEndTempOptOut(original_state, *installer_state)); } else if (cmd_line.HasSwitch(installer::switches::kChromeFrameQuickEnable)) { *exit_code = installer::ChromeFrameQuickEnable(original_state, installer_state); } else { handled = false; } return handled; } bool ShowRebootDialog() { // Get a token for this process. HANDLE token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) { LOG(ERROR) << "Failed to open token."; return false; } // Use a ScopedHandle to keep track of and eventually close our handle. // TODO(robertshield): Add a Receive() method to base's ScopedHandle. base::win::ScopedHandle scoped_handle(token); // Get the LUID for the shutdown privilege. TOKEN_PRIVILEGES tkp = {0}; LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); tkp.PrivilegeCount = 1; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // Get the shutdown privilege for this process. AdjustTokenPrivileges(token, FALSE, &tkp, 0, reinterpret_cast(NULL), 0); if (GetLastError() != ERROR_SUCCESS) { LOG(ERROR) << "Unable to get shutdown privileges."; return false; } // Popup a dialog that will prompt to reboot using the default system message. // TODO(robertshield): Add a localized, more specific string to the prompt. RestartDialog(NULL, NULL, EWX_REBOOT | EWX_FORCEIFHUNG); return true; } // Class to manage COM initialization and uninitialization class AutoCom { public: AutoCom() : initialized_(false) { } ~AutoCom() { if (initialized_) CoUninitialize(); } bool Init(bool system_install) { if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK) { LOG(ERROR) << "COM initialization failed."; return false; } initialized_ = true; return true; } private: bool initialized_; }; // Returns the Custom information for the client identified by the exe path // passed in. This information is used for crash reporting. google_breakpad::CustomClientInfo* GetCustomInfo(const wchar_t* exe_path) { std::wstring product; std::wstring version; scoped_ptr version_info(FileVersionInfo::CreateFileVersionInfo(FilePath(exe_path))); if (version_info.get()) { version = version_info->product_version(); product = version_info->product_short_name(); } if (version.empty()) version = L"0.1.0.0"; if (product.empty()) product = L"Chrome Installer"; static google_breakpad::CustomInfoEntry ver_entry(L"ver", version.c_str()); static google_breakpad::CustomInfoEntry prod_entry(L"prod", product.c_str()); static google_breakpad::CustomInfoEntry plat_entry(L"plat", L"Win32"); static google_breakpad::CustomInfoEntry type_entry(L"ptype", L"Chrome Installer"); static google_breakpad::CustomInfoEntry entries[] = { ver_entry, prod_entry, plat_entry, type_entry }; static google_breakpad::CustomClientInfo custom_info = { entries, arraysize(entries) }; return &custom_info; } // Initialize crash reporting for this process. This involves connecting to // breakpad, etc. google_breakpad::ExceptionHandler* InitializeCrashReporting( bool system_install) { // Only report crashes if the user allows it. if (!GoogleUpdateSettings::GetCollectStatsConsent()) return NULL; // Get the alternate dump directory. We use the temp path. FilePath temp_directory; if (!file_util::GetTempDir(&temp_directory) || temp_directory.empty()) return NULL; wchar_t exe_path[MAX_PATH * 2] = {0}; GetModuleFileName(NULL, exe_path, arraysize(exe_path)); // Build the pipe name. It can be either: // System-wide install: "NamedPipe\GoogleCrashServices\S-1-5-18" // Per-user install: "NamedPipe\GoogleCrashServices\" std::wstring user_sid = kSystemPrincipalSid; if (!system_install) { if (!base::win::GetUserSidString(&user_sid)) { return NULL; } } std::wstring pipe_name = kGoogleUpdatePipeName; pipe_name += user_sid; google_breakpad::ExceptionHandler* breakpad = new google_breakpad::ExceptionHandler( temp_directory.value(), NULL, NULL, NULL, google_breakpad::ExceptionHandler::HANDLER_ALL, kLargerDumpType, pipe_name.c_str(), GetCustomInfo(exe_path)); return breakpad; } } // namespace int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, wchar_t* command_line, int show_command) { // The exit manager is in charge of calling the dtors of singletons. base::AtExitManager exit_manager; CommandLine::Init(0, NULL); const MasterPreferences& prefs = MasterPreferences::ForCurrentProcess(); installer::InitInstallerLogging(prefs); const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); VLOG(1) << "Command Line: " << cmd_line.command_line_string(); VLOG(1) << "multi install is " << prefs.is_multi_install(); bool system_install = false; prefs.GetBool(installer::master_preferences::kSystemLevel, &system_install); VLOG(1) << "system install is " << system_install; google_breakpad::scoped_ptr breakpad( InitializeCrashReporting(system_install)); InstallationState original_state; original_state.Initialize(); InstallerState installer_state; installer_state.Initialize(cmd_line, prefs, original_state); const bool is_uninstall = cmd_line.HasSwitch(installer::switches::kUninstall); // Check to make sure current system is WinXP or later. If not, log // error message and get out. if (!InstallUtil::IsOSSupported()) { LOG(ERROR) << "Chrome only supports Windows XP or later."; InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), installer::OS_NOT_SUPPORTED, IDS_INSTALL_OS_NOT_SUPPORTED_BASE, NULL); return installer::OS_NOT_SUPPORTED; } // Initialize COM for use later. AutoCom auto_com; if (!auto_com.Init(system_install)) { InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), installer::OS_ERROR, IDS_INSTALL_OS_ERROR_BASE, NULL); return installer::OS_ERROR; } // Some command line options don't work with SxS install/uninstall if (InstallUtil::IsChromeSxSProcess()) { if (system_install || prefs.is_multi_install() || cmd_line.HasSwitch(installer::switches::kForceUninstall) || cmd_line.HasSwitch(installer::switches::kMakeChromeDefault) || cmd_line.HasSwitch(installer::switches::kRegisterChromeBrowser) || cmd_line.HasSwitch( installer::switches::kRemoveChromeRegistration) || cmd_line.HasSwitch(installer::switches::kInactiveUserToast) || cmd_line.HasSwitch(installer::switches::kSystemLevelToast) || cmd_line.HasSwitch(installer::switches::kChromeFrameQuickEnable)) { return installer::SXS_OPTION_NOT_SUPPORTED; } } int exit_code = 0; if (HandleNonInstallCmdLineOptions(original_state, cmd_line, &installer_state, &exit_code)) return exit_code; if (system_install && !IsUserAnAdmin()) { if (base::win::GetVersion() >= base::win::VERSION_VISTA && !cmd_line.HasSwitch(installer::switches::kRunAsAdmin)) { CommandLine new_cmd(CommandLine::NO_PROGRAM); new_cmd.AppendArguments(cmd_line, true); // Append --run-as-admin flag to let the new instance of setup.exe know // that we already tried to launch ourselves as admin. new_cmd.AppendSwitch(installer::switches::kRunAsAdmin); DWORD exit_code = installer::UNKNOWN_STATUS; InstallUtil::ExecuteExeAsAdmin(new_cmd, &exit_code); return exit_code; } else { LOG(ERROR) << "Non admin user can not install system level Chrome."; InstallUtil::WriteInstallerResult(system_install, installer_state.state_key(), installer::INSUFFICIENT_RIGHTS, IDS_INSTALL_INSUFFICIENT_RIGHTS_BASE, NULL); return installer::INSUFFICIENT_RIGHTS; } } installer::InstallStatus install_status = installer::UNKNOWN_STATUS; // If --uninstall option is given, uninstall the identified product(s) if (is_uninstall) { const Products& products = installer_state.products(); for (size_t i = 0; i < products.size(); ++i) { install_status = UninstallProduct(original_state, installer_state, cmd_line, *products[i]); } } else { // If --uninstall option is not specified, we assume it is install case. VLOG(1) << "Installing to " << installer_state.target_path().value(); install_status = InstallProducts(original_state, cmd_line, prefs, &installer_state); } // Validate that the machine is now in a good state following the operation. // TODO(grt): change this to log at DFATAL once we're convinced that the // validator handles all cases properly. InstallationValidator::InstallationType installation_type = InstallationValidator::NO_PRODUCTS; LOG_IF(ERROR, !InstallationValidator::ValidateInstallationType(system_install, &installation_type)); const Product* cf_install = installer_state.FindProduct(BrowserDistribution::CHROME_FRAME); if (cf_install && !cmd_line.HasSwitch(installer::switches::kForceUninstall)) { if (install_status == installer::UNINSTALL_REQUIRES_REBOOT) { ShowRebootDialog(); } else if (is_uninstall) { // Only show the message box if Chrome Frame was the only product being // uninstalled. if (installer_state.products().size() == 1U) { ::MessageBoxW(NULL, installer::GetLocalizedString( IDS_UNINSTALL_COMPLETE_BASE).c_str(), cf_install->distribution()->GetApplicationName().c_str(), MB_OK); } } } int return_code = 0; // MSI demands that custom actions always return 0 (ERROR_SUCCESS) or it will // rollback the action. If we're uninstalling we want to avoid this, so always // report success, squashing any more informative return codes. if (!(installer_state.is_msi() && is_uninstall)) // Note that we allow the status installer::UNINSTALL_REQUIRES_REBOOT // to pass through, since this is only returned on uninstall which is // never invoked directly by Google Update. return_code = InstallUtil::GetInstallReturnCode(install_status); VLOG(1) << "Installation complete, returning: " << return_code; return return_code; }