• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2015 The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19"""payload_info: Show information about an update payload."""
20
21from __future__ import print_function
22
23import argparse
24import itertools
25import sys
26import textwrap
27
28import update_payload
29
30MAJOR_PAYLOAD_VERSION_CHROMEOS = 1
31MAJOR_PAYLOAD_VERSION_BRILLO = 2
32
33def DisplayValue(key, value):
34  """Print out a key, value pair with values left-aligned."""
35  if value != None:
36    print('%-*s %s' % (28, key + ':', value))
37  else:
38    raise ValueError('Cannot display an empty value.')
39
40
41def DisplayHexData(data, indent=0):
42  """Print out binary data as a hex values."""
43  for off in range(0, len(data), 16):
44    chunk = data[off:off + 16]
45    print(' ' * indent +
46          ' '.join('%.2x' % ord(c) for c in chunk) +
47          '   ' * (16 - len(chunk)) +
48          ' | ' +
49          ''.join(c if 32 <= ord(c) < 127 else '.' for c in chunk))
50
51
52class PayloadCommand(object):
53  """Show basic information about an update payload.
54
55  This command parses an update payload and displays information from
56  its header and manifest.
57  """
58
59  def __init__(self, options):
60    self.options = options
61    self.payload = None
62
63  def _DisplayHeader(self):
64    """Show information from the payload header."""
65    header = self.payload.header
66    DisplayValue('Payload version', header.version)
67    DisplayValue('Manifest length', header.manifest_len)
68
69  def _DisplayManifest(self):
70    """Show information from the payload manifest."""
71    manifest = self.payload.manifest
72    if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO:
73      DisplayValue('Number of partitions', len(manifest.partitions))
74      for partition in manifest.partitions:
75        DisplayValue('  Number of "%s" ops' % partition.partition_name,
76                     len(partition.operations))
77    else:
78      DisplayValue('Number of operations', len(manifest.install_operations))
79      DisplayValue('Number of kernel ops',
80                   len(manifest.kernel_install_operations))
81    DisplayValue('Block size', manifest.block_size)
82    DisplayValue('Minor version', manifest.minor_version)
83
84  def _DisplaySignatures(self):
85    """Show information about the signatures from the manifest."""
86    header = self.payload.header
87    if header.metadata_signature_len:
88      offset = header.size + header.manifest_len
89      DisplayValue('Metadata signatures blob',
90                   'file_offset=%d (%d bytes)' %
91                   (offset, header.metadata_signature_len))
92      # pylint: disable=invalid-unary-operand-type
93      signatures_blob = self.payload.ReadDataBlob(
94          -header.metadata_signature_len,
95          header.metadata_signature_len)
96      self._DisplaySignaturesBlob('Metadata', signatures_blob)
97    else:
98      print('No metadata signatures stored in the payload')
99
100    manifest = self.payload.manifest
101    if manifest.HasField('signatures_offset'):
102      signature_msg = 'blob_offset=%d' % manifest.signatures_offset
103      if manifest.signatures_size:
104        signature_msg += ' (%d bytes)' % manifest.signatures_size
105      DisplayValue('Payload signatures blob', signature_msg)
106      signatures_blob = self.payload.ReadDataBlob(manifest.signatures_offset,
107                                                  manifest.signatures_size)
108      self._DisplaySignaturesBlob('Payload', signatures_blob)
109    else:
110      print('No payload signatures stored in the payload')
111
112  @staticmethod
113  def _DisplaySignaturesBlob(signature_name, signatures_blob):
114    """Show information about the signatures blob."""
115    signatures = update_payload.update_metadata_pb2.Signatures()
116    signatures.ParseFromString(signatures_blob)
117    print('%s signatures: (%d entries)' %
118          (signature_name, len(signatures.signatures)))
119    for signature in signatures.signatures:
120      print('  version=%s, hex_data: (%d bytes)' %
121            (signature.version if signature.HasField('version') else None,
122             len(signature.data)))
123      DisplayHexData(signature.data, indent=4)
124
125
126  def _DisplayOps(self, name, operations):
127    """Show information about the install operations from the manifest.
128
129    The list shown includes operation type, data offset, data length, source
130    extents, source length, destination extents, and destinations length.
131
132    Args:
133      name: The name you want displayed above the operation table.
134      operations: The install_operations object that you want to display
135                  information about.
136    """
137    def _DisplayExtents(extents, name):
138      """Show information about extents."""
139      num_blocks = sum([ext.num_blocks for ext in extents])
140      ext_str = ' '.join(
141          '(%s,%s)' % (ext.start_block, ext.num_blocks) for ext in extents)
142      # Make extent list wrap around at 80 chars.
143      ext_str = '\n      '.join(textwrap.wrap(ext_str, 74))
144      extent_plural = 's' if len(extents) > 1 else ''
145      block_plural = 's' if num_blocks > 1 else ''
146      print('    %s: %d extent%s (%d block%s)' %
147            (name, len(extents), extent_plural, num_blocks, block_plural))
148      print('      %s' % ext_str)
149
150    op_dict = update_payload.common.OpType.NAMES
151    print('%s:' % name)
152    for op, op_count in itertools.izip(operations, itertools.count()):
153      print('  %d: %s' % (op_count, op_dict[op.type]))
154      if op.HasField('data_offset'):
155        print('    Data offset: %s' % op.data_offset)
156      if op.HasField('data_length'):
157        print('    Data length: %s' % op.data_length)
158      if op.src_extents:
159        _DisplayExtents(op.src_extents, 'Source')
160      if op.dst_extents:
161        _DisplayExtents(op.dst_extents, 'Destination')
162
163  def _GetStats(self, manifest):
164    """Returns various statistics about a payload file.
165
166    Returns a dictionary containing the number of blocks read during payload
167    application, the number of blocks written, and the number of seeks done
168    when writing during operation application.
169    """
170    read_blocks = 0
171    written_blocks = 0
172    num_write_seeks = 0
173    if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO:
174      partitions_operations = [part.operations for part in manifest.partitions]
175    else:
176      partitions_operations = [manifest.install_operations,
177                               manifest.kernel_install_operations]
178    for operations in partitions_operations:
179      last_ext = None
180      for curr_op in operations:
181        read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents])
182        written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents])
183        for curr_ext in curr_op.dst_extents:
184          # See if the extent is contiguous with the last extent seen.
185          if last_ext and (curr_ext.start_block !=
186                           last_ext.start_block + last_ext.num_blocks):
187            num_write_seeks += 1
188          last_ext = curr_ext
189
190    if manifest.minor_version == 1:
191      # Rootfs and kernel are written during the filesystem copy in version 1.
192      written_blocks += manifest.old_rootfs_info.size / manifest.block_size
193      written_blocks += manifest.old_kernel_info.size / manifest.block_size
194    # Old and new rootfs and kernel are read once during verification
195    read_blocks += manifest.old_rootfs_info.size / manifest.block_size
196    read_blocks += manifest.old_kernel_info.size / manifest.block_size
197    read_blocks += manifest.new_rootfs_info.size / manifest.block_size
198    read_blocks += manifest.new_kernel_info.size / manifest.block_size
199    stats = {'read_blocks': read_blocks,
200             'written_blocks': written_blocks,
201             'num_write_seeks': num_write_seeks}
202    return stats
203
204  def _DisplayStats(self, manifest):
205    stats = self._GetStats(manifest)
206    DisplayValue('Blocks read', stats['read_blocks'])
207    DisplayValue('Blocks written', stats['written_blocks'])
208    DisplayValue('Seeks when writing', stats['num_write_seeks'])
209
210  def Run(self):
211    """Parse the update payload and display information from it."""
212    self.payload = update_payload.Payload(self.options.payload_file)
213    self.payload.Init()
214    self._DisplayHeader()
215    self._DisplayManifest()
216    if self.options.signatures:
217      self._DisplaySignatures()
218    if self.options.stats:
219      self._DisplayStats(self.payload.manifest)
220    if self.options.list_ops:
221      print()
222      if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO:
223        for partition in self.payload.manifest.partitions:
224          self._DisplayOps('%s install operations' % partition.partition_name,
225                           partition.operations)
226      else:
227        self._DisplayOps('Install operations',
228                         self.payload.manifest.install_operations)
229        self._DisplayOps('Kernel install operations',
230                         self.payload.manifest.kernel_install_operations)
231
232
233def main():
234  parser = argparse.ArgumentParser(
235      description='Show information about an update payload.')
236  parser.add_argument('payload_file', type=file,
237                      help='The update payload file.')
238  parser.add_argument('--list_ops', default=False, action='store_true',
239                      help='List the install operations and their extents.')
240  parser.add_argument('--stats', default=False, action='store_true',
241                      help='Show information about overall input/output.')
242  parser.add_argument('--signatures', default=False, action='store_true',
243                      help='Show signatures stored in the payload.')
244  args = parser.parse_args()
245
246  PayloadCommand(args).Run()
247
248if __name__ == '__main__':
249  sys.exit(main())
250