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: |
Kelvin Zhang | 7a26575 | 2020-10-29 15:51:35 -0400 | [diff] [blame] | 78 | DisplayValue(" Timestamp for " + |
Kelvin Zhang | f2e7ee5 | 2020-08-13 14:58:43 -0400 | [diff] [blame] | 79 | partition.partition_name, partition.version) |
Kelvin Zhang | 7a26575 | 2020-10-29 15:51:35 -0400 | [diff] [blame] | 80 | for partition in manifest.partitions: |
| 81 | DisplayValue(" COW Size for " + |
| 82 | partition.partition_name, partition.estimate_cow_size) |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 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. |
Amin Hassani | 55c7541 | 2019-10-07 11:20:39 -0700 | [diff] [blame] | 136 | operations: The operations object that you want to display information |
| 137 | about. |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 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) |
Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame] | 154 | for op_count, op in enumerate(operations): |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 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 |
Amin Hassani | 55c7541 | 2019-10-07 11:20:39 -0700 | [diff] [blame] | 175 | for partition in manifest.partitions: |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 176 | last_ext = None |
Amin Hassani | 55c7541 | 2019-10-07 11:20:39 -0700 | [diff] [blame] | 177 | for curr_op in partition.operations: |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 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 | |
Amin Hassani | 55c7541 | 2019-10-07 11:20:39 -0700 | [diff] [blame] | 187 | # Old and new partitions are read once during verification. |
Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame] | 188 | read_blocks += partition.old_partition_info.size // manifest.block_size |
| 189 | read_blocks += partition.new_partition_info.size // manifest.block_size |
Amin Hassani | 55c7541 | 2019-10-07 11:20:39 -0700 | [diff] [blame] | 190 | |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 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() |
Amin Hassani | 55c7541 | 2019-10-07 11:20:39 -0700 | [diff] [blame] | 214 | for partition in self.payload.manifest.partitions: |
| 215 | self._DisplayOps('%s install operations' % partition.partition_name, |
| 216 | partition.operations) |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 217 | |
| 218 | |
| 219 | def main(): |
| 220 | parser = argparse.ArgumentParser( |
| 221 | description='Show information about an update payload.') |
Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame] | 222 | parser.add_argument('payload_file', type=argparse.FileType('rb'), |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 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 | |
Andrew Lassalle | 165843c | 2019-11-05 13:30:34 -0800 | [diff] [blame] | 234 | |
Sen Jiang | e6e0f04 | 2018-05-24 15:40:41 -0700 | [diff] [blame] | 235 | if __name__ == '__main__': |
| 236 | sys.exit(main()) |