You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
13 KiB
324 lines
13 KiB
// |
|
// Copyright (C) 2015 The Android Open Source Project |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
// |
|
|
|
#include "update_engine/payload_generator/ab_generator.h" |
|
|
|
#include <algorithm> |
|
|
|
#include <base/strings/stringprintf.h> |
|
|
|
#include "update_engine/common/hash_calculator.h" |
|
#include "update_engine/common/utils.h" |
|
#include "update_engine/payload_consumer/payload_constants.h" |
|
#include "update_engine/payload_generator/annotated_operation.h" |
|
#include "update_engine/payload_generator/bzip.h" |
|
#include "update_engine/payload_generator/delta_diff_generator.h" |
|
#include "update_engine/payload_generator/delta_diff_utils.h" |
|
|
|
using chromeos_update_engine::diff_utils::IsAReplaceOperation; |
|
using std::string; |
|
using std::vector; |
|
|
|
namespace chromeos_update_engine { |
|
|
|
bool ABGenerator::GenerateOperations( |
|
const PayloadGenerationConfig& config, |
|
const PartitionConfig& old_part, |
|
const PartitionConfig& new_part, |
|
BlobFileWriter* blob_file, |
|
vector<AnnotatedOperation>* aops) { |
|
TEST_AND_RETURN_FALSE(old_part.name == new_part.name); |
|
|
|
ssize_t hard_chunk_blocks = (config.hard_chunk_size == -1 ? -1 : |
|
config.hard_chunk_size / config.block_size); |
|
size_t soft_chunk_blocks = config.soft_chunk_size / config.block_size; |
|
|
|
aops->clear(); |
|
TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(aops, |
|
old_part, |
|
new_part, |
|
hard_chunk_blocks, |
|
soft_chunk_blocks, |
|
config.version, |
|
blob_file)); |
|
LOG(INFO) << "done reading " << new_part.name; |
|
|
|
TEST_AND_RETURN_FALSE( |
|
FragmentOperations(config.version, aops, new_part.path, blob_file)); |
|
SortOperationsByDestination(aops); |
|
|
|
// Use the soft_chunk_size when merging operations to prevent merging all |
|
// the operations into a huge one if there's no hard limit. |
|
size_t merge_chunk_blocks = soft_chunk_blocks; |
|
if (hard_chunk_blocks != -1 && |
|
static_cast<size_t>(hard_chunk_blocks) < soft_chunk_blocks) { |
|
merge_chunk_blocks = hard_chunk_blocks; |
|
} |
|
|
|
TEST_AND_RETURN_FALSE(MergeOperations( |
|
aops, config.version, merge_chunk_blocks, new_part.path, blob_file)); |
|
|
|
if (config.version.minor >= kOpSrcHashMinorPayloadVersion) |
|
TEST_AND_RETURN_FALSE(AddSourceHash(aops, old_part.path)); |
|
|
|
return true; |
|
} |
|
|
|
void ABGenerator::SortOperationsByDestination( |
|
vector<AnnotatedOperation>* aops) { |
|
sort(aops->begin(), aops->end(), diff_utils::CompareAopsByDestination); |
|
} |
|
|
|
bool ABGenerator::FragmentOperations(const PayloadVersion& version, |
|
vector<AnnotatedOperation>* aops, |
|
const string& target_part_path, |
|
BlobFileWriter* blob_file) { |
|
vector<AnnotatedOperation> fragmented_aops; |
|
for (const AnnotatedOperation& aop : *aops) { |
|
// Only do split if the operation has more than one dst extents. |
|
if (aop.op.dst_extents_size() > 1) { |
|
if (aop.op.type() == InstallOperation::SOURCE_COPY) { |
|
TEST_AND_RETURN_FALSE(SplitSourceCopy(aop, &fragmented_aops)); |
|
continue; |
|
} |
|
if (IsAReplaceOperation(aop.op.type())) { |
|
TEST_AND_RETURN_FALSE(SplitAReplaceOp( |
|
version, aop, target_part_path, &fragmented_aops, blob_file)); |
|
continue; |
|
} |
|
} |
|
fragmented_aops.push_back(aop); |
|
} |
|
*aops = std::move(fragmented_aops); |
|
return true; |
|
} |
|
|
|
bool ABGenerator::SplitSourceCopy( |
|
const AnnotatedOperation& original_aop, |
|
vector<AnnotatedOperation>* result_aops) { |
|
InstallOperation original_op = original_aop.op; |
|
TEST_AND_RETURN_FALSE(original_op.type() == InstallOperation::SOURCE_COPY); |
|
// Keeps track of the index of curr_src_ext. |
|
int curr_src_ext_index = 0; |
|
Extent curr_src_ext = original_op.src_extents(curr_src_ext_index); |
|
for (int i = 0; i < original_op.dst_extents_size(); i++) { |
|
const Extent& dst_ext = original_op.dst_extents(i); |
|
// The new operation which will have only one dst extent. |
|
InstallOperation new_op; |
|
uint64_t blocks_left = dst_ext.num_blocks(); |
|
while (blocks_left > 0) { |
|
if (curr_src_ext.num_blocks() <= blocks_left) { |
|
// If the curr_src_ext is smaller than dst_ext, add it. |
|
blocks_left -= curr_src_ext.num_blocks(); |
|
*(new_op.add_src_extents()) = curr_src_ext; |
|
if (curr_src_ext_index + 1 < original_op.src_extents().size()) { |
|
curr_src_ext = original_op.src_extents(++curr_src_ext_index); |
|
} else { |
|
break; |
|
} |
|
} else { |
|
// Split src_exts that are bigger than the dst_ext we're dealing with. |
|
Extent first_ext; |
|
first_ext.set_num_blocks(blocks_left); |
|
first_ext.set_start_block(curr_src_ext.start_block()); |
|
*(new_op.add_src_extents()) = first_ext; |
|
// Keep the second half of the split op. |
|
curr_src_ext.set_num_blocks(curr_src_ext.num_blocks() - blocks_left); |
|
curr_src_ext.set_start_block(curr_src_ext.start_block() + blocks_left); |
|
blocks_left -= first_ext.num_blocks(); |
|
} |
|
} |
|
// Fix up our new operation and add it to the results. |
|
new_op.set_type(InstallOperation::SOURCE_COPY); |
|
*(new_op.add_dst_extents()) = dst_ext; |
|
|
|
AnnotatedOperation new_aop; |
|
new_aop.op = new_op; |
|
new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i); |
|
result_aops->push_back(new_aop); |
|
} |
|
if (curr_src_ext_index != original_op.src_extents().size() - 1) { |
|
LOG(FATAL) << "Incorrectly split SOURCE_COPY operation. Did not use all " |
|
<< "source extents."; |
|
} |
|
return true; |
|
} |
|
|
|
bool ABGenerator::SplitAReplaceOp(const PayloadVersion& version, |
|
const AnnotatedOperation& original_aop, |
|
const string& target_part_path, |
|
vector<AnnotatedOperation>* result_aops, |
|
BlobFileWriter* blob_file) { |
|
InstallOperation original_op = original_aop.op; |
|
TEST_AND_RETURN_FALSE(IsAReplaceOperation(original_op.type())); |
|
const bool is_replace = original_op.type() == InstallOperation::REPLACE; |
|
|
|
uint32_t data_offset = original_op.data_offset(); |
|
for (int i = 0; i < original_op.dst_extents_size(); i++) { |
|
const Extent& dst_ext = original_op.dst_extents(i); |
|
// Make a new operation with only one dst extent. |
|
InstallOperation new_op; |
|
*(new_op.add_dst_extents()) = dst_ext; |
|
uint32_t data_size = dst_ext.num_blocks() * kBlockSize; |
|
new_op.set_dst_length(data_size); |
|
// If this is a REPLACE, attempt to reuse portions of the existing blob. |
|
if (is_replace) { |
|
new_op.set_type(InstallOperation::REPLACE); |
|
new_op.set_data_length(data_size); |
|
new_op.set_data_offset(data_offset); |
|
data_offset += data_size; |
|
} |
|
|
|
AnnotatedOperation new_aop; |
|
new_aop.op = new_op; |
|
new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i); |
|
TEST_AND_RETURN_FALSE( |
|
AddDataAndSetType(&new_aop, version, target_part_path, blob_file)); |
|
|
|
result_aops->push_back(new_aop); |
|
} |
|
return true; |
|
} |
|
|
|
bool ABGenerator::MergeOperations(vector<AnnotatedOperation>* aops, |
|
const PayloadVersion& version, |
|
size_t chunk_blocks, |
|
const string& target_part_path, |
|
BlobFileWriter* blob_file) { |
|
vector<AnnotatedOperation> new_aops; |
|
for (const AnnotatedOperation& curr_aop : *aops) { |
|
if (new_aops.empty()) { |
|
new_aops.push_back(curr_aop); |
|
continue; |
|
} |
|
AnnotatedOperation& last_aop = new_aops.back(); |
|
bool last_is_a_replace = IsAReplaceOperation(last_aop.op.type()); |
|
|
|
if (last_aop.op.dst_extents_size() <= 0 || |
|
curr_aop.op.dst_extents_size() <= 0) { |
|
new_aops.push_back(curr_aop); |
|
continue; |
|
} |
|
uint32_t last_dst_idx = last_aop.op.dst_extents_size() - 1; |
|
uint32_t last_end_block = |
|
last_aop.op.dst_extents(last_dst_idx).start_block() + |
|
last_aop.op.dst_extents(last_dst_idx).num_blocks(); |
|
uint32_t curr_start_block = curr_aop.op.dst_extents(0).start_block(); |
|
uint32_t combined_block_count = |
|
last_aop.op.dst_extents(last_dst_idx).num_blocks() + |
|
curr_aop.op.dst_extents(0).num_blocks(); |
|
bool is_a_replace = IsAReplaceOperation(curr_aop.op.type()); |
|
|
|
bool is_delta_op = curr_aop.op.type() == InstallOperation::SOURCE_COPY; |
|
if (((is_delta_op && (last_aop.op.type() == curr_aop.op.type())) || |
|
(is_a_replace && last_is_a_replace)) && |
|
last_end_block == curr_start_block && |
|
combined_block_count <= chunk_blocks) { |
|
// If the operations have the same type (which is a type that we can |
|
// merge), are contiguous, are fragmented to have one destination extent, |
|
// and their combined block count would be less than chunk size, merge |
|
// them. |
|
last_aop.name = base::StringPrintf("%s,%s", |
|
last_aop.name.c_str(), |
|
curr_aop.name.c_str()); |
|
|
|
if (is_delta_op) { |
|
ExtendExtents(last_aop.op.mutable_src_extents(), |
|
curr_aop.op.src_extents()); |
|
if (curr_aop.op.src_length() > 0) |
|
last_aop.op.set_src_length(last_aop.op.src_length() + |
|
curr_aop.op.src_length()); |
|
} |
|
ExtendExtents(last_aop.op.mutable_dst_extents(), |
|
curr_aop.op.dst_extents()); |
|
if (curr_aop.op.dst_length() > 0) |
|
last_aop.op.set_dst_length(last_aop.op.dst_length() + |
|
curr_aop.op.dst_length()); |
|
// Set the data length to zero so we know to add the blob later. |
|
if (is_a_replace) |
|
last_aop.op.set_data_length(0); |
|
} else { |
|
// Otherwise just include the extent as is. |
|
new_aops.push_back(curr_aop); |
|
} |
|
} |
|
|
|
// Set the blobs for REPLACE/REPLACE_BZ/REPLACE_XZ operations that have been |
|
// merged. |
|
for (AnnotatedOperation& curr_aop : new_aops) { |
|
if (curr_aop.op.data_length() == 0 && |
|
IsAReplaceOperation(curr_aop.op.type())) { |
|
TEST_AND_RETURN_FALSE( |
|
AddDataAndSetType(&curr_aop, version, target_part_path, blob_file)); |
|
} |
|
} |
|
|
|
*aops = new_aops; |
|
return true; |
|
} |
|
|
|
bool ABGenerator::AddDataAndSetType(AnnotatedOperation* aop, |
|
const PayloadVersion& version, |
|
const string& target_part_path, |
|
BlobFileWriter* blob_file) { |
|
TEST_AND_RETURN_FALSE(IsAReplaceOperation(aop->op.type())); |
|
|
|
brillo::Blob data(aop->op.dst_length()); |
|
vector<Extent> dst_extents; |
|
ExtentsToVector(aop->op.dst_extents(), &dst_extents); |
|
TEST_AND_RETURN_FALSE(utils::ReadExtents(target_part_path, |
|
dst_extents, |
|
&data, |
|
data.size(), |
|
kBlockSize)); |
|
|
|
brillo::Blob blob; |
|
InstallOperation_Type op_type; |
|
TEST_AND_RETURN_FALSE( |
|
diff_utils::GenerateBestFullOperation(data, version, &blob, &op_type)); |
|
|
|
// If the operation doesn't point to a data blob or points to a data blob of |
|
// a different type then we add it. |
|
if (aop->op.type() != op_type || aop->op.data_length() != blob.size()) { |
|
aop->op.set_type(op_type); |
|
aop->SetOperationBlob(blob, blob_file); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool ABGenerator::AddSourceHash(vector<AnnotatedOperation>* aops, |
|
const string& source_part_path) { |
|
for (AnnotatedOperation& aop : *aops) { |
|
if (aop.op.src_extents_size() == 0) |
|
continue; |
|
|
|
vector<Extent> src_extents; |
|
ExtentsToVector(aop.op.src_extents(), &src_extents); |
|
brillo::Blob src_data, src_hash; |
|
uint64_t src_length = |
|
aop.op.has_src_length() |
|
? aop.op.src_length() |
|
: BlocksInExtents(aop.op.src_extents()) * kBlockSize; |
|
TEST_AND_RETURN_FALSE(utils::ReadExtents( |
|
source_part_path, src_extents, &src_data, src_length, kBlockSize)); |
|
TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfData(src_data, &src_hash)); |
|
aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); |
|
} |
|
return true; |
|
} |
|
|
|
} // namespace chromeos_update_engine
|
|
|