Implement XzExtentWriter.
The new XzExtentWriter class is an ExtentWriter that uses xz-embedded
to decompress an xz stream and write it to the underlying extent
writer. This will be used to implement REPLACE_XZ in a follow up CL.
Bug: 23604708
Test: Added unittests.
Change-Id: Ifd2ba0c917b66ab33e5cff1d6069da833ad54882
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