Commit 9f0f325d authored by Samuel Huang's avatar Samuel Huang Committed by Commit Bot

[Zucchini] Refactor Zucchini-gen in zucchini_io layer.

This CL moves Zucchini-gen invocation code from zucchini_commands.cc
(in target zucchini) to zucchini_integration.cc (in target zucchini_io)
to clean up layering in Zucchini API, i.e.:
- zucchini_lib: Operates on buffers only.
- zucchini_io: Adds files interface, uses memory-mapped I/O.
- zucchini: Stand-alone executable that parses command-line arguments.

Other changes:
- Rename zucchini_lib functions (zuchcini.h), to dedup names and
  emphasize that these functions operate on buffers:
  - GenerateEnsemble() -> GenerateBuffer(),
  - GenerateEnsembleWithImposedMatches() -> GenerateBufferImposed(),
  - GenerateRaw() -> GenerateBufferRaw(),
  - Apply() -> ApplyBuffer().
  These renames only affect Zucchini and various tests.
- Variable renames and parameter reordering in zucchini_integration.cc.
- Remove '-dd' param in help text of Zucchini-detect (was never ported
  from Trunk, and has been recently deleted there as well).
- Replace all base::File&& with base::File.
- Miscellaneous cleanup for header include.
- Update README.md.

Change-Id: I835b80d4d3d7b291fa822a7a89dab225bf9171e9
Reviewed-on: https://chromium-review.googlesource.com/1105625Reviewed-by: default avatarSamuel Huang <huangs@chromium.org>
Reviewed-by: default avatarGreg Thompson <grt@chromium.org>
Commit-Queue: Samuel Huang <huangs@chromium.org>
Cr-Commit-Position: refs/heads/master@{#569274}
parent cbfb84c5
...@@ -145,6 +145,17 @@ values that describe content on a higher level of abstraction, masking away ...@@ -145,6 +145,17 @@ values that describe content on a higher level of abstraction, masking away
undesirable noise in raw content. Notably, the projection encodes references undesirable noise in raw content. Notably, the projection encodes references
based on their associated label. based on their associated label.
## Interfaces
zucchini_lib: Core Zucchini library that operate on buffers to generate and
apply patches.
zucchini_io: Wrapper on zucchini_lib that handles file I/O, using memory-mapped
I/O to interface with zucchini_lib.
zucchini: Stand-alone executable that parses command-line arguments, and passes
the results to zucchini_io. Also implements various helper flows.
## Zucchini Ensemble Patch Format ## Zucchini Ensemble Patch Format
### Types ### Types
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "components/zucchini/element_detection.h" #include "components/zucchini/element_detection.h"
#include <map>
#include <vector> #include <vector>
#include "base/bind.h" #include "base/bind.h"
......
...@@ -54,6 +54,6 @@ DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) { ...@@ -54,6 +54,6 @@ DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) {
zucchini::MutableBufferView new_image(new_data.data(), new_size); zucchini::MutableBufferView new_image(new_data.data(), new_size);
// Fuzz target. // Fuzz target.
zucchini::Apply(old_image, *patch_reader, new_image); zucchini::ApplyBuffer(old_image, *patch_reader, new_image);
// No need to check whether output exist, or if so, whether it's valid. // No need to check whether output exist, or if so, whether it's valid.
} }
...@@ -56,7 +56,7 @@ DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) { ...@@ -56,7 +56,7 @@ DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) {
zucchini::EnsemblePatchWriter patch_writer(old_image, new_image); zucchini::EnsemblePatchWriter patch_writer(old_image, new_image);
// Fuzz Target. // Fuzz Target.
zucchini::GenerateRaw(old_image, new_image, &patch_writer); zucchini::GenerateBufferRaw(old_image, new_image, &patch_writer);
// Check that the patch size is sane. Crash the fuzzer if this isn't the case // Check that the patch size is sane. Crash the fuzzer if this isn't the case
// as it is a failure in Zucchini's patch performance that is worth // as it is a failure in Zucchini's patch performance that is worth
......
...@@ -57,7 +57,7 @@ DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) { ...@@ -57,7 +57,7 @@ DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) {
zucchini::EnsemblePatchWriter patch_writer(old_image, new_image); zucchini::EnsemblePatchWriter patch_writer(old_image, new_image);
// Fuzz Target. // Fuzz Target.
zucchini::GenerateEnsemble(old_image, new_image, &patch_writer); zucchini::GenerateBuffer(old_image, new_image, &patch_writer);
// Write to buffer to avoid IO. // Write to buffer to avoid IO.
size_t patch_size = patch_writer.SerializedSize(); size_t patch_size = patch_writer.SerializedSize();
......
...@@ -49,8 +49,8 @@ void TestGenApply(const std::string& old_filename, ...@@ -49,8 +49,8 @@ void TestGenApply(const std::string& old_filename,
// Generate patch from "old" to "new". // Generate patch from "old" to "new".
ASSERT_EQ(status::kStatusSuccess, ASSERT_EQ(status::kStatusSuccess,
raw ? GenerateRaw(old_region, new_region, &patch_writer) raw ? GenerateBufferRaw(old_region, new_region, &patch_writer)
: GenerateEnsemble(old_region, new_region, &patch_writer)); : GenerateBuffer(old_region, new_region, &patch_writer));
size_t patch_size = patch_writer.SerializedSize(); size_t patch_size = patch_writer.SerializedSize();
EXPECT_GE(patch_size, 80U); // Minimum size is empty patch. EXPECT_GE(patch_size, 80U); // Minimum size is empty patch.
...@@ -73,9 +73,9 @@ void TestGenApply(const std::string& old_filename, ...@@ -73,9 +73,9 @@ void TestGenApply(const std::string& old_filename,
// Apply patch to "old" to get "patched new", ensure it's identical to "new". // Apply patch to "old" to get "patched new", ensure it's identical to "new".
std::vector<uint8_t> patched_new_buffer(new_region.size()); std::vector<uint8_t> patched_new_buffer(new_region.size());
ASSERT_EQ(status::kStatusSuccess, ASSERT_EQ(status::kStatusSuccess, ApplyBuffer(old_region, *patch_reader,
Apply(old_region, *patch_reader, {patched_new_buffer.data(),
{patched_new_buffer.data(), patched_new_buffer.size()})); patched_new_buffer.size()}));
// Note that |new_region| and |patched_new_buffer| are the same size. // Note that |new_region| and |patched_new_buffer| are the same size.
EXPECT_TRUE(std::equal(new_region.begin(), new_region.end(), EXPECT_TRUE(std::equal(new_region.begin(), new_region.end(),
......
...@@ -70,7 +70,7 @@ constexpr Command kCommands[] = { ...@@ -70,7 +70,7 @@ constexpr Command kCommands[] = {
{"apply", "-apply <old_file> <patch_file> <new_file> [-keep]", 3, {"apply", "-apply <old_file> <patch_file> <new_file> [-keep]", 3,
&MainApply}, &MainApply},
{"read", "-read <exe> [-dump]", 1, &MainRead}, {"read", "-read <exe> [-dump]", 1, &MainRead},
{"detect", "-detect <archive_file> [-dd=format#]", 1, &MainDetect}, {"detect", "-detect <archive_file>", 1, &MainDetect},
{"match", "-match <old_file> <new_file> [-impose=#+#=#+#,#+#=#+#,...]", 2, {"match", "-match <old_file> <new_file> [-impose=#+#=#+#,#+#=#+#,...]", 2,
&MainMatch}, &MainMatch},
{"crc32", "-crc32 <file>", 1, &MainCrc32}, {"crc32", "-crc32 <file>", 1, &MainCrc32},
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
namespace zucchini { namespace zucchini {
MappedFileReader::MappedFileReader(base::File&& file) { MappedFileReader::MappedFileReader(base::File file) {
if (!file.IsValid()) { if (!file.IsValid()) {
error_ = "Invalid file."; error_ = "Invalid file.";
return; // |buffer_| will be uninitialized, and therefore invalid. return; // |buffer_| will be uninitialized, and therefore invalid.
...@@ -23,7 +23,7 @@ MappedFileReader::MappedFileReader(base::File&& file) { ...@@ -23,7 +23,7 @@ MappedFileReader::MappedFileReader(base::File&& file) {
} }
MappedFileWriter::MappedFileWriter(const base::FilePath& file_path, MappedFileWriter::MappedFileWriter(const base::FilePath& file_path,
base::File&& file, base::File file,
size_t length) size_t length)
: file_path_(file_path), delete_behavior_(kManualDeleteOnClose) { : file_path_(file_path), delete_behavior_(kManualDeleteOnClose) {
if (!file.IsValid()) { if (!file.IsValid()) {
......
...@@ -23,7 +23,7 @@ class MappedFileReader { ...@@ -23,7 +23,7 @@ class MappedFileReader {
public: public:
// Maps |file| to memory for reading. Also validates |file|. Errors are // Maps |file| to memory for reading. Also validates |file|. Errors are
// available via HasError() and error(). // available via HasError() and error().
explicit MappedFileReader(base::File&& file); explicit MappedFileReader(base::File file);
const uint8_t* data() const { return buffer_.data(); } const uint8_t* data() const { return buffer_.data(); }
size_t length() const { return buffer_.length(); } size_t length() const { return buffer_.length(); }
...@@ -47,7 +47,7 @@ class MappedFileWriter { ...@@ -47,7 +47,7 @@ class MappedFileWriter {
// UNIX systems, but can be empty if auto delete is not needed. Errors are // UNIX systems, but can be empty if auto delete is not needed. Errors are
// available via HasError() and error(). // available via HasError() and error().
MappedFileWriter(const base::FilePath& file_path, MappedFileWriter(const base::FilePath& file_path,
base::File&& file, base::File file,
size_t length); size_t length);
~MappedFileWriter(); ~MappedFileWriter();
......
...@@ -11,7 +11,11 @@ ...@@ -11,7 +11,11 @@
#include "components/zucchini/patch_reader.h" #include "components/zucchini/patch_reader.h"
#include "components/zucchini/patch_writer.h" #include "components/zucchini/patch_writer.h"
// Definitions, structures, and interfaces for the Zucchini library. // Core Zucchini library, consisting of:
// - Global constants.
// - Patch gen and apply functions, where "old" and "new" data are represented
// as buffers, and patch data represented as EnsemblePatchWriter or
// EnsemblePatchReader.
namespace zucchini { namespace zucchini {
...@@ -36,9 +40,9 @@ enum Code { ...@@ -36,9 +40,9 @@ enum Code {
// Generates ensemble patch from |old_image| to |new_image| using the default // Generates ensemble patch from |old_image| to |new_image| using the default
// element detection and matching heuristics, writes the results to // element detection and matching heuristics, writes the results to
// |patch_writer|, and returns a status::Code. // |patch_writer|, and returns a status::Code.
status::Code GenerateEnsemble(ConstBufferView old_image, status::Code GenerateBuffer(ConstBufferView old_image,
ConstBufferView new_image, ConstBufferView new_image,
EnsemblePatchWriter* patch_writer); EnsemblePatchWriter* patch_writer);
// Same as GenerateEnsemble(), but if |imposed_matches| is non-empty, then // Same as GenerateEnsemble(), but if |imposed_matches| is non-empty, then
// overrides default element detection and matching heuristics with custom // overrides default element detection and matching heuristics with custom
...@@ -46,23 +50,22 @@ status::Code GenerateEnsemble(ConstBufferView old_image, ...@@ -46,23 +50,22 @@ status::Code GenerateEnsemble(ConstBufferView old_image,
// "#+#=#+#,#+#=#+#,..." (e.g., "1+2=3+4", "1+2=3+4,5+6=7+8"), // "#+#=#+#,#+#=#+#,..." (e.g., "1+2=3+4", "1+2=3+4,5+6=7+8"),
// where "#+#=#+#" encodes a match as 4 unsigned integers: // where "#+#=#+#" encodes a match as 4 unsigned integers:
// [offset in "old", size in "old", offset in "new", size in "new"]. // [offset in "old", size in "old", offset in "new", size in "new"].
status::Code GenerateEnsembleWithImposedMatches( status::Code GenerateBufferImposed(ConstBufferView old_image,
ConstBufferView old_image, ConstBufferView new_image,
ConstBufferView new_image, std::string imposed_matches,
std::string imposed_matches, EnsemblePatchWriter* patch_writer);
EnsemblePatchWriter* patch_writer);
// Generates raw patch from |old_image| to |new_image|, and writes it to // Generates raw patch from |old_image| to |new_image|, and writes it to
// |patch_writer|. // |patch_writer|.
status::Code GenerateRaw(ConstBufferView old_image, status::Code GenerateBufferRaw(ConstBufferView old_image,
ConstBufferView new_image, ConstBufferView new_image,
EnsemblePatchWriter* patch_writer); EnsemblePatchWriter* patch_writer);
// Applies |patch_reader| to |old_image| to build |new_image|, which refers to // Applies |patch_reader| to |old_image| to build |new_image|, which refers to
// preallocated memory of sufficient size. // preallocated memory of sufficient size.
status::Code Apply(ConstBufferView old_image, status::Code ApplyBuffer(ConstBufferView old_image,
const EnsemblePatchReader& patch_reader, const EnsemblePatchReader& patch_reader,
MutableBufferView new_image); MutableBufferView new_image);
} // namespace zucchini } // namespace zucchini
......
...@@ -188,9 +188,9 @@ bool ApplyElement(ExecutableType exe_type, ...@@ -188,9 +188,9 @@ bool ApplyElement(ExecutableType exe_type,
/******** Exported Functions ********/ /******** Exported Functions ********/
status::Code Apply(ConstBufferView old_image, status::Code ApplyBuffer(ConstBufferView old_image,
const EnsemblePatchReader& patch_reader, const EnsemblePatchReader& patch_reader,
MutableBufferView new_image) { MutableBufferView new_image) {
if (!patch_reader.CheckOldFile(old_image)) { if (!patch_reader.CheckOldFile(old_image)) {
LOG(ERROR) << "Invalid old_image."; LOG(ERROR) << "Invalid old_image.";
return status::kStatusInvalidOldImage; return status::kStatusInvalidOldImage;
......
...@@ -38,67 +38,11 @@ constexpr char kSwitchRaw[] = "raw"; ...@@ -38,67 +38,11 @@ constexpr char kSwitchRaw[] = "raw";
zucchini::status::Code MainGen(MainParams params) { zucchini::status::Code MainGen(MainParams params) {
CHECK_EQ(3U, params.file_paths.size()); CHECK_EQ(3U, params.file_paths.size());
return zucchini::Generate(
// TODO(huangs): Move implementation to zucchini_integration.cc. params.file_paths[0], params.file_paths[1], params.file_paths[2],
using base::File; params.command_line.HasSwitch(kSwitchKeep),
File old_file(params.file_paths[0], File::FLAG_OPEN | File::FLAG_READ); params.command_line.HasSwitch(kSwitchRaw),
zucchini::MappedFileReader old_image(std::move(old_file)); params.command_line.GetSwitchValueASCII(kSwitchImpose));
if (old_image.HasError()) {
LOG(ERROR) << "Error with file " << params.file_paths[0].value() << ": "
<< old_image.error();
return zucchini::status::kStatusFileReadError;
}
File new_file(params.file_paths[1], File::FLAG_OPEN | File::FLAG_READ);
zucchini::MappedFileReader new_image(std::move(new_file));
if (new_image.HasError()) {
LOG(ERROR) << "Error with file " << params.file_paths[1].value() << ": "
<< new_image.error();
return zucchini::status::kStatusFileReadError;
}
zucchini::EnsemblePatchWriter patch_writer(old_image.region(),
new_image.region());
zucchini::status::Code result = zucchini::status::kStatusSuccess;
if (params.command_line.HasSwitch(kSwitchRaw)) {
result = GenerateRaw(old_image.region(), new_image.region(), &patch_writer);
} else {
// May be empty.
std::string imposed_matches =
params.command_line.GetSwitchValueASCII(kSwitchImpose);
result = GenerateEnsembleWithImposedMatches(
old_image.region(), new_image.region(), std::move(imposed_matches),
&patch_writer);
}
if (result != zucchini::status::kStatusSuccess) {
params.out << "Fatal error encountered when generating patch." << std::endl;
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.
File patch_file(params.file_paths[2], File::FLAG_CREATE_ALWAYS |
File::FLAG_READ | File::FLAG_WRITE |
File::FLAG_SHARE_DELETE |
File::FLAG_CAN_DELETE_ON_CLOSE);
zucchini::MappedFileWriter patch(params.file_paths[2], std::move(patch_file),
patch_writer.SerializedSize());
if (patch.HasError()) {
LOG(ERROR) << "Error with file " << params.file_paths[2].value() << ": "
<< patch.error();
return zucchini::status::kStatusFileWriteError;
}
if (params.command_line.HasSwitch(kSwitchKeep))
patch.Keep();
if (!patch_writer.SerializeInto(patch.region()))
return zucchini::status::kStatusPatchWriteError;
// Successfully created patch. Explicitly request file to be kept.
if (!patch.Keep())
return zucchini::status::kStatusFileWriteError;
return zucchini::status::kStatusSuccess;
} }
zucchini::status::Code MainApply(MainParams params) { zucchini::status::Code MainApply(MainParams params) {
......
...@@ -319,13 +319,13 @@ bool GenerateExecutableElement(ExecutableType exe_type, ...@@ -319,13 +319,13 @@ bool GenerateExecutableElement(ExecutableType exe_type,
reference_bytes_mixer.get(), patch_writer); reference_bytes_mixer.get(), patch_writer);
} }
status::Code GenerateEnsembleCommon(ConstBufferView old_image, status::Code GenerateBufferCommon(ConstBufferView old_image,
ConstBufferView new_image, ConstBufferView new_image,
std::unique_ptr<EnsembleMatcher> matcher, std::unique_ptr<EnsembleMatcher> matcher,
EnsemblePatchWriter* patch_writer) { EnsemblePatchWriter* patch_writer) {
if (!matcher->RunMatch(old_image, new_image)) { if (!matcher->RunMatch(old_image, new_image)) {
LOG(INFO) << "RunMatch() failed, generating raw patch."; LOG(INFO) << "RunMatch() failed, generating raw patch.";
return GenerateRaw(old_image, new_image, patch_writer); return GenerateBufferRaw(old_image, new_image, patch_writer);
} }
const std::vector<ElementMatch>& matches = matcher->matches(); const std::vector<ElementMatch>& matches = matcher->matches();
...@@ -335,7 +335,7 @@ status::Code GenerateEnsembleCommon(ConstBufferView old_image, ...@@ -335,7 +335,7 @@ status::Code GenerateEnsembleCommon(ConstBufferView old_image,
size_t num_elements = matches.size(); size_t num_elements = matches.size();
if (num_elements == 0) { if (num_elements == 0) {
LOG(INFO) << "No nontrival matches, generating raw patch."; LOG(INFO) << "No nontrival matches, generating raw patch.";
return GenerateRaw(old_image, new_image, patch_writer); return GenerateBufferRaw(old_image, new_image, patch_writer);
} }
// "Gaps" are |new_image| bytes not covered by new_elements in |matches|. // "Gaps" are |new_image| bytes not covered by new_elements in |matches|.
...@@ -421,30 +421,29 @@ status::Code GenerateEnsembleCommon(ConstBufferView old_image, ...@@ -421,30 +421,29 @@ status::Code GenerateEnsembleCommon(ConstBufferView old_image,
/******** Exported Functions ********/ /******** Exported Functions ********/
status::Code GenerateEnsemble(ConstBufferView old_image, status::Code GenerateBuffer(ConstBufferView old_image,
ConstBufferView new_image, ConstBufferView new_image,
EnsemblePatchWriter* patch_writer) { EnsemblePatchWriter* patch_writer) {
return GenerateEnsembleCommon( return GenerateBufferCommon(
old_image, new_image, std::make_unique<HeuristicEnsembleMatcher>(nullptr), old_image, new_image, std::make_unique<HeuristicEnsembleMatcher>(nullptr),
patch_writer); patch_writer);
} }
status::Code GenerateEnsembleWithImposedMatches( status::Code GenerateBufferImposed(ConstBufferView old_image,
ConstBufferView old_image, ConstBufferView new_image,
ConstBufferView new_image, std::string imposed_matches,
std::string imposed_matches, EnsemblePatchWriter* patch_writer) {
EnsemblePatchWriter* patch_writer) {
if (imposed_matches.empty()) if (imposed_matches.empty())
return GenerateEnsemble(old_image, new_image, patch_writer); return GenerateBuffer(old_image, new_image, patch_writer);
return GenerateEnsembleCommon( return GenerateBufferCommon(
old_image, new_image, old_image, new_image,
std::make_unique<ImposedEnsembleMatcher>(imposed_matches), patch_writer); std::make_unique<ImposedEnsembleMatcher>(imposed_matches), patch_writer);
} }
status::Code GenerateRaw(ConstBufferView old_image, status::Code GenerateBufferRaw(ConstBufferView old_image,
ConstBufferView new_image, ConstBufferView new_image,
EnsemblePatchWriter* patch_writer) { EnsemblePatchWriter* patch_writer) {
ImageIndex old_image_index(old_image); ImageIndex old_image_index(old_image);
EncodedView old_view(old_image_index); EncodedView old_view(old_image_index);
std::vector<offset_t> old_sa = std::vector<offset_t> old_sa =
......
...@@ -19,92 +19,172 @@ struct FileNames { ...@@ -19,92 +19,172 @@ struct FileNames {
FileNames() : is_dummy(true) { FileNames() : is_dummy(true) {
// Use fake names. // Use fake names.
old_name = old_name.AppendASCII("old_name"); old_name = old_name.AppendASCII("old_name");
patch_name = patch_name.AppendASCII("patch_name");
new_name = new_name.AppendASCII("new_name"); new_name = new_name.AppendASCII("new_name");
patch_name = patch_name.AppendASCII("patch_name");
} }
FileNames(const base::FilePath& old_name, FileNames(const base::FilePath& old_name,
const base::FilePath& patch_name, const base::FilePath& new_name,
const base::FilePath& new_name) const base::FilePath& patch_name)
: old_name(old_name), : old_name(old_name),
patch_name(patch_name),
new_name(new_name), new_name(new_name),
patch_name(patch_name),
is_dummy(false) {} is_dummy(false) {}
base::FilePath old_name; base::FilePath old_name;
base::FilePath patch_name;
base::FilePath new_name; base::FilePath new_name;
base::FilePath patch_name;
// A flag to decide whether the filenames are only for error output. // A flag to decide whether the filenames are only for error output.
const bool is_dummy; const bool is_dummy;
}; };
status::Code ApplyCommon(base::File&& old_file_handle, status::Code GenerateCommon(base::File old_file,
base::File&& patch_file_handle, base::File new_file,
base::File&& new_file_handle, 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, const FileNames& names,
bool force_keep) { bool force_keep) {
MappedFileReader patch_file(std::move(patch_file_handle)); MappedFileReader mapped_patch(std::move(patch_file));
if (patch_file.HasError()) { if (mapped_patch.HasError()) {
LOG(ERROR) << "Error with file " << names.patch_name.value() << ": " LOG(ERROR) << "Error with file " << names.patch_name.value() << ": "
<< patch_file.error(); << mapped_patch.error();
return status::kStatusFileReadError; return status::kStatusFileReadError;
} }
auto patch_reader = auto patch_reader = EnsemblePatchReader::Create(mapped_patch.region());
zucchini::EnsemblePatchReader::Create(patch_file.region());
if (!patch_reader.has_value()) { if (!patch_reader.has_value()) {
LOG(ERROR) << "Error reading patch header."; LOG(ERROR) << "Error reading patch header.";
return status::kStatusPatchReadError; return status::kStatusPatchReadError;
} }
MappedFileReader old_file(std::move(old_file_handle)); MappedFileReader mapped_old(std::move(old_file));
if (old_file.HasError()) { if (mapped_old.HasError()) {
LOG(ERROR) << "Error with file " << names.old_name.value() << ": " LOG(ERROR) << "Error with file " << names.old_name.value() << ": "
<< old_file.error(); << mapped_old.error();
return status::kStatusFileReadError; return status::kStatusFileReadError;
} }
zucchini::PatchHeader header = patch_reader->header(); PatchHeader header = patch_reader->header();
// By default, delete output on destruction, to avoid having lingering files // 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. // in case of a failure. On Windows deletion can be done by the OS.
base::FilePath file_path; MappedFileWriter mapped_new(names.new_name, std::move(new_file),
if (!names.is_dummy) header.new_size);
file_path = base::FilePath(names.new_name); if (mapped_new.HasError()) {
MappedFileWriter new_file(file_path, std::move(new_file_handle),
header.new_size);
if (new_file.HasError()) {
LOG(ERROR) << "Error with file " << names.new_name.value() << ": " LOG(ERROR) << "Error with file " << names.new_name.value() << ": "
<< new_file.error(); << mapped_new.error();
return status::kStatusFileWriteError; return status::kStatusFileWriteError;
} }
if (force_keep) if (force_keep)
new_file.Keep(); mapped_new.Keep();
zucchini::status::Code result = status::Code result =
zucchini::Apply(old_file.region(), *patch_reader, new_file.region()); ApplyBuffer(mapped_old.region(), *patch_reader, mapped_new.region());
if (result != status::kStatusSuccess) { if (result != status::kStatusSuccess) {
LOG(ERROR) << "Fatal error encountered while applying patch."; LOG(ERROR) << "Fatal error encountered while applying patch.";
return result; return result;
} }
// Successfully patch |new_file|. Explicitly request file to be kept. // Successfully patch |mapped_new|. Explicitly request file to be kept.
if (!new_file.Keep()) if (!mapped_new.Keep())
return status::kStatusFileWriteError; return status::kStatusFileWriteError;
return status::kStatusSuccess; return status::kStatusSuccess;
} }
} // namespace } // namespace
status::Code Apply(base::File&& old_file_handle, status::Code Generate(base::File old_file,
base::File&& patch_file_handle, base::File new_file,
base::File&& new_file_handle, 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);
File new_file(new_path, File::FLAG_OPEN | File::FLAG_READ);
File patch_file(patch_path, File::FLAG_CREATE_ALWAYS | File::FLAG_READ |
File::FLAG_WRITE | File::FLAG_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) { bool force_keep) {
const FileNames file_names; const FileNames file_names;
return ApplyCommon(std::move(old_file_handle), std::move(patch_file_handle), return ApplyCommon(std::move(old_file), std::move(patch_file),
std::move(new_file_handle), file_names, force_keep); std::move(new_file), file_names, force_keep);
} }
status::Code Apply(const base::FilePath& old_path, status::Code Apply(const base::FilePath& old_path,
...@@ -117,7 +197,7 @@ status::Code Apply(const base::FilePath& old_path, ...@@ -117,7 +197,7 @@ status::Code Apply(const base::FilePath& old_path,
File new_file(new_path, File::FLAG_CREATE_ALWAYS | File::FLAG_READ | File new_file(new_path, File::FLAG_CREATE_ALWAYS | File::FLAG_READ |
File::FLAG_WRITE | File::FLAG_SHARE_DELETE | File::FLAG_WRITE | File::FLAG_SHARE_DELETE |
File::FLAG_CAN_DELETE_ON_CLOSE); File::FLAG_CAN_DELETE_ON_CLOSE);
const FileNames file_names(old_path, patch_path, new_path); const FileNames file_names(old_path, new_path, patch_path);
return ApplyCommon(std::move(old_file), std::move(patch_file), return ApplyCommon(std::move(old_file), std::move(patch_file),
std::move(new_file), file_names, force_keep); std::move(new_file), file_names, force_keep);
} }
......
...@@ -5,28 +5,59 @@ ...@@ -5,28 +5,59 @@
#ifndef COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_ #ifndef COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_
#define COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_ #define COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_
#include <string>
#include "base/files/file.h" #include "base/files/file.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "components/zucchini/zucchini.h" #include "components/zucchini/zucchini.h"
// Zucchini integration interface to wrap core Zucchini library with file I/O.
namespace zucchini { namespace zucchini {
// Applies the patch in |patch_file| to the bytes in |old_file| and writes the // Generates a patch to transform |old_file| to |new_file|, and writes the
// result to |new_file|. Since this uses memory mapped files, crashes are // result to |patch_file|. Since this uses memory mapped files, crashes are
// expected in case of I/O errors. On Windows, |new_file| is kept iff returned // expected in case of I/O errors. On Windows, |patch_file| is kept iff returned
// code is kStatusSuccess or if |force_keep == true|, and is deleted otherwise. // code is kStatusSuccess or if |force_keep == true|, and is deleted otherwise.
// For UNIX systems the caller needs to do cleanup since it has ownership of the // For UNIX systems the caller needs to do cleanup since it has ownership of the
// base::File params and Zucchini has no knowledge of which base::FilePath to // base::File params, and Zucchini has no knowledge of which base::FilePath to
// delete. If |is_raw == true| then uses Raw Zucchini. If |imposed_matches| is
// non-empty, then overrides default element detection and matching heuristics
// with custom element matching encoded in |imposed_matches|, which should be
// formatted as:
// "#+#=#+#,#+#=#+#,..." (e.g., "1+2=3+4", "1+2=3+4,5+6=7+8"),
// where "#+#=#+#" encodes a match as 4 unsigned integers:
// [offset in "old", size in "old", offset in "new", size in "new"].
status::Code Generate(base::File old_file,
base::File new_file,
base::File patch_file,
bool force_keep = false,
bool is_raw = false,
std::string imposed_matches = "");
// Alternative Generate() interface that takes base::FilePath as arguments.
// Performs proper cleanup in Windows and UNIX if failure occurs.
status::Code Generate(const base::FilePath& old_path,
const base::FilePath& new_path,
const base::FilePath& patch_path,
bool force_keep = false,
bool is_raw = false,
std::string imposed_matches = "");
// Applies the patch in |patch_file| to |old_file|, and writes the result to
// |new_file|. Since this uses memory mapped files, crashes are expected in case
// of I/O errors. On Windows, |new_file| is kept iff returned code is
// kStatusSuccess or if |force_keep == true|, and is deleted otherwise. For UNIX
// systems the caller needs to do cleanup since it has ownership of the
// base::File params, and Zucchini has no knowledge of which base::FilePath to
// delete. // delete.
status::Code Apply(base::File&& old_file, status::Code Apply(base::File old_file,
base::File&& patch_file, base::File patch_file,
base::File&& new_file, base::File new_file,
bool force_keep = false); bool force_keep = false);
// Applies the patch in |patch_path| to the bytes in |old_path| and writes the // Alternative Apply() interface that takes base::FilePath as arguments.
// result to |new_path|. Since this uses memory mapped files, crashes are // Performs proper cleanup in Windows and UNIX if failure occurs.
// expected in case of I/O errors. |new_path| is kept iff returned code is
// kStatusSuccess or if |force_keep == true|, and is deleted otherwise.
status::Code Apply(const base::FilePath& old_path, status::Code Apply(const base::FilePath& old_path,
const base::FilePath& patch_path, const base::FilePath& patch_path,
const base::FilePath& new_path, const base::FilePath& new_path,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment