[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