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
undesirable noise in raw content. Notably, the projection encodes references
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
### Types
......
......@@ -4,6 +4,7 @@
#include "components/zucchini/element_detection.h"
#include <map>
#include <vector>
#include "base/bind.h"
......
......@@ -54,6 +54,6 @@ DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) {
zucchini::MutableBufferView new_image(new_data.data(), new_size);
// 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.
}
......@@ -56,7 +56,7 @@ DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) {
zucchini::EnsemblePatchWriter patch_writer(old_image, new_image);
// 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
// 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) {
zucchini::EnsemblePatchWriter patch_writer(old_image, new_image);
// Fuzz Target.
zucchini::GenerateEnsemble(old_image, new_image, &patch_writer);
zucchini::GenerateBuffer(old_image, new_image, &patch_writer);
// Write to buffer to avoid IO.
size_t patch_size = patch_writer.SerializedSize();
......
......@@ -49,8 +49,8 @@ void TestGenApply(const std::string& old_filename,
// Generate patch from "old" to "new".
ASSERT_EQ(status::kStatusSuccess,
raw ? GenerateRaw(old_region, new_region, &patch_writer)
: GenerateEnsemble(old_region, new_region, &patch_writer));
raw ? GenerateBufferRaw(old_region, new_region, &patch_writer)
: GenerateBuffer(old_region, new_region, &patch_writer));
size_t patch_size = patch_writer.SerializedSize();
EXPECT_GE(patch_size, 80U); // Minimum size is empty patch.
......@@ -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".
std::vector<uint8_t> patched_new_buffer(new_region.size());
ASSERT_EQ(status::kStatusSuccess,
Apply(old_region, *patch_reader,
{patched_new_buffer.data(), patched_new_buffer.size()}));
ASSERT_EQ(status::kStatusSuccess, ApplyBuffer(old_region, *patch_reader,
{patched_new_buffer.data(),
patched_new_buffer.size()}));
// Note that |new_region| and |patched_new_buffer| are the same size.
EXPECT_TRUE(std::equal(new_region.begin(), new_region.end(),
......
......@@ -70,7 +70,7 @@ constexpr Command kCommands[] = {
{"apply", "-apply <old_file> <patch_file> <new_file> [-keep]", 3,
&MainApply},
{"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,
&MainMatch},
{"crc32", "-crc32 <file>", 1, &MainCrc32},
......
......@@ -12,7 +12,7 @@
namespace zucchini {
MappedFileReader::MappedFileReader(base::File&& file) {
MappedFileReader::MappedFileReader(base::File file) {
if (!file.IsValid()) {
error_ = "Invalid file.";
return; // |buffer_| will be uninitialized, and therefore invalid.
......@@ -23,7 +23,7 @@ MappedFileReader::MappedFileReader(base::File&& file) {
}
MappedFileWriter::MappedFileWriter(const base::FilePath& file_path,
base::File&& file,
base::File file,
size_t length)
: file_path_(file_path), delete_behavior_(kManualDeleteOnClose) {
if (!file.IsValid()) {
......
......@@ -23,7 +23,7 @@ class MappedFileReader {
public:
// Maps |file| to memory for reading. Also validates |file|. Errors are
// available via HasError() and error().
explicit MappedFileReader(base::File&& file);
explicit MappedFileReader(base::File file);
const uint8_t* data() const { return buffer_.data(); }
size_t length() const { return buffer_.length(); }
......@@ -47,7 +47,7 @@ class MappedFileWriter {
// UNIX systems, but can be empty if auto delete is not needed. Errors are
// available via HasError() and error().
MappedFileWriter(const base::FilePath& file_path,
base::File&& file,
base::File file,
size_t length);
~MappedFileWriter();
......
......@@ -11,7 +11,11 @@
#include "components/zucchini/patch_reader.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 {
......@@ -36,7 +40,7 @@ enum Code {
// Generates ensemble patch from |old_image| to |new_image| using the default
// element detection and matching heuristics, writes the results to
// |patch_writer|, and returns a status::Code.
status::Code GenerateEnsemble(ConstBufferView old_image,
status::Code GenerateBuffer(ConstBufferView old_image,
ConstBufferView new_image,
EnsemblePatchWriter* patch_writer);
......@@ -46,21 +50,20 @@ status::Code GenerateEnsemble(ConstBufferView old_image,
// "#+#=#+#,#+#=#+#,..." (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 GenerateEnsembleWithImposedMatches(
ConstBufferView old_image,
status::Code GenerateBufferImposed(ConstBufferView old_image,
ConstBufferView new_image,
std::string imposed_matches,
EnsemblePatchWriter* patch_writer);
// Generates raw patch from |old_image| to |new_image|, and writes it to
// |patch_writer|.
status::Code GenerateRaw(ConstBufferView old_image,
status::Code GenerateBufferRaw(ConstBufferView old_image,
ConstBufferView new_image,
EnsemblePatchWriter* patch_writer);
// Applies |patch_reader| to |old_image| to build |new_image|, which refers to
// preallocated memory of sufficient size.
status::Code Apply(ConstBufferView old_image,
status::Code ApplyBuffer(ConstBufferView old_image,
const EnsemblePatchReader& patch_reader,
MutableBufferView new_image);
......
......@@ -188,7 +188,7 @@ bool ApplyElement(ExecutableType exe_type,
/******** Exported Functions ********/
status::Code Apply(ConstBufferView old_image,
status::Code ApplyBuffer(ConstBufferView old_image,
const EnsemblePatchReader& patch_reader,
MutableBufferView new_image) {
if (!patch_reader.CheckOldFile(old_image)) {
......
......@@ -38,67 +38,11 @@ constexpr char kSwitchRaw[] = "raw";
zucchini::status::Code MainGen(MainParams params) {
CHECK_EQ(3U, params.file_paths.size());
// TODO(huangs): Move implementation to zucchini_integration.cc.
using base::File;
File old_file(params.file_paths[0], File::FLAG_OPEN | File::FLAG_READ);
zucchini::MappedFileReader old_image(std::move(old_file));
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;
return zucchini::Generate(
params.file_paths[0], params.file_paths[1], params.file_paths[2],
params.command_line.HasSwitch(kSwitchKeep),
params.command_line.HasSwitch(kSwitchRaw),
params.command_line.GetSwitchValueASCII(kSwitchImpose));
}
zucchini::status::Code MainApply(MainParams params) {
......
......@@ -319,13 +319,13 @@ bool GenerateExecutableElement(ExecutableType exe_type,
reference_bytes_mixer.get(), patch_writer);
}
status::Code GenerateEnsembleCommon(ConstBufferView old_image,
status::Code GenerateBufferCommon(ConstBufferView old_image,
ConstBufferView new_image,
std::unique_ptr<EnsembleMatcher> matcher,
EnsemblePatchWriter* patch_writer) {
if (!matcher->RunMatch(old_image, new_image)) {
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();
......@@ -335,7 +335,7 @@ status::Code GenerateEnsembleCommon(ConstBufferView old_image,
size_t num_elements = matches.size();
if (num_elements == 0) {
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|.
......@@ -421,28 +421,27 @@ status::Code GenerateEnsembleCommon(ConstBufferView old_image,
/******** Exported Functions ********/
status::Code GenerateEnsemble(ConstBufferView old_image,
status::Code GenerateBuffer(ConstBufferView old_image,
ConstBufferView new_image,
EnsemblePatchWriter* patch_writer) {
return GenerateEnsembleCommon(
return GenerateBufferCommon(
old_image, new_image, std::make_unique<HeuristicEnsembleMatcher>(nullptr),
patch_writer);
}
status::Code GenerateEnsembleWithImposedMatches(
ConstBufferView old_image,
status::Code GenerateBufferImposed(ConstBufferView old_image,
ConstBufferView new_image,
std::string imposed_matches,
EnsemblePatchWriter* patch_writer) {
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,
std::make_unique<ImposedEnsembleMatcher>(imposed_matches), patch_writer);
}
status::Code GenerateRaw(ConstBufferView old_image,
status::Code GenerateBufferRaw(ConstBufferView old_image,
ConstBufferView new_image,
EnsemblePatchWriter* patch_writer) {
ImageIndex old_image_index(old_image);
......
......@@ -19,92 +19,172 @@ struct FileNames {
FileNames() : is_dummy(true) {
// Use fake names.
old_name = old_name.AppendASCII("old_name");
patch_name = patch_name.AppendASCII("patch_name");
new_name = new_name.AppendASCII("new_name");
patch_name = patch_name.AppendASCII("patch_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),
patch_name(patch_name),
new_name(new_name),
patch_name(patch_name),
is_dummy(false) {}
base::FilePath old_name;
base::FilePath patch_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 ApplyCommon(base::File&& old_file_handle,
base::File&& patch_file_handle,
base::File&& new_file_handle,
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 patch_file(std::move(patch_file_handle));
if (patch_file.HasError()) {
MappedFileReader mapped_patch(std::move(patch_file));
if (mapped_patch.HasError()) {
LOG(ERROR) << "Error with file " << names.patch_name.value() << ": "
<< patch_file.error();
<< mapped_patch.error();
return status::kStatusFileReadError;
}
auto patch_reader =
zucchini::EnsemblePatchReader::Create(patch_file.region());
auto patch_reader = EnsemblePatchReader::Create(mapped_patch.region());
if (!patch_reader.has_value()) {
LOG(ERROR) << "Error reading patch header.";
return status::kStatusPatchReadError;
}
MappedFileReader old_file(std::move(old_file_handle));
if (old_file.HasError()) {
MappedFileReader mapped_old(std::move(old_file));
if (mapped_old.HasError()) {
LOG(ERROR) << "Error with file " << names.old_name.value() << ": "
<< old_file.error();
<< mapped_old.error();
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
// in case of a failure. On Windows deletion can be done by the OS.
base::FilePath file_path;
if (!names.is_dummy)
file_path = base::FilePath(names.new_name);
MappedFileWriter new_file(file_path, std::move(new_file_handle),
MappedFileWriter mapped_new(names.new_name, std::move(new_file),
header.new_size);
if (new_file.HasError()) {
if (mapped_new.HasError()) {
LOG(ERROR) << "Error with file " << names.new_name.value() << ": "
<< new_file.error();
<< mapped_new.error();
return status::kStatusFileWriteError;
}
if (force_keep)
new_file.Keep();
mapped_new.Keep();
zucchini::status::Code result =
zucchini::Apply(old_file.region(), *patch_reader, new_file.region());
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 |new_file|. Explicitly request file to be kept.
if (!new_file.Keep())
// Successfully patch |mapped_new|. Explicitly request file to be kept.
if (!mapped_new.Keep())
return status::kStatusFileWriteError;
return status::kStatusSuccess;
}
} // namespace
status::Code Apply(base::File&& old_file_handle,
base::File&& patch_file_handle,
base::File&& new_file_handle,
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);
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) {
const FileNames file_names;
return ApplyCommon(std::move(old_file_handle), std::move(patch_file_handle),
std::move(new_file_handle), file_names, force_keep);
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,
......@@ -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::FLAG_WRITE | File::FLAG_SHARE_DELETE |
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),
std::move(new_file), file_names, force_keep);
}
......
......@@ -5,28 +5,59 @@
#ifndef COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_
#define COMPONENTS_ZUCCHINI_ZUCCHINI_INTEGRATION_H_
#include <string>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "components/zucchini/zucchini.h"
// Zucchini integration interface to wrap core Zucchini library with file I/O.
namespace zucchini {
// Applies the patch in |patch_file| to the bytes in |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
// Generates a patch to transform |old_file| to |new_file|, and writes the
// result to |patch_file|. Since this uses memory mapped files, crashes are
// 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.
// 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.
status::Code Apply(base::File&& old_file,
base::File&& patch_file,
base::File&& new_file,
status::Code Apply(base::File old_file,
base::File patch_file,
base::File new_file,
bool force_keep = false);
// Applies the patch in |patch_path| to the bytes in |old_path| and writes the
// result to |new_path|. Since this uses memory mapped files, crashes are
// 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.
// Alternative Apply() interface that takes base::FilePath as arguments.
// Performs proper cleanup in Windows and UNIX if failure occurs.
status::Code Apply(const base::FilePath& old_path,
const base::FilePath& patch_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