1//
2// Copyright (C) 2015 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "update_engine/payload_consumer/xz_extent_writer.h"
18
19using google::protobuf::RepeatedPtrField;
20
21namespace chromeos_update_engine {
22
23namespace {
24const brillo::Blob::size_type kOutputBufferLength = 16 * 1024;
25
26// xz uses a variable dictionary size which impacts on the compression ratio
27// and is required to be reconstructed in RAM during decompression. While we
28// control the required memory from the compressor side, the decompressor allows
29// to set a limit on this dictionary size, rejecting compressed streams that
30// require more than that. "xz -9" requires up to 64 MiB, so a 64 MiB limit
31// will allow compressed streams up to -9, the maximum compression setting.
32const uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
33
34const char* XzErrorString(enum xz_ret error) {
35  #define __XZ_ERROR_STRING_CASE(code) case code: return #code;
36  switch (error) {
37    __XZ_ERROR_STRING_CASE(XZ_OK)
38    __XZ_ERROR_STRING_CASE(XZ_STREAM_END)
39    __XZ_ERROR_STRING_CASE(XZ_UNSUPPORTED_CHECK)
40    __XZ_ERROR_STRING_CASE(XZ_MEM_ERROR)
41    __XZ_ERROR_STRING_CASE(XZ_MEMLIMIT_ERROR)
42    __XZ_ERROR_STRING_CASE(XZ_FORMAT_ERROR)
43    __XZ_ERROR_STRING_CASE(XZ_OPTIONS_ERROR)
44    __XZ_ERROR_STRING_CASE(XZ_DATA_ERROR)
45    __XZ_ERROR_STRING_CASE(XZ_BUF_ERROR)
46    default:
47      return "<unknown xz error>";
48  }
49  #undef __XZ_ERROR_STRING_CASE
50}
51}  // namespace
52
53XzExtentWriter::~XzExtentWriter() {
54  xz_dec_end(stream_);
55}
56
57bool XzExtentWriter::Init(FileDescriptorPtr fd,
58                          const RepeatedPtrField<Extent>& extents,
59                          uint32_t block_size) {
60  stream_ = xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize);
61  TEST_AND_RETURN_FALSE(stream_ != nullptr);
62  return underlying_writer_->Init(fd, extents, block_size);
63}
64
65bool XzExtentWriter::Write(const void* bytes, size_t count) {
66  // Copy the input data into |input_buffer_| only if |input_buffer_| already
67  // contains unconsumed data. Otherwise, process the data directly from the
68  // source.
69  const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
70  if (!input_buffer_.empty()) {
71    input_buffer_.insert(input_buffer_.end(), input, input + count);
72    input = input_buffer_.data();
73    count = input_buffer_.size();
74  }
75
76  xz_buf request;
77  request.in = input;
78  request.in_pos = 0;
79  request.in_size = count;
80
81  brillo::Blob output_buffer(kOutputBufferLength);
82  request.out = output_buffer.data();
83  request.out_size = output_buffer.size();
84  for (;;) {
85    request.out_pos = 0;
86
87    xz_ret ret = xz_dec_run(stream_, &request);
88    if (ret != XZ_OK && ret != XZ_STREAM_END) {
89      LOG(ERROR) << "xz_dec_run returned " << XzErrorString(ret);
90      return false;
91    }
92
93    if (request.out_pos == 0)
94      break;
95
96    TEST_AND_RETURN_FALSE(
97        underlying_writer_->Write(output_buffer.data(), request.out_pos));
98    if (ret == XZ_STREAM_END)
99      CHECK_EQ(request.in_size, request.in_pos);
100    if (request.in_size == request.in_pos)
101      break;  // No more input to process.
102  }
103  output_buffer.clear();
104
105  // Store unconsumed data (if any) in |input_buffer_|. Since |input| can point
106  // to the existing |input_buffer_| we create a new one before assigning it.
107  brillo::Blob new_input_buffer(request.in + request.in_pos,
108                                request.in + request.in_size);
109  input_buffer_ = std::move(new_input_buffer);
110  return true;
111}
112
113bool XzExtentWriter::EndImpl() {
114  TEST_AND_RETURN_FALSE(input_buffer_.empty());
115  return underlying_writer_->End();
116}
117
118}  // namespace chromeos_update_engine
119