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