// // 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/inplace_generator.h" #include <algorithm> #include <map> #include <set> #include <string> #include <utility> #include <vector> #include <base/stl_util.h> #include "update_engine/common/utils.h" #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/payload_generator/cycle_breaker.h" #include "update_engine/payload_generator/delta_diff_generator.h" #include "update_engine/payload_generator/delta_diff_utils.h" #include "update_engine/payload_generator/extent_ranges.h" #include "update_engine/payload_generator/graph_types.h" #include "update_engine/payload_generator/graph_utils.h" #include "update_engine/payload_generator/topological_sort.h" #include "update_engine/update_metadata.pb.h" using std::make_pair; using std::map; using std::pair; using std::set; using std::string; using std::vector; namespace chromeos_update_engine { using Block = InplaceGenerator::Block; namespace { // The only PayloadVersion supported by this implementation. const PayloadVersion kInPlacePayloadVersion{kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion}; // This class allocates non-existent temp blocks, starting from // kTempBlockStart. Other code is responsible for converting these // temp blocks into real blocks, as the client can't read or write to // these blocks. class DummyExtentAllocator { public: vector<Extent> Allocate(const uint64_t block_count) { vector<Extent> ret(1); ret[0].set_start_block(next_block_); ret[0].set_num_blocks(block_count); next_block_ += block_count; return ret; } private: uint64_t next_block_{kTempBlockStart}; }; // Takes a vector of blocks and returns an equivalent vector of Extent // objects. vector<Extent> CompressExtents(const vector<uint64_t>& blocks) { vector<Extent> new_extents; for (uint64_t block : blocks) { AppendBlockToExtents(&new_extents, block); } return new_extents; } // Helper class to compare two operations by start block of the first Extent in // their destination extents given the index of the operations in the graph. class IndexedInstallOperationsDstComparator { public: explicit IndexedInstallOperationsDstComparator(Graph* graph) : graph_(graph) {} // Compares the operations in the vertex a and b of graph_. bool operator()(size_t a, size_t b) const { return diff_utils::CompareAopsByDestination((*graph_)[a].aop, (*graph_)[b].aop); } private: const Graph* const graph_; }; } // namespace void InplaceGenerator::CheckGraph(const Graph& graph) { for (const Vertex& v : graph) { CHECK(v.aop.op.has_type()); } } void InplaceGenerator::SubstituteBlocks(Vertex* vertex, const vector<Extent>& remove_extents, const vector<Extent>& replace_extents) { // First, expand out the blocks that op reads from vector<uint64_t> read_blocks = ExpandExtents(vertex->aop.op.src_extents()); { // Expand remove_extents and replace_extents vector<uint64_t> remove_extents_expanded = ExpandExtents(remove_extents); vector<uint64_t> replace_extents_expanded = ExpandExtents(replace_extents); CHECK_EQ(remove_extents_expanded.size(), replace_extents_expanded.size()); map<uint64_t, uint64_t> conversion; for (vector<uint64_t>::size_type i = 0; i < replace_extents_expanded.size(); i++) { conversion[remove_extents_expanded[i]] = replace_extents_expanded[i]; } ApplyMap(&read_blocks, conversion); for (auto& edge_prop_pair : vertex->out_edges) { vector<uint64_t> write_before_deps_expanded = ExpandExtents(edge_prop_pair.second.write_extents); ApplyMap(&write_before_deps_expanded, conversion); edge_prop_pair.second.write_extents = CompressExtents(write_before_deps_expanded); } } // Convert read_blocks back to extents vertex->aop.op.clear_src_extents(); vector<Extent> new_extents = CompressExtents(read_blocks); StoreExtents(new_extents, vertex->aop.op.mutable_src_extents()); } bool InplaceGenerator::CutEdges(Graph* graph, const set<Edge>& edges, vector<CutEdgeVertexes>* out_cuts) { DummyExtentAllocator scratch_allocator; vector<CutEdgeVertexes> cuts; cuts.reserve(edges.size()); uint64_t scratch_blocks_used = 0; for (const Edge& edge : edges) { cuts.resize(cuts.size() + 1); vector<Extent> old_extents = (*graph)[edge.first].out_edges[edge.second].extents; // Choose some scratch space scratch_blocks_used += graph_utils::EdgeWeight(*graph, edge); cuts.back().tmp_extents = scratch_allocator.Allocate(graph_utils::EdgeWeight(*graph, edge)); // create vertex to copy original->scratch cuts.back().new_vertex = graph->size(); graph->emplace_back(); cuts.back().old_src = edge.first; cuts.back().old_dst = edge.second; EdgeProperties& cut_edge_properties = (*graph)[edge.first].out_edges.find(edge.second)->second; // This should never happen, as we should only be cutting edges between // real file nodes, and write-before relationships are created from // a real file node to a temp copy node: CHECK(cut_edge_properties.write_extents.empty()) << "Can't cut edge that has write-before relationship."; // make node depend on the copy operation (*graph)[edge.first].out_edges.insert( make_pair(graph->size() - 1, cut_edge_properties)); // Set src/dst extents and other proto variables for copy operation graph->back().aop.op.set_type(InstallOperation::MOVE); StoreExtents(cut_edge_properties.extents, graph->back().aop.op.mutable_src_extents()); StoreExtents(cuts.back().tmp_extents, graph->back().aop.op.mutable_dst_extents()); graph->back().aop.op.set_src_length(graph_utils::EdgeWeight(*graph, edge) * kBlockSize); graph->back().aop.op.set_dst_length(graph->back().aop.op.src_length()); // make the dest node read from the scratch space SubstituteBlocks(&((*graph)[edge.second]), (*graph)[edge.first].out_edges[edge.second].extents, cuts.back().tmp_extents); // delete the old edge CHECK_EQ(static_cast<Graph::size_type>(1), (*graph)[edge.first].out_edges.erase(edge.second)); // Add an edge from dst to copy operation EdgeProperties write_before_edge_properties; write_before_edge_properties.write_extents = cuts.back().tmp_extents; (*graph)[edge.second].out_edges.insert( make_pair(graph->size() - 1, write_before_edge_properties)); } out_cuts->swap(cuts); return true; } // Creates all the edges for the graph. Writers of a block point to // readers of the same block. This is because for an edge A->B, B // must complete before A executes. void InplaceGenerator::CreateEdges(Graph* graph, const vector<Block>& blocks) { for (vector<Block>::size_type i = 0; i < blocks.size(); i++) { // Blocks with both a reader and writer get an edge if (blocks[i].reader == Vertex::kInvalidIndex || blocks[i].writer == Vertex::kInvalidIndex) continue; // Don't have a node depend on itself if (blocks[i].reader == blocks[i].writer) continue; // See if there's already an edge we can add onto Vertex::EdgeMap::iterator edge_it = (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader); if (edge_it == (*graph)[blocks[i].writer].out_edges.end()) { // No existing edge. Create one (*graph)[blocks[i].writer].out_edges.insert( make_pair(blocks[i].reader, EdgeProperties())); edge_it = (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader); CHECK(edge_it != (*graph)[blocks[i].writer].out_edges.end()); } AppendBlockToExtents(&edge_it->second.extents, i); } } namespace { class SortCutsByTopoOrderLess { public: explicit SortCutsByTopoOrderLess( const vector<vector<Vertex::Index>::size_type>& table) : table_(table) {} bool operator()(const CutEdgeVertexes& a, const CutEdgeVertexes& b) { return table_[a.old_dst] < table_[b.old_dst]; } private: const vector<vector<Vertex::Index>::size_type>& table_; }; } // namespace void InplaceGenerator::GenerateReverseTopoOrderMap( const vector<Vertex::Index>& op_indexes, vector<vector<Vertex::Index>::size_type>* reverse_op_indexes) { vector<vector<Vertex::Index>::size_type> table(op_indexes.size()); for (vector<Vertex::Index>::size_type i = 0, e = op_indexes.size(); i != e; ++i) { Vertex::Index node = op_indexes[i]; if (table.size() < (node + 1)) { table.resize(node + 1); } table[node] = i; } reverse_op_indexes->swap(table); } void InplaceGenerator::SortCutsByTopoOrder( const vector<Vertex::Index>& op_indexes, vector<CutEdgeVertexes>* cuts) { // first, make a reverse lookup table. vector<vector<Vertex::Index>::size_type> table; GenerateReverseTopoOrderMap(op_indexes, &table); SortCutsByTopoOrderLess less(table); sort(cuts->begin(), cuts->end(), less); } void InplaceGenerator::MoveAndSortFullOpsToBack( Graph* graph, vector<Vertex::Index>* op_indexes) { vector<Vertex::Index> ret; vector<Vertex::Index> full_ops; ret.reserve(op_indexes->size()); for (auto op_index : *op_indexes) { InstallOperation::Type type = (*graph)[op_index].aop.op.type(); if (type == InstallOperation::REPLACE || type == InstallOperation::REPLACE_BZ) { full_ops.push_back(op_index); } else { ret.push_back(op_index); } } LOG(INFO) << "Stats: " << full_ops.size() << " full ops out of " << (full_ops.size() + ret.size()) << " total ops."; // Sort full ops according to their dst_extents. sort(full_ops.begin(), full_ops.end(), IndexedInstallOperationsDstComparator(graph)); ret.insert(ret.end(), full_ops.begin(), full_ops.end()); op_indexes->swap(ret); } namespace { template <typename T> bool TempBlocksExistInExtents(const T& extents) { for (const auto& extent : extents) { uint64_t start = extent.start_block(); uint64_t num = extent.num_blocks(); if (start >= kTempBlockStart || (start + num) >= kTempBlockStart) { LOG(ERROR) << "temp block!"; LOG(ERROR) << "start: " << start << ", num: " << num; LOG(ERROR) << "kTempBlockStart: " << kTempBlockStart; LOG(ERROR) << "returning true"; return true; } // check for wrap-around, which would be a bug: CHECK(start <= (start + num)); } return false; } // Converts the cuts, which must all have the same |old_dst| member, // to full. It does this by converting the |old_dst| to REPLACE or // REPLACE_BZ, dropping all incoming edges to |old_dst|, and marking // all temp nodes invalid. bool ConvertCutsToFull( Graph* graph, const string& new_part, BlobFileWriter* blob_file, vector<Vertex::Index>* op_indexes, vector<vector<Vertex::Index>::size_type>* reverse_op_indexes, const vector<CutEdgeVertexes>& cuts) { CHECK(!cuts.empty()); set<Vertex::Index> deleted_nodes; for (const CutEdgeVertexes& cut : cuts) { TEST_AND_RETURN_FALSE( InplaceGenerator::ConvertCutToFullOp(graph, cut, new_part, blob_file)); deleted_nodes.insert(cut.new_vertex); } deleted_nodes.insert(cuts[0].old_dst); vector<Vertex::Index> new_op_indexes; new_op_indexes.reserve(op_indexes->size()); for (Vertex::Index vertex_index : *op_indexes) { if (base::ContainsKey(deleted_nodes, vertex_index)) continue; new_op_indexes.push_back(vertex_index); } new_op_indexes.push_back(cuts[0].old_dst); op_indexes->swap(new_op_indexes); InplaceGenerator::GenerateReverseTopoOrderMap(*op_indexes, reverse_op_indexes); return true; } // Tries to assign temp blocks for a collection of cuts, all of which share // the same old_dst member. If temp blocks can't be found, old_dst will be // converted to a REPLACE or REPLACE_BZ operation. Returns true on success, // which can happen even if blocks are converted to full. Returns false // on exceptional error cases. bool AssignBlockForAdjoiningCuts( Graph* graph, const string& new_part, BlobFileWriter* blob_file, vector<Vertex::Index>* op_indexes, vector<vector<Vertex::Index>::size_type>* reverse_op_indexes, const vector<CutEdgeVertexes>& cuts) { CHECK(!cuts.empty()); const Vertex::Index old_dst = cuts[0].old_dst; // Calculate # of blocks needed uint64_t blocks_needed = 0; vector<uint64_t> cuts_blocks_needed(cuts.size()); for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) { uint64_t cut_blocks_needed = 0; for (const Extent& extent : cuts[i].tmp_extents) { cut_blocks_needed += extent.num_blocks(); } blocks_needed += cut_blocks_needed; cuts_blocks_needed[i] = cut_blocks_needed; } // Find enough blocks ExtentRanges scratch_ranges; // Each block that's supplying temp blocks and the corresponding blocks: typedef vector<pair<Vertex::Index, ExtentRanges>> SupplierVector; SupplierVector block_suppliers; uint64_t scratch_blocks_found = 0; for (vector<Vertex::Index>::size_type i = (*reverse_op_indexes)[old_dst] + 1, e = op_indexes->size(); i < e; ++i) { Vertex::Index test_node = (*op_indexes)[i]; if (!(*graph)[test_node].valid) continue; // See if this node has sufficient blocks ExtentRanges ranges; ranges.AddRepeatedExtents((*graph)[test_node].aop.op.dst_extents()); ranges.SubtractExtent( ExtentForRange(kTempBlockStart, kSparseHole - kTempBlockStart)); ranges.SubtractRepeatedExtents((*graph)[test_node].aop.op.src_extents()); // For now, for simplicity, subtract out all blocks in read-before // dependencies. for (Vertex::EdgeMap::const_iterator edge_i = (*graph)[test_node].out_edges.begin(), edge_e = (*graph)[test_node].out_edges.end(); edge_i != edge_e; ++edge_i) { ranges.SubtractExtents(edge_i->second.extents); } // Prevent using the block 0 as scratch space due to crbug.com/480751. if (ranges.ContainsBlock(0)) { LOG(INFO) << "Removing block 0 from the selected scratch range in vertex " << i; ranges.SubtractBlock(0); } if (ranges.blocks() == 0) continue; if (ranges.blocks() + scratch_blocks_found > blocks_needed) { // trim down ranges vector<Extent> new_ranges = ranges.GetExtentsForBlockCount(blocks_needed - scratch_blocks_found); ranges = ExtentRanges(); ranges.AddExtents(new_ranges); } scratch_ranges.AddRanges(ranges); block_suppliers.push_back(make_pair(test_node, ranges)); scratch_blocks_found += ranges.blocks(); if (scratch_ranges.blocks() >= blocks_needed) break; } if (scratch_ranges.blocks() < blocks_needed) { LOG(INFO) << "Unable to find sufficient scratch"; TEST_AND_RETURN_FALSE(ConvertCutsToFull( graph, new_part, blob_file, op_indexes, reverse_op_indexes, cuts)); return true; } // Use the scratch we found TEST_AND_RETURN_FALSE(scratch_ranges.blocks() == scratch_blocks_found); // Make all the suppliers depend on this node for (const auto& index_range_pair : block_suppliers) { graph_utils::AddReadBeforeDepExtents( &(*graph)[index_range_pair.first], old_dst, index_range_pair.second.GetExtentsForBlockCount( index_range_pair.second.blocks())); } // Replace temp blocks in each cut for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) { const CutEdgeVertexes& cut = cuts[i]; vector<Extent> real_extents = scratch_ranges.GetExtentsForBlockCount(cuts_blocks_needed[i]); scratch_ranges.SubtractExtents(real_extents); // Fix the old dest node w/ the real blocks InplaceGenerator::SubstituteBlocks( &(*graph)[old_dst], cut.tmp_extents, real_extents); // Fix the new node w/ the real blocks. Since the new node is just a // copy operation, we can replace all the dest extents w/ the real // blocks. InstallOperation* op = &(*graph)[cut.new_vertex].aop.op; op->clear_dst_extents(); StoreExtents(real_extents, op->mutable_dst_extents()); } return true; } } // namespace bool InplaceGenerator::AssignTempBlocks( Graph* graph, const string& new_part, BlobFileWriter* blob_file, vector<Vertex::Index>* op_indexes, vector<vector<Vertex::Index>::size_type>* reverse_op_indexes, const vector<CutEdgeVertexes>& cuts) { CHECK(!cuts.empty()); // group of cuts w/ the same old_dst: vector<CutEdgeVertexes> cuts_group; for (vector<CutEdgeVertexes>::size_type i = cuts.size() - 1, e = 0; true; --i) { LOG(INFO) << "Fixing temp blocks in cut " << i << ": old dst: " << cuts[i].old_dst << " new vertex: " << cuts[i].new_vertex << " path: " << (*graph)[cuts[i].old_dst].aop.name; if (cuts_group.empty() || (cuts_group[0].old_dst == cuts[i].old_dst)) { cuts_group.push_back(cuts[i]); } else { CHECK(!cuts_group.empty()); TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts(graph, new_part, blob_file, op_indexes, reverse_op_indexes, cuts_group)); cuts_group.clear(); cuts_group.push_back(cuts[i]); } if (i == e) { // break out of for() loop break; } } CHECK(!cuts_group.empty()); TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts( graph, new_part, blob_file, op_indexes, reverse_op_indexes, cuts_group)); return true; } bool InplaceGenerator::NoTempBlocksRemain(const Graph& graph) { size_t idx = 0; for (Graph::const_iterator it = graph.begin(), e = graph.end(); it != e; ++it, ++idx) { if (!it->valid) continue; const InstallOperation& op = it->aop.op; if (TempBlocksExistInExtents(op.dst_extents()) || TempBlocksExistInExtents(op.src_extents())) { LOG(INFO) << "bad extents in node " << idx; LOG(INFO) << "so yeah"; return false; } // Check out-edges: for (const auto& edge_prop_pair : it->out_edges) { if (TempBlocksExistInExtents(edge_prop_pair.second.extents) || TempBlocksExistInExtents(edge_prop_pair.second.write_extents)) { LOG(INFO) << "bad out edge in node " << idx; LOG(INFO) << "so yeah"; return false; } } } return true; } bool InplaceGenerator::ConvertCutToFullOp(Graph* graph, const CutEdgeVertexes& cut, const string& new_part, BlobFileWriter* blob_file) { // Drop all incoming edges, keep all outgoing edges // Keep all outgoing edges if ((*graph)[cut.old_dst].aop.op.type() != InstallOperation::REPLACE_BZ && (*graph)[cut.old_dst].aop.op.type() != InstallOperation::REPLACE) { Vertex::EdgeMap out_edges = (*graph)[cut.old_dst].out_edges; graph_utils::DropWriteBeforeDeps(&out_edges); // Replace the operation with a REPLACE or REPLACE_BZ to generate the same // |new_extents| list of blocks and update the graph. vector<AnnotatedOperation> new_aop; vector<Extent> new_extents; ExtentsToVector((*graph)[cut.old_dst].aop.op.dst_extents(), &new_extents); TEST_AND_RETURN_FALSE(diff_utils::DeltaReadFile( &new_aop, "", // old_part new_part, vector<Extent>(), // old_extents new_extents, {}, // old_deflates {}, // new_deflates (*graph)[cut.old_dst].aop.name, -1, // chunk_blocks, forces to have a single operation. kInPlacePayloadVersion, blob_file)); TEST_AND_RETURN_FALSE(new_aop.size() == 1); TEST_AND_RETURN_FALSE(AddInstallOpToGraph( graph, cut.old_dst, nullptr, new_aop.front().op, new_aop.front().name)); (*graph)[cut.old_dst].out_edges = out_edges; // Right now we don't have doubly-linked edges, so we have to scan // the whole graph. graph_utils::DropIncomingEdgesTo(graph, cut.old_dst); } // Delete temp node (*graph)[cut.old_src].out_edges.erase(cut.new_vertex); CHECK((*graph)[cut.old_dst].out_edges.find(cut.new_vertex) == (*graph)[cut.old_dst].out_edges.end()); (*graph)[cut.new_vertex].valid = false; LOG(INFO) << "marked node invalid: " << cut.new_vertex; return true; } bool InplaceGenerator::ConvertGraphToDag(Graph* graph, const string& new_part, BlobFileWriter* blob_file, vector<Vertex::Index>* final_order, Vertex::Index scratch_vertex) { CycleBreaker cycle_breaker; LOG(INFO) << "Finding cycles..."; set<Edge> cut_edges; cycle_breaker.BreakCycles(*graph, &cut_edges); LOG(INFO) << "done finding cycles"; CheckGraph(*graph); // Calculate number of scratch blocks needed LOG(INFO) << "Cutting cycles..."; vector<CutEdgeVertexes> cuts; TEST_AND_RETURN_FALSE(CutEdges(graph, cut_edges, &cuts)); LOG(INFO) << "done cutting cycles"; LOG(INFO) << "There are " << cuts.size() << " cuts."; CheckGraph(*graph); LOG(INFO) << "Creating initial topological order..."; TopologicalSort(*graph, final_order); LOG(INFO) << "done with initial topo order"; CheckGraph(*graph); LOG(INFO) << "Moving full ops to the back"; MoveAndSortFullOpsToBack(graph, final_order); LOG(INFO) << "done moving full ops to back"; vector<vector<Vertex::Index>::size_type> inverse_final_order; GenerateReverseTopoOrderMap(*final_order, &inverse_final_order); SortCutsByTopoOrder(*final_order, &cuts); if (!cuts.empty()) TEST_AND_RETURN_FALSE(AssignTempBlocks( graph, new_part, blob_file, final_order, &inverse_final_order, cuts)); LOG(INFO) << "Making sure all temp blocks have been allocated"; // Remove the scratch node, if any if (scratch_vertex != Vertex::kInvalidIndex) { final_order->erase(final_order->begin() + inverse_final_order[scratch_vertex]); (*graph)[scratch_vertex].valid = false; GenerateReverseTopoOrderMap(*final_order, &inverse_final_order); } graph_utils::DumpGraph(*graph); CHECK(NoTempBlocksRemain(*graph)); LOG(INFO) << "done making sure all temp blocks are allocated"; return true; } void InplaceGenerator::CreateScratchNode(uint64_t start_block, uint64_t num_blocks, Vertex* vertex) { vertex->aop.name = "<scratch>"; vertex->aop.op.set_type(InstallOperation::REPLACE_BZ); vertex->aop.op.set_data_offset(0); vertex->aop.op.set_data_length(0); Extent* extent = vertex->aop.op.add_dst_extents(); extent->set_start_block(start_block); extent->set_num_blocks(num_blocks); } bool InplaceGenerator::AddInstallOpToBlocksVector( const InstallOperation& operation, const Graph& graph, Vertex::Index vertex, vector<Block>* blocks) { // See if this is already present. TEST_AND_RETURN_FALSE(operation.dst_extents_size() > 0); enum BlockField { READER = 0, WRITER, BLOCK_FIELD_COUNT }; for (int field = READER; field < BLOCK_FIELD_COUNT; field++) { const char* past_participle = (field == READER) ? "read" : "written"; const google::protobuf::RepeatedPtrField<Extent>& extents = (field == READER) ? operation.src_extents() : operation.dst_extents(); Vertex::Index Block::*access_type = (field == READER) ? &Block::reader : &Block::writer; for (const Extent& extent : extents) { for (uint64_t block = extent.start_block(); block < (extent.start_block() + extent.num_blocks()); block++) { if ((*blocks)[block].*access_type != Vertex::kInvalidIndex) { LOG(FATAL) << "Block " << block << " is already " << past_participle << " by " << (*blocks)[block].*access_type << "(" << graph[(*blocks)[block].*access_type].aop.name << ") and also " << vertex << "(" << graph[vertex].aop.name << ")"; } (*blocks)[block].*access_type = vertex; } } } return true; } bool InplaceGenerator::AddInstallOpToGraph(Graph* graph, Vertex::Index existing_vertex, vector<Block>* blocks, const InstallOperation& operation, const string& op_name) { Vertex::Index vertex = existing_vertex; if (vertex == Vertex::kInvalidIndex) { graph->emplace_back(); vertex = graph->size() - 1; } (*graph)[vertex].aop.op = operation; CHECK((*graph)[vertex].aop.op.has_type()); (*graph)[vertex].aop.name = op_name; if (blocks) TEST_AND_RETURN_FALSE(InplaceGenerator::AddInstallOpToBlocksVector( (*graph)[vertex].aop.op, *graph, vertex, blocks)); return true; } void InplaceGenerator::ApplyMap(vector<uint64_t>* collection, const map<uint64_t, uint64_t>& the_map) { for (uint64_t& elem : *collection) { const auto& map_it = the_map.find(elem); if (map_it != the_map.end()) elem = map_it->second; } } bool InplaceGenerator::ResolveReadAfterWriteDependencies( const PartitionConfig& old_part, const PartitionConfig& new_part, uint64_t partition_size, size_t block_size, BlobFileWriter* blob_file, vector<AnnotatedOperation>* aops) { // Convert the operations to the graph. Graph graph; CheckGraph(graph); vector<Block> blocks(std::max(old_part.size, new_part.size) / block_size); for (const auto& aop : *aops) { AddInstallOpToGraph( &graph, Vertex::kInvalidIndex, &blocks, aop.op, aop.name); } CheckGraph(graph); // Final scratch block (if there's space) Vertex::Index scratch_vertex = Vertex::kInvalidIndex; if (blocks.size() < (partition_size / block_size)) { scratch_vertex = graph.size(); graph.emplace_back(); size_t scratch_blocks = (partition_size / block_size) - blocks.size(); LOG(INFO) << "Added " << scratch_blocks << " scratch space blocks."; CreateScratchNode(blocks.size(), scratch_blocks, &graph.back()); } CheckGraph(graph); LOG(INFO) << "Creating edges..."; CreateEdges(&graph, blocks); LOG(INFO) << "Done creating edges"; CheckGraph(graph); vector<Vertex::Index> final_order; TEST_AND_RETURN_FALSE(ConvertGraphToDag( &graph, new_part.path, blob_file, &final_order, scratch_vertex)); // Copy operations over to the |aops| vector in the final_order generated by // the topological sort. aops->clear(); aops->reserve(final_order.size()); for (const Vertex::Index vertex_index : final_order) { const Vertex& vertex = graph[vertex_index]; aops->push_back(vertex.aop); } return true; } bool InplaceGenerator::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); TEST_AND_RETURN_FALSE(config.version.major == kInPlacePayloadVersion.major); TEST_AND_RETURN_FALSE(config.version.minor == kInPlacePayloadVersion.minor); 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; uint64_t partition_size = new_part.size; if (new_part.name == kPartitionNameRoot) partition_size = config.rootfs_partition_size; LOG(INFO) << "Delta compressing " << new_part.name << " partition..."; 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(ResolveReadAfterWriteDependencies( old_part, new_part, partition_size, config.block_size, blob_file, aops)); LOG(INFO) << "Done reordering " << new_part.name; return true; } }; // namespace chromeos_update_engine