blob: 7625ee8e6571b69695c609a9b5488929fb7a75e9 [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:
78 DisplayValue("Timestamp for " +
79 partition.partition_name, partition.version)
Sen Jiange6e0f042018-05-24 15:40:41 -070080 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 Hassani55c75412019-10-07 11:20:39 -0700133 operations: The operations object that you want to display information
134 about.
Sen Jiange6e0f042018-05-24 15:40:41 -0700135 """
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 Lassalle165843c2019-11-05 13:30:34 -0800151 for op_count, op in enumerate(operations):
Sen Jiange6e0f042018-05-24 15:40:41 -0700152 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 Hassani55c75412019-10-07 11:20:39 -0700172 for partition in manifest.partitions:
Sen Jiange6e0f042018-05-24 15:40:41 -0700173 last_ext = None
Amin Hassani55c75412019-10-07 11:20:39 -0700174 for curr_op in partition.operations:
Sen Jiange6e0f042018-05-24 15:40:41 -0700175 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 Hassani55c75412019-10-07 11:20:39 -0700184 # Old and new partitions are read once during verification.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800185 read_blocks += partition.old_partition_info.size // manifest.block_size
186 read_blocks += partition.new_partition_info.size // manifest.block_size
Amin Hassani55c75412019-10-07 11:20:39 -0700187
Sen Jiange6e0f042018-05-24 15:40:41 -0700188 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 Hassani55c75412019-10-07 11:20:39 -0700211 for partition in self.payload.manifest.partitions:
212 self._DisplayOps('%s install operations' % partition.partition_name,
213 partition.operations)
Sen Jiange6e0f042018-05-24 15:40:41 -0700214
215
216def main():
217 parser = argparse.ArgumentParser(
218 description='Show information about an update payload.')
Andrew Lassalle165843c2019-11-05 13:30:34 -0800219 parser.add_argument('payload_file', type=argparse.FileType('rb'),
Sen Jiange6e0f042018-05-24 15:40:41 -0700220 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 Lassalle165843c2019-11-05 13:30:34 -0800231
Sen Jiange6e0f042018-05-24 15:40:41 -0700232if __name__ == '__main__':
233 sys.exit(main())