diff options
author | Ben Murdoch <benm@google.com> | 2013-07-23 11:17:05 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2013-07-23 11:17:05 +0100 |
commit | ca12bfac764ba476d6cd062bf1dde12cc64c3f40 (patch) | |
tree | 1cd09db25ea5de98e73c8efbe572e103daee8b2b /chrome/installer | |
parent | fcb3e05bdd21d752df9c3dff28b6bbf29b5b733b (diff) | |
download | chromium_org-ca12bfac764ba476d6cd062bf1dde12cc64c3f40.tar.gz |
Merge from Chromium at DEPS revision r213057
This commit was generated by merge_to_master.py.
Change-Id: I3e2e2506eb9b0080157e9c5f133559df3e600388
Diffstat (limited to 'chrome/installer')
-rw-r--r-- | chrome/installer/mini_installer.gyp | 2 | ||||
-rw-r--r-- | chrome/installer/mini_installer_syzygy.gyp | 2 | ||||
-rw-r--r-- | chrome/installer/setup/archive_patch_helper.cc | 105 | ||||
-rw-r--r-- | chrome/installer/setup/archive_patch_helper.h | 97 | ||||
-rw-r--r-- | chrome/installer/setup/archive_patch_helper_unittest.cc | 61 | ||||
-rw-r--r-- | chrome/installer/setup/setup_main.cc | 732 | ||||
-rw-r--r-- | chrome/installer/setup/setup_util.cc | 74 | ||||
-rw-r--r-- | chrome/installer/setup/setup_util.h | 16 | ||||
-rw-r--r-- | chrome/installer/setup/setup_util_unittest.cc | 181 | ||||
-rw-r--r-- | chrome/installer/util/util_constants.cc | 10 | ||||
-rw-r--r-- | chrome/installer/util/util_constants.h | 6 |
11 files changed, 849 insertions, 437 deletions
diff --git a/chrome/installer/mini_installer.gyp b/chrome/installer/mini_installer.gyp index 62fa5bdf60..05d54d2875 100644 --- a/chrome/installer/mini_installer.gyp +++ b/chrome/installer/mini_installer.gyp @@ -132,7 +132,7 @@ '<(branding_dir)/BRANDING', ], 'outputs': [ - '<(INTERMEDIATE_DIR)/mini_installer_exe_version.rc', + '<(PRODUCT_DIR)/mini_installer_exe_version.rc', ], 'action': [ 'python', '<(version_py)', diff --git a/chrome/installer/mini_installer_syzygy.gyp b/chrome/installer/mini_installer_syzygy.gyp index 3af4c5ceba..74050ab106 100644 --- a/chrome/installer/mini_installer_syzygy.gyp +++ b/chrome/installer/mini_installer_syzygy.gyp @@ -15,7 +15,7 @@ ], 'conditions': [ # This target won't build in fastbuild, since there are no PDBs. - ['OS=="win" and fastbuild==0 and chrome_split_dll==0', { + ['OS=="win" and fastbuild==0 and chrome_multiple_dll==0', { 'targets': [ { 'target_name': 'mini_installer_syzygy', diff --git a/chrome/installer/setup/archive_patch_helper.cc b/chrome/installer/setup/archive_patch_helper.cc new file mode 100644 index 0000000000..c45189de86 --- /dev/null +++ b/chrome/installer/setup/archive_patch_helper.cc @@ -0,0 +1,105 @@ +// Copyright 2013 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/archive_patch_helper.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "chrome/installer/util/lzma_util.h" +#include "courgette/courgette.h" +#include "third_party/bspatch/mbspatch.h" + +namespace installer { + +ArchivePatchHelper::ArchivePatchHelper(const base::FilePath& working_directory, + const base::FilePath& compressed_archive, + const base::FilePath& patch_source, + const base::FilePath& target) + : working_directory_(working_directory), + compressed_archive_(compressed_archive), + patch_source_(patch_source), + target_(target) {} + +ArchivePatchHelper::~ArchivePatchHelper() {} + +// static +bool ArchivePatchHelper::UncompressAndPatch( + const base::FilePath& working_directory, + const base::FilePath& compressed_archive, + const base::FilePath& patch_source, + const base::FilePath& target) { + ArchivePatchHelper instance(working_directory, compressed_archive, + patch_source, target); + return (instance.Uncompress(NULL) && + (instance.EnsemblePatch() || instance.BinaryPatch())); +} + +bool ArchivePatchHelper::Uncompress(base::FilePath* last_uncompressed_file) { + // The target shouldn't already exist. + DCHECK(!base::PathExists(target_)); + + // UnPackArchive takes care of logging. + string16 output_file; + int32 lzma_result = LzmaUtil::UnPackArchive(compressed_archive_.value(), + working_directory_.value(), + &output_file); + if (lzma_result != NO_ERROR) + return false; + + last_uncompressed_file_ = base::FilePath(output_file); + if (last_uncompressed_file) + *last_uncompressed_file = last_uncompressed_file_; + return true; +} + +bool ArchivePatchHelper::EnsemblePatch() { + if (last_uncompressed_file_.empty()) { + LOG(ERROR) << "No patch file found in compressed archive."; + return false; + } + + courgette::Status result = + courgette::ApplyEnsemblePatch(patch_source_.value().c_str(), + last_uncompressed_file_.value().c_str(), + target_.value().c_str()); + if (result == courgette::C_OK) + return true; + + LOG(ERROR) + << "Failed to apply patch " << last_uncompressed_file_.value() + << " to file " << patch_source_.value() + << " and generating file " << target_.value() + << " using courgette. err=" << result; + + // Ensure a partial output is not left behind. + base::DeleteFile(target_, false); + + return false; +} + +bool ArchivePatchHelper::BinaryPatch() { + if (last_uncompressed_file_.empty()) { + LOG(ERROR) << "No patch file found in compressed archive."; + return false; + } + + int result = ApplyBinaryPatch(patch_source_.value().c_str(), + last_uncompressed_file_.value().c_str(), + target_.value().c_str()); + if (result == OK) + return true; + + LOG(ERROR) + << "Failed to apply patch " << last_uncompressed_file_.value() + << " to file " << patch_source_.value() + << " and generating file " << target_.value() + << " using bsdiff. err=" << result; + + // Ensure a partial output is not left behind. + base::DeleteFile(target_, false); + + return false; +} + +} // namespace installer diff --git a/chrome/installer/setup/archive_patch_helper.h b/chrome/installer/setup/archive_patch_helper.h new file mode 100644 index 0000000000..99d430e252 --- /dev/null +++ b/chrome/installer/setup/archive_patch_helper.h @@ -0,0 +1,97 @@ +// Copyright 2013 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. + +#ifndef CHROME_INSTALLER_SETUP_ARCHIVE_PATCH_HELPER_H_ +#define CHROME_INSTALLER_SETUP_ARCHIVE_PATCH_HELPER_H_ + +#include "base/basictypes.h" +#include "base/files/file_path.h" + +namespace installer { + +// A helper class that facilitates uncompressing and patching the chrome archive +// and installer. +// +// Chrome's installer is deployed along with a compressed archive containing +// either 1) an uncompressd archive of the product binaries or 2) a patch file +// to be applied to the uncompressed archive of the version being updated. To +// obtain the uncompressed archive, the contents of the compressed archive are +// uncompressed and extracted. Installation proceeds directly if the +// uncompressed archive is found after this step. Otherwise, the patch is +// applied to the previous version's uncompressed archive using either +// Courgette's ensemble patching or bspatch. +// +// Chrome's installer itself may also be deployed as a patch against the +// previous version's saved installer binary. The same process is followed to +// obtain the new installer. The compressed archive unconditionally contains a +// patch file in this case. +class ArchivePatchHelper { + public: + // Constructs an instance that can uncompress |compressed_archive| into + // |working_directory| and optionally apply the extracted patch file to + // |patch_source|, writing the result to |target|. + ArchivePatchHelper(const base::FilePath& working_directory, + const base::FilePath& compressed_archive, + const base::FilePath& patch_source, + const base::FilePath& target); + + ~ArchivePatchHelper(); + + // Uncompresses |compressed_archive| in |working_directory| then applies the + // extracted patch file to |patch_source|, writing the result to |target|. + // Ensemble patching via Courgette is attempted first. If that fails, bspatch + // is attempted. Returns false if uncompression or both patching steps fail. + static bool UncompressAndPatch(const base::FilePath& working_directory, + const base::FilePath& compressed_archive, + const base::FilePath& patch_source, + const base::FilePath& target); + + // Uncompresses compressed_archive() into the working directory. On success, + // last_uncompressed_file (if not NULL) is populated with the path to the last + // file extracted from the archive. + bool Uncompress(base::FilePath* last_uncompressed_file); + + // Attempts to use courgette to apply last_uncompressed_file() to + // patch_source() to generate target(). Returns false if patching fails. + bool EnsemblePatch(); + + // Attempts to use bspatch to apply last_uncompressed_file() to patch_source() + // to generate target(). Returns false if patching fails. + bool BinaryPatch(); + + const base::FilePath& compressed_archive() const { + return compressed_archive_; + } + void set_patch_source(const base::FilePath& patch_source) { + patch_source_ = patch_source; + } + const base::FilePath& patch_source() const { + return patch_source_; + } + const base::FilePath& target() const { + return target_; + } + + // Returns the path of the last file extracted by Uncompress(). + const base::FilePath& last_uncompressed_file() const { + return last_uncompressed_file_; + } + void set_last_uncompressed_file( + const base::FilePath& last_uncompressed_file) { + last_uncompressed_file_ = last_uncompressed_file; + } + + private: + base::FilePath working_directory_; + base::FilePath compressed_archive_; + base::FilePath patch_source_; + base::FilePath target_; + base::FilePath last_uncompressed_file_; + + DISALLOW_COPY_AND_ASSIGN(ArchivePatchHelper); +}; + +} // namespace installer + +#endif // CHROME_INSTALLER_SETUP_ARCHIVE_PATCH_HELPER_H_ diff --git a/chrome/installer/setup/archive_patch_helper_unittest.cc b/chrome/installer/setup/archive_patch_helper_unittest.cc new file mode 100644 index 0000000000..17ff00329b --- /dev/null +++ b/chrome/installer/setup/archive_patch_helper_unittest.cc @@ -0,0 +1,61 @@ +// Copyright 2013 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 "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/installer/setup/archive_patch_helper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class ArchivePatchHelperTest : public testing::Test { + protected: + static void SetUpTestCase() { + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_dir_)); + data_dir_ = data_dir_.AppendASCII("installer"); + ASSERT_TRUE(base::PathExists(data_dir_)); + } + + static void TearDownTestCase() { + data_dir_.clear(); + } + + virtual void SetUp() OVERRIDE { + // Create a temp directory for testing. + ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); + } + + virtual void TearDown() OVERRIDE { + // Clean up test directory manually so we can fail if it leaks. + ASSERT_TRUE(test_dir_.Delete()); + } + + // The path to input data used in tests. + static base::FilePath data_dir_; + + // The temporary directory used to contain the test operations. + base::ScopedTempDir test_dir_; +}; + +base::FilePath ArchivePatchHelperTest::data_dir_; + +} // namespace + +// Test that patching works. +TEST_F(ArchivePatchHelperTest, Patching) { + base::FilePath src = data_dir_.AppendASCII("archive1.7z"); + base::FilePath patch = data_dir_.AppendASCII("archive.diff"); + base::FilePath dest = test_dir_.path().AppendASCII("archive2.7z"); + installer::ArchivePatchHelper archive_helper(test_dir_.path(), + base::FilePath(), + src, + dest); + archive_helper.set_last_uncompressed_file(patch); + EXPECT_TRUE(archive_helper.EnsemblePatch() || archive_helper.BinaryPatch()); + base::FilePath base = data_dir_.AppendASCII("archive2.7z"); + EXPECT_TRUE(base::ContentsEqual(dest, base)); +} diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc index dab094d2cf..4b59a2c0e4 100644 --- a/chrome/installer/setup/setup_main.cc +++ b/chrome/installer/setup/setup_main.cc @@ -33,6 +33,7 @@ #include "breakpad/src/client/windows/handler/exception_handler.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" +#include "chrome/installer/setup/archive_patch_helper.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" @@ -85,71 +86,103 @@ const MINIDUMP_TYPE kLargerDumpType = static_cast<MINIDUMP_TYPE>( 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 base::FilePath& archive, - const InstallerState& installer_state, - const base::FilePath& temp_path, - const base::FilePath& output_directory, - installer::ArchiveType* archive_type) { - DCHECK(archive_type); - - installer_state.UpdateStage(installer::UNCOMPRESSING); +// Returns NULL if no compressed archive is available for processing, otherwise +// returns a patch helper configured to uncompress and patch. +scoped_ptr<installer::ArchivePatchHelper> CreateChromeArchiveHelper( + const CommandLine& command_line, + const installer::InstallerState& installer_state, + const base::FilePath& working_directory) { + // A compressed archive is ordinarily given on the command line by the mini + // installer. If one was not given, look for chrome.packed.7z next to the + // running program. + base::FilePath compressed_archive( + command_line.GetSwitchValuePath(installer::switches::kInstallArchive)); + bool compressed_archive_specified = !compressed_archive.empty(); + if (!compressed_archive_specified) { + compressed_archive = + command_line.GetProgram().DirName().Append( + installer::kChromeCompressedArchive); + } - // First uncompress the payload. This could be a differential - // update (patch.7z) or full archive (chrome.7z). If this uncompress fails - // return with error. - string16 unpacked_file; - int32 ret = LzmaUtil::UnPackArchive(archive.value(), temp_path.value(), - &unpacked_file); - if (ret != NO_ERROR) - return ret; - - base::FilePath uncompressed_archive( - temp_path.Append(installer::kChromeArchive)); - scoped_ptr<Version> 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 (!base::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; + // Fail if no compressed archive is found. + if (!base::PathExists(compressed_archive)) { + if (compressed_archive_specified) { + LOG(ERROR) << installer::switches::kInstallArchive << "=" + << compressed_archive.value() << " not found."; } + return scoped_ptr<installer::ArchivePatchHelper>(); + } - base::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, - base::FilePath(unpacked_file), - uncompressed_archive, - &installer_state)) { - LOG(ERROR) << "Binary patching failed with error " << i; - return i; - } - } else { + // chrome.7z is either extracted directly from the compressed archive into the + // working dir or is the target of patching in the working dir. + base::FilePath target(working_directory.Append(installer::kChromeArchive)); + DCHECK(!base::PathExists(target)); + + // Specify an empty path for the patch source since it isn't yet known that + // one is needed. It will be supplied in UncompressAndPatchChromeArchive if it + // is. + return scoped_ptr<installer::ArchivePatchHelper>( + new installer::ArchivePatchHelper(working_directory, + compressed_archive, + base::FilePath(), + target)); +} + +// Workhorse for producing an uncompressed archive (chrome.7z) given a +// chrome.packed.7z containing either a patch file based on the version of +// chrome being updated or the full uncompressed archive. Returns true on +// success, in which case |archive_type| is populated based on what was found. +// Returns false on failure, in which case |install_status| contains the error +// code and the result is written to the registry (via WriteInstallerResult). +bool UncompressAndPatchChromeArchive( + const installer::InstallationState& original_state, + const installer::InstallerState& installer_state, + installer::ArchivePatchHelper* archive_helper, + installer::ArchiveType* archive_type, + installer::InstallStatus* install_status) { + installer_state.UpdateStage(installer::UNCOMPRESSING); + if (!archive_helper->Uncompress(NULL)) { + *install_status = installer::UNCOMPRESSION_FAILED; + installer_state.WriteInstallerResult(*install_status, + IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, + NULL); + return false; + } + + // Short-circuit if uncompression produced the uncompressed archive rather + // than a patch file. + if (base::PathExists(archive_helper->target())) { *archive_type = installer::FULL_ARCHIVE_TYPE; + return true; } - installer_state.UpdateStage(installer::UNPACKING); + // Find the installed version's archive to serve as the source for patching. + base::FilePath patch_source(installer::FindArchiveToPatch(original_state, + installer_state)); + if (patch_source.empty()) { + LOG(ERROR) << "Failed to find archive to patch."; + *install_status = installer::DIFF_PATCH_SOURCE_MISSING; + installer_state.WriteInstallerResult(*install_status, + IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, + NULL); + return false; + } + archive_helper->set_patch_source(patch_source); + + // Try courgette first. Failing that, try bspatch. + if ((installer_state.UpdateStage(installer::ENSEMBLE_PATCHING), + !archive_helper->EnsemblePatch()) && + (installer_state.UpdateStage(installer::BINARY_PATCHING), + !archive_helper->BinaryPatch())) { + *install_status = installer::APPLY_DIFF_PATCH_FAILED; + installer_state.WriteInstallerResult(*install_status, + IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, + NULL); + return false; + } - // Unpack the uncompressed archive. - return LzmaUtil::UnPackArchive(uncompressed_archive.value(), - output_directory.value(), &unpacked_file); + *archive_type = installer::INCREMENTAL_ARCHIVE_TYPE; + return true; } // In multi-install, adds all products to |installer_state| that are @@ -627,6 +660,32 @@ bool CheckPreInstallConditions(const InstallationState& original_state, return true; } +// Initializes |temp_path| to "Temp" within the target directory, and +// |unpack_path| to a random directory beginning with "source" within +// |temp_path|. Returns false on error. +bool CreateTemporaryAndUnpackDirectories( + const InstallerState& installer_state, + installer::SelfCleaningTempDir* temp_path, + base::FilePath* unpack_path) { + DCHECK(temp_path && unpack_path); + + if (!temp_path->Initialize(installer_state.target_path().DirName(), + installer::kInstallTempDir)) { + PLOG(ERROR) << "Could not create temporary path."; + return false; + } + VLOG(1) << "Created path " << temp_path->path().value(); + + if (!file_util::CreateTemporaryDirInDir(temp_path->path(), + installer::kInstallSourceDir, + unpack_path)) { + PLOG(ERROR) << "Could not create temporary path for unpacked archive."; + return false; + } + + return true; +} + installer::InstallStatus InstallProductsHelper( const InstallationState& original_state, const CommandLine& cmd_line, @@ -635,6 +694,7 @@ installer::InstallStatus InstallProductsHelper( installer::ArchiveType* archive_type, bool* delegated_to_existing) { DCHECK(archive_type); + DCHECK(delegated_to_existing); const bool system_install = installer_state.system_install(); installer::InstallStatus install_status = installer::UNKNOWN_STATUS; @@ -643,308 +703,284 @@ installer::InstallStatus InstallProductsHelper( bool entered_background_mode = installer::AdjustProcessPriority(); VLOG_IF(1, entered_background_mode) << "Entered background processing mode."; - // For install the default location for chrome.packed.7z is in current - // folder, so get that value first. - base::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); - } - - 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."; + base::FilePath unpack_path; + if (!CreateTemporaryAndUnpackDirectories(installer_state, &temp_path, + &unpack_path)) { installer_state.WriteInstallerResult(installer::TEMP_DIR_FAILED, - IDS_INSTALL_TEMP_DIR_FAILED_BASE, NULL); + IDS_INSTALL_TEMP_DIR_FAILED_BASE, + NULL); return installer::TEMP_DIR_FAILED; } - VLOG(1) << "created path " << temp_path.path().value(); - base::FilePath unpack_path; - if (!file_util::CreateTemporaryDirInDir(temp_path.path(), - installer::kInstallSourceDir, - &unpack_path)) { - PLOG(ERROR) << "Could not create temporary path for unpacked archive."; - installer_state.WriteInstallerResult(installer::TEMP_DIR_FAILED, - IDS_INSTALL_TEMP_DIR_FAILED_BASE, NULL); - return installer::TEMP_DIR_FAILED; + // Uncompress and optionally patch the archive if an uncompressed archive was + // not specified on the command line and a compressed archive is found. + *archive_type = installer::UNKNOWN_ARCHIVE_TYPE; + base::FilePath uncompressed_archive(cmd_line.GetSwitchValuePath( + installer::switches::kUncompressedArchive)); + if (uncompressed_archive.empty()) { + scoped_ptr<installer::ArchivePatchHelper> archive_helper( + CreateChromeArchiveHelper(cmd_line, installer_state, unpack_path)); + if (archive_helper) { + VLOG(1) << "Installing Chrome from compressed archive " + << archive_helper->compressed_archive().value(); + if (!UncompressAndPatchChromeArchive(original_state, + installer_state, + archive_helper.get(), + archive_type, + &install_status)) { + DCHECK_NE(install_status, installer::UNKNOWN_STATUS); + return install_status; + } + uncompressed_archive = archive_helper->target(); + DCHECK(!uncompressed_archive.empty()); + } + } + + // Check for an uncompressed archive alongside the current executable if one + // was not given or generated. + if (uncompressed_archive.empty()) { + uncompressed_archive = + cmd_line.GetProgram().DirName().Append(installer::kChromeArchive); } - bool unpacked = false; - - // We want to keep uncompressed archive (chrome.7z) that we get after - // uncompressing and binary patching. Get the location for this file. - base::FilePath archive_to_copy; - if (base::PathExists(archive)) { - VLOG(1) << "Archive found to install Chrome " << archive.value(); - if (UnPackArchive(archive, installer_state, temp_path.path(), unpack_path, - archive_type)) { - install_status = (*archive_type) == installer::INCREMENTAL_ARCHIVE_TYPE ? - installer::APPLY_DIFF_PATCH_FAILED : installer::UNCOMPRESSION_FAILED; + if (*archive_type == installer::UNKNOWN_ARCHIVE_TYPE) { + // An archive was not uncompressed or patched above. + if (uncompressed_archive.empty() || + !base::PathExists(uncompressed_archive)) { + LOG(ERROR) << "Cannot install Chrome without an uncompressed archive."; installer_state.WriteInstallerResult( - install_status, - IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, - NULL); - } else { - unpacked = true; - archive_to_copy = temp_path.path().Append(installer::kChromeArchive); + installer::INVALID_ARCHIVE, IDS_INSTALL_INVALID_ARCHIVE_BASE, NULL); + return installer::INVALID_ARCHIVE; } + *archive_type = installer::FULL_ARCHIVE_TYPE; + } + + // Unpack the uncompressed archive. + if (LzmaUtil::UnPackArchive(uncompressed_archive.value(), + unpack_path.value(), + NULL)) { + installer_state.WriteInstallerResult( + installer::UNCOMPRESSION_FAILED, + IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, + NULL); + return installer::UNCOMPRESSION_FAILED; + } + + VLOG(1) << "unpacked to " << unpack_path.value(); + base::FilePath src_path( + unpack_path.Append(installer::kInstallSourceChromeDir)); + scoped_ptr<Version> + 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; + installer_state.WriteInstallerResult(install_status, + IDS_INSTALL_INVALID_ARCHIVE_BASE, NULL); } else { - base::FilePath uncompressed_archive(cmd_line.GetProgram().DirName().Append( - installer::kChromeArchive)); - - if (base::PathExists(uncompressed_archive)) { - VLOG(1) << "Uncompressed archive found to install Chrome " - << uncompressed_archive.value(); - *archive_type = installer::FULL_ARCHIVE_TYPE; - string16 unpacked_file; - if (LzmaUtil::UnPackArchive(uncompressed_archive.value(), - unpack_path.value(), &unpacked_file)) { - installer_state.WriteInstallerResult( - installer::UNCOMPRESSION_FAILED, - IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, - NULL); - } else { - unpacked = true; - archive_to_copy = uncompressed_archive; + VLOG(1) << "version to install: " << installer_version->GetString(); + bool proceed_with_installation = true; + + if (installer_state.operation() == InstallerState::MULTI_INSTALL) { + // This is a new install of a multi-install product. Rather than give up + // in case a higher version of the binaries (including a single-install + // of Chrome, which can safely be migrated to multi-install by way of + // CheckMultiInstallConditions) is already installed, delegate to the + // installed setup.exe to install the product at hand. + base::FilePath setup_exe; + if (GetExistingHigherInstaller(original_state, system_install, + *installer_version, &setup_exe)) { + VLOG(1) << "Deferring to existing installer."; + installer_state.UpdateStage(installer::DEFERRING_TO_HIGHER_VERSION); + if (DeferToExistingInstall(setup_exe, cmd_line, installer_state, + temp_path.path(), &install_status)) { + *delegated_to_existing = true; + return install_status; + } } } - } - if (unpacked) { - VLOG(1) << "unpacked to " << unpack_path.value(); - base::FilePath src_path( - unpack_path.Append(installer::kInstallSourceChromeDir)); - scoped_ptr<Version> - 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; - installer_state.WriteInstallerResult(install_status, - IDS_INSTALL_INVALID_ARCHIVE_BASE, NULL); - } else { - VLOG(1) << "version to install: " << installer_version->GetString(); - bool proceed_with_installation = true; - - if (installer_state.operation() == InstallerState::MULTI_INSTALL) { - // This is a new install of a multi-install product. Rather than give up - // in case a higher version of the binaries (including a single-install - // of Chrome, which can safely be migrated to multi-install by way of - // CheckMultiInstallConditions) is already installed, delegate to the - // installed setup.exe to install the product at hand. - base::FilePath setup_exe; - if (GetExistingHigherInstaller(original_state, system_install, - *installer_version, &setup_exe)) { - VLOG(1) << "Deferring to existing installer."; - installer_state.UpdateStage(installer::DEFERRING_TO_HIGHER_VERSION); - if (DeferToExistingInstall(setup_exe, cmd_line, installer_state, - temp_path.path(), &install_status)) { - *delegated_to_existing = true; - return install_status; - } - } + + uint32 higher_products = 0; + COMPILE_ASSERT( + sizeof(higher_products) * 8 > BrowserDistribution::NUM_TYPES, + too_many_distribution_types_); + const Products& products = installer_state.products(); + for (Products::const_iterator it = products.begin(); it < products.end(); + ++it) { + const Product& product = **it; + 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 of " + << product.distribution()->GetAppShortCutName() + << " is already installed."; + higher_products |= (1 << product.distribution()->GetType()); } + } - uint32 higher_products = 0; - COMPILE_ASSERT( - sizeof(higher_products) * 8 > BrowserDistribution::NUM_TYPES, - too_many_distribution_types_); - const Products& products = installer_state.products(); - for (Products::const_iterator it = products.begin(); it < products.end(); - ++it) { - const Product& product = **it; - 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 of " - << product.distribution()->GetAppShortCutName() - << " is already installed."; - higher_products |= (1 << product.distribution()->GetType()); - } + if (higher_products != 0) { + COMPILE_ASSERT(BrowserDistribution::NUM_TYPES == 4, + add_support_for_new_products_here_); + const uint32 kBrowserBit = 1 << BrowserDistribution::CHROME_BROWSER; + const uint32 kGCFBit = 1 << BrowserDistribution::CHROME_FRAME; + const uint32 kAppHostBit = 1 << BrowserDistribution::CHROME_APP_HOST; + int message_id = 0; + + proceed_with_installation = false; + install_status = installer::HIGHER_VERSION_EXISTS; + switch (higher_products) { + case kBrowserBit: + message_id = IDS_INSTALL_HIGHER_VERSION_BASE; + break; + case kGCFBit: + message_id = IDS_INSTALL_HIGHER_VERSION_CF_BASE; + break; + case kGCFBit | kBrowserBit: + message_id = IDS_INSTALL_HIGHER_VERSION_CB_CF_BASE; + break; + default: + message_id = IDS_INSTALL_HIGHER_VERSION_APP_LAUNCHER_BASE; + break; } - if (higher_products != 0) { - COMPILE_ASSERT(BrowserDistribution::NUM_TYPES == 4, - add_support_for_new_products_here_); - const uint32 kBrowserBit = 1 << BrowserDistribution::CHROME_BROWSER; - const uint32 kGCFBit = 1 << BrowserDistribution::CHROME_FRAME; - const uint32 kAppHostBit = 1 << BrowserDistribution::CHROME_APP_HOST; - int message_id = 0; - - proceed_with_installation = false; - install_status = installer::HIGHER_VERSION_EXISTS; - switch (higher_products) { - case kBrowserBit: - message_id = IDS_INSTALL_HIGHER_VERSION_BASE; - break; - case kGCFBit: - message_id = IDS_INSTALL_HIGHER_VERSION_CF_BASE; - break; - case kGCFBit | kBrowserBit: - message_id = IDS_INSTALL_HIGHER_VERSION_CB_CF_BASE; - break; - default: - message_id = IDS_INSTALL_HIGHER_VERSION_APP_LAUNCHER_BASE; - break; - } + installer_state.WriteInstallerResult(install_status, message_id, NULL); + } - installer_state.WriteInstallerResult(install_status, message_id, NULL); + proceed_with_installation = + proceed_with_installation && + CheckGroupPolicySettings(original_state, installer_state, + *installer_version, &install_status); + + if (proceed_with_installation) { + // If Google Update is absent at user-level, install it using the + // Google Update installer from an existing system-level installation. + // This is for quick-enable App Host install from a system-level + // Chrome Binaries installation. + if (!system_install && installer_state.ensure_google_update_present()) { + if (!google_update::EnsureUserLevelGoogleUpdatePresent()) { + LOG(ERROR) << "Failed to install Google Update"; + proceed_with_installation = false; + install_status = installer::INSTALL_OF_GOOGLE_UPDATE_FAILED; + installer_state.WriteInstallerResult(install_status, 0, NULL); + } } + } - proceed_with_installation = - proceed_with_installation && - CheckGroupPolicySettings(original_state, installer_state, - *installer_version, &install_status); - - if (proceed_with_installation) { - // If Google Update is absent at user-level, install it using the - // Google Update installer from an existing system-level installation. - // This is for quick-enable App Host install from a system-level - // Chrome Binaries installation. - if (!system_install && installer_state.ensure_google_update_present()) { - if (!google_update::EnsureUserLevelGoogleUpdatePresent()) { - LOG(ERROR) << "Failed to install Google Update"; - proceed_with_installation = false; - install_status = installer::INSTALL_OF_GOOGLE_UPDATE_FAILED; - installer_state.WriteInstallerResult(install_status, 0, NULL); - } + if (proceed_with_installation) { + base::FilePath prefs_source_path(cmd_line.GetSwitchValueNative( + installer::switches::kInstallerData)); + install_status = installer::InstallOrUpdateProduct( + original_state, installer_state, cmd_line.GetProgram(), + uncompressed_archive, temp_path.path(), src_path, prefs_source_path, + prefs, *installer_version); + + int install_msg_base = IDS_INSTALL_FAILED_BASE; + string16 chrome_exe; + string16 quoted_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(); + quoted_chrome_exe = L"\"" + chrome_exe + L"\""; + install_msg_base = 0; } } - if (proceed_with_installation) { - base::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(), src_path, prefs_source_path, - prefs, *installer_version); - - int install_msg_base = IDS_INSTALL_FAILED_BASE; - string16 chrome_exe; - string16 quoted_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(); - quoted_chrome_exe = L"\"" + chrome_exe + L"\""; - install_msg_base = 0; - } - } + installer_state.UpdateStage(installer::FINISHING); - installer_state.UpdateStage(installer::FINISHING); + // 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; - // 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 do_not_register_for_update_launch = false; + if (chrome_install) { + prefs.GetBool( + installer::master_preferences::kDoNotRegisterForUpdateLaunch, + &do_not_register_for_update_launch); + } else { + do_not_register_for_update_launch = true; // Never register. + } + + bool write_chrome_launch_string = + (!do_not_register_for_update_launch && + install_status != installer::IN_USE_UPDATED); + + installer_state.WriteInstallerResult(install_status, install_msg_base, + write_chrome_launch_string ? "ed_chrome_exe : NULL); - bool do_not_register_for_update_launch = false; + 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::kDoNotRegisterForUpdateLaunch, - &do_not_register_for_update_launch); - } else { - do_not_register_for_update_launch = true; // Never register. - } - - bool write_chrome_launch_string = - (!do_not_register_for_update_launch && - install_status != installer::IN_USE_UPDATED); - - installer_state.WriteInstallerResult(install_status, install_msg_base, - write_chrome_launch_string ? "ed_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)) { - const Product* chrome = installer_state.FindProduct( - BrowserDistribution::CHROME_BROWSER); - if (chrome != NULL) { - DCHECK_NE(chrome_exe, string16()); - installer::RemoveChromeLegacyRegistryKeys(chrome->distribution(), - chrome_exe); - } + installer::master_preferences::kDoNotLaunchChrome, + &do_not_launch_chrome); + if (!system_install && !do_not_launch_chrome) + chrome_install->LaunchChrome(installer_state.target_path()); } - - if (prefs.install_chrome_app_launcher() && - InstallUtil::GetInstallReturnCode(install_status) == 0) { - std::string webstore_item(google_update::GetUntrustedDataValue( - installer::kInstallFromWebstore)); - if (!webstore_item.empty()) { - bool success = installer::InstallFromWebstore(webstore_item); - VLOG_IF(1, !success) << "Failed to launch app installation."; - } + } else if ((install_status == installer::NEW_VERSION_UPDATED) || + (install_status == installer::IN_USE_UPDATED)) { + const Product* chrome = installer_state.FindProduct( + BrowserDistribution::CHROME_BROWSER); + if (chrome != NULL) { + DCHECK_NE(chrome_exe, string16()); + installer::RemoveChromeLegacyRegistryKeys(chrome->distribution(), + chrome_exe); } } - } - // 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(). - { - // If installation failed, use the path to the currently running setup. - // If installation succeeded, use the path to setup in the installer dir. - base::FilePath setup_path(cmd_line.GetProgram()); - if (InstallUtil::GetInstallReturnCode(install_status) == 0) { - setup_path = installer_state.GetInstallerDirectory(*installer_version) - .Append(setup_path.BaseName()); - } - for (Products::const_iterator it = products.begin(); it < products.end(); - ++it) { - const Product& product = **it; - product.LaunchUserExperiment(setup_path, install_status, - system_install); + if (prefs.install_chrome_app_launcher() && + InstallUtil::GetInstallReturnCode(install_status) == 0) { + std::string webstore_item(google_update::GetUntrustedDataValue( + installer::kInstallFromWebstore)); + if (!webstore_item.empty()) { + bool success = installer::InstallFromWebstore(webstore_item); + VLOG_IF(1, !success) << "Failed to launch app installation."; + } } } } - // 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)) { - base::FilePath prefs_path(cmd_line.GetSwitchValuePath( - installer::switches::kInstallerData)); - if (!base::DeleteFile(prefs_path, true)) { - LOG(ERROR) << "Failed deleting master preferences file " - << prefs_path.value() - << ", scheduling for deletion after reboot."; - ScheduleFileSystemEntityForDeletion(prefs_path.value().c_str()); + // 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(). + { + // If installation failed, use the path to the currently running setup. + // If installation succeeded, use the path to setup in the installer dir. + base::FilePath setup_path(cmd_line.GetProgram()); + if (InstallUtil::GetInstallReturnCode(install_status) == 0) { + setup_path = installer_state.GetInstallerDirectory(*installer_version) + .Append(setup_path.BaseName()); + } + const Products& products = installer_state.products(); + for (Products::const_iterator it = products.begin(); it < products.end(); + ++it) { + const Product& product = **it; + product.LaunchUserExperiment(setup_path, install_status, + system_install); } } @@ -964,7 +1000,7 @@ installer::InstallStatus InstallProducts( 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; + bool delegated_to_existing = 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. @@ -973,21 +1009,34 @@ installer::InstallStatus InstallProducts( if (CheckPreInstallConditions(original_state, installer_state, &install_status)) { VLOG(1) << "Installing to " << installer_state->target_path().value(); - bool delegated_to_existing = false; install_status = InstallProductsHelper( original_state, cmd_line, prefs, *installer_state, &archive_type, &delegated_to_existing); - // Early exit if this setup.exe delegated to another, since that one would - // have taken care of UpdateInstallStatus and UpdateStage. - if (delegated_to_existing) - return install_status; } else { // CheckPreInstallConditions must set the status on failure. DCHECK_NE(install_status, installer::UNKNOWN_STATUS); } - const Products& products = installer_state->products(); + // Delete the master preferences 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)) { + base::FilePath prefs_path(cmd_line.GetSwitchValuePath( + installer::switches::kInstallerData)); + if (!base::DeleteFile(prefs_path, false)) { + LOG(ERROR) << "Failed deleting master preferences file " + << prefs_path.value() + << ", scheduling for deletion after reboot."; + ScheduleFileSystemEntityForDeletion(prefs_path.value().c_str()); + } + } + // Early exit if this setup.exe delegated to another, since that one would + // have taken care of UpdateInstallStatus and UpdateStage. + if (delegated_to_existing) + return install_status; + + const Products& products = installer_state->products(); for (Products::const_iterator it = products.begin(); it < products.end(); ++it) { (*it)->distribution()->UpdateInstallStatus( @@ -1261,20 +1310,15 @@ bool HandleNonInstallCmdLineOptions(const InstallationState& original_state, if (!temp_path.CreateUniqueTempDir()) { PLOG(ERROR) << "Could not create temporary path."; } else { - string16 setup_patch = cmd_line.GetSwitchValueNative( - installer::switches::kUpdateSetupExe); - VLOG(1) << "Opening archive " << setup_patch; - string16 uncompressed_patch; - if (LzmaUtil::UnPackArchive(setup_patch, temp_path.path().value(), - &uncompressed_patch) == NO_ERROR) { - base::FilePath old_setup_exe = cmd_line.GetProgram(); - base::FilePath new_setup_exe = cmd_line.GetSwitchValuePath( - installer::switches::kNewSetupExe); - if (!installer::ApplyDiffPatch(old_setup_exe, - base::FilePath(uncompressed_patch), - new_setup_exe, - installer_state)) - status = installer::NEW_VERSION_UPDATED; + base::FilePath compressed_archive(cmd_line.GetSwitchValuePath( + installer::switches::kUpdateSetupExe)); + VLOG(1) << "Opening archive " << compressed_archive.value(); + if (installer::ArchivePatchHelper::UncompressAndPatch( + temp_path.path(), + compressed_archive, + cmd_line.GetProgram(), + cmd_line.GetSwitchValuePath(installer::switches::kNewSetupExe))) { + status = installer::NEW_VERSION_UPDATED; } if (!temp_path.Delete()) { // PLOG would be nice, but Delete() doesn't leave a meaningful value in diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc index d67927402c..4ca4447c36 100644 --- a/chrome/installer/setup/setup_util.cc +++ b/chrome/installer/setup/setup_util.cc @@ -17,6 +17,7 @@ #include "base/strings/string_util.h" #include "base/version.h" #include "base/win/windows_version.h" +#include "chrome/installer/setup/setup_constants.h" #include "chrome/installer/util/copy_tree_work_item.h" #include "chrome/installer/util/installation_state.h" #include "chrome/installer/util/installer_state.h" @@ -134,53 +135,6 @@ int BsdiffPatchFiles(const base::FilePath& src, return exit_code; } -int ApplyDiffPatch(const base::FilePath& src, - const base::FilePath& patch, - const base::FilePath& dest, - const InstallerState* installer_state) { - VLOG(1) << "Applying patch " << patch.value() << " to file " - << src.value() << " and generating file " << dest.value(); - - if (installer_state != NULL) - installer_state->UpdateStage(installer::ENSEMBLE_PATCHING); - - // Try Courgette first. Courgette checks the patch file first and fails - // quickly if the patch file does not have a valid Courgette header. - courgette::Status patch_status = - courgette::ApplyEnsemblePatch(src.value().c_str(), - patch.value().c_str(), - dest.value().c_str()); - if (patch_status == courgette::C_OK) - return 0; - - LOG(ERROR) - << "Failed to apply patch " << patch.value() - << " to file " << src.value() << " and generating file " << dest.value() - << " using courgette. err=" << patch_status; - - // If we ran out of memory or disk space, then these are likely the errors - // we will see. If we run into them, return an error and stay on the - // 'ENSEMBLE_PATCHING' update stage. - if (patch_status == courgette::C_DISASSEMBLY_FAILED || - patch_status == courgette::C_STREAM_ERROR) { - return MEM_ERROR; - } - - if (installer_state != NULL) - installer_state->UpdateStage(installer::BINARY_PATCHING); - - int binary_patch_status = ApplyBinaryPatch(src.value().c_str(), - patch.value().c_str(), - dest.value().c_str()); - - LOG_IF(ERROR, binary_patch_status != OK) - << "Failed to apply patch " << patch.value() - << " to file " << src.value() << " and generating file " << dest.value() - << " using bsdiff. err=" << binary_patch_status; - - return binary_patch_status; -} - Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) { VLOG(1) << "Looking for Chrome version folder under " << chrome_path.value(); Version* version = NULL; @@ -208,6 +162,32 @@ Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) { return (version_found ? max_version.release() : NULL); } +base::FilePath FindArchiveToPatch(const InstallationState& original_state, + const InstallerState& installer_state) { + // Check based on the version number advertised to Google Update, since that + // is the value used to select a specific differential update. If an archive + // can't be found using that, fallback to using the newest version present. + base::FilePath patch_source; + const ProductState* product = + original_state.GetProductState(installer_state.system_install(), + installer_state.state_type()); + if (product) { + patch_source = installer_state.GetInstallerDirectory(product->version()) + .Append(installer::kChromeArchive); + if (base::PathExists(patch_source)) + return patch_source; + } + scoped_ptr<Version> version( + installer::GetMaxVersionFromArchiveDir(installer_state.target_path())); + if (version) { + patch_source = installer_state.GetInstallerDirectory(*version) + .Append(installer::kChromeArchive); + if (base::PathExists(patch_source)) + return patch_source; + } + return base::FilePath(); +} + bool DeleteFileFromTempProcess(const base::FilePath& path, uint32 delay_before_delete_ms) { static const wchar_t kRunDll32Path[] = diff --git a/chrome/installer/setup/setup_util.h b/chrome/installer/setup/setup_util.h index 97bf4d4715..031b1cb99b 100644 --- a/chrome/installer/setup/setup_util.h +++ b/chrome/installer/setup/setup_util.h @@ -30,17 +30,6 @@ class InstallationState; class InstallerState; class ProductState; -// Apply a diff patch to source file. First tries to apply it using Courgette -// since it checks for Courgette header and fails quickly. If that fails -// tries to apply the patch using regular bsdiff. Returns status code as -// defined by the bsdiff code (see third_party/bspatch/mbspatch.h for the -// definitions of the codes). -// The installer stage is updated if |installer_state| is non-NULL. -int ApplyDiffPatch(const base::FilePath& src, - const base::FilePath& patch, - const base::FilePath& dest, - const InstallerState* installer_state); - // Applies a patch file to source file using Courgette. Returns 0 in case of // success. In case of errors, it returns kCourgetteErrorOffset + a Courgette // status code, as defined in courgette/courgette.h @@ -61,6 +50,11 @@ int BsdiffPatchFiles(const base::FilePath& src, // Returns the maximum version found or NULL if no version is found. Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path); +// Returns the uncompressed archive of the installed version that serves as the +// source for patching. +base::FilePath FindArchiveToPatch(const InstallationState& original_state, + const InstallerState& installer_state); + // Spawns a new process that waits for a specified amount of time before // attempting to delete |path|. This is useful for setup to delete the // currently running executable or a file that we cannot close right away but diff --git a/chrome/installer/setup/setup_util_unittest.cc b/chrome/installer/setup/setup_util_unittest.cc index 177d548f80..490475ccb8 100644 --- a/chrome/installer/setup/setup_util_unittest.cc +++ b/chrome/installer/setup/setup_util_unittest.cc @@ -12,39 +12,35 @@ #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/memory/scoped_ptr.h" -#include "base/path_service.h" #include "base/process_util.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" +#include "base/version.h" #include "base/win/scoped_handle.h" #include "base/win/windows_version.h" -#include "chrome/common/chrome_paths.h" #include "chrome/installer/setup/setup_util.h" +#include "chrome/installer/setup/setup_constants.h" +#include "chrome/installer/util/installation_state.h" +#include "chrome/installer/util/installer_state.h" +#include "chrome/installer/util/util_constants.h" #include "testing/gtest/include/gtest/gtest.h" namespace { class SetupUtilTestWithDir : public testing::Test { protected: - virtual void SetUp() { - ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_dir_)); - data_dir_ = data_dir_.AppendASCII("installer"); - ASSERT_TRUE(base::PathExists(data_dir_)); - + virtual void SetUp() OVERRIDE { // Create a temp directory for testing. ASSERT_TRUE(test_dir_.CreateUniqueTempDir()); } - virtual void TearDown() { + virtual void TearDown() OVERRIDE { // Clean up test directory manually so we can fail if it leaks. ASSERT_TRUE(test_dir_.Delete()); } // The temporary directory used to contain the test operations. base::ScopedTempDir test_dir_; - - // The path to input data used in tests. - base::FilePath data_dir_; }; // The privilege tested in ScopeTokenPrivilege tests below. @@ -95,26 +91,6 @@ bool CurrentProcessHasPrivilege(const wchar_t* privilege_name) { } // namespace // Test that we are parsing Chrome version correctly. -TEST_F(SetupUtilTestWithDir, ApplyDiffPatchTest) { - base::FilePath work_dir(test_dir_.path()); - work_dir = work_dir.AppendASCII("ApplyDiffPatchTest"); - ASSERT_FALSE(base::PathExists(work_dir)); - EXPECT_TRUE(file_util::CreateDirectory(work_dir)); - ASSERT_TRUE(base::PathExists(work_dir)); - - base::FilePath src = data_dir_.AppendASCII("archive1.7z"); - base::FilePath patch = data_dir_.AppendASCII("archive.diff"); - base::FilePath dest = work_dir.AppendASCII("archive2.7z"); - EXPECT_EQ(installer::ApplyDiffPatch(src, patch, dest, NULL), 0); - base::FilePath base = data_dir_.AppendASCII("archive2.7z"); - EXPECT_TRUE(base::ContentsEqual(dest, base)); - - EXPECT_EQ(installer::ApplyDiffPatch(base::FilePath(), base::FilePath(), - base::FilePath(), NULL), - 6); -} - -// Test that we are parsing Chrome version correctly. TEST_F(SetupUtilTestWithDir, GetMaxVersionFromArchiveDirTest) { // Create a version dir base::FilePath chrome_dir = test_dir_.path().AppendASCII("1.0.0.0"); @@ -277,3 +253,146 @@ TEST(SetupUtilTest, AdjustFromBelowNormalPriority) { else EXPECT_EQ(PCCR_UNCHANGED, RelaunchAndDoProcessPriorityAdjustment()); } + +namespace { + +// A test fixture that configures an InstallationState and an InstallerState +// with a product being updated. +class FindArchiveToPatchTest : public SetupUtilTestWithDir { + protected: + class FakeInstallationState : public installer::InstallationState { + }; + + class FakeProductState : public installer::ProductState { + public: + static FakeProductState* FromProductState(const ProductState* product) { + return static_cast<FakeProductState*>(const_cast<ProductState*>(product)); + } + + void set_version(const Version& version) { + if (version.IsValid()) + version_.reset(new Version(version)); + else + version_.reset(); + } + + void set_uninstall_command(const CommandLine& uninstall_command) { + uninstall_command_ = uninstall_command; + } + }; + + virtual void SetUp() OVERRIDE { + SetupUtilTestWithDir::SetUp(); + product_version_ = Version("30.0.1559.0"); + max_version_ = Version("47.0.1559.0"); + + // Install the product according to the version. + original_state_.reset(new FakeInstallationState()); + InstallProduct(); + + // Prepare to update the product in the temp dir. + installer_state_.reset(new installer::InstallerState( + kSystemInstall_ ? installer::InstallerState::SYSTEM_LEVEL : + installer::InstallerState::USER_LEVEL)); + installer_state_->AddProductFromState( + kProductType_, + *original_state_->GetProductState(kSystemInstall_, kProductType_)); + + // Create archives in the two version dirs. + ASSERT_TRUE( + file_util::CreateDirectory(GetProductVersionArchivePath().DirName())); + ASSERT_EQ(1, file_util::WriteFile(GetProductVersionArchivePath(), "a", 1)); + ASSERT_TRUE( + file_util::CreateDirectory(GetMaxVersionArchivePath().DirName())); + ASSERT_EQ(1, file_util::WriteFile(GetMaxVersionArchivePath(), "b", 1)); + } + + virtual void TearDown() OVERRIDE { + original_state_.reset(); + SetupUtilTestWithDir::TearDown(); + } + + base::FilePath GetArchivePath(const Version& version) const { + return test_dir_.path() + .AppendASCII(version.GetString()) + .Append(installer::kInstallerDir) + .Append(installer::kChromeArchive); + } + + base::FilePath GetMaxVersionArchivePath() const { + return GetArchivePath(max_version_); + } + + base::FilePath GetProductVersionArchivePath() const { + return GetArchivePath(product_version_); + } + + void InstallProduct() { + FakeProductState* product = FakeProductState::FromProductState( + original_state_->GetNonVersionedProductState(kSystemInstall_, + kProductType_)); + + product->set_version(product_version_); + CommandLine uninstall_command( + test_dir_.path().AppendASCII(product_version_.GetString()) + .Append(installer::kInstallerDir) + .Append(installer::kSetupExe)); + uninstall_command.AppendSwitch(installer::switches::kUninstall); + product->set_uninstall_command(uninstall_command); + } + + void UninstallProduct() { + FakeProductState::FromProductState( + original_state_->GetNonVersionedProductState(kSystemInstall_, + kProductType_)) + ->set_version(Version()); + } + + static const bool kSystemInstall_; + static const BrowserDistribution::Type kProductType_; + Version product_version_; + Version max_version_; + scoped_ptr<FakeInstallationState> original_state_; + scoped_ptr<installer::InstallerState> installer_state_; +}; + +const bool FindArchiveToPatchTest::kSystemInstall_ = false; +const BrowserDistribution::Type FindArchiveToPatchTest::kProductType_ = + BrowserDistribution::CHROME_BROWSER; + +} // namespace + +// Test that the path to the advertised product version is found. +TEST_F(FindArchiveToPatchTest, ProductVersionFound) { + base::FilePath patch_source(installer::FindArchiveToPatch( + *original_state_, *installer_state_)); + EXPECT_EQ(GetProductVersionArchivePath().value(), patch_source.value()); +} + +// Test that the path to the max version is found if the advertised version is +// missing. +TEST_F(FindArchiveToPatchTest, MaxVersionFound) { + // The patch file is absent. + ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false)); + base::FilePath patch_source(installer::FindArchiveToPatch( + *original_state_, *installer_state_)); + EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value()); + + // The product doesn't appear to be installed, so the max version is found. + UninstallProduct(); + patch_source = installer::FindArchiveToPatch( + *original_state_, *installer_state_); + EXPECT_EQ(GetMaxVersionArchivePath().value(), patch_source.value()); +} + +// Test that an empty path is returned if no version is found. +TEST_F(FindArchiveToPatchTest, NoVersionFound) { + // The product doesn't appear to be installed and no archives are present. + UninstallProduct(); + ASSERT_TRUE(base::DeleteFile(GetProductVersionArchivePath(), false)); + ASSERT_TRUE(base::DeleteFile(GetMaxVersionArchivePath(), false)); + + base::FilePath patch_source(installer::FindArchiveToPatch( + *original_state_, *installer_state_)); + EXPECT_EQ(base::FilePath::StringType(), patch_source.value()); +} diff --git a/chrome/installer/util/util_constants.cc b/chrome/installer/util/util_constants.cc index e293e76af9..962012a33a 100644 --- a/chrome/installer/util/util_constants.cc +++ b/chrome/installer/util/util_constants.cc @@ -94,7 +94,10 @@ const char kForceConfigureUserSettings[] = "force-configure-user-settings"; // confirmation from user. const char kForceUninstall[] = "force-uninstall"; -// Specify the file path of Chrome archive for install. +// Specify the path to the compressed Chrome archive for install. If not +// specified, chrome.packed.7z or chrome.7z in the same directory as setup.exe +// is used (the packed file is preferred; see kUncompressedArchive to force use +// of an uncompressed archive). const char kInstallArchive[] = "install-archive"; // Specify the file path of Chrome master preference file. @@ -174,6 +177,10 @@ const char kUninstall[] = "uninstall"; // path given by --new-setup-exe. const char kUpdateSetupExe[] = "update-setup-exe"; +// Use the given uncompressed chrome.7z archive as the source of files to +// install. +const char kUncompressedArchive[] = "uncompressed-archive"; + // Enable verbose logging (info level). const char kVerboseLogging[] = "verbose-logging"; @@ -219,6 +226,7 @@ const char kOutputFile[] = "output-file"; const wchar_t kActiveSetupExe[] = L"chrmstp.exe"; const wchar_t kChromeAppHostExe[] = L"app_host.exe"; const wchar_t kChromeDll[] = L"chrome.dll"; +const wchar_t kChromeChildDll[] = L"chrome_child.dll"; const wchar_t kChromeExe[] = L"chrome.exe"; const wchar_t kChromeFrameDll[] = L"npchrome_frame.dll"; const wchar_t kChromeFrameHelperExe[] = L"chrome_frame_helper.exe"; diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h index 3f9a496a5f..b651a61339 100644 --- a/chrome/installer/util/util_constants.h +++ b/chrome/installer/util/util_constants.h @@ -82,12 +82,14 @@ enum InstallStatus { WAIT_FOR_EXISTING_FAILED, // 48. OS error waiting for existing setup.exe. PATCH_INVALID_ARGUMENTS, // 49. The arguments of --patch were missing or // they were invalid for any reason. + DIFF_PATCH_SOURCE_MISSING, // 50. No previous version archive found for + // differential update. // Friendly reminder: note the COMPILE_ASSERT below. }; // Existing InstallStatus values must not change. Always add to the end. -COMPILE_ASSERT(installer::PATCH_INVALID_ARGUMENTS == 49, +COMPILE_ASSERT(installer::DIFF_PATCH_SOURCE_MISSING == 50, dont_change_enum); // The type of an update archive. @@ -173,6 +175,7 @@ extern const char kSelfDestruct[]; extern const char kSystemLevel[]; extern const char kUninstall[]; extern const char kUpdateSetupExe[]; +extern const char kUncompressedArchive[]; extern const char kVerboseLogging[]; extern const char kShowEula[]; extern const char kShowEulaForMetro[]; @@ -190,6 +193,7 @@ extern const char kOutputFile[]; extern const wchar_t kActiveSetupExe[]; extern const wchar_t kChromeAppHostExe[]; extern const wchar_t kChromeDll[]; +extern const wchar_t kChromeChildDll[]; extern const wchar_t kChromeExe[]; extern const wchar_t kChromeFrameDll[]; extern const wchar_t kChromeFrameHelperExe[]; |