text: // Copyright 2017 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 "components/zucchini/zucchini_integration.h"
  
  #include <utility>
  
  #include "base/logging.h"
  #include "components/zucchini/buffer_view.h"
  #include "components/zucchini/mapped_file.h"
  #include "components/zucchini/patch_reader.h"
  
  namespace zucchini {
  
  namespace {
  
  struct FileNames {
    FileNames() : is_dummy(true) {
      // Use fake names.
      old_name = old_name.AppendASCII("old_name");
      new_name = new_name.AppendASCII("new_name");
      patch_name = patch_name.AppendASCII("patch_name");
    }
  
    FileNames(const base::FilePath& old_name,
              const base::FilePath& new_name,
              const base::FilePath& patch_name)
        : old_name(old_name),
          new_name(new_name),
          patch_name(patch_name),
          is_dummy(false) {}
  
    base::FilePath old_name;
    base::FilePath new_name;
    base::FilePath patch_name;
  
    // A flag to decide whether the filenames are only for error output.
    const bool is_dummy;
  };
  
  status::Code GenerateCommon(base::File old_file,
                              base::File new_file,
                              base::File patch_file,
                              const FileNames& names,
                              bool force_keep,
                              bool is_raw,
                              std::string imposed_matches) {
    MappedFileReader mapped_old(std::move(old_file));
    if (mapped_old.HasError()) {
      LOG(ERROR) << "Error with file " << names.old_name.value() << ": "
                 << mapped_old.error();
      return status::kStatusFileReadError;
    }
  
    MappedFileReader mapped_new(std::move(new_file));
    if (mapped_new.HasError()) {
      LOG(ERROR) << "Error with file " << names.new_name.value() << ": "
                 << mapped_new.error();
      return status::kStatusFileReadError;
    }
  
    status::Code result = status::kStatusSuccess;
    EnsemblePatchWriter patch_writer(mapped_old.region(), mapped_new.region());
    if (is_raw) {
      result = GenerateBufferRaw(mapped_old.region(), mapped_new.region(),
                                 &patch_writer);
    } else {
      result = GenerateBufferImposed(mapped_old.region(), mapped_new.region(),
                                     std::move(imposed_matches), &patch_writer);
    }
    if (result != status::kStatusSuccess) {
      LOG(ERROR) << "Fatal error encountered when generating patch.";
      return result;
    }
  
    // By default, delete patch on destruction, to avoid having lingering files in
    // case of a failure. On Windows deletion can be done by the OS.
    MappedFileWriter mapped_patch(names.patch_name, std::move(patch_file),
                                  patch_writer.SerializedSize());
    if (mapped_patch.HasError()) {
      LOG(ERROR) << "Error with file " << names.patch_name.value() << ": "
                 << mapped_patch.error();
      return status::kStatusFileWriteError;
    }
    if (force_keep)
      mapped_patch.Keep();
  
    if (!patch_writer.SerializeInto(mapped_patch.region()))
      return status::kStatusPatchWriteError;
  
    // Successfully created patch. Explicitly request file to be kept.
    if (!mapped_patch.Keep())
      return status::kStatusFileWriteError;
    return status::kStatusSuccess;
  }
  
  status::Code ApplyCommon(base::File old_file,
                           base::File patch_file,
                           base::File new_file,
                           const FileNames& names,
                           bool force_keep) {
    MappedFileReader mapped_patch(std::move(patch_file));
    if (mapped_patch.HasError()) {
      LOG(ERROR) << "Error with file " << names.patch_name.value() << ": "
                 << mapped_patch.error();
      return status::kStatusFileReadError;
    }
  
    auto patch_reader = EnsemblePatchReader::Create(mapped_patch.region());
    if (!patch_reader.has_value()) {
      LOG(ERROR) << "Error reading patch header.";
      return status::kStatusPatchReadError;
    }
  
    MappedFileReader mapped_old(std::move(old_file));
    if (mapped_old.HasError()) {
      LOG(ERROR) << "Error with file " << names.old_name.value() << ": "
                 << mapped_old.error();
      return status::kStatusFileReadError;
    }
  
    PatchHeader header = patch_reader->header();
    // By default, delete output on destruction, to avoid having lingering files
    // in case of a failure. On Windows deletion can be done by the OS.
    MappedFileWriter mapped_new(names.new_name, std::move(new_file),
                                header.new_size);
    if (mapped_new.HasError()) {
      LOG(ERROR) << "Error with file " << names.new_name.value() << ": "
                 << mapped_new.error();
      return status::kStatusFileWriteError;
    }
    if (force_keep)
      mapped_new.Keep();
  
    status::Code result =
        ApplyBuffer(mapped_old.region(), *patch_reader, mapped_new.region());
    if (result != status::kStatusSuccess) {
      LOG(ERROR) << "Fatal error encountered while applying patch.";
      return result;
    }
  
    // Successfully patch |mapped_new|. Explicitly request file to be kept.
    if (!mapped_new.Keep())
      return status::kStatusFileWriteError;
    return status::kStatusSuccess;
  }
  
  status::Code VerifyPatchCommon(base::File patch_file,
                                 base::FilePath patch_name) {
    MappedFileReader mapped_patch(std::move(patch_file));
    if (mapped_patch.HasError()) {
      LOG(ERROR) << "Error with file " << patch_name.value() << ": "
                 << mapped_patch.error();
      return status::kStatusFileReadError;
    }
    auto patch_reader = EnsemblePatchReader::Create(mapped_patch.region());
    if (!patch_reader.has_value()) {
      LOG(ERROR) << "Error reading patch header.";
      return status::kStatusPatchReadError;
    }
    return status::kStatusSuccess;
  }
  
  }  // namespace
  
  status::Code Generate(base::File old_file,
                        base::File new_file,
                        base::File patch_file,
                        bool force_keep,
                        bool is_raw,
                        std::string imposed_matches) {
    const FileNames file_names;
    return GenerateCommon(std::move(old_file), std::move(new_file),
                          std::move(patch_file), file_names, force_keep, is_raw,
                          std::move(imposed_matches));
  }
  
  status::Code Generate(const base::FilePath& old_path,
                        const base::FilePath& new_path,
                        const base::FilePath& patch_path,
                        bool force_keep,
                        bool is_raw,
                        std::string imposed_matches) {
    using base::File;
    File old_file(old_path, File::FLAG_OPEN | File::FLAG_READ |
                                base::File::FLAG_WIN_SHARE_DELETE);
    File new_file(new_path, File::FLAG_OPEN | File::FLAG_READ |
                                base::File::FLAG_WIN_SHARE_DELETE);
    File patch_file(patch_path, File::FLAG_CREATE_ALWAYS | File::FLAG_READ |
                                    File::FLAG_WRITE |
                                    File::FLAG_WIN_SHARE_DELETE |
                                    File::FLAG_CAN_DELETE_ON_CLOSE);
    const FileNames file_names(old_path, new_path, patch_path);
    return GenerateCommon(std::move(old_file), std::move(new_file),
                          std::move(patch_file), file_names, force_keep, is_raw,
                          std::move(imposed_matches));
  }
  
  status::Code Apply(base::File old_file,
                     base::File patch_file,
                     base::File new_file,
                     bool force_keep) {
    const FileNames file_names;
    return ApplyCommon(std::move(old_file), std::move(patch_file),
                       std::move(new_file), file_names, force_keep);
  }
  
  status::Code Apply(const base::FilePath& old_path,
                     const base::FilePath& patch_path,
                     const base::FilePath& new_path,
                     bool force_keep) {
    using base::File;
    File old_file(old_path, File::FLAG_OPEN | File::FLAG_READ |
                                base::File::FLAG_WIN_SHARE_DELETE);
    File patch_file(patch_path, File::FLAG_OPEN | File::FLAG_READ |
                                    base::File::FLAG_WIN_SHARE_DELETE);
    File new_file(new_path, File::FLAG_CREATE_ALWAYS | File::FLAG_READ |
                                File::FLAG_WRITE | File::FLAG_WIN_SHARE_DELETE |
                                File::FLAG_CAN_DELETE_ON_CLOSE);
    const FileNames file_names(old_path, new_path, patch_path);
    return ApplyCommon(std::move(old_file), std::move(patch_file),
                       std::move(new_file), file_names, force_keep);
  }
  
  status::Code VerifyPatch(base::File patch_file) {
    return VerifyPatchCommon(std::move(patch_file), base::FilePath());
  }
  
  status::Code VerifyPatch(const base::FilePath& patch_path) {
    using base::File;
    File patch_file(patch_path, File::FLAG_OPEN | File::FLAG_READ |
                                    base::File::FLAG_WIN_SHARE_DELETE);
    return VerifyPatchCommon(std::move(patch_file), patch_path);
  }
  
  }  // namespace zucchini
  