# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Tracing block data source through a Chrome OS update payload. This module is used internally by the main Payload class for tracing block content through an update payload. This is a useful feature in debugging payload applying functionality in this package. The interface for invoking the tracer is as follows: tracer = PayloadBlockTracer(payload) tracer.Run(...) """ from __future__ import print_function import common # # Payload block tracing. # class PayloadBlockTracer(object): """Tracing the origin of block data through update instructions. This is a short-lived object whose purpose is to isolate the logic used for tracing the origin of destination partition blocks. """ def __init__(self, payload): assert payload.is_init, 'uninitialized update payload' self.payload = payload @staticmethod def _TraceBlock(block, skip, trace_out_file, operations, base_name): """Trace the origin of a given block through a sequence of operations. This method tries to map the given dest block to the corresponding source block from which its content originates in the course of an update. It further tries to trace transitive origins through MOVE operations. It is rather efficient, doing the actual tracing by means of a single reverse sweep through the operation sequence. It dumps a log of operations and source blocks responsible for the data in the given dest block to the provided output file. Args: block: the block number to trace skip: number of initial transitive origins to ignore trace_out_file: a file object to dump the trace to operations: the sequence of operations base_name: name of the operation sequence """ # Traverse operations backwards. for op, op_name in common.OperationIter(operations, base_name, reverse=True): total_block_offset = 0 found = False # Is the traced block mentioned in the dest extents? for dst_ex, dst_ex_name in common.ExtentIter(op.dst_extents, op_name + '.dst_extents'): if (block >= dst_ex.start_block and block < dst_ex.start_block + dst_ex.num_blocks): if skip: skip -= 1 else: total_block_offset += block - dst_ex.start_block trace_out_file.write( '%d: %s: found %s (total block offset: %d)\n' % (block, dst_ex_name, common.FormatExtent(dst_ex), total_block_offset)) found = True break total_block_offset += dst_ex.num_blocks if found: # Don't trace further, unless it's a MOVE. if op.type != common.OpType.MOVE: break # For MOVE, find corresponding source block and keep tracing. for src_ex, src_ex_name in common.ExtentIter(op.src_extents, op_name + '.src_extents'): if total_block_offset < src_ex.num_blocks: block = src_ex.start_block + total_block_offset trace_out_file.write( '%s: mapped to %s (%d)\n' % (src_ex_name, common.FormatExtent(src_ex), block)) break total_block_offset -= src_ex.num_blocks def Run(self, block, skip, trace_out_file, is_kernel): """Block tracer entry point, invoking the actual search. Args: block: the block number whose origin to trace skip: the number of first origin mappings to skip trace_out_file: file object to dump the trace to is_kernel: trace through kernel (True) or rootfs (False) operations """ if is_kernel: operations = self.payload.manifest.kernel_install_operations base_name = 'kernel_install_operations' else: operations = self.payload.manifest.install_operations base_name = 'install_operations' self._TraceBlock(block, skip, trace_out_file, operations, base_name)