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