chromium/chrome/installer/setup/archive_patch_helper.cc

// Copyright 2013 The Chromium Authors
// 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 <stdint.h>

#include <optional>

#include "base/files/file_util.h"
#include "base/logging.h"
#include "chrome/installer/util/lzma_util.h"
#include "components/zucchini/zucchini.h"
#include "components/zucchini/zucchini_integration.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,
                                       UnPackConsumer consumer)
    : working_directory_(working_directory),
      compressed_archive_(compressed_archive),
      patch_source_(patch_source),
      target_(target),
      consumer_(consumer) {}

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,
    UnPackConsumer consumer) {
  ArchivePatchHelper instance(working_directory, compressed_archive,
                              patch_source, target, consumer);
  return (instance.Uncompress(nullptr) && instance.ApplyAndDeletePatch());
}

bool ArchivePatchHelper::Uncompress(base::FilePath* last_uncompressed_file) {
  // The target shouldn't already exist.
  DCHECK(!base::PathExists(target_));

  // UnPackArchive takes care of logging.
  base::FilePath output_file;
  UnPackStatus unpack_status =
      UnPackArchive(compressed_archive_, working_directory_, &output_file);
  RecordUnPackMetrics(unpack_status, consumer_);
  if (unpack_status != UNPACK_NO_ERROR)
    return false;

  last_uncompressed_file_ = output_file;
  if (last_uncompressed_file)
    *last_uncompressed_file = last_uncompressed_file_;
  return true;
}

bool ArchivePatchHelper::ApplyAndDeletePatch() {
  const bool succeeded = ZucchiniEnsemblePatch() || BinaryPatch();
  if (!last_uncompressed_file_.empty()) {
    base::DeleteFile(last_uncompressed_file_);
  }
  return succeeded;
}

bool ArchivePatchHelper::ZucchiniEnsemblePatch() {
  if (last_uncompressed_file_.empty()) {
    LOG(ERROR) << "No patch file found in compressed archive.";
    return false;
  }

  zucchini::status::Code result =
      zucchini::Apply(patch_source_, last_uncompressed_file_, target_);

  if (result == zucchini::status::kStatusSuccess)
    return true;

  LOG(ERROR) << "Failed to apply patch " << last_uncompressed_file_.value()
             << " to file " << patch_source_.value() << " and generating file "
             << target_.value()
             << " using Zucchini. err=" << static_cast<uint32_t>(result);

  // Ensure a partial output is not left behind.
  base::DeleteFile(target_);

  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_);

  return false;
}

}  // namespace installer