[Zucchini] Add support to setup.exe for Zucchini patches
Adds support to setup.exe to both upgrade itself and chrome.7z through
Zucchini patches when "use_zucchini = true" as a gn arg.
Zucchini patches are NOT created at this time! This is primarily for
experimentation with the release infra. A security review will be
conducted before launch.
This has been successfully tested using Zucchini-based versions of
chrome_updater.exe for the following cases:
Patchers
| setup.exe | chrome.7z |
-------------------------
| Courgette | Courgette | Default (can also be used for downgrades)
| Courgette | Zucchini | Upgrade: once enabled
| Zucchini | Zucchini | Target Default
| Zucchini | Courgette | Downgrade: in the event of issues
Child Changes:
- setup.rc PATCHERTYPE:
https://chromium-review.googlesource.com/937400
- BUILD.gn zucchini.exe:
https://chromium-review.googlesource.com/937396
Bug: 729154
Change-Id: Iec2e514a8b8a5ee311fe210505ac2b31c0418f54
Reviewed-on: https://chromium-review.googlesource.com/916553
Commit-Queue: Calder Kitagawa <ckitagawa@google.com>
Reviewed-by: Samuel Huang <huangs@chromium.org>
Reviewed-by: Greg Thompson <grt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#539854}
diff --git a/chrome/installer/setup/BUILD.gn b/chrome/installer/setup/BUILD.gn
index 32524d4..c15e6aa 100644
--- a/chrome/installer/setup/BUILD.gn
+++ b/chrome/installer/setup/BUILD.gn
@@ -2,9 +2,18 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//build/buildflag_header.gni")
+import("//chrome/installer/setup/buildflags.gni")
import("//chrome/process_version_rc_template.gni")
import("//testing/test.gni")
+buildflag_header("buildflags") {
+ header = "buildflags.h"
+
+ # Use ZUCCHINI since ZUCCHINI_ENABLED is too long a name for setup.rc.
+ flags = [ "ZUCCHINI=$use_zucchini" ]
+}
+
if (is_win) {
executable("setup") {
sources = [
@@ -21,6 +30,7 @@
configs += [ "//build/config/win:windowed" ]
deps = [
+ ":buildflags",
":lib",
":setup_exe_version",
"//build/config:exe_and_shlib_deps",
@@ -69,12 +79,14 @@
]
public_deps = [
+ ":buildflags",
"//base",
"//chrome/common:constants",
"//chrome/common:version_header",
"//chrome/install_static:install_static_util",
"//chrome/installer/util:with_rc_strings",
"//chrome/installer/zucchini:zucchini_io",
+ "//chrome/installer/zucchini:zucchini_lib",
"//chrome_elf:constants",
"//components/base32",
"//components/crash/content/app",
diff --git a/chrome/installer/setup/DEPS b/chrome/installer/setup/DEPS
index 2d10b6a..3633eda 100644
--- a/chrome/installer/setup/DEPS
+++ b/chrome/installer/setup/DEPS
@@ -1,5 +1,6 @@
include_rules = [
"+chrome_elf/chrome_elf_constants.h",
+ "+chrome/installer/zucchini",
"+chrome/install_static",
"+components/base32",
"+courgette",
diff --git a/chrome/installer/setup/archive_patch_helper.cc b/chrome/installer/setup/archive_patch_helper.cc
index ea0bd13..35fc5bf65 100644
--- a/chrome/installer/setup/archive_patch_helper.cc
+++ b/chrome/installer/setup/archive_patch_helper.cc
@@ -8,7 +8,9 @@
#include "base/files/file_util.h"
#include "base/logging.h"
+#include "chrome/installer/setup/buildflags.h"
#include "chrome/installer/util/lzma_util.h"
+#include "chrome/installer/zucchini/zucchini.h"
#include "chrome/installer/zucchini/zucchini_integration.h"
#include "courgette/courgette.h"
#include "third_party/bspatch/mbspatch.h"
@@ -37,8 +39,7 @@
UnPackConsumer consumer) {
ArchivePatchHelper instance(working_directory, compressed_archive,
patch_source, target, consumer);
- return (instance.Uncompress(NULL) &&
- (instance.CourgetteEnsemblePatch() || instance.BinaryPatch()));
+ return (instance.Uncompress(NULL) && instance.ApplyPatch());
}
bool ArchivePatchHelper::Uncompress(base::FilePath* last_uncompressed_file) {
@@ -61,6 +62,14 @@
return true;
}
+bool ArchivePatchHelper::ApplyPatch() {
+#if BUILDFLAG(ZUCCHINI)
+ if (ZucchiniEnsemblePatch())
+ return true;
+#endif // BUILDFLAG(ZUCCHINI)
+ return CourgetteEnsemblePatch() || BinaryPatch();
+}
+
bool ArchivePatchHelper::CourgetteEnsemblePatch() {
if (last_uncompressed_file_.empty()) {
LOG(ERROR) << "No patch file found in compressed archive.";
diff --git a/chrome/installer/setup/archive_patch_helper.h b/chrome/installer/setup/archive_patch_helper.h
index 95d9fe3..fa9eecb 100644
--- a/chrome/installer/setup/archive_patch_helper.h
+++ b/chrome/installer/setup/archive_patch_helper.h
@@ -43,8 +43,9 @@
// 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.
+ // Ensemble patching via Zucchini is attempted first (if it is enabled). If
+ // that fails Courgette is attempted with fallback to bspatch. Returns false
+ // if uncompression or all patching steps fail.
static bool UncompressAndPatch(const base::FilePath& working_directory,
const base::FilePath& compressed_archive,
const base::FilePath& patch_source,
@@ -56,6 +57,13 @@
// file extracted from the archive.
bool Uncompress(base::FilePath* last_uncompressed_file);
+ // Performs ensemble patching on the uncompressed version of
+ // |compressed_archive| in |working_directory| as specified in the
+ // constructor using files from |patch_source|. Ensemble patching via
+ // Zucchini is attempted first (if it is enabled). If that fails patching via
+ // Courgette is attempted. Courgette falls back to bspatch if unsuccessful.
+ bool ApplyPatch();
+
// Attempts to use Courgette to apply last_uncompressed_file() to
// patch_source() to generate target(). Returns false if patching fails.
bool CourgetteEnsemblePatch();
diff --git a/chrome/installer/setup/archive_patch_helper_unittest.cc b/chrome/installer/setup/archive_patch_helper_unittest.cc
index 64dc8e7..3a742d19 100644
--- a/chrome/installer/setup/archive_patch_helper_unittest.cc
+++ b/chrome/installer/setup/archive_patch_helper_unittest.cc
@@ -46,9 +46,9 @@
} // namespace
// Test that patching works.
-TEST_F(ArchivePatchHelperTest, Patching) {
+TEST_F(ArchivePatchHelperTest, CourgettePatching) {
base::FilePath src = data_dir_.AppendASCII("archive1.7z");
- base::FilePath patch = data_dir_.AppendASCII("archive.diff");
+ base::FilePath patch = data_dir_.AppendASCII("courgette_archive.diff");
base::FilePath dest = test_dir_.GetPath().AppendASCII("archive2.7z");
installer::ArchivePatchHelper archive_helper(
test_dir_.GetPath(), base::FilePath(), src, dest,
@@ -60,6 +60,19 @@
EXPECT_TRUE(base::ContentsEqual(dest, base));
}
+TEST_F(ArchivePatchHelperTest, ZucchiniPatching) {
+ base::FilePath src = data_dir_.AppendASCII("archive1.7z");
+ base::FilePath patch = data_dir_.AppendASCII("zucchini_archive.diff");
+ base::FilePath dest = test_dir_.GetPath().AppendASCII("archive2.7z");
+ installer::ArchivePatchHelper archive_helper(
+ test_dir_.GetPath(), base::FilePath(), src, dest,
+ installer::UnPackConsumer::SETUP_EXE_PATCH);
+ archive_helper.set_last_uncompressed_file(patch);
+ EXPECT_TRUE(archive_helper.ZucchiniEnsemblePatch());
+ base::FilePath base = data_dir_.AppendASCII("archive2.7z");
+ EXPECT_TRUE(base::ContentsEqual(dest, base));
+}
+
TEST_F(ArchivePatchHelperTest, InvalidDiff_MisalignedCblen) {
base::FilePath src = data_dir_.AppendASCII("bin.old");
base::FilePath patch = data_dir_.AppendASCII("misaligned_cblen.diff");
diff --git a/chrome/installer/setup/buildflags.gni b/chrome/installer/setup/buildflags.gni
new file mode 100644
index 0000000..2d2867ed
--- /dev/null
+++ b/chrome/installer/setup/buildflags.gni
@@ -0,0 +1,9 @@
+# Copyright 2018 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.
+
+declare_args() {
+ # Specify if the Zucchini patcher features should be included in setup.exe.
+ # See //chrome/installer/zucchini for more information.
+ use_zucchini = false
+}
diff --git a/chrome/installer/setup/setup.rc b/chrome/installer/setup/setup.rc
index 479e835..9f5342e 100644
--- a/chrome/installer/setup/setup.rc
+++ b/chrome/installer/setup/setup.rc
@@ -14,6 +14,8 @@
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
+#include "chrome/installer/setup/buildflags.h"
+
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
@@ -135,6 +137,10 @@
// on their integer resource names.
//
#define IDR_PATCHER_TYPE_COURGETTE 1
+#define IDR_PATCHER_TYPE_ZUCCHINI 2
IDR_PATCHER_TYPE_COURGETTE PATCHERTYPE { 0L }
+#if BUILDFLAG(ZUCCHINI)
+IDR_PATCHER_TYPE_ZUCCHINI PATCHERTYPE { 0L }
+#endif // BUILDFLAG(ZUCCHINI)
diff --git a/chrome/installer/setup/setup_constants.cc b/chrome/installer/setup/setup_constants.cc
index e93f004..4be3d16b 100644
--- a/chrome/installer/setup/setup_constants.cc
+++ b/chrome/installer/setup/setup_constants.cc
@@ -19,6 +19,12 @@
const wchar_t kMediaPlayerRegPath[] =
L"Software\\Microsoft\\MediaPlayer\\ShimInclusionList";
+const char kCourgette[] = "courgette";
+const char kBsdiff[] = "bsdiff";
+#if BUILDFLAG(ZUCCHINI)
+const char kZucchini[] = "zucchini";
+#endif // BUILDFLAG(ZUCCHINI)
+
namespace switches {
// Setting this will delay the operation of setup by the specified number of
diff --git a/chrome/installer/setup/setup_constants.h b/chrome/installer/setup/setup_constants.h
index 359ff06..4c7993f7 100644
--- a/chrome/installer/setup/setup_constants.h
+++ b/chrome/installer/setup/setup_constants.h
@@ -7,6 +7,8 @@
#ifndef CHROME_INSTALLER_SETUP_SETUP_CONSTANTS_H__
#define CHROME_INSTALLER_SETUP_SETUP_CONSTANTS_H__
+#include "chrome/installer/setup/buildflags.h"
+
namespace installer {
extern const wchar_t kChromeArchive[];
@@ -19,6 +21,20 @@
extern const wchar_t kMediaPlayerRegPath[];
+// The range of error values among the installer, Courgette, BSDiff and
+// Zucchini overlap. These offset values disambiguate between different sets
+// of errors by shifting the values up with the specified offset.
+const int kCourgetteErrorOffset = 300;
+const int kBsdiffErrorOffset = 600;
+const int kZucchiniErrorOffset = 900;
+
+// Arguments to --patch switch
+extern const char kCourgette[];
+extern const char kBsdiff[];
+#if BUILDFLAG(ZUCCHINI)
+extern const char kZucchini[];
+#endif // BUILDFLAG(ZUCCHINI)
+
namespace switches {
extern const char kDelay[];
diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc
index 0ab5cb1..a843c52 100644
--- a/chrome/installer/setup/setup_main.cc
+++ b/chrome/installer/setup/setup_main.cc
@@ -47,6 +47,7 @@
#include "chrome/install_static/install_details.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/setup/archive_patch_helper.h"
+#include "chrome/installer/setup/buildflags.h"
#include "chrome/installer/setup/install.h"
#include "chrome/installer/setup/install_worker.h"
#include "chrome/installer/setup/installer_crash_reporting.h"
@@ -311,18 +312,15 @@
}
archive_helper->set_patch_source(patch_source);
- // Try courgette first. Failing that, try bspatch.
// Patch application sometimes takes a very long time, so use 100 buckets for
// up to an hour.
start_time = base::TimeTicks::Now();
installer_state.SetStage(installer::PATCHING);
- if (!archive_helper->CourgetteEnsemblePatch()) {
- if (!archive_helper->BinaryPatch()) {
- *install_status = installer::APPLY_DIFF_PATCH_FAILED;
- installer_state.WriteInstallerResult(
- *install_status, IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, NULL);
- return false;
- }
+ if (!archive_helper->ApplyPatch()) {
+ *install_status = installer::APPLY_DIFF_PATCH_FAILED;
+ installer_state.WriteInstallerResult(
+ *install_status, IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, NULL);
+ return false;
}
// Record patch time only if it was successful.
@@ -1000,6 +998,11 @@
*exit_code = installer::BsdiffPatchFiles(input_file,
patch_file,
output_file);
+#if BUILDFLAG(ZUCCHINI)
+ } else if (patch_type_str == installer::kZucchini) {
+ *exit_code =
+ installer::ZucchiniPatchFiles(input_file, patch_file, output_file);
+#endif // BUILDFLAG(ZUCCHINI)
} else {
*exit_code = installer::PATCH_INVALID_ARGUMENTS;
}
diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc
index fb298ac..74bcb7a 100644
--- a/chrome/installer/setup/setup_util.cc
+++ b/chrome/installer/setup/setup_util.cc
@@ -53,6 +53,8 @@
#include "chrome/installer/util/non_updating_app_registration_data.h"
#include "chrome/installer/util/updating_app_registration_data.h"
#include "chrome/installer/util/util_constants.h"
+#include "chrome/installer/zucchini/zucchini.h"
+#include "chrome/installer/zucchini/zucchini_integration.h"
#include "courgette/courgette.h"
#include "courgette/third_party/bsdiff/bsdiff.h"
@@ -322,6 +324,29 @@
return exit_code;
}
+int ZucchiniPatchFiles(const base::FilePath& src,
+ const base::FilePath& patch,
+ const base::FilePath& dest) {
+ VLOG(1) << "Applying Zucchini patch " << patch.value() << " to file "
+ << src.value() << " and generating file " << dest.value();
+
+ if (src.empty() || patch.empty() || dest.empty())
+ return installer::PATCH_INVALID_ARGUMENTS;
+
+ const zucchini::status::Code patch_status = zucchini::Apply(src, patch, dest);
+ const int exit_code =
+ (patch_status != zucchini::status::kStatusSuccess)
+ ? static_cast<int>(patch_status) + kZucchiniErrorOffset
+ : 0;
+
+ LOG_IF(ERROR, exit_code) << "Failed to apply Zucchini patch " << patch.value()
+ << " to file " << src.value()
+ << " and generating file " << dest.value()
+ << ". err=" << exit_code;
+
+ return exit_code;
+}
+
base::Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) {
VLOG(1) << "Looking for Chrome version folder under " << chrome_path.value();
base::FileEnumerator version_enum(chrome_path, false,
diff --git a/chrome/installer/setup/setup_util.h b/chrome/installer/setup/setup_util.h
index 41b4da6..ed655efc 100644
--- a/chrome/installer/setup/setup_util.h
+++ b/chrome/installer/setup/setup_util.h
@@ -60,6 +60,13 @@
const base::FilePath& patch,
const base::FilePath& dest);
+// Applies a patch file to source file using Zucchini. Returns 0 in case of
+// success. In case of errors, it returns kZucchiniErrorOffset + a Zucchini
+// status code, as defined in chrome/installer/zucchini/zucchini.h
+int ZucchiniPatchFiles(const base::FilePath& src,
+ const base::FilePath& patch,
+ const base::FilePath& dest);
+
// Find the version of Chrome from an install source directory.
// Chrome_path should contain at least one version folder.
// Returns the maximum version found or NULL if no version is found.
diff --git a/chrome/installer/util/util_constants.cc b/chrome/installer/util/util_constants.cc
index 0da2433..601fdd0 100644
--- a/chrome/installer/util/util_constants.cc
+++ b/chrome/installer/util/util_constants.cc
@@ -220,9 +220,6 @@
const size_t kMaxAppModelIdLength = 64U;
-const char kCourgette[] = "courgette";
-const char kBsdiff[] = "bsdiff";
-
const char kSetupHistogramAllocatorName[] = "SetupMetrics";
} // namespace installer
diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h
index bed4d8e0..eaf99442 100644
--- a/chrome/installer/util/util_constants.h
+++ b/chrome/installer/util/util_constants.h
@@ -123,23 +123,23 @@
// this are the fork-and-join for diff vs. full installers (where there are
// additional (costly) stages for the former) and rollback in case of error.
enum InstallerStage {
- NO_STAGE, // No stage to report.
- UPDATING_SETUP, // Courgette patching setup.exe (diff).
- PRECONDITIONS, // Evaluating pre-install conditions.
- UNCOMPRESSING, // Uncompressing chrome.packed.7z.
- PATCHING, // Patching chrome.7z using Courgette (diff).
- UNPACKING, // Unpacking chrome.7z.
- CREATING_VISUAL_MANIFEST, // Creating VisualElementsManifest.xml.
- BUILDING, // Building the install work item list.
- EXECUTING, // Executing the install work item list.
- UPDATING_CHANNELS, // Updating channel information.
- COPYING_PREFERENCES_FILE, // Copying preferences file.
- CREATING_SHORTCUTS, // Creating shortcuts.
- REGISTERING_CHROME, // Performing Chrome registration.
- REMOVING_OLD_VERSIONS, // Deleting old version directories.
- ROLLINGBACK, // Rolling-back the install work item list.
- FINISHING, // Finishing the install.
- NUM_STAGES // The number of stages.
+ NO_STAGE, // No stage to report.
+ UPDATING_SETUP, // Patching setup.exe with differential update.
+ PRECONDITIONS, // Evaluating pre-install conditions.
+ UNCOMPRESSING, // Uncompressing chrome.packed.7z.
+ PATCHING, // Patching chrome.7z with differential update.
+ UNPACKING, // Unpacking chrome.7z.
+ CREATING_VISUAL_MANIFEST, // Creating VisualElementsManifest.xml.
+ BUILDING, // Building the install work item list.
+ EXECUTING, // Executing the install work item list.
+ UPDATING_CHANNELS, // Updating channel information.
+ COPYING_PREFERENCES_FILE, // Copying preferences file.
+ CREATING_SHORTCUTS, // Creating shortcuts.
+ REGISTERING_CHROME, // Performing Chrome registration.
+ REMOVING_OLD_VERSIONS, // Deleting old version directories.
+ ROLLINGBACK, // Rolling-back the install work item list.
+ FINISHING, // Finishing the install.
+ NUM_STAGES // The number of stages.
};
namespace switches {
@@ -234,16 +234,6 @@
extern const size_t kMaxAppModelIdLength;
-// The range of error values for the installer, Courgette, and bsdiff is
-// overlapping. These offset values disambiguate between different sets
-// of errors by shifting the values up with the specified offset.
-const int kCourgetteErrorOffset = 300;
-const int kBsdiffErrorOffset = 600;
-
-// Arguments to --patch switch
-extern const char kCourgette[];
-extern const char kBsdiff[];
-
// Name of the allocator (and associated file) for storing histograms to be
// reported by Chrome during its next upload.
extern const char kSetupHistogramAllocatorName[];
diff --git a/chrome/test/data/installer/archive.diff b/chrome/test/data/installer/courgette_archive.diff
similarity index 100%
rename from chrome/test/data/installer/archive.diff
rename to chrome/test/data/installer/courgette_archive.diff
Binary files differ
diff --git a/chrome/test/data/installer/zucchini_archive.diff b/chrome/test/data/installer/zucchini_archive.diff
new file mode 100644
index 0000000..f7bb9b1
--- /dev/null
+++ b/chrome/test/data/installer/zucchini_archive.diff
Binary files differ