// // Copyright (C) 2020 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/merge_sequence_generator.h" #include #include #include "update_engine/payload_generator/delta_diff_generator.h" #include "update_engine/payload_generator/extent_ranges.h" #include "update_engine/payload_generator/extent_utils.h" #include "update_engine/update_metadata.pb.h" namespace chromeos_update_engine { CowMergeOperation CreateCowMergeOperation(const Extent& src_extent, const Extent& dst_extent, CowMergeOperation::Type op_type, uint32_t src_offset) { CowMergeOperation ret; ret.set_type(op_type); *ret.mutable_src_extent() = src_extent; *ret.mutable_dst_extent() = dst_extent; ret.set_src_offset(src_offset); return ret; } std::ostream& operator<<(std::ostream& os, const CowMergeOperation& merge_operation) { os << "CowMergeOperation src extent: " << ExtentsToString({merge_operation.src_extent()}) << ", dst extent: " << ExtentsToString({merge_operation.dst_extent()}); if (merge_operation.has_src_offset()) { os << ", src offset: " << merge_operation.src_offset(); } os << " op_type: "; if (merge_operation.type() == CowMergeOperation::COW_COPY) { os << "COW_COPY"; } else if (merge_operation.type() == CowMergeOperation::COW_XOR) { os << "COW_XOR"; } else { os << merge_operation.type(); } return os; } // The OTA generation guarantees that all blocks in the dst extent will be // written only once. So we can use it to order the CowMergeOperation. bool operator<(const CowMergeOperation& op1, const CowMergeOperation& op2) { return op1.dst_extent().start_block() < op2.dst_extent().start_block(); } bool operator==(const CowMergeOperation& op1, const CowMergeOperation& op2) { return op1.type() == op2.type() && op1.src_extent() == op2.src_extent() && op1.dst_extent() == op2.dst_extent(); } template constexpr T GetDifference(T first, T second) { T abs_diff = (first > second) ? (first - second) : (second - first); return abs_diff; } CowMergeOperation::Type GetCowOpType(InstallOperation::Type install_op_type) { switch (install_op_type) { case InstallOperation::SOURCE_COPY: return CowMergeOperation::COW_COPY; case InstallOperation::SOURCE_BSDIFF: case InstallOperation::BROTLI_BSDIFF: case InstallOperation::PUFFDIFF: return CowMergeOperation::COW_XOR; default: CHECK(false) << "Unknown install op type: " << install_op_type; return CowMergeOperation::COW_REPLACE; } } void SplitSelfOverlapping(const Extent& src_extent, const Extent& dst_extent, std::vector* sequence) { CHECK_EQ(src_extent.num_blocks(), dst_extent.num_blocks()); if (src_extent.start_block() == dst_extent.start_block()) { sequence->emplace_back(CreateCowMergeOperation( src_extent, dst_extent, CowMergeOperation::COW_COPY)); return; } const size_t diff = GetDifference(src_extent.start_block(), dst_extent.start_block()); for (size_t i = 0; i < src_extent.num_blocks(); i += diff) { auto num_blocks = std::min(diff, src_extent.num_blocks() - i); sequence->emplace_back(CreateCowMergeOperation( ExtentForRange(i + src_extent.start_block(), num_blocks), ExtentForRange(i + dst_extent.start_block(), num_blocks), CowMergeOperation::COW_COPY)); } } static bool ProcessXorOps(std::vector* sequence, const AnnotatedOperation& aop) { const auto size_before = sequence->size(); sequence->insert(sequence->end(), aop.xor_ops.begin(), aop.xor_ops.end()); std::for_each( sequence->begin() + size_before, sequence->end(), [](CowMergeOperation& op) { CHECK_EQ(op.type(), CowMergeOperation::COW_XOR); // If |src_offset| is greater than 0, then we are reading 1 // extra block at the end of src_extent. This dependency must // be honored during merge sequence generation, or we can end // up with a corrupted device after merge. if (op.src_offset() > 0) { if (op.src_extent().num_blocks() == op.dst_extent().num_blocks()) { op.mutable_src_extent()->set_num_blocks( op.src_extent().num_blocks() + 1); } CHECK_EQ(op.src_extent().num_blocks(), op.dst_extent().num_blocks() + 1); } CHECK_NE(op.src_extent().start_block(), std::numeric_limits::max()); }); return true; } static bool ProcessCopyOps(std::vector* sequence, const AnnotatedOperation& aop) { CHECK_EQ(GetCowOpType(aop.op.type()), CowMergeOperation::COW_COPY); if (aop.op.dst_extents().size() != 1) { std::vector out_extents; ExtentsToVector(aop.op.dst_extents(), &out_extents); LOG(ERROR) << "The dst extents for source_copy are expected to be contiguous," << " dst extents: " << ExtentsToString(out_extents); return false; } // Split the source extents. size_t used_blocks = 0; for (const auto& src_extent : aop.op.src_extents()) { // The dst_extent in the merge sequence will be a subset of // InstallOperation's dst_extent. This will simplify the OTA -> COW // conversion when we install the payload. Extent dst_extent = ExtentForRange(aop.op.dst_extents(0).start_block() + used_blocks, src_extent.num_blocks()); // Self-overlapping operation, must split into multiple non // self-overlapping ops if (ExtentRanges::ExtentsOverlap(src_extent, dst_extent)) { SplitSelfOverlapping(src_extent, dst_extent, sequence); } else { sequence->emplace_back(CreateCowMergeOperation( src_extent, dst_extent, CowMergeOperation::COW_COPY)); } used_blocks += src_extent.num_blocks(); } if (used_blocks != aop.op.dst_extents(0).num_blocks()) { LOG(ERROR) << "Number of blocks in src extents doesn't equal to the" << " ones in the dst extents, src blocks " << used_blocks << ", dst blocks " << aop.op.dst_extents(0).num_blocks(); return false; } return true; } std::unique_ptr MergeSequenceGenerator::Create( const std::vector& aops) { std::vector sequence; for (const auto& aop : aops) { if (aop.op.type() == InstallOperation::SOURCE_COPY) { if (!ProcessCopyOps(&sequence, aop)) { return nullptr; } } else if (!aop.xor_ops.empty()) { if (!ProcessXorOps(&sequence, aop)) { return nullptr; } } } std::sort(sequence.begin(), sequence.end()); return std::unique_ptr( new MergeSequenceGenerator(sequence)); } bool MergeSequenceGenerator::FindDependency( std::map>* result) const { CHECK(result); LOG(INFO) << "Finding dependencies"; // Since the OTA operation may reuse some source blocks, use the binary // search on sorted dst extents to find overlaps. std::map> merge_after; for (const auto& op : operations_) { // lower bound (inclusive): dst extent's end block >= src extent's start // block. const auto lower_it = std::lower_bound( operations_.begin(), operations_.end(), op, [](const CowMergeOperation& it, const CowMergeOperation& op) { auto dst_end_block = it.dst_extent().start_block() + it.dst_extent().num_blocks() - 1; return dst_end_block < op.src_extent().start_block(); }); // upper bound: dst extent's start block > src extent's end block const auto upper_it = std::upper_bound( lower_it, operations_.end(), op, [](const CowMergeOperation& op, const CowMergeOperation& it) { auto src_end_block = op.src_extent().start_block() + op.src_extent().num_blocks() - 1; return src_end_block < it.dst_extent().start_block(); }); // TODO(xunchang) skip inserting the empty set to merge_after. if (lower_it == upper_it) { merge_after.insert({op, {}}); } else { std::set operations(lower_it, upper_it); auto it = operations.find(op); if (it != operations.end()) { LOG(INFO) << "Self overlapping " << op; operations.erase(it); } auto ret = merge_after.emplace(op, std::move(operations)); // Check the insertion indeed happens. CHECK(ret.second) << op; } } *result = std::move(merge_after); return true; } bool MergeSequenceGenerator::Generate( std::vector* sequence) const { sequence->clear(); std::map> merge_after; if (!FindDependency(&merge_after)) { LOG(ERROR) << "Failed to find dependencies"; return false; } LOG(INFO) << "Generating sequence"; // Use the non-DFS version of the topology sort. So we can control the // operations to discard to break cycles; thus yielding a deterministic // sequence. std::map incoming_edges; for (const auto& it : merge_after) { for (const auto& blocked : it.second) { // Value is default initialized to 0. incoming_edges[blocked] += 1; } } // Technically, we can use std::unordered_set or just a std::vector. but // std::set gives the benefit where operations are sorted by dst blocks. This // will ensure that operations that do not have dependency constraints appear // in increasing block order. Such order would help snapuserd batch merges and // improve boot time, but isn't strictly needed for correctness. std::set free_operations; for (const auto& op : operations_) { if (incoming_edges.find(op) == incoming_edges.end()) { free_operations.insert(op); } } std::vector merge_sequence; std::set convert_to_raw; while (!incoming_edges.empty()) { if (!free_operations.empty()) { merge_sequence.insert( merge_sequence.end(), free_operations.begin(), free_operations.end()); } else { auto to_convert = incoming_edges.begin()->first; free_operations.insert(to_convert); convert_to_raw.insert(to_convert); LOG(INFO) << "Converting operation to raw " << to_convert; } std::set next_free_operations; for (const auto& op : free_operations) { incoming_edges.erase(op); // Now that this particular operation is merged, other operations // blocked by this one may be free. Decrement the count of blocking // operations, and set up the free operations for the next iteration. for (const auto& blocked : merge_after[op]) { auto it = incoming_edges.find(blocked); if (it == incoming_edges.end()) { continue; } auto blocking_transfer_count = &it->second; if (*blocking_transfer_count <= 0) { LOG(ERROR) << "Unexpected count in merge after map " << blocking_transfer_count; return false; } // This operation is no longer blocked by anyone. Add it to the merge // sequence in the next iteration. *blocking_transfer_count -= 1; if (*blocking_transfer_count == 0) { next_free_operations.insert(blocked); } } } LOG(INFO) << "Remaining transfers " << incoming_edges.size() << ", free transfers " << free_operations.size() << ", merge_sequence size " << merge_sequence.size(); free_operations = std::move(next_free_operations); } if (!free_operations.empty()) { merge_sequence.insert( merge_sequence.end(), free_operations.begin(), free_operations.end()); } CHECK_EQ(operations_.size(), merge_sequence.size() + convert_to_raw.size()); size_t blocks_in_sequence = 0; for (const CowMergeOperation& transfer : merge_sequence) { blocks_in_sequence += transfer.dst_extent().num_blocks(); } size_t blocks_in_raw = 0; for (const CowMergeOperation& transfer : convert_to_raw) { blocks_in_raw += transfer.dst_extent().num_blocks(); } LOG(INFO) << "Blocks in merge sequence " << blocks_in_sequence << ", blocks in raw " << blocks_in_raw; if (!ValidateSequence(merge_sequence)) { LOG(ERROR) << "Invalid Sequence"; return false; } *sequence = std::move(merge_sequence); return true; } bool MergeSequenceGenerator::ValidateSequence( const std::vector& sequence) { LOG(INFO) << "Validating merge sequence"; ExtentRanges visited; for (const auto& op : sequence) { // If |src_offset| is greater than zero, dependency should include 1 extra // block at end of src_extent, as the OP actually references data past // original src_extent. if (op.src_offset() > 0) { CHECK_EQ(op.src_extent().num_blocks(), op.dst_extent().num_blocks() + 1) << op; } else { CHECK_EQ(op.src_extent().num_blocks(), op.dst_extent().num_blocks()) << op; } if (visited.OverlapsWithExtent(op.src_extent())) { LOG(ERROR) << "Transfer violates the merge sequence " << op << "Visited extent ranges: "; visited.Dump(); return false; } CHECK(!visited.OverlapsWithExtent(op.dst_extent())) << "dst extent should write only once."; visited.AddExtent(op.dst_extent()); } return true; } } // namespace chromeos_update_engine