1# Copyright (c) 2013 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"""Tracing block data source through a Chrome OS update payload.
6
7This module is used internally by the main Payload class for tracing block
8content through an update payload. This is a useful feature in debugging
9payload applying functionality in this package. The interface for invoking the
10tracer is as follows:
11
12  tracer = PayloadBlockTracer(payload)
13  tracer.Run(...)
14
15"""
16
17from __future__ import print_function
18
19import common
20
21
22#
23# Payload block tracing.
24#
25class PayloadBlockTracer(object):
26  """Tracing the origin of block data through update instructions.
27
28  This is a short-lived object whose purpose is to isolate the logic used for
29  tracing the origin of destination partition blocks.
30
31  """
32
33  def __init__(self, payload):
34    assert payload.is_init, 'uninitialized update payload'
35    self.payload = payload
36
37  @staticmethod
38  def _TraceBlock(block, skip, trace_out_file, operations, base_name):
39    """Trace the origin of a given block through a sequence of operations.
40
41    This method tries to map the given dest block to the corresponding source
42    block from which its content originates in the course of an update. It
43    further tries to trace transitive origins through MOVE operations. It is
44    rather efficient, doing the actual tracing by means of a single reverse
45    sweep through the operation sequence. It dumps a log of operations and
46    source blocks responsible for the data in the given dest block to the
47    provided output file.
48
49    Args:
50      block: the block number to trace
51      skip: number of initial transitive origins to ignore
52      trace_out_file: a file object to dump the trace to
53      operations: the sequence of operations
54      base_name: name of the operation sequence
55    """
56    # Traverse operations backwards.
57    for op, op_name in common.OperationIter(operations, base_name,
58                                            reverse=True):
59      total_block_offset = 0
60      found = False
61
62      # Is the traced block mentioned in the dest extents?
63      for dst_ex, dst_ex_name in common.ExtentIter(op.dst_extents,
64                                                   op_name + '.dst_extents'):
65        if (block >= dst_ex.start_block
66            and block < dst_ex.start_block + dst_ex.num_blocks):
67          if skip:
68            skip -= 1
69          else:
70            total_block_offset += block - dst_ex.start_block
71            trace_out_file.write(
72                '%d: %s: found %s (total block offset: %d)\n' %
73                (block, dst_ex_name, common.FormatExtent(dst_ex),
74                 total_block_offset))
75            found = True
76            break
77
78        total_block_offset += dst_ex.num_blocks
79
80      if found:
81        # Don't trace further, unless it's a MOVE.
82        if op.type != common.OpType.MOVE:
83          break
84
85        # For MOVE, find corresponding source block and keep tracing.
86        for src_ex, src_ex_name in common.ExtentIter(op.src_extents,
87                                                     op_name + '.src_extents'):
88          if total_block_offset < src_ex.num_blocks:
89            block = src_ex.start_block + total_block_offset
90            trace_out_file.write(
91                '%s:  mapped to %s (%d)\n' %
92                (src_ex_name, common.FormatExtent(src_ex), block))
93            break
94
95          total_block_offset -= src_ex.num_blocks
96
97  def Run(self, block, skip, trace_out_file, is_kernel):
98    """Block tracer entry point, invoking the actual search.
99
100    Args:
101      block: the block number whose origin to trace
102      skip: the number of first origin mappings to skip
103      trace_out_file: file object to dump the trace to
104      is_kernel: trace through kernel (True) or rootfs (False) operations
105    """
106    if is_kernel:
107      operations = self.payload.manifest.kernel_install_operations
108      base_name = 'kernel_install_operations'
109    else:
110      operations = self.payload.manifest.install_operations
111      base_name = 'install_operations'
112
113    self._TraceBlock(block, skip, trace_out_file, operations, base_name)
114