ab_generator.cc revision 55c4f9ba7f5c59e3345f2c1869464433ffa8dc2b
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_generator/ab_generator.h"
18
19#include <algorithm>
20
21#include <base/strings/stringprintf.h>
22
23#include "update_engine/common/hash_calculator.h"
24#include "update_engine/common/utils.h"
25#include "update_engine/payload_consumer/payload_constants.h"
26#include "update_engine/payload_generator/annotated_operation.h"
27#include "update_engine/payload_generator/bzip.h"
28#include "update_engine/payload_generator/delta_diff_generator.h"
29#include "update_engine/payload_generator/delta_diff_utils.h"
30
31using std::string;
32using std::vector;
33
34namespace chromeos_update_engine {
35
36bool ABGenerator::GenerateOperations(
37    const PayloadGenerationConfig& config,
38    const PartitionConfig& old_part,
39    const PartitionConfig& new_part,
40    BlobFileWriter* blob_file,
41    vector<AnnotatedOperation>* aops) {
42  TEST_AND_RETURN_FALSE(old_part.name == new_part.name);
43
44  ssize_t hard_chunk_blocks = (config.hard_chunk_size == -1 ? -1 :
45                               config.hard_chunk_size / config.block_size);
46  size_t soft_chunk_blocks = config.soft_chunk_size / config.block_size;
47
48  aops->clear();
49  TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(
50      aops,
51      old_part,
52      new_part,
53      hard_chunk_blocks,
54      soft_chunk_blocks,
55      blob_file,
56      config.imgdiff_allowed,
57      true));  // src_ops_allowed
58  LOG(INFO) << "done reading " << new_part.name;
59
60  TEST_AND_RETURN_FALSE(FragmentOperations(aops,
61                                           new_part.path,
62                                           blob_file));
63  SortOperationsByDestination(aops);
64
65  // Use the soft_chunk_size when merging operations to prevent merging all
66  // the operations into a huge one if there's no hard limit.
67  size_t merge_chunk_blocks = soft_chunk_blocks;
68  if (hard_chunk_blocks != -1 &&
69      static_cast<size_t>(hard_chunk_blocks) < soft_chunk_blocks) {
70    merge_chunk_blocks = hard_chunk_blocks;
71  }
72
73  TEST_AND_RETURN_FALSE(MergeOperations(aops,
74                                        merge_chunk_blocks,
75                                        new_part.path,
76                                        blob_file));
77
78  if (config.minor_version >= kOpSrcHashMinorPayloadVersion)
79    TEST_AND_RETURN_FALSE(AddSourceHash(aops, old_part.path));
80
81  return true;
82}
83
84void ABGenerator::SortOperationsByDestination(
85    vector<AnnotatedOperation>* aops) {
86  sort(aops->begin(), aops->end(), diff_utils::CompareAopsByDestination);
87}
88
89bool ABGenerator::FragmentOperations(
90    vector<AnnotatedOperation>* aops,
91    const string& target_part_path,
92    BlobFileWriter* blob_file) {
93  vector<AnnotatedOperation> fragmented_aops;
94  for (const AnnotatedOperation& aop : *aops) {
95    if (aop.op.type() == InstallOperation::SOURCE_COPY) {
96      TEST_AND_RETURN_FALSE(SplitSourceCopy(aop, &fragmented_aops));
97    } else if ((aop.op.type() == InstallOperation::REPLACE) ||
98               (aop.op.type() == InstallOperation::REPLACE_BZ)) {
99      TEST_AND_RETURN_FALSE(SplitReplaceOrReplaceBz(aop, &fragmented_aops,
100                                                    target_part_path,
101                                                    blob_file));
102    } else {
103      fragmented_aops.push_back(aop);
104    }
105  }
106  *aops = fragmented_aops;
107  return true;
108}
109
110bool ABGenerator::SplitSourceCopy(
111    const AnnotatedOperation& original_aop,
112    vector<AnnotatedOperation>* result_aops) {
113  InstallOperation original_op = original_aop.op;
114  TEST_AND_RETURN_FALSE(original_op.type() == InstallOperation::SOURCE_COPY);
115  // Keeps track of the index of curr_src_ext.
116  int curr_src_ext_index = 0;
117  Extent curr_src_ext = original_op.src_extents(curr_src_ext_index);
118  for (int i = 0; i < original_op.dst_extents_size(); i++) {
119    Extent dst_ext = original_op.dst_extents(i);
120    // The new operation which will have only one dst extent.
121    InstallOperation new_op;
122    uint64_t blocks_left = dst_ext.num_blocks();
123    while (blocks_left > 0) {
124      if (curr_src_ext.num_blocks() <= blocks_left) {
125        // If the curr_src_ext is smaller than dst_ext, add it.
126        blocks_left -= curr_src_ext.num_blocks();
127        *(new_op.add_src_extents()) = curr_src_ext;
128        if (curr_src_ext_index + 1 < original_op.src_extents().size()) {
129          curr_src_ext = original_op.src_extents(++curr_src_ext_index);
130        } else {
131          break;
132        }
133      } else {
134        // Split src_exts that are bigger than the dst_ext we're dealing with.
135        Extent first_ext;
136        first_ext.set_num_blocks(blocks_left);
137        first_ext.set_start_block(curr_src_ext.start_block());
138        *(new_op.add_src_extents()) = first_ext;
139        // Keep the second half of the split op.
140        curr_src_ext.set_num_blocks(curr_src_ext.num_blocks() - blocks_left);
141        curr_src_ext.set_start_block(curr_src_ext.start_block() + blocks_left);
142        blocks_left -= first_ext.num_blocks();
143      }
144    }
145    // Fix up our new operation and add it to the results.
146    new_op.set_type(InstallOperation::SOURCE_COPY);
147    *(new_op.add_dst_extents()) = dst_ext;
148    new_op.set_src_length(dst_ext.num_blocks() * kBlockSize);
149    new_op.set_dst_length(dst_ext.num_blocks() * kBlockSize);
150
151    AnnotatedOperation new_aop;
152    new_aop.op = new_op;
153    new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
154    result_aops->push_back(new_aop);
155  }
156  if (curr_src_ext_index != original_op.src_extents().size() - 1) {
157    LOG(FATAL) << "Incorrectly split SOURCE_COPY operation. Did not use all "
158               << "source extents.";
159  }
160  return true;
161}
162
163bool ABGenerator::SplitReplaceOrReplaceBz(
164    const AnnotatedOperation& original_aop,
165    vector<AnnotatedOperation>* result_aops,
166    const string& target_part_path,
167    BlobFileWriter* blob_file) {
168  InstallOperation original_op = original_aop.op;
169  const bool is_replace = original_op.type() == InstallOperation::REPLACE;
170  TEST_AND_RETURN_FALSE(is_replace ||
171                        original_op.type() == InstallOperation::REPLACE_BZ);
172
173  uint32_t data_offset = original_op.data_offset();
174  for (int i = 0; i < original_op.dst_extents_size(); i++) {
175    Extent dst_ext = original_op.dst_extents(i);
176    // Make a new operation with only one dst extent.
177    InstallOperation new_op;
178    *(new_op.add_dst_extents()) = dst_ext;
179    uint32_t data_size = dst_ext.num_blocks() * kBlockSize;
180    new_op.set_dst_length(data_size);
181    // If this is a REPLACE, attempt to reuse portions of the existing blob.
182    if (is_replace) {
183      new_op.set_type(InstallOperation::REPLACE);
184      new_op.set_data_length(data_size);
185      new_op.set_data_offset(data_offset);
186      data_offset += data_size;
187    }
188
189    AnnotatedOperation new_aop;
190    new_aop.op = new_op;
191    new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
192    TEST_AND_RETURN_FALSE(AddDataAndSetType(&new_aop, target_part_path,
193                                            blob_file));
194
195    result_aops->push_back(new_aop);
196  }
197  return true;
198}
199
200bool ABGenerator::MergeOperations(vector<AnnotatedOperation>* aops,
201                                  size_t chunk_blocks,
202                                  const string& target_part_path,
203                                  BlobFileWriter* blob_file) {
204  vector<AnnotatedOperation> new_aops;
205  for (const AnnotatedOperation& curr_aop : *aops) {
206    if (new_aops.empty()) {
207      new_aops.push_back(curr_aop);
208      continue;
209    }
210    AnnotatedOperation& last_aop = new_aops.back();
211
212    if (last_aop.op.dst_extents_size() <= 0 ||
213        curr_aop.op.dst_extents_size() <= 0) {
214      new_aops.push_back(curr_aop);
215      continue;
216    }
217    uint32_t last_dst_idx = last_aop.op.dst_extents_size() - 1;
218    uint32_t last_end_block =
219        last_aop.op.dst_extents(last_dst_idx).start_block() +
220        last_aop.op.dst_extents(last_dst_idx).num_blocks();
221    uint32_t curr_start_block = curr_aop.op.dst_extents(0).start_block();
222    uint32_t combined_block_count =
223        last_aop.op.dst_extents(last_dst_idx).num_blocks() +
224        curr_aop.op.dst_extents(0).num_blocks();
225    bool good_op_type = curr_aop.op.type() == InstallOperation::SOURCE_COPY ||
226                        curr_aop.op.type() == InstallOperation::REPLACE ||
227                        curr_aop.op.type() == InstallOperation::REPLACE_BZ;
228    if (good_op_type &&
229        last_aop.op.type() == curr_aop.op.type() &&
230        last_end_block == curr_start_block &&
231        combined_block_count <= chunk_blocks) {
232      // If the operations have the same type (which is a type that we can
233      // merge), are contiguous, are fragmented to have one destination extent,
234      // and their combined block count would be less than chunk size, merge
235      // them.
236      last_aop.name = base::StringPrintf("%s,%s",
237                                         last_aop.name.c_str(),
238                                         curr_aop.name.c_str());
239
240      ExtendExtents(last_aop.op.mutable_src_extents(),
241                    curr_aop.op.src_extents());
242      if (curr_aop.op.src_length() > 0)
243        last_aop.op.set_src_length(last_aop.op.src_length() +
244                                   curr_aop.op.src_length());
245      ExtendExtents(last_aop.op.mutable_dst_extents(),
246                    curr_aop.op.dst_extents());
247      if (curr_aop.op.dst_length() > 0)
248        last_aop.op.set_dst_length(last_aop.op.dst_length() +
249                                   curr_aop.op.dst_length());
250      // Set the data length to zero so we know to add the blob later.
251      if (curr_aop.op.type() == InstallOperation::REPLACE ||
252          curr_aop.op.type() == InstallOperation::REPLACE_BZ) {
253        last_aop.op.set_data_length(0);
254      }
255    } else {
256      // Otherwise just include the extent as is.
257      new_aops.push_back(curr_aop);
258    }
259  }
260
261  // Set the blobs for REPLACE/REPLACE_BZ operations that have been merged.
262  for (AnnotatedOperation& curr_aop : new_aops) {
263    if (curr_aop.op.data_length() == 0 &&
264        (curr_aop.op.type() == InstallOperation::REPLACE ||
265         curr_aop.op.type() == InstallOperation::REPLACE_BZ)) {
266      TEST_AND_RETURN_FALSE(AddDataAndSetType(&curr_aop, target_part_path,
267                                              blob_file));
268    }
269  }
270
271  *aops = new_aops;
272  return true;
273}
274
275bool ABGenerator::AddDataAndSetType(AnnotatedOperation* aop,
276                                    const string& target_part_path,
277                                    BlobFileWriter* blob_file) {
278  TEST_AND_RETURN_FALSE(aop->op.type() == InstallOperation::REPLACE ||
279                        aop->op.type() == InstallOperation::REPLACE_BZ);
280
281  brillo::Blob data(aop->op.dst_length());
282  vector<Extent> dst_extents;
283  ExtentsToVector(aop->op.dst_extents(), &dst_extents);
284  TEST_AND_RETURN_FALSE(utils::ReadExtents(target_part_path,
285                                           dst_extents,
286                                           &data,
287                                           data.size(),
288                                           kBlockSize));
289
290  brillo::Blob data_bz;
291  TEST_AND_RETURN_FALSE(BzipCompress(data, &data_bz));
292  CHECK(!data_bz.empty());
293
294  brillo::Blob* data_p = nullptr;
295  InstallOperation_Type new_op_type;
296  if (data_bz.size() < data.size()) {
297    new_op_type = InstallOperation::REPLACE_BZ;
298    data_p = &data_bz;
299  } else {
300    new_op_type = InstallOperation::REPLACE;
301    data_p = &data;
302  }
303
304  // If the operation doesn't point to a data blob, then we add it.
305  if (aop->op.type() != new_op_type ||
306      aop->op.data_length() != data_p->size()) {
307    aop->op.set_type(new_op_type);
308    aop->SetOperationBlob(data_p, blob_file);
309  }
310
311  return true;
312}
313
314bool ABGenerator::AddSourceHash(vector<AnnotatedOperation>* aops,
315                                const string& source_part_path) {
316  for (AnnotatedOperation& aop : *aops) {
317    if (aop.op.src_extents_size() == 0)
318      continue;
319
320    vector<Extent> src_extents;
321    ExtentsToVector(aop.op.src_extents(), &src_extents);
322    brillo::Blob src_data, src_hash;
323    uint64_t src_length =
324        aop.op.has_src_length()
325            ? aop.op.src_length()
326            : BlocksInExtents(aop.op.src_extents()) * kBlockSize;
327    TEST_AND_RETURN_FALSE(utils::ReadExtents(
328        source_part_path, src_extents, &src_data, src_length, kBlockSize));
329    TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfData(src_data, &src_hash));
330    aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
331  }
332  return true;
333}
334
335}  // namespace chromeos_update_engine
336