mirror of
https://github.com/expo/bsdiff.git
synced 2026-11-14 17:49:03 -10:00
Merge "Add an interface for bspatch reader"
This commit is contained in:
commit
68540f685e
15 files changed
+583
-186
No files matched your search
@@ -41,12 +41,15 @@ cc_library_static {
|
||||
|
||||
srcs: [
|
||||
"bspatch.cc",
|
||||
"bz2_decompressor.cc",
|
||||
"buffer_file.cc",
|
||||
"extents.cc",
|
||||
"extents_file.cc",
|
||||
"file.cc",
|
||||
"memory_file.cc",
|
||||
"patch_reader.cc",
|
||||
"sink_file.cc",
|
||||
"utils.cc",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -105,6 +108,7 @@ cc_test {
|
||||
"diff_encoder_unittest.cc",
|
||||
"extents_file_unittest.cc",
|
||||
"extents_unittest.cc",
|
||||
"patch_reader_unittest.cc",
|
||||
"patch_writer_unittest.cc",
|
||||
"split_patch_writer_unittest.cc",
|
||||
"suffix_array_index_unittest.cc",
|
||||
|
||||
@@ -43,12 +43,15 @@ bsdiff_src_files := \
|
||||
# "bspatch" program.
|
||||
bspatch_src_files := \
|
||||
bspatch.cc \
|
||||
bz2_decompressor.cc \
|
||||
buffer_file.cc \
|
||||
extents.cc \
|
||||
extents_file.cc \
|
||||
file.cc \
|
||||
memory_file.cc \
|
||||
sink_file.cc
|
||||
patch_reader.cc \
|
||||
sink_file.cc \
|
||||
utils.cc
|
||||
|
||||
# Unit test files.
|
||||
bsdiff_common_unittests := \
|
||||
@@ -57,6 +60,7 @@ bsdiff_common_unittests := \
|
||||
diff_encoder_unittest.cc \
|
||||
extents_file_unittest.cc \
|
||||
extents_unittest.cc \
|
||||
patch_reader_unittest.cc \
|
||||
patch_writer_unittest.cc \
|
||||
split_patch_writer_unittest.cc \
|
||||
suffix_array_index_unittest.cc \
|
||||
|
||||
@@ -31,12 +31,15 @@
|
||||
'variables': {
|
||||
'bspatch_sources': [
|
||||
'bspatch.cc',
|
||||
'bz2_decompressor.cc',
|
||||
'buffer_file.cc',
|
||||
'extents.cc',
|
||||
'extents_file.cc',
|
||||
'file.cc',
|
||||
'memory_file.cc',
|
||||
'patch_reader.cc',
|
||||
'sink_file.cc',
|
||||
'utils.cc',
|
||||
],
|
||||
},
|
||||
'targets': [
|
||||
@@ -132,6 +135,7 @@
|
||||
'diff_encoder_unittest.cc',
|
||||
'extents_file_unittest.cc',
|
||||
'extents_unittest.cc',
|
||||
'patch_reader_unittest.cc',
|
||||
'patch_writer_unittest.cc',
|
||||
'split_patch_writer_unittest.cc',
|
||||
'suffix_array_index_unittest.cc',
|
||||
|
||||
+66
-162
@@ -30,7 +30,6 @@ __FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:
|
||||
|
||||
#include "bsdiff/bspatch.h"
|
||||
|
||||
#include <bzlib.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
@@ -41,75 +40,42 @@ __FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "bsdiff/buffer_file.h"
|
||||
#include "bsdiff/control_entry.h"
|
||||
#include "bsdiff/extents.h"
|
||||
#include "bsdiff/extents_file.h"
|
||||
#include "bsdiff/file.h"
|
||||
#include "bsdiff/file_interface.h"
|
||||
#include "bsdiff/memory_file.h"
|
||||
#include "bsdiff/patch_reader.h"
|
||||
#include "bsdiff/sink_file.h"
|
||||
#include "bsdiff/utils.h"
|
||||
|
||||
namespace {
|
||||
|
||||
int64_t ParseInt64(const u_char* buf) {
|
||||
int64_t y;
|
||||
|
||||
y = buf[7] & 0x7F;
|
||||
y = y * 256;
|
||||
y += buf[6];
|
||||
y = y * 256;
|
||||
y += buf[5];
|
||||
y = y * 256;
|
||||
y += buf[4];
|
||||
y = y * 256;
|
||||
y += buf[3];
|
||||
y = y * 256;
|
||||
y += buf[2];
|
||||
y = y * 256;
|
||||
y += buf[1];
|
||||
y = y * 256;
|
||||
y += buf[0];
|
||||
|
||||
if (buf[7] & 0x80)
|
||||
y = -y;
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
bool ReadBZ2(bz_stream* stream, uint8_t* data, size_t size) {
|
||||
stream->next_out = (char*)data;
|
||||
// Read the data in |stream| and write |size| decompressed data to |file|;
|
||||
// using the buffer in |buf| with size |buf_size|.
|
||||
// Returns 0 on success, 1 on I/O error and 2 on data error.
|
||||
int ReadStreamAndWriteAll(
|
||||
const std::unique_ptr<bsdiff::FileInterface>& file,
|
||||
size_t size,
|
||||
uint8_t* buf,
|
||||
size_t buf_size,
|
||||
const std::function<bool(uint8_t*, size_t)>& read_func) {
|
||||
while (size > 0) {
|
||||
unsigned int read_size = std::min(
|
||||
static_cast<size_t>(std::numeric_limits<unsigned int>::max()), size);
|
||||
stream->avail_out = read_size;
|
||||
int bz2err = BZ2_bzDecompress(stream);
|
||||
if (bz2err != BZ_OK && bz2err != BZ_STREAM_END)
|
||||
return false;
|
||||
size -= read_size - stream->avail_out;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int ReadBZ2AndWriteAll(const std::unique_ptr<bsdiff::FileInterface>& file,
|
||||
bz_stream* stream,
|
||||
size_t size,
|
||||
uint8_t* buf,
|
||||
size_t buf_size) {
|
||||
while (size > 0) {
|
||||
size_t bytes_to_read = std::min(size, buf_size);
|
||||
if (!ReadBZ2(stream, buf, bytes_to_read)) {
|
||||
fprintf(stderr, "Failed to read bzip stream.\n");
|
||||
size_t bytes_to_output = std::min(size, buf_size);
|
||||
if (!read_func(buf, bytes_to_output)) {
|
||||
fprintf(stderr, "Failed to read stream.\n");
|
||||
return 2;
|
||||
}
|
||||
if (!WriteAll(file, buf, bytes_to_read)) {
|
||||
|
||||
if (!WriteAll(file, buf, bytes_to_output)) {
|
||||
perror("WriteAll() failed");
|
||||
return 1;
|
||||
}
|
||||
size -= bytes_to_read;
|
||||
size -= bytes_to_output;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -282,76 +248,14 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
|
||||
const std::unique_ptr<FileInterface>& new_file,
|
||||
const uint8_t* patch_data,
|
||||
size_t patch_size) {
|
||||
int bz2err;
|
||||
u_char buf[8];
|
||||
off_t ctrl[3];
|
||||
|
||||
// File format:
|
||||
// 0 8 "BSDIFF40"
|
||||
// 8 8 X
|
||||
// 16 8 Y
|
||||
// 24 8 sizeof(new_filename)
|
||||
// 32 X bzip2(control block)
|
||||
// 32+X Y bzip2(diff block)
|
||||
// 32+X+Y ??? bzip2(extra block)
|
||||
// with control block a set of triples (x,y,z) meaning "add x bytes
|
||||
// from oldfile to x bytes from the diff block; copy y bytes from the
|
||||
// extra block; seek forwards in oldfile by z bytes".
|
||||
|
||||
// Check for appropriate magic.
|
||||
if (memcmp(patch_data, "BSDIFF40", 8) != 0) {
|
||||
fprintf(stderr, "Not a bsdiff patch.\n");
|
||||
BsdiffPatchReader patch_reader;
|
||||
if (!patch_reader.Init(patch_data, patch_size)) {
|
||||
fprintf(stderr, "Failed to initialize patch reader\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Read lengths from header.
|
||||
uint64_t oldsize, newsize;
|
||||
int64_t ctrl_len = ParseInt64(patch_data + 8);
|
||||
int64_t data_len = ParseInt64(patch_data + 16);
|
||||
int64_t signed_newsize = ParseInt64(patch_data + 24);
|
||||
newsize = signed_newsize;
|
||||
if ((ctrl_len < 0) || (data_len < 0) || (signed_newsize < 0) ||
|
||||
(32 + ctrl_len + data_len > static_cast<int64_t>(patch_size))) {
|
||||
fprintf(stderr, "Corrupt patch.\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
bz_stream cstream;
|
||||
cstream.next_in = (char*)patch_data + 32;
|
||||
cstream.avail_in = ctrl_len;
|
||||
cstream.bzalloc = nullptr;
|
||||
cstream.bzfree = nullptr;
|
||||
cstream.opaque = nullptr;
|
||||
if ((bz2err = BZ2_bzDecompressInit(&cstream, 0, 0)) != BZ_OK) {
|
||||
fprintf(stderr, "Failed to bzinit control stream (%d)\n", bz2err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
bz_stream dstream;
|
||||
dstream.next_in = (char*)patch_data + 32 + ctrl_len;
|
||||
dstream.avail_in = data_len;
|
||||
dstream.bzalloc = nullptr;
|
||||
dstream.bzfree = nullptr;
|
||||
dstream.opaque = nullptr;
|
||||
if ((bz2err = BZ2_bzDecompressInit(&dstream, 0, 0)) != BZ_OK) {
|
||||
fprintf(stderr, "Failed to bzinit diff stream (%d)\n", bz2err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
bz_stream estream;
|
||||
estream.next_in = (char*)patch_data + 32 + ctrl_len + data_len;
|
||||
estream.avail_in = patch_size - (32 + ctrl_len + data_len);
|
||||
estream.bzalloc = nullptr;
|
||||
estream.bzfree = nullptr;
|
||||
estream.opaque = nullptr;
|
||||
if ((bz2err = BZ2_bzDecompressInit(&estream, 0, 0)) != BZ_OK) {
|
||||
fprintf(stderr, "Failed to bzinit extra stream (%d)\n", bz2err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
uint64_t old_file_pos = 0;
|
||||
|
||||
if (!old_file->GetSize(&oldsize)) {
|
||||
uint64_t old_file_size;
|
||||
if (!old_file->GetSize(&old_file_size)) {
|
||||
fprintf(stderr, "Cannot obtain the size of old file.\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -359,26 +263,18 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
|
||||
// The oldpos can be negative, but the new pos is only incremented linearly.
|
||||
int64_t oldpos = 0;
|
||||
uint64_t newpos = 0;
|
||||
std::vector<uint8_t> old_buf(1024 * 1024), new_buf(1024 * 1024);
|
||||
while (newpos < newsize) {
|
||||
int64_t i;
|
||||
// Read control data.
|
||||
for (i = 0; i <= 2; i++) {
|
||||
if (!ReadBZ2(&cstream, buf, 8)) {
|
||||
fprintf(stderr, "Failed to read control stream.\n");
|
||||
return 2;
|
||||
}
|
||||
ctrl[i] = ParseInt64(buf);
|
||||
}
|
||||
|
||||
// Sanity-check.
|
||||
if (ctrl[0] < 0 || ctrl[1] < 0) {
|
||||
fprintf(stderr, "Corrupt patch.\n");
|
||||
std::vector<uint8_t> old_buf(1024 * 1024);
|
||||
std::vector<uint8_t> new_buf(1024 * 1024);
|
||||
uint64_t old_file_pos = 0;
|
||||
while (newpos < patch_reader.new_file_size()) {
|
||||
ControlEntry control_entry(0, 0, 0);
|
||||
if (!patch_reader.ParseControlEntry(&control_entry)) {
|
||||
fprintf(stderr, "Failed to read control stream\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Sanity-check.
|
||||
if (newpos + ctrl[0] > newsize) {
|
||||
if (newpos + control_entry.diff_size > patch_reader.new_file_size()) {
|
||||
fprintf(stderr, "Corrupt patch.\n");
|
||||
return 2;
|
||||
}
|
||||
@@ -386,26 +282,30 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
|
||||
int ret = 0;
|
||||
// Add old data to diff string. It is enough to fseek once, at
|
||||
// the beginning of the sequence, to avoid unnecessary overhead.
|
||||
if ((i = oldpos) < 0) {
|
||||
int64_t seek_offset = oldpos;
|
||||
if (seek_offset < 0) {
|
||||
// Write diff block directly to new file without adding old data,
|
||||
// because we will skip part where |oldpos| < 0.
|
||||
ret = ReadBZ2AndWriteAll(new_file, &dstream, -i, new_buf.data(),
|
||||
new_buf.size());
|
||||
ret = ReadStreamAndWriteAll(
|
||||
new_file, oldpos - old_file_size, new_buf.data(), new_buf.size(),
|
||||
std::bind(&BsdiffPatchReader::ReadDiffStream, &patch_reader,
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
if (ret)
|
||||
return ret;
|
||||
i = 0;
|
||||
seek_offset = 0;
|
||||
}
|
||||
|
||||
// We just checked that |i| is not negative.
|
||||
if (static_cast<uint64_t>(i) != old_file_pos && !old_file->Seek(i)) {
|
||||
fprintf(stderr, "Error seeking input file to offset %" PRId64 ": %s\n", i,
|
||||
strerror(errno));
|
||||
// We just checked that |seek_offset| is not negative.
|
||||
if (static_cast<uint64_t>(seek_offset) != old_file_pos &&
|
||||
!old_file->Seek(seek_offset)) {
|
||||
fprintf(stderr, "Error seeking input file to offset %" PRId64 ": %s\n",
|
||||
seek_offset, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if ((old_file_pos = oldpos + ctrl[0]) > oldsize)
|
||||
old_file_pos = oldsize;
|
||||
|
||||
size_t chunk_size = old_file_pos - i;
|
||||
old_file_pos =
|
||||
std::min<uint64_t>(oldpos + control_entry.diff_size, old_file_size);
|
||||
size_t chunk_size = old_file_pos - seek_offset;
|
||||
while (chunk_size > 0) {
|
||||
size_t read_bytes;
|
||||
size_t bytes_to_read = std::min(chunk_size, old_buf.size());
|
||||
@@ -418,7 +318,7 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
|
||||
return 2;
|
||||
}
|
||||
// Read same amount of bytes from diff block
|
||||
if (!ReadBZ2(&dstream, new_buf.data(), read_bytes)) {
|
||||
if (!patch_reader.ReadDiffStream(new_buf.data(), read_bytes)) {
|
||||
fprintf(stderr, "Failed to read diff stream.\n");
|
||||
return 2;
|
||||
}
|
||||
@@ -433,42 +333,46 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
|
||||
}
|
||||
|
||||
// Adjust pointers.
|
||||
newpos += ctrl[0];
|
||||
oldpos += ctrl[0];
|
||||
newpos += control_entry.diff_size;
|
||||
oldpos += control_entry.diff_size;
|
||||
|
||||
if (oldpos > static_cast<int64_t>(oldsize)) {
|
||||
if (oldpos > static_cast<int64_t>(old_file_size)) {
|
||||
// Write diff block directly to new file without adding old data,
|
||||
// because we skipped part where |oldpos| > oldsize.
|
||||
ret = ReadBZ2AndWriteAll(new_file, &dstream, oldpos - oldsize,
|
||||
new_buf.data(), new_buf.size());
|
||||
// because we skipped part where |oldpos| > old_file_size.
|
||||
ret = ReadStreamAndWriteAll(
|
||||
new_file, oldpos - old_file_size, new_buf.data(), new_buf.size(),
|
||||
std::bind(&BsdiffPatchReader::ReadDiffStream, &patch_reader,
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Sanity-check.
|
||||
if (newpos + ctrl[1] > newsize) {
|
||||
if (newpos + control_entry.extra_size > patch_reader.new_file_size()) {
|
||||
fprintf(stderr, "Corrupt patch.\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Read extra block.
|
||||
ret = ReadBZ2AndWriteAll(new_file, &estream, ctrl[1], new_buf.data(),
|
||||
new_buf.size());
|
||||
ret = ReadStreamAndWriteAll(
|
||||
new_file, control_entry.extra_size, new_buf.data(), new_buf.size(),
|
||||
std::bind(&BsdiffPatchReader::ReadExtraStream, &patch_reader,
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
// Adjust pointers.
|
||||
newpos += ctrl[1];
|
||||
oldpos += ctrl[2];
|
||||
newpos += control_entry.extra_size;
|
||||
oldpos += control_entry.offset_increment;
|
||||
}
|
||||
|
||||
// Close input file.
|
||||
old_file->Close();
|
||||
|
||||
// Clean up the bzip2 reads.
|
||||
BZ2_bzDecompressEnd(&cstream);
|
||||
BZ2_bzDecompressEnd(&dstream);
|
||||
BZ2_bzDecompressEnd(&estream);
|
||||
if (!patch_reader.Finish()) {
|
||||
fprintf(stderr, "Failed to finish the patch reader\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (!new_file->Close()) {
|
||||
perror("Error closing new file");
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2017 The Chromium OS 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 "bsdiff/bz2_decompressor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include <bzlib.h>
|
||||
|
||||
#include "bsdiff/logging.h"
|
||||
|
||||
using std::endl;
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
bool BZ2Decompressor::SetInputData(const uint8_t* input_data, size_t size) {
|
||||
// TODO(xunchang) update the avail_in for size > 2GB.
|
||||
if (size > std::numeric_limits<unsigned int>::max()) {
|
||||
LOG(ERROR) << "Oversized input data" << size << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
stream_.next_in = reinterpret_cast<char*>(const_cast<uint8_t*>(input_data));
|
||||
stream_.avail_in = size;
|
||||
stream_.bzalloc = nullptr;
|
||||
stream_.bzfree = nullptr;
|
||||
stream_.opaque = nullptr;
|
||||
int bz2err = BZ2_bzDecompressInit(&stream_, 0, 0);
|
||||
if (bz2err != BZ_OK) {
|
||||
LOG(ERROR) << "Failed to bzinit control stream: " << bz2err << endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BZ2Decompressor::Read(uint8_t* output_data, size_t bytes_to_output) {
|
||||
stream_.next_out = reinterpret_cast<char*>(output_data);
|
||||
while (bytes_to_output > 0) {
|
||||
size_t output_size = std::min<size_t>(
|
||||
std::numeric_limits<unsigned int>::max(), bytes_to_output);
|
||||
stream_.avail_out = output_size;
|
||||
|
||||
int bz2err = BZ2_bzDecompress(&stream_);
|
||||
if (bz2err != BZ_OK && bz2err != BZ_STREAM_END) {
|
||||
LOG(ERROR) << "Failed to decompress " << output_size
|
||||
<< " bytes of data, error: " << bz2err << endl;
|
||||
return false;
|
||||
}
|
||||
bytes_to_output -= (output_size - stream_.avail_out);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BZ2Decompressor::Close() {
|
||||
int bz2err = BZ2_bzDecompressEnd(&stream_);
|
||||
if (bz2err != BZ_OK) {
|
||||
LOG(ERROR) << "BZ2_bzDecompressEnd returns with " << bz2err << endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace bsdiff
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef _BSDIFF_BZ2_DECOMPRESSOR_H_
|
||||
#define _BSDIFF_BZ2_DECOMPRESSOR_H_
|
||||
|
||||
#include <bzlib.h>
|
||||
|
||||
#include "bsdiff/decompressor_interface.h"
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
class BZ2Decompressor : public DecompressorInterface {
|
||||
public:
|
||||
BZ2Decompressor() = default;
|
||||
|
||||
bool SetInputData(const uint8_t* input_data, size_t size) override;
|
||||
|
||||
bool Read(uint8_t* output_data, size_t bytes_to_output) override;
|
||||
|
||||
bool Close() override;
|
||||
|
||||
private:
|
||||
bz_stream stream_;
|
||||
};
|
||||
|
||||
} // namespace bsdiff
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef _BSDIFF_DECOMPRESSOR_INTERFACE_H_
|
||||
#define _BSDIFF_DECOMPRESSOR_INTERFACE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
class DecompressorInterface {
|
||||
public:
|
||||
virtual ~DecompressorInterface() {}
|
||||
|
||||
// Set the buffer starting from |input_data| with length |size| as the input
|
||||
// compressed stream. This buffer is owned by the caller and should stay
|
||||
// valid until the Close() function is called.
|
||||
virtual bool SetInputData(const uint8_t* input_data, size_t size) = 0;
|
||||
|
||||
// Decompress the stream and output |bytes_to_output| bytes of data to
|
||||
// |output_data|. Returns false if not all bytes_to_output can be
|
||||
// decompressed from the stream due to a decompressor error or EOF.
|
||||
virtual bool Read(uint8_t* output_data, size_t bytes_to_output) = 0;
|
||||
|
||||
// Close the decompression stream.
|
||||
virtual bool Close() = 0;
|
||||
};
|
||||
|
||||
} // namespace bsdiff
|
||||
#endif // _BSDIFF_DECOMPRESSOR_INTERFACE_H_
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef _BSDIFF_CONTROL_ENTRY_H_
|
||||
#define _BSDIFF_CONTROL_ENTRY_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct ControlEntry {
|
||||
ControlEntry(uint64_t diff_size,
|
||||
uint64_t extra_size,
|
||||
int64_t offset_increment)
|
||||
: diff_size(diff_size),
|
||||
extra_size(extra_size),
|
||||
offset_increment(offset_increment) {}
|
||||
|
||||
// The number of bytes to copy from the source and diff stream.
|
||||
uint64_t diff_size;
|
||||
|
||||
// The number of bytes to copy from the extra stream.
|
||||
uint64_t extra_size;
|
||||
|
||||
// The value to add to the source pointer after patching from the diff stream.
|
||||
int64_t offset_increment;
|
||||
|
||||
bool operator==(const ControlEntry& o) const {
|
||||
return diff_size == o.diff_size && extra_size == o.extra_size &&
|
||||
offset_increment == o.offset_increment;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _BSDIFF_CONTROL_ENTRY_H_
|
||||
@@ -9,31 +9,10 @@
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "bsdiff/control_entry.h"
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
struct ControlEntry {
|
||||
ControlEntry(uint64_t diff_size,
|
||||
uint64_t extra_size,
|
||||
int64_t offset_increment)
|
||||
: diff_size(diff_size),
|
||||
extra_size(extra_size),
|
||||
offset_increment(offset_increment) {}
|
||||
|
||||
// The number of bytes to copy from the source and diff stream.
|
||||
uint64_t diff_size;
|
||||
|
||||
// The number of bytes to copy from the extra stream.
|
||||
uint64_t extra_size;
|
||||
|
||||
// The value to add to the source pointer after patching from the diff stream.
|
||||
int64_t offset_increment;
|
||||
|
||||
bool operator==(const ControlEntry& o) const {
|
||||
return diff_size == o.diff_size && extra_size == o.extra_size &&
|
||||
offset_increment == o.offset_increment;
|
||||
}
|
||||
};
|
||||
|
||||
class PatchWriterInterface {
|
||||
public:
|
||||
virtual ~PatchWriterInterface() = default;
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
// Copyright 2017 The Chromium OS 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 "bsdiff/patch_reader.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "bsdiff/bspatch.h"
|
||||
#include "bsdiff/bz2_decompressor.h"
|
||||
#include "bsdiff/logging.h"
|
||||
#include "bsdiff/utils.h"
|
||||
|
||||
using std::endl;
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
const uint8_t kMagicHeader[] = "BSDIFF40";
|
||||
|
||||
bool BsdiffPatchReader::Init(const uint8_t* patch_data, size_t patch_size) {
|
||||
// File format:
|
||||
// 0 8 "BSDIFF40"
|
||||
// 8 8 X
|
||||
// 16 8 Y
|
||||
// 24 8 new_file_size
|
||||
// 32 X compressed control block
|
||||
// 32+X Y compressed diff block
|
||||
// 32+X+Y ??? compressed extra block
|
||||
// with control block a set of triples (x,y,z) meaning "add x bytes
|
||||
// from oldfile to x bytes from the diff block; copy y bytes from the
|
||||
// extra block; seek forwards in oldfile by z bytes".
|
||||
|
||||
// Check for appropriate magic.
|
||||
if (memcmp(patch_data, kMagicHeader, 8) != 0) {
|
||||
LOG(ERROR) << "Not a bsdiff patch." << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read lengths from header.
|
||||
int64_t ctrl_len = ParseInt64(patch_data + 8);
|
||||
int64_t diff_len = ParseInt64(patch_data + 16);
|
||||
int64_t signed_newsize = ParseInt64(patch_data + 24);
|
||||
if ((ctrl_len < 0) || (diff_len < 0) || (signed_newsize < 0) ||
|
||||
(32 + ctrl_len + diff_len > static_cast<int64_t>(patch_size))) {
|
||||
LOG(ERROR) << "Corrupt patch. ctrl_len: " << ctrl_len
|
||||
<< ", data_len: " << diff_len
|
||||
<< ", new_file_size: " << signed_newsize
|
||||
<< ", patch_size: " << patch_size;
|
||||
return false;
|
||||
}
|
||||
new_file_size_ = signed_newsize;
|
||||
|
||||
// TODO(xunchang) set the correct decompressor based on the info in the
|
||||
// header.
|
||||
ctrl_stream_.reset(new BZ2Decompressor());
|
||||
diff_stream_.reset(new BZ2Decompressor());
|
||||
extra_stream_.reset(new BZ2Decompressor());
|
||||
|
||||
int64_t offset = 32;
|
||||
if (!ctrl_stream_->SetInputData(const_cast<uint8_t*>(patch_data) + offset,
|
||||
ctrl_len)) {
|
||||
LOG(ERROR) << "Failed to init ctrl stream, ctrl_len: " << ctrl_len << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
offset += ctrl_len;
|
||||
if (!diff_stream_->SetInputData(const_cast<uint8_t*>(patch_data) + offset,
|
||||
diff_len)) {
|
||||
LOG(ERROR) << "Failed to init ctrl stream, diff_len: " << diff_len << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
offset += diff_len;
|
||||
if (!extra_stream_->SetInputData(const_cast<uint8_t*>(patch_data) + offset,
|
||||
patch_size - offset)) {
|
||||
LOG(ERROR) << "Failed to init extra stream, extra_offset: " << offset
|
||||
<< ", patch_size: " << patch_size << endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BsdiffPatchReader::ParseControlEntry(ControlEntry* control_entry) {
|
||||
if (!control_entry)
|
||||
return false;
|
||||
|
||||
uint8_t buf[8];
|
||||
if (!ctrl_stream_->Read(buf, 8))
|
||||
return false;
|
||||
int64_t diff_size = ParseInt64(buf);
|
||||
|
||||
if (!ctrl_stream_->Read(buf, 8))
|
||||
return false;
|
||||
int64_t extra_size = ParseInt64(buf);
|
||||
|
||||
// Sanity check.
|
||||
if (diff_size < 0 || extra_size < 0) {
|
||||
LOG(ERROR) << "Corrupt patch; diff_size: " << diff_size
|
||||
<< ", extra_size: " << extra_size << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
control_entry->diff_size = diff_size;
|
||||
control_entry->extra_size = extra_size;
|
||||
|
||||
if (!ctrl_stream_->Read(buf, 8))
|
||||
return false;
|
||||
control_entry->offset_increment = ParseInt64(buf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BsdiffPatchReader::ReadDiffStream(uint8_t* buf, size_t size) {
|
||||
return diff_stream_->Read(buf, size);
|
||||
}
|
||||
|
||||
bool BsdiffPatchReader::ReadExtraStream(uint8_t* buf, size_t size) {
|
||||
return extra_stream_->Read(buf, size);
|
||||
}
|
||||
|
||||
bool BsdiffPatchReader::Finish() {
|
||||
if (!ctrl_stream_->Close()) {
|
||||
LOG(ERROR) << "Failed to close the control stream";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!diff_stream_->Close()) {
|
||||
LOG(ERROR) << "Failed to close the diff stream";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!extra_stream_->Close()) {
|
||||
LOG(ERROR) << "Failed to close the extra stream";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace bsdiff
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef _BSDIFF_PATCH_READER_H_
|
||||
#define _BSDIFF_PATCH_READER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "bsdiff/control_entry.h"
|
||||
#include "bsdiff/decompressor_interface.h"
|
||||
#include "bsdiff/file_interface.h"
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
extern const uint8_t kMagicHeader[];
|
||||
|
||||
// A wrapper class to read and parse the data in a bsdiff patch. The reader
|
||||
// class contains the concatenated streams of control, diff, and extra data.
|
||||
// After initialization, this class provides the function to read the metadata
|
||||
// embedded in the control stream sequentially and decompresses the diff & extra
|
||||
// stream based on this metadata.
|
||||
|
||||
class BsdiffPatchReader {
|
||||
public:
|
||||
BsdiffPatchReader() = default;
|
||||
|
||||
// Initialize the control stream, diff stream and extra stream from the
|
||||
// corresponding offset of |patch_data|.
|
||||
bool Init(const uint8_t* data_start, size_t patch_size);
|
||||
|
||||
// Read the control stream and parse the metadata of |diff_size_|,
|
||||
// |extra_size_| and |offset_incremental_|.
|
||||
bool ParseControlEntry(ControlEntry* control_entry);
|
||||
|
||||
// Read the data in |diff_stream_| and write |size| decompressed data to
|
||||
// |buf|.
|
||||
bool ReadDiffStream(uint8_t* buf, size_t size);
|
||||
|
||||
// Read the data in |extra_stream_| and write |size| decompressed data to
|
||||
// |buf|.
|
||||
bool ReadExtraStream(uint8_t* buf, size_t size);
|
||||
|
||||
uint64_t new_file_size() const { return new_file_size_; }
|
||||
|
||||
// Close the control/diff/extra stream. Return false if errors occur when
|
||||
// closing any of these streams.
|
||||
bool Finish();
|
||||
|
||||
private:
|
||||
// The compressed stream that contains the control data; i.e. length of each
|
||||
// diff/extra block and the corresponding offset to read in the source file.
|
||||
std::unique_ptr<DecompressorInterface> ctrl_stream_{nullptr};
|
||||
// The compressed stream that contains the concatenated diff blocks.
|
||||
std::unique_ptr<DecompressorInterface> diff_stream_{nullptr};
|
||||
// The compressed stream that contains the concatenated extra blocks.
|
||||
std::unique_ptr<DecompressorInterface> extra_stream_{nullptr};
|
||||
|
||||
// Size of the target file.
|
||||
uint64_t new_file_size_;
|
||||
};
|
||||
|
||||
} // namespace bsdiff
|
||||
|
||||
#endif // _BSDIFF_PATCH_READER_H_
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2017 The Chromium OS 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 <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "bsdiff/bz2_compressor.h"
|
||||
#include "bsdiff/patch_reader.h"
|
||||
#include "bsdiff/utils.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void EncodeInt64(int64_t x, uint8_t* buf) {
|
||||
uint64_t y = x < 0 ? (1ULL << 63ULL) - x : x;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
buf[i] = y & 0xff;
|
||||
y /= 256;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
TEST(PatchReaderTest, BZ2PatchReaderSmoke) {
|
||||
BZ2Compressor ctrl_stream;
|
||||
BZ2Compressor diff_stream;
|
||||
BZ2Compressor extra_stream;
|
||||
|
||||
// Write 3 blocks of data to diff stream & extra stream respectively.
|
||||
std::vector<std::string> diff_data = {"HelloWorld", "BspatchPatchTest",
|
||||
"BspatchDiffData"};
|
||||
std::vector<std::string> extra_data = {"HelloWorld!", "BZ2PatchReaderSmoke",
|
||||
"BspatchExtraData"};
|
||||
std::vector<int64_t> offset_increment = {100, 200, 300};
|
||||
for (size_t i = 0; i < diff_data.size(); i++) {
|
||||
uint8_t buf[24];
|
||||
EncodeInt64(diff_data[i].size(), buf);
|
||||
EncodeInt64(extra_data[i].size(), buf + 8);
|
||||
EncodeInt64(offset_increment[i], buf + 16);
|
||||
EXPECT_TRUE(ctrl_stream.Write(buf, sizeof(buf)));
|
||||
EXPECT_TRUE(
|
||||
diff_stream.Write(reinterpret_cast<const uint8_t*>(diff_data[i].data()),
|
||||
diff_data[i].size()));
|
||||
EXPECT_TRUE(extra_stream.Write(
|
||||
reinterpret_cast<const uint8_t*>(extra_data[i].data()),
|
||||
extra_data[i].size()));
|
||||
}
|
||||
EXPECT_TRUE(ctrl_stream.Finish());
|
||||
EXPECT_TRUE(diff_stream.Finish());
|
||||
EXPECT_TRUE(extra_stream.Finish());
|
||||
|
||||
// Encode the header
|
||||
uint8_t buf[24];
|
||||
EncodeInt64(ctrl_stream.GetCompressedData().size(), buf);
|
||||
EncodeInt64(diff_stream.GetCompressedData().size(), buf + 8);
|
||||
EncodeInt64(500, buf + 16);
|
||||
|
||||
std::vector<uint8_t> patch_data;
|
||||
std::copy(kMagicHeader, kMagicHeader + 8, std::back_inserter(patch_data));
|
||||
std::copy(buf, buf + sizeof(buf), std::back_inserter(patch_data));
|
||||
|
||||
// Concatenate the three streams into one patch.
|
||||
std::copy(ctrl_stream.GetCompressedData().begin(),
|
||||
ctrl_stream.GetCompressedData().end(),
|
||||
std::back_inserter(patch_data));
|
||||
std::copy(diff_stream.GetCompressedData().begin(),
|
||||
diff_stream.GetCompressedData().end(),
|
||||
std::back_inserter(patch_data));
|
||||
std::copy(extra_stream.GetCompressedData().begin(),
|
||||
extra_stream.GetCompressedData().end(),
|
||||
std::back_inserter(patch_data));
|
||||
|
||||
BsdiffPatchReader patch_reader;
|
||||
EXPECT_TRUE(patch_reader.Init(patch_data.data(), patch_data.size()));
|
||||
EXPECT_EQ(500ull, patch_reader.new_file_size());
|
||||
// Check that the decompressed data matches what we wrote.
|
||||
for (size_t i = 0; i < diff_data.size(); i++) {
|
||||
ControlEntry control_entry(0, 0, 0);
|
||||
EXPECT_TRUE(patch_reader.ParseControlEntry(&control_entry));
|
||||
uint8_t buffer[128] = {};
|
||||
EXPECT_TRUE(patch_reader.ReadDiffStream(buffer, diff_data[i].size()));
|
||||
EXPECT_EQ(0, memcmp(buffer, diff_data[i].data(), diff_data[i].size()));
|
||||
EXPECT_TRUE(patch_reader.ReadExtraStream(buffer, extra_data[i].size()));
|
||||
EXPECT_EQ(0, memcmp(buffer, extra_data[i].data(), extra_data[i].size()));
|
||||
}
|
||||
EXPECT_TRUE(patch_reader.Finish());
|
||||
}
|
||||
|
||||
} // namespace bsdiff
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "bsdiff/control_entry.h"
|
||||
#include "bsdiff/logging.h"
|
||||
|
||||
using std::endl;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bsdiff/utils.h"
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
|
||||
int64_t ParseInt64(const uint8_t* buf) {
|
||||
int64_t result = buf[7] & 0x7F;
|
||||
for (int i = 6; i >= 0; i--) {
|
||||
result <<= 8;
|
||||
result |= buf[i];
|
||||
}
|
||||
|
||||
if (buf[7] & 0x80)
|
||||
result = -result;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace bsdiff
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef _BSDIFF_UTILS_H_
|
||||
#define _BSDIFF_UTILS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace bsdiff {
|
||||
|
||||
int64_t ParseInt64(const uint8_t* buf);
|
||||
|
||||
} // namespace bsdiff
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user