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 
19 using std::vector;
20 
21 namespace chromeos_update_engine {
22 
23 namespace {
24 const 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.
32 const uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
33 
XzErrorString(enum xz_ret error)34 const 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 
~XzExtentWriter()53 XzExtentWriter::~XzExtentWriter() {
54   xz_dec_end(stream_);
55 }
56 
Init(FileDescriptorPtr fd,const vector<Extent> & extents,uint32_t block_size)57 bool XzExtentWriter::Init(FileDescriptorPtr fd,
58                           const vector<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 
Write(const void * bytes,size_t count)65 bool 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 
EndImpl()113 bool XzExtentWriter::EndImpl() {
114   TEST_AND_RETURN_FALSE(input_buffer_.empty());
115   return underlying_writer_->End();
116 }
117 
118 }  // namespace chromeos_update_engine
119