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