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