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 for partition in manifest.partitions: 78 DisplayValue(" Timestamp for " + 79 partition.partition_name, partition.version) 80 for partition in manifest.partitions: 81 DisplayValue(" COW Size for " + 82 partition.partition_name, partition.estimate_cow_size) 83 DisplayValue('Block size', manifest.block_size) 84 DisplayValue('Minor version', manifest.minor_version) 85 86 def _DisplaySignatures(self): 87 """Show information about the signatures from the manifest.""" 88 header = self.payload.header 89 if header.metadata_signature_len: 90 offset = header.size + header.manifest_len 91 DisplayValue('Metadata signatures blob', 92 'file_offset=%d (%d bytes)' % 93 (offset, header.metadata_signature_len)) 94 # pylint: disable=invalid-unary-operand-type 95 signatures_blob = self.payload.ReadDataBlob( 96 -header.metadata_signature_len, 97 header.metadata_signature_len) 98 self._DisplaySignaturesBlob('Metadata', signatures_blob) 99 else: 100 print('No metadata signatures stored in the payload') 101 102 manifest = self.payload.manifest 103 if manifest.HasField('signatures_offset'): 104 signature_msg = 'blob_offset=%d' % manifest.signatures_offset 105 if manifest.signatures_size: 106 signature_msg += ' (%d bytes)' % manifest.signatures_size 107 DisplayValue('Payload signatures blob', signature_msg) 108 signatures_blob = self.payload.ReadDataBlob(manifest.signatures_offset, 109 manifest.signatures_size) 110 self._DisplaySignaturesBlob('Payload', signatures_blob) 111 else: 112 print('No payload signatures stored in the payload') 113 114 @staticmethod 115 def _DisplaySignaturesBlob(signature_name, signatures_blob): 116 """Show information about the signatures blob.""" 117 signatures = update_payload.update_metadata_pb2.Signatures() 118 signatures.ParseFromString(signatures_blob) 119 print('%s signatures: (%d entries)' % 120 (signature_name, len(signatures.signatures))) 121 for signature in signatures.signatures: 122 print(' version=%s, hex_data: (%d bytes)' % 123 (signature.version if signature.HasField('version') else None, 124 len(signature.data))) 125 DisplayHexData(signature.data, indent=4) 126 127 128 def _DisplayOps(self, name, operations): 129 """Show information about the install operations from the manifest. 130 131 The list shown includes operation type, data offset, data length, source 132 extents, source length, destination extents, and destinations length. 133 134 Args: 135 name: The name you want displayed above the operation table. 136 operations: The operations object that you want to display information 137 about. 138 """ 139 def _DisplayExtents(extents, name): 140 """Show information about extents.""" 141 num_blocks = sum([ext.num_blocks for ext in extents]) 142 ext_str = ' '.join( 143 '(%s,%s)' % (ext.start_block, ext.num_blocks) for ext in extents) 144 # Make extent list wrap around at 80 chars. 145 ext_str = '\n '.join(textwrap.wrap(ext_str, 74)) 146 extent_plural = 's' if len(extents) > 1 else '' 147 block_plural = 's' if num_blocks > 1 else '' 148 print(' %s: %d extent%s (%d block%s)' % 149 (name, len(extents), extent_plural, num_blocks, block_plural)) 150 print(' %s' % ext_str) 151 152 op_dict = update_payload.common.OpType.NAMES 153 print('%s:' % name) 154 for op_count, op in enumerate(operations): 155 print(' %d: %s' % (op_count, op_dict[op.type])) 156 if op.HasField('data_offset'): 157 print(' Data offset: %s' % op.data_offset) 158 if op.HasField('data_length'): 159 print(' Data length: %s' % op.data_length) 160 if op.src_extents: 161 _DisplayExtents(op.src_extents, 'Source') 162 if op.dst_extents: 163 _DisplayExtents(op.dst_extents, 'Destination') 164 165 def _GetStats(self, manifest): 166 """Returns various statistics about a payload file. 167 168 Returns a dictionary containing the number of blocks read during payload 169 application, the number of blocks written, and the number of seeks done 170 when writing during operation application. 171 """ 172 read_blocks = 0 173 written_blocks = 0 174 num_write_seeks = 0 175 for partition in manifest.partitions: 176 last_ext = None 177 for curr_op in partition.operations: 178 read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents]) 179 written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents]) 180 for curr_ext in curr_op.dst_extents: 181 # See if the extent is contiguous with the last extent seen. 182 if last_ext and (curr_ext.start_block != 183 last_ext.start_block + last_ext.num_blocks): 184 num_write_seeks += 1 185 last_ext = curr_ext 186 187 # Old and new partitions are read once during verification. 188 read_blocks += partition.old_partition_info.size // manifest.block_size 189 read_blocks += partition.new_partition_info.size // manifest.block_size 190 191 stats = {'read_blocks': read_blocks, 192 'written_blocks': written_blocks, 193 'num_write_seeks': num_write_seeks} 194 return stats 195 196 def _DisplayStats(self, manifest): 197 stats = self._GetStats(manifest) 198 DisplayValue('Blocks read', stats['read_blocks']) 199 DisplayValue('Blocks written', stats['written_blocks']) 200 DisplayValue('Seeks when writing', stats['num_write_seeks']) 201 202 def Run(self): 203 """Parse the update payload and display information from it.""" 204 self.payload = update_payload.Payload(self.options.payload_file) 205 self.payload.Init() 206 self._DisplayHeader() 207 self._DisplayManifest() 208 if self.options.signatures: 209 self._DisplaySignatures() 210 if self.options.stats: 211 self._DisplayStats(self.payload.manifest) 212 if self.options.list_ops: 213 print() 214 for partition in self.payload.manifest.partitions: 215 self._DisplayOps('%s install operations' % partition.partition_name, 216 partition.operations) 217 218 219def main(): 220 parser = argparse.ArgumentParser( 221 description='Show information about an update payload.') 222 parser.add_argument('payload_file', type=argparse.FileType('rb'), 223 help='The update payload file.') 224 parser.add_argument('--list_ops', default=False, action='store_true', 225 help='List the install operations and their extents.') 226 parser.add_argument('--stats', default=False, action='store_true', 227 help='Show information about overall input/output.') 228 parser.add_argument('--signatures', default=False, action='store_true', 229 help='Show signatures stored in the payload.') 230 args = parser.parse_args() 231 232 PayloadCommand(args).Run() 233 234 235if __name__ == '__main__': 236 sys.exit(main()) 237