payload_info.py 9.9 KB

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