Merge "Use file size instead of filesystem size for all partitions."
diff --git a/Android.mk b/Android.mk
index c12fc16..abbae26 100644
--- a/Android.mk
+++ b/Android.mk
@@ -106,7 +106,8 @@
update_engine-dbus-libcros-client \
update_engine_client-dbus-proxies \
libbz \
- libfs_mgr
+ libfs_mgr \
+ libxz
LOCAL_SHARED_LIBRARIES += \
libprotobuf-cpp-lite-rtti \
libdbus \
@@ -176,7 +177,8 @@
update_manager/real_updater_provider.cc \
update_manager/state_factory.cc \
update_manager/update_manager.cc \
- utils.cc
+ utils.cc \
+ xz_extent_writer.cc
$(eval $(update_engine_common))
include $(BUILD_STATIC_LIBRARY)
@@ -190,6 +192,7 @@
libupdate_engine \
libbz \
libfs_mgr \
+ libxz \
update_metadata-protos \
update_engine-dbus-adaptor \
update_engine-dbus-libcros-client \
@@ -237,6 +240,7 @@
libupdate_engine \
libbz \
libfs_mgr \
+ libxz \
update_metadata-protos \
update_engine-dbus-adaptor \
update_engine-dbus-libcros-client \
@@ -287,6 +291,7 @@
libupdate_engine \
libbz \
libfs_mgr \
+ libxz \
update_metadata-protos \
update_engine-dbus-adaptor \
update_engine-dbus-libcros-client \
diff --git a/delta_performer.cc b/delta_performer.cc
index 0071834..c8a00d3 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -47,6 +47,7 @@
#include "update_engine/subprocess.h"
#include "update_engine/terminator.h"
#include "update_engine/update_attempter.h"
+#include "update_engine/xz_extent_writer.h"
using google::protobuf::RepeatedPtrField;
using std::min;
@@ -594,28 +595,28 @@
ScopedTerminatorExitUnblocker(); // Avoids a compiler unused var bug.
bool op_result;
- if (op.type() == InstallOperation::REPLACE ||
- op.type() == InstallOperation::REPLACE_BZ)
- op_result = HandleOpResult(
- PerformReplaceOperation(op, is_kernel_partition), "replace", error);
- else if (op.type() == InstallOperation::MOVE)
- op_result = HandleOpResult(
- PerformMoveOperation(op, is_kernel_partition), "move", error);
- else if (op.type() == InstallOperation::BSDIFF)
- op_result = HandleOpResult(
- PerformBsdiffOperation(op, is_kernel_partition), "bsdiff", error);
- else if (op.type() == InstallOperation::SOURCE_COPY)
- op_result =
- HandleOpResult(PerformSourceCopyOperation(op, is_kernel_partition),
- "source_copy", error);
- else if (op.type() == InstallOperation::SOURCE_BSDIFF)
- op_result =
- HandleOpResult(PerformSourceBsdiffOperation(op, is_kernel_partition),
- "source_bsdiff", error);
- else
- op_result = HandleOpResult(false, "unknown", error);
-
- if (!op_result)
+ switch (op.type()) {
+ case InstallOperation::REPLACE:
+ case InstallOperation::REPLACE_BZ:
+ case InstallOperation::REPLACE_XZ:
+ op_result = PerformReplaceOperation(op, is_kernel_partition);
+ break;
+ case InstallOperation::MOVE:
+ op_result = PerformMoveOperation(op, is_kernel_partition);
+ break;
+ case InstallOperation::BSDIFF:
+ op_result = PerformBsdiffOperation(op, is_kernel_partition);
+ break;
+ case InstallOperation::SOURCE_COPY:
+ op_result = PerformSourceCopyOperation(op, is_kernel_partition);
+ break;
+ case InstallOperation::SOURCE_BSDIFF:
+ op_result = PerformSourceBsdiffOperation(op, is_kernel_partition);
+ break;
+ default:
+ op_result = false;
+ }
+ if (!HandleOpResult(op_result, InstallOperationTypeName(op.type()), error))
return false;
next_operation_num_++;
@@ -650,7 +651,8 @@
bool DeltaPerformer::PerformReplaceOperation(const InstallOperation& operation,
bool is_kernel_partition) {
CHECK(operation.type() == InstallOperation::REPLACE ||
- operation.type() == InstallOperation::REPLACE_BZ);
+ operation.type() == InstallOperation::REPLACE_BZ ||
+ operation.type() == InstallOperation::REPLACE_XZ);
// Since we delete data off the beginning of the buffer as we use it,
// the data we need should be exactly at the beginning of the buffer.
@@ -665,8 +667,11 @@
chromeos::make_unique_ptr(new ZeroPadExtentWriter(
chromeos::make_unique_ptr(new DirectExtentWriter())));
- if (operation.type() == InstallOperation::REPLACE_BZ)
+ if (operation.type() == InstallOperation::REPLACE_BZ) {
writer.reset(new BzipExtentWriter(std::move(writer)));
+ } else if (operation.type() == InstallOperation::REPLACE_XZ) {
+ writer.reset(new XzExtentWriter(std::move(writer)));
+ }
// Create a vector of extents to pass to the ExtentWriter.
vector<Extent> extents;
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index cc3d6e2..0b509e3 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -23,9 +23,9 @@
#include <base/files/file_path.h>
#include <base/files/file_util.h>
-#include <base/strings/stringprintf.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
#include <google/protobuf/repeated_field.h>
#include <gtest/gtest.h>
@@ -46,10 +46,8 @@
using std::string;
using std::vector;
-using testing::Return;
-using testing::_;
-using test_utils::kRandomString;
using test_utils::System;
+using test_utils::kRandomString;
extern const char* kUnittestPrivateKeyPath;
extern const char* kUnittestPublicKeyPath;
@@ -72,6 +70,16 @@
kValidMetadataSignature,
};
+// Compressed data without checksum, generated with:
+// echo -n a | xz -9 --check=none | hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kXzCompressedData[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0x01, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x01,
+ 0xad, 0xa6, 0x58, 0x04, 0x06, 0x72, 0x9e, 0x7a, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x59, 0x5a,
+};
+
} // namespace
class DeltaPerformerTest : public ::testing::Test {
@@ -315,6 +323,27 @@
EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
}
+TEST_F(DeltaPerformerTest, ReplaceXzOperationTest) {
+ chromeos::Blob xz_data(std::begin(kXzCompressedData),
+ std::end(kXzCompressedData));
+ // The compressed xz data contains only a single "a", but the operation should
+ // pad the rest of the two blocks with zeros.
+ chromeos::Blob expected_data = chromeos::Blob(4096, 0);
+ expected_data[0] = 'a';
+
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(xz_data.size());
+ aop.op.set_type(InstallOperation::REPLACE_XZ);
+ vector<AnnotatedOperation> aops = {aop};
+
+ chromeos::Blob payload_data = GeneratePayload(xz_data, aops, false,
+ kSourceMinorPayloadVersion);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+}
+
TEST_F(DeltaPerformerTest, SourceCopyOperationTest) {
chromeos::Blob expected_data = chromeos::Blob(std::begin(kRandomString),
std::end(kRandomString));
diff --git a/fake_extent_writer.h b/fake_extent_writer.h
new file mode 100644
index 0000000..a876893
--- /dev/null
+++ b/fake_extent_writer.h
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_FAKE_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_FAKE_EXTENT_WRITER_H_
+
+#include <memory>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/extent_writer.h"
+
+namespace chromeos_update_engine {
+
+// FakeExtentWriter is a concrete ExtentWriter subclass that keeps track of all
+// the written data, useful for testing.
+class FakeExtentWriter : public ExtentWriter {
+ public:
+ FakeExtentWriter() = default;
+ ~FakeExtentWriter() override = default;
+
+ // ExtentWriter overrides.
+ bool Init(FileDescriptorPtr /* fd */,
+ const std::vector<Extent>& /* extents */,
+ uint32_t /* block_size */) override {
+ init_called_ = true;
+ return true;
+ };
+ bool Write(const void* bytes, size_t count) override {
+ if (!init_called_ || end_called_)
+ return false;
+ written_data_.insert(written_data_.end(),
+ reinterpret_cast<const uint8_t*>(bytes),
+ reinterpret_cast<const uint8_t*>(bytes) + count);
+ return true;
+ }
+ bool EndImpl() override {
+ end_called_ = true;
+ return true;
+ }
+
+ // Fake methods.
+ bool InitCalled() { return init_called_; }
+ bool EndCalled() { return end_called_; }
+ chromeos::Blob WrittenData() { return written_data_; }
+
+ private:
+ bool init_called_{false};
+ bool end_called_{false};
+ chromeos::Blob written_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeExtentWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_EXTENT_WRITER_H_
diff --git a/main.cc b/main.cc
index 516c72c..f56d447 100644
--- a/main.cc
+++ b/main.cc
@@ -14,7 +14,10 @@
// limitations under the License.
//
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
+#include <xz.h>
#include <string>
@@ -27,8 +30,6 @@
#include <chromeos/flag_helper.h>
#include <chromeos/message_loops/base_message_loop.h>
#include <metrics/metrics_library.h>
-#include <sys/stat.h>
-#include <sys/types.h>
#include "update_engine/daemon.h"
#include "update_engine/terminator.h"
@@ -113,6 +114,9 @@
LOG(INFO) << "Chrome OS Update Engine starting";
+ // xz-embedded requires to initialize its CRC-32 table once on startup.
+ xz_crc32_init();
+
// Ensure that all written files have safe permissions.
// This is a mask, so we _block_ all permissions for the group owner and other
// users but allow all permissions for the user owner. We allow execution
diff --git a/payload_constants.cc b/payload_constants.cc
index aeddf11..b28461b 100644
--- a/payload_constants.cc
+++ b/payload_constants.cc
@@ -31,4 +31,28 @@
const char kDeltaMagic[] = "CrAU";
const char kBspatchPath[] = "bspatch";
+const char* InstallOperationTypeName(InstallOperation_Type op_type) {
+ switch (op_type) {
+ case InstallOperation::BSDIFF:
+ return "BSDIFF";
+ case InstallOperation::MOVE:
+ return "MOVE";
+ case InstallOperation::REPLACE:
+ return "REPLACE";
+ case InstallOperation::REPLACE_BZ:
+ return "REPLACE_BZ";
+ case InstallOperation::SOURCE_COPY:
+ return "SOURCE_COPY";
+ case InstallOperation::SOURCE_BSDIFF:
+ return "SOURCE_BSDIFF";
+ case InstallOperation::ZERO:
+ return "ZERO";
+ case InstallOperation::DISCARD:
+ return "DISCARD";
+ case InstallOperation::REPLACE_XZ:
+ return "REPLACE_XZ";
+ }
+ return "<unknown_op>";
+}
+
}; // namespace chromeos_update_engine
diff --git a/payload_constants.h b/payload_constants.h
index 21dab00..81bc36a 100644
--- a/payload_constants.h
+++ b/payload_constants.h
@@ -21,6 +21,8 @@
#include <limits>
+#include "update_engine/update_metadata.pb.h"
+
namespace chromeos_update_engine {
// The major version used by Chrome OS.
@@ -52,6 +54,9 @@
// section of blocks not present on disk on a sparse file.
const uint64_t kSparseHole = std::numeric_limits<uint64_t>::max();
+// Return the name of the operation type.
+const char* InstallOperationTypeName(InstallOperation_Type op_type);
+
} // namespace chromeos_update_engine
#endif // UPDATE_ENGINE_PAYLOAD_CONSTANTS_H_
diff --git a/payload_generator/annotated_operation.cc b/payload_generator/annotated_operation.cc
index 3fbea2c..ca900ce 100644
--- a/payload_generator/annotated_operation.cc
+++ b/payload_generator/annotated_operation.cc
@@ -20,10 +20,9 @@
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
+#include "update_engine/payload_constants.h"
#include "update_engine/utils.h"
-using std::string;
-
namespace chromeos_update_engine {
namespace {
@@ -46,30 +45,6 @@
return true;
}
-string InstallOperationTypeName(InstallOperation_Type op_type) {
- switch (op_type) {
- case InstallOperation::BSDIFF:
- return "BSDIFF";
- case InstallOperation::MOVE:
- return "MOVE";
- case InstallOperation::REPLACE:
- return "REPLACE";
- case InstallOperation::REPLACE_BZ:
- return "REPLACE_BZ";
- case InstallOperation::SOURCE_COPY:
- return "SOURCE_COPY";
- case InstallOperation::SOURCE_BSDIFF:
- return "SOURCE_BSDIFF";
- case InstallOperation::ZERO:
- return "ZERO";
- case InstallOperation::DISCARD:
- return "DISCARD";
- case InstallOperation::REPLACE_XZ:
- return "REPLACE_XZ";
- }
- return "UNK";
-}
-
std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop) {
// For example, this prints:
// REPLACE_BZ 500 @3000
diff --git a/payload_generator/annotated_operation.h b/payload_generator/annotated_operation.h
index a6afbaa..08a0d0b 100644
--- a/payload_generator/annotated_operation.h
+++ b/payload_generator/annotated_operation.h
@@ -44,8 +44,6 @@
// For logging purposes.
std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop);
-std::string InstallOperationTypeName(InstallOperation_Type op_type);
-
} // namespace chromeos_update_engine
#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
diff --git a/testrunner.cc b/testrunner.cc
index 325d1af..35295a9 100644
--- a/testrunner.cc
+++ b/testrunner.cc
@@ -16,6 +16,8 @@
// based on pam_google_testrunner.cc
+#include <xz.h>
+
#include <base/at_exit.h>
#include <base/command_line.h>
#include <chromeos/test_helpers.h>
@@ -26,6 +28,8 @@
int main(int argc, char **argv) {
LOG(INFO) << "started";
base::AtExitManager exit_manager;
+ // xz-embedded requires to initialize its CRC-32 table once on startup.
+ xz_crc32_init();
// TODO(garnold) temporarily cause the unittest binary to exit with status
// code 2 upon catching a SIGTERM. This will help diagnose why the unittest
// binary is perceived as failing by the buildbot. We should revert it to use
diff --git a/update_engine.gyp b/update_engine.gyp
index 05bb6b4..2a1768e 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -130,6 +130,7 @@
'libshill-client',
'libssl',
'expat',
+ 'xz-embedded',
],
'deps': ['<@(exported_deps)'],
},
@@ -213,6 +214,7 @@
'update_manager/state_factory.cc',
'update_manager/update_manager.cc',
'utils.cc',
+ 'xz_extent_writer.cc',
],
'conditions': [
['USE_mtd == 1', {
@@ -447,6 +449,7 @@
'update_manager/update_manager_unittest.cc',
'update_manager/variable_unittest.cc',
'utils_unittest.cc',
+ 'xz_extent_writer_unittest.cc',
'zip_unittest.cc',
# Main entry point for runnning tests.
'testrunner.cc',
diff --git a/xz_extent_writer.cc b/xz_extent_writer.cc
new file mode 100644
index 0000000..99eb023
--- /dev/null
+++ b/xz_extent_writer.cc
@@ -0,0 +1,118 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/xz_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const chromeos::Blob::size_type kOutputBufferLength = 16 * 1024;
+
+// xz uses a variable dictionary size which impacts on the compression ratio
+// and is required to be reconstructed in RAM during decompression. While we
+// control the required memory from the compressor side, the decompressor allows
+// to set a limit on this dictionary size, rejecting compressed streams that
+// require more than that. "xz -9" requires up to 64 MiB, so a 64 MiB limit
+// will allow compressed streams up to -9, the maximum compression setting.
+const uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
+
+const char* XzErrorString(enum xz_ret error) {
+ #define __XZ_ERROR_STRING_CASE(code) case code: return #code;
+ switch (error) {
+ __XZ_ERROR_STRING_CASE(XZ_OK)
+ __XZ_ERROR_STRING_CASE(XZ_STREAM_END)
+ __XZ_ERROR_STRING_CASE(XZ_UNSUPPORTED_CHECK)
+ __XZ_ERROR_STRING_CASE(XZ_MEM_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_MEMLIMIT_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_FORMAT_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_OPTIONS_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_DATA_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_BUF_ERROR)
+ default:
+ return "<unknown xz error>";
+ }
+ #undef __XZ_ERROR_STRING_CASE
+};
+} // namespace
+
+XzExtentWriter::~XzExtentWriter() {
+ xz_dec_end(stream_);
+}
+
+bool XzExtentWriter::Init(FileDescriptorPtr fd,
+ const vector<Extent>& extents,
+ uint32_t block_size) {
+ stream_ = xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize);
+ TEST_AND_RETURN_FALSE(stream_ != nullptr);
+ return underlying_writer_->Init(fd, extents, block_size);
+}
+
+bool XzExtentWriter::Write(const void* bytes, size_t count) {
+ // Copy the input data into |input_buffer_| only if |input_buffer_| already
+ // contains unconsumed data. Otherwise, process the data directly from the
+ // source.
+ const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+ if (!input_buffer_.empty()) {
+ input_buffer_.insert(input_buffer_.end(), input, input + count);
+ input = input_buffer_.data();
+ count = input_buffer_.size();
+ }
+
+ xz_buf request;
+ request.in = input;
+ request.in_pos = 0;
+ request.in_size = count;
+
+ chromeos::Blob output_buffer(kOutputBufferLength);
+ request.out = output_buffer.data();
+ request.out_size = output_buffer.size();
+ for (;;) {
+ request.out_pos = 0;
+
+ xz_ret ret = xz_dec_run(stream_, &request);
+ if (ret != XZ_OK && ret != XZ_STREAM_END) {
+ LOG(ERROR) << "xz_dec_run returned " << XzErrorString(ret);
+ return false;
+ }
+
+ if (request.out_pos == 0)
+ break;
+
+ TEST_AND_RETURN_FALSE(
+ underlying_writer_->Write(output_buffer.data(), request.out_pos));
+ if (ret == XZ_STREAM_END)
+ CHECK_EQ(request.in_size, request.in_pos);
+ if (request.in_size == request.in_pos)
+ break; // No more input to process.
+ }
+ output_buffer.clear();
+
+ // Store unconsumed data (if any) in |input_buffer_|. Since |input| can point
+ // to the existing |input_buffer_| we create a new one before assigning it.
+ chromeos::Blob new_input_buffer(request.in + request.in_pos,
+ request.in + request.in_size);
+ input_buffer_ = std::move(new_input_buffer);
+ return true;
+}
+
+bool XzExtentWriter::EndImpl() {
+ TEST_AND_RETURN_FALSE(input_buffer_.empty());
+ return underlying_writer_->End();
+}
+
+} // namespace chromeos_update_engine
diff --git a/xz_extent_writer.h b/xz_extent_writer.h
new file mode 100644
index 0000000..0b9ee17
--- /dev/null
+++ b/xz_extent_writer.h
@@ -0,0 +1,60 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_XZ_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_XZ_EXTENT_WRITER_H_
+
+#include <xz.h>
+
+#include <memory>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/extent_writer.h"
+
+// XzExtentWriter is a concrete ExtentWriter subclass that xz-decompresses
+// what it's given in Write using xz-embedded. Note that xz-embedded only
+// supports files with either no CRC or CRC-32. It passes the decompressed data
+// to an underlying ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class XzExtentWriter : public ExtentWriter {
+ public:
+ explicit XzExtentWriter(std::unique_ptr<ExtentWriter> underlying_writer)
+ : underlying_writer_(std::move(underlying_writer)) {}
+ ~XzExtentWriter() override;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override;
+ bool Write(const void* bytes, size_t count) override;
+ bool EndImpl() override;
+
+ private:
+ // The underlying ExtentWriter.
+ std::unique_ptr<ExtentWriter> underlying_writer_;
+ // The opaque xz decompressor struct.
+ xz_dec* stream_{nullptr};
+ chromeos::Blob input_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(XzExtentWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_XZ_EXTENT_WRITER_H_
diff --git a/xz_extent_writer_unittest.cc b/xz_extent_writer_unittest.cc
new file mode 100644
index 0000000..2fd580d
--- /dev/null
+++ b/xz_extent_writer_unittest.cc
@@ -0,0 +1,165 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/xz_extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <chromeos/make_unique_ptr.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_extent_writer.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const char kSampleData[] = "Redundaaaaaaaaaaaaaant\n";
+
+// Compressed data with CRC-32 check, generated with:
+// echo "Redundaaaaaaaaaaaaaant" | xz -9 --check=crc32 |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressedDataCRC32[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x00, 0x16, 0x00, 0x10, 0x5d, 0x00, 0x29, 0x19, 0x48, 0x87, 0x88,
+ 0xec, 0x49, 0x88, 0x73, 0x8b, 0x5d, 0xa6, 0x46, 0xb4, 0x00, 0x00, 0x00,
+ 0x68, 0xfc, 0x7b, 0x25, 0x00, 0x01, 0x28, 0x17, 0x46, 0x9e, 0x08, 0xfe,
+ 0x90, 0x42, 0x99, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a,
+};
+
+// Compressed data without checksum, generated with:
+// echo "Redundaaaaaaaaaaaaaant" | xz -9 --check=none |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressedDataNoCheck[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x00, 0x16, 0x00, 0x10, 0x5d, 0x00, 0x29, 0x19, 0x48, 0x87, 0x88,
+ 0xec, 0x49, 0x88, 0x73, 0x8b, 0x5d, 0xa6, 0x46, 0xb4, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x24, 0x17, 0x4a, 0xd1, 0xbd, 0x52, 0x06, 0x72, 0x9e, 0x7a,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x5a,
+};
+
+// Highly redundant data bigger than the internal buffer, generated with:
+// dd if=/dev/zero bs=30K count=1 | tr '\0' 'a' | xz -9 --check=crc32 |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressed30KiBofA[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x77, 0xff, 0x00, 0x41, 0x5d, 0x00, 0x30, 0xef, 0xfb, 0xbf, 0xfe,
+ 0xa3, 0xb1, 0x5e, 0xe5, 0xf8, 0x3f, 0xb2, 0xaa, 0x26, 0x55, 0xf8, 0x68,
+ 0x70, 0x41, 0x70, 0x15, 0x0f, 0x8d, 0xfd, 0x1e, 0x4c, 0x1b, 0x8a, 0x42,
+ 0xb7, 0x19, 0xf4, 0x69, 0x18, 0x71, 0xae, 0x66, 0x23, 0x8a, 0x8a, 0x4d,
+ 0x2f, 0xa3, 0x0d, 0xd9, 0x7f, 0xa6, 0xe3, 0x8c, 0x23, 0x11, 0x53, 0xe0,
+ 0x59, 0x18, 0xc5, 0x75, 0x8a, 0xe2, 0x76, 0x4c, 0xee, 0x30, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf9, 0x47, 0xb5, 0xee, 0x00, 0x01, 0x59, 0x80,
+ 0xf0, 0x01, 0x00, 0x00, 0xe0, 0x41, 0x96, 0xde, 0x3e, 0x30, 0x0d, 0x8b,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a,
+};
+
+} // namespace
+
+class XzExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ fake_extent_writer_ = new FakeExtentWriter();
+ xz_writer_.reset(
+ new XzExtentWriter(chromeos::make_unique_ptr(fake_extent_writer_)));
+ }
+
+ void WriteAll(const chromeos::Blob& compressed) {
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ EXPECT_TRUE(xz_writer_->Write(compressed.data(), compressed.size()));
+ EXPECT_TRUE(xz_writer_->End());
+
+ EXPECT_TRUE(fake_extent_writer_->InitCalled());
+ EXPECT_TRUE(fake_extent_writer_->EndCalled());
+ }
+
+ // Owned by |xz_writer_|. This object is invalidated after |xz_writer_| is
+ // deleted.
+ FakeExtentWriter* fake_extent_writer_{nullptr};
+ std::unique_ptr<XzExtentWriter> xz_writer_;
+
+ const chromeos::Blob sample_data_{
+ std::begin(kSampleData),
+ std::begin(kSampleData) + strlen(kSampleData)};
+ FileDescriptorPtr fd_;
+};
+
+TEST_F(XzExtentWriterTest, CreateAndDestroy) {
+ // Test that no Init() or End() called doesn't crash the program.
+ EXPECT_FALSE(fake_extent_writer_->InitCalled());
+ EXPECT_FALSE(fake_extent_writer_->EndCalled());
+}
+
+TEST_F(XzExtentWriterTest, CompressedSampleData) {
+ WriteAll(chromeos::Blob(std::begin(kCompressedDataNoCheck),
+ std::end(kCompressedDataNoCheck)));
+ EXPECT_EQ(sample_data_, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, CompressedSampleDataWithCrc) {
+ WriteAll(chromeos::Blob(std::begin(kCompressedDataCRC32),
+ std::end(kCompressedDataCRC32)));
+ EXPECT_EQ(sample_data_, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, CompressedDataBiggerThanTheBuffer) {
+ // Test that even if the output data is bigger than the internal buffer, all
+ // the data is written.
+ WriteAll(chromeos::Blob(std::begin(kCompressed30KiBofA),
+ std::end(kCompressed30KiBofA)));
+ chromeos::Blob expected_data(30 * 1024, 'a');
+ EXPECT_EQ(expected_data, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, GarbageDataRejected) {
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ // The sample_data_ is an uncompressed string.
+ EXPECT_FALSE(xz_writer_->Write(sample_data_.data(), sample_data_.size()));
+ EXPECT_TRUE(xz_writer_->End());
+
+ EXPECT_TRUE(fake_extent_writer_->EndCalled());
+}
+
+TEST_F(XzExtentWriterTest, PartialDataIsKept) {
+ chromeos::Blob compressed(std::begin(kCompressed30KiBofA),
+ std::end(kCompressed30KiBofA));
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ for (uint8_t byte : compressed) {
+ EXPECT_TRUE(xz_writer_->Write(&byte, 1));
+ }
+ EXPECT_TRUE(xz_writer_->End());
+
+ // The sample_data_ is an uncompressed string.
+ chromeos::Blob expected_data(30 * 1024, 'a');
+ EXPECT_EQ(expected_data, fake_extent_writer_->WrittenData());
+}
+
+} // namespace chromeos_update_engine