| // |
| // 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/payload_consumer/xz_extent_writer.h" |
| |
| using std::vector; |
| |
| namespace chromeos_update_engine { |
| |
| namespace { |
| const brillo::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; |
| |
| brillo::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. |
| brillo::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 |