You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
9.0 KiB
237 lines
9.0 KiB
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# 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.
|
|
#
|
|
|
|
"""payload_info: Show information about an update payload."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import sys
|
|
import textwrap
|
|
|
|
from six.moves import range
|
|
import update_payload
|
|
|
|
|
|
MAJOR_PAYLOAD_VERSION_BRILLO = 2
|
|
|
|
def DisplayValue(key, value):
|
|
"""Print out a key, value pair with values left-aligned."""
|
|
if value != None:
|
|
print('%-*s %s' % (28, key + ':', value))
|
|
else:
|
|
raise ValueError('Cannot display an empty value.')
|
|
|
|
|
|
def DisplayHexData(data, indent=0):
|
|
"""Print out binary data as a hex values."""
|
|
for off in range(0, len(data), 16):
|
|
chunk = bytearray(data[off:off + 16])
|
|
print(' ' * indent +
|
|
' '.join('%.2x' % c for c in chunk) +
|
|
' ' * (16 - len(chunk)) +
|
|
' | ' +
|
|
''.join(chr(c) if 32 <= c < 127 else '.' for c in chunk))
|
|
|
|
|
|
class PayloadCommand(object):
|
|
"""Show basic information about an update payload.
|
|
|
|
This command parses an update payload and displays information from
|
|
its header and manifest.
|
|
"""
|
|
|
|
def __init__(self, options):
|
|
self.options = options
|
|
self.payload = None
|
|
|
|
def _DisplayHeader(self):
|
|
"""Show information from the payload header."""
|
|
header = self.payload.header
|
|
DisplayValue('Payload version', header.version)
|
|
DisplayValue('Manifest length', header.manifest_len)
|
|
|
|
def _DisplayManifest(self):
|
|
"""Show information from the payload manifest."""
|
|
manifest = self.payload.manifest
|
|
DisplayValue('Number of partitions', len(manifest.partitions))
|
|
for partition in manifest.partitions:
|
|
DisplayValue(' Number of "%s" ops' % partition.partition_name,
|
|
len(partition.operations))
|
|
for partition in manifest.partitions:
|
|
DisplayValue(" Timestamp for " +
|
|
partition.partition_name, partition.version)
|
|
for partition in manifest.partitions:
|
|
DisplayValue(" COW Size for " +
|
|
partition.partition_name, partition.estimate_cow_size)
|
|
DisplayValue('Block size', manifest.block_size)
|
|
DisplayValue('Minor version', manifest.minor_version)
|
|
|
|
def _DisplaySignatures(self):
|
|
"""Show information about the signatures from the manifest."""
|
|
header = self.payload.header
|
|
if header.metadata_signature_len:
|
|
offset = header.size + header.manifest_len
|
|
DisplayValue('Metadata signatures blob',
|
|
'file_offset=%d (%d bytes)' %
|
|
(offset, header.metadata_signature_len))
|
|
# pylint: disable=invalid-unary-operand-type
|
|
signatures_blob = self.payload.ReadDataBlob(
|
|
-header.metadata_signature_len,
|
|
header.metadata_signature_len)
|
|
self._DisplaySignaturesBlob('Metadata', signatures_blob)
|
|
else:
|
|
print('No metadata signatures stored in the payload')
|
|
|
|
manifest = self.payload.manifest
|
|
if manifest.HasField('signatures_offset'):
|
|
signature_msg = 'blob_offset=%d' % manifest.signatures_offset
|
|
if manifest.signatures_size:
|
|
signature_msg += ' (%d bytes)' % manifest.signatures_size
|
|
DisplayValue('Payload signatures blob', signature_msg)
|
|
signatures_blob = self.payload.ReadDataBlob(manifest.signatures_offset,
|
|
manifest.signatures_size)
|
|
self._DisplaySignaturesBlob('Payload', signatures_blob)
|
|
else:
|
|
print('No payload signatures stored in the payload')
|
|
|
|
@staticmethod
|
|
def _DisplaySignaturesBlob(signature_name, signatures_blob):
|
|
"""Show information about the signatures blob."""
|
|
signatures = update_payload.update_metadata_pb2.Signatures()
|
|
signatures.ParseFromString(signatures_blob)
|
|
print('%s signatures: (%d entries)' %
|
|
(signature_name, len(signatures.signatures)))
|
|
for signature in signatures.signatures:
|
|
print(' version=%s, hex_data: (%d bytes)' %
|
|
(signature.version if signature.HasField('version') else None,
|
|
len(signature.data)))
|
|
DisplayHexData(signature.data, indent=4)
|
|
|
|
|
|
def _DisplayOps(self, name, operations):
|
|
"""Show information about the install operations from the manifest.
|
|
|
|
The list shown includes operation type, data offset, data length, source
|
|
extents, source length, destination extents, and destinations length.
|
|
|
|
Args:
|
|
name: The name you want displayed above the operation table.
|
|
operations: The operations object that you want to display information
|
|
about.
|
|
"""
|
|
def _DisplayExtents(extents, name):
|
|
"""Show information about extents."""
|
|
num_blocks = sum([ext.num_blocks for ext in extents])
|
|
ext_str = ' '.join(
|
|
'(%s,%s)' % (ext.start_block, ext.num_blocks) for ext in extents)
|
|
# Make extent list wrap around at 80 chars.
|
|
ext_str = '\n '.join(textwrap.wrap(ext_str, 74))
|
|
extent_plural = 's' if len(extents) > 1 else ''
|
|
block_plural = 's' if num_blocks > 1 else ''
|
|
print(' %s: %d extent%s (%d block%s)' %
|
|
(name, len(extents), extent_plural, num_blocks, block_plural))
|
|
print(' %s' % ext_str)
|
|
|
|
op_dict = update_payload.common.OpType.NAMES
|
|
print('%s:' % name)
|
|
for op_count, op in enumerate(operations):
|
|
print(' %d: %s' % (op_count, op_dict[op.type]))
|
|
if op.HasField('data_offset'):
|
|
print(' Data offset: %s' % op.data_offset)
|
|
if op.HasField('data_length'):
|
|
print(' Data length: %s' % op.data_length)
|
|
if op.src_extents:
|
|
_DisplayExtents(op.src_extents, 'Source')
|
|
if op.dst_extents:
|
|
_DisplayExtents(op.dst_extents, 'Destination')
|
|
|
|
def _GetStats(self, manifest):
|
|
"""Returns various statistics about a payload file.
|
|
|
|
Returns a dictionary containing the number of blocks read during payload
|
|
application, the number of blocks written, and the number of seeks done
|
|
when writing during operation application.
|
|
"""
|
|
read_blocks = 0
|
|
written_blocks = 0
|
|
num_write_seeks = 0
|
|
for partition in manifest.partitions:
|
|
last_ext = None
|
|
for curr_op in partition.operations:
|
|
read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents])
|
|
written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents])
|
|
for curr_ext in curr_op.dst_extents:
|
|
# See if the extent is contiguous with the last extent seen.
|
|
if last_ext and (curr_ext.start_block !=
|
|
last_ext.start_block + last_ext.num_blocks):
|
|
num_write_seeks += 1
|
|
last_ext = curr_ext
|
|
|
|
# Old and new partitions are read once during verification.
|
|
read_blocks += partition.old_partition_info.size // manifest.block_size
|
|
read_blocks += partition.new_partition_info.size // manifest.block_size
|
|
|
|
stats = {'read_blocks': read_blocks,
|
|
'written_blocks': written_blocks,
|
|
'num_write_seeks': num_write_seeks}
|
|
return stats
|
|
|
|
def _DisplayStats(self, manifest):
|
|
stats = self._GetStats(manifest)
|
|
DisplayValue('Blocks read', stats['read_blocks'])
|
|
DisplayValue('Blocks written', stats['written_blocks'])
|
|
DisplayValue('Seeks when writing', stats['num_write_seeks'])
|
|
|
|
def Run(self):
|
|
"""Parse the update payload and display information from it."""
|
|
self.payload = update_payload.Payload(self.options.payload_file)
|
|
self.payload.Init()
|
|
self._DisplayHeader()
|
|
self._DisplayManifest()
|
|
if self.options.signatures:
|
|
self._DisplaySignatures()
|
|
if self.options.stats:
|
|
self._DisplayStats(self.payload.manifest)
|
|
if self.options.list_ops:
|
|
print()
|
|
for partition in self.payload.manifest.partitions:
|
|
self._DisplayOps('%s install operations' % partition.partition_name,
|
|
partition.operations)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Show information about an update payload.')
|
|
parser.add_argument('payload_file', type=argparse.FileType('rb'),
|
|
help='The update payload file.')
|
|
parser.add_argument('--list_ops', default=False, action='store_true',
|
|
help='List the install operations and their extents.')
|
|
parser.add_argument('--stats', default=False, action='store_true',
|
|
help='Show information about overall input/output.')
|
|
parser.add_argument('--signatures', default=False, action='store_true',
|
|
help='Show signatures stored in the payload.')
|
|
args = parser.parse_args()
|
|
|
|
PayloadCommand(args).Run()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|