blob: 8343d212805fa2dc475eaea1df7def3322a10f86 [file] [log] [blame]
Andrew Lassalle165843c2019-11-05 13:30:34 -08001#!/usr/bin/env python
Sen Jiange6e0f042018-05-24 15:40:41 -07002# -*- 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 Lassalle165843c2019-11-05 13:30:34 -080021from __future__ import absolute_import
Sen Jiange6e0f042018-05-24 15:40:41 -070022from __future__ import print_function
23
24import argparse
Sen Jiange6e0f042018-05-24 15:40:41 -070025import sys
26import textwrap
27
Andrew Lassalle165843c2019-11-05 13:30:34 -080028from six.moves import range
Sen Jiange6e0f042018-05-24 15:40:41 -070029import update_payload
30
Andrew Lassalle165843c2019-11-05 13:30:34 -080031
Sen Jiange6e0f042018-05-24 15:40:41 -070032MAJOR_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):
Andrew Lassalle165843c2019-11-05 13:30:34 -080045 chunk = bytearray(data[off:off + 16])
Sen Jiange6e0f042018-05-24 15:40:41 -070046 print(' ' * indent +
Andrew Lassalle165843c2019-11-05 13:30:34 -080047 ' '.join('%.2x' % c for c in chunk) +
Sen Jiange6e0f042018-05-24 15:40:41 -070048 ' ' * (16 - len(chunk)) +
49 ' | ' +
Andrew Lassalle165843c2019-11-05 13:30:34 -080050 ''.join(chr(c) if 32 <= c < 127 else '.' for c in chunk))
Sen Jiange6e0f042018-05-24 15:40:41 -070051
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
Amin Hassani55c75412019-10-07 11:20:39 -070073 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 Zhangf2e7ee52020-08-13 14:58:43 -040077 for partition in manifest.partitions:
Kelvin Zhang7a265752020-10-29 15:51:35 -040078 DisplayValue(" Timestamp for " +
Kelvin Zhangf2e7ee52020-08-13 14:58:43 -040079 partition.partition_name, partition.version)
Kelvin Zhang7a265752020-10-29 15:51:35 -040080 for partition in manifest.partitions:
81 DisplayValue(" COW Size for " +
82 partition.partition_name, partition.estimate_cow_size)
Sen Jiange6e0f042018-05-24 15:40:41 -070083 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 Hassani55c75412019-10-07 11:20:39 -0700136 operations: The operations object that you want to display information
137 about.
Sen Jiange6e0f042018-05-24 15:40:41 -0700138 """
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 Lassalle165843c2019-11-05 13:30:34 -0800154 for op_count, op in enumerate(operations):
Sen Jiange6e0f042018-05-24 15:40:41 -0700155 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 Hassani55c75412019-10-07 11:20:39 -0700175 for partition in manifest.partitions:
Sen Jiange6e0f042018-05-24 15:40:41 -0700176 last_ext = None
Amin Hassani55c75412019-10-07 11:20:39 -0700177 for curr_op in partition.operations:
Sen Jiange6e0f042018-05-24 15:40:41 -0700178 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 Hassani55c75412019-10-07 11:20:39 -0700187 # Old and new partitions are read once during verification.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800188 read_blocks += partition.old_partition_info.size // manifest.block_size
189 read_blocks += partition.new_partition_info.size // manifest.block_size
Amin Hassani55c75412019-10-07 11:20:39 -0700190
Sen Jiange6e0f042018-05-24 15:40:41 -0700191 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 Hassani55c75412019-10-07 11:20:39 -0700214 for partition in self.payload.manifest.partitions:
215 self._DisplayOps('%s install operations' % partition.partition_name,
216 partition.operations)
Sen Jiange6e0f042018-05-24 15:40:41 -0700217
218
219def main():
220 parser = argparse.ArgumentParser(
221 description='Show information about an update payload.')
Andrew Lassalle165843c2019-11-05 13:30:34 -0800222 parser.add_argument('payload_file', type=argparse.FileType('rb'),
Sen Jiange6e0f042018-05-24 15:40:41 -0700223 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 Lassalle165843c2019-11-05 13:30:34 -0800234
Sen Jiange6e0f042018-05-24 15:40:41 -0700235if __name__ == '__main__':
236 sys.exit(main())