blob: ddf894770719a63734fa4b1b29d73a7fc31975eb [file] [log] [blame]
Håkan Kvist178375a2024-02-23 10:52:24 +01001#!/usr/bin/env python3
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
Håkan Kvist178375a2024-02-23 10:52:24 +010029import update_metadata_pb2
Sen Jiange6e0f042018-05-24 15:40:41 -070030import update_payload
31
Andrew Lassalle165843c2019-11-05 13:30:34 -080032
Sen Jiange6e0f042018-05-24 15:40:41 -070033MAJOR_PAYLOAD_VERSION_BRILLO = 2
34
35def DisplayValue(key, value):
36 """Print out a key, value pair with values left-aligned."""
Håkan Kvist178375a2024-02-23 10:52:24 +010037 if value is not None:
Sen Jiange6e0f042018-05-24 15:40:41 -070038 print('%-*s %s' % (28, key + ':', value))
39 else:
40 raise ValueError('Cannot display an empty value.')
41
42
43def DisplayHexData(data, indent=0):
44 """Print out binary data as a hex values."""
45 for off in range(0, len(data), 16):
Andrew Lassalle165843c2019-11-05 13:30:34 -080046 chunk = bytearray(data[off:off + 16])
Sen Jiange6e0f042018-05-24 15:40:41 -070047 print(' ' * indent +
Andrew Lassalle165843c2019-11-05 13:30:34 -080048 ' '.join('%.2x' % c for c in chunk) +
Sen Jiange6e0f042018-05-24 15:40:41 -070049 ' ' * (16 - len(chunk)) +
50 ' | ' +
Andrew Lassalle165843c2019-11-05 13:30:34 -080051 ''.join(chr(c) if 32 <= c < 127 else '.' for c in chunk))
Sen Jiange6e0f042018-05-24 15:40:41 -070052
53
Håkan Kvist178375a2024-02-23 10:52:24 +010054class PayloadCommand:
Sen Jiange6e0f042018-05-24 15:40:41 -070055 """Show basic information about an update payload.
56
57 This command parses an update payload and displays information from
58 its header and manifest.
59 """
60
61 def __init__(self, options):
62 self.options = options
63 self.payload = None
64
65 def _DisplayHeader(self):
66 """Show information from the payload header."""
67 header = self.payload.header
68 DisplayValue('Payload version', header.version)
69 DisplayValue('Manifest length', header.manifest_len)
70
71 def _DisplayManifest(self):
72 """Show information from the payload manifest."""
73 manifest = self.payload.manifest
Håkan Kvist178375a2024-02-23 10:52:24 +010074 # pylint: disable=no-member
Amin Hassani55c75412019-10-07 11:20:39 -070075 DisplayValue('Number of partitions', len(manifest.partitions))
76 for partition in manifest.partitions:
77 DisplayValue(' Number of "%s" ops' % partition.partition_name,
78 len(partition.operations))
Kelvin Zhangf2e7ee52020-08-13 14:58:43 -040079 for partition in manifest.partitions:
Kelvin Zhang7a265752020-10-29 15:51:35 -040080 DisplayValue(" Timestamp for " +
Kelvin Zhangf2e7ee52020-08-13 14:58:43 -040081 partition.partition_name, partition.version)
Kelvin Zhang7a265752020-10-29 15:51:35 -040082 for partition in manifest.partitions:
83 DisplayValue(" COW Size for " +
84 partition.partition_name, partition.estimate_cow_size)
Sen Jiange6e0f042018-05-24 15:40:41 -070085 DisplayValue('Block size', manifest.block_size)
86 DisplayValue('Minor version', manifest.minor_version)
87
88 def _DisplaySignatures(self):
89 """Show information about the signatures from the manifest."""
90 header = self.payload.header
91 if header.metadata_signature_len:
92 offset = header.size + header.manifest_len
93 DisplayValue('Metadata signatures blob',
94 'file_offset=%d (%d bytes)' %
95 (offset, header.metadata_signature_len))
96 # pylint: disable=invalid-unary-operand-type
97 signatures_blob = self.payload.ReadDataBlob(
98 -header.metadata_signature_len,
99 header.metadata_signature_len)
100 self._DisplaySignaturesBlob('Metadata', signatures_blob)
101 else:
102 print('No metadata signatures stored in the payload')
103
104 manifest = self.payload.manifest
105 if manifest.HasField('signatures_offset'):
Håkan Kvist178375a2024-02-23 10:52:24 +0100106 # pylint: disable=no-member
Sen Jiange6e0f042018-05-24 15:40:41 -0700107 signature_msg = 'blob_offset=%d' % manifest.signatures_offset
108 if manifest.signatures_size:
109 signature_msg += ' (%d bytes)' % manifest.signatures_size
110 DisplayValue('Payload signatures blob', signature_msg)
111 signatures_blob = self.payload.ReadDataBlob(manifest.signatures_offset,
112 manifest.signatures_size)
113 self._DisplaySignaturesBlob('Payload', signatures_blob)
114 else:
115 print('No payload signatures stored in the payload')
116
117 @staticmethod
118 def _DisplaySignaturesBlob(signature_name, signatures_blob):
119 """Show information about the signatures blob."""
Håkan Kvist178375a2024-02-23 10:52:24 +0100120 signatures = update_metadata_pb2.Signatures()
Sen Jiange6e0f042018-05-24 15:40:41 -0700121 signatures.ParseFromString(signatures_blob)
Håkan Kvist178375a2024-02-23 10:52:24 +0100122 # pylint: disable=no-member
Sen Jiange6e0f042018-05-24 15:40:41 -0700123 print('%s signatures: (%d entries)' %
124 (signature_name, len(signatures.signatures)))
125 for signature in signatures.signatures:
126 print(' version=%s, hex_data: (%d bytes)' %
127 (signature.version if signature.HasField('version') else None,
128 len(signature.data)))
129 DisplayHexData(signature.data, indent=4)
130
131
132 def _DisplayOps(self, name, operations):
133 """Show information about the install operations from the manifest.
134
135 The list shown includes operation type, data offset, data length, source
136 extents, source length, destination extents, and destinations length.
137
138 Args:
139 name: The name you want displayed above the operation table.
Amin Hassani55c75412019-10-07 11:20:39 -0700140 operations: The operations object that you want to display information
141 about.
Sen Jiange6e0f042018-05-24 15:40:41 -0700142 """
143 def _DisplayExtents(extents, name):
144 """Show information about extents."""
145 num_blocks = sum([ext.num_blocks for ext in extents])
146 ext_str = ' '.join(
147 '(%s,%s)' % (ext.start_block, ext.num_blocks) for ext in extents)
148 # Make extent list wrap around at 80 chars.
149 ext_str = '\n '.join(textwrap.wrap(ext_str, 74))
150 extent_plural = 's' if len(extents) > 1 else ''
151 block_plural = 's' if num_blocks > 1 else ''
152 print(' %s: %d extent%s (%d block%s)' %
153 (name, len(extents), extent_plural, num_blocks, block_plural))
154 print(' %s' % ext_str)
155
156 op_dict = update_payload.common.OpType.NAMES
157 print('%s:' % name)
Andrew Lassalle165843c2019-11-05 13:30:34 -0800158 for op_count, op in enumerate(operations):
Sen Jiange6e0f042018-05-24 15:40:41 -0700159 print(' %d: %s' % (op_count, op_dict[op.type]))
160 if op.HasField('data_offset'):
161 print(' Data offset: %s' % op.data_offset)
162 if op.HasField('data_length'):
163 print(' Data length: %s' % op.data_length)
164 if op.src_extents:
165 _DisplayExtents(op.src_extents, 'Source')
166 if op.dst_extents:
167 _DisplayExtents(op.dst_extents, 'Destination')
168
169 def _GetStats(self, manifest):
170 """Returns various statistics about a payload file.
171
172 Returns a dictionary containing the number of blocks read during payload
173 application, the number of blocks written, and the number of seeks done
174 when writing during operation application.
175 """
176 read_blocks = 0
177 written_blocks = 0
178 num_write_seeks = 0
Amin Hassani55c75412019-10-07 11:20:39 -0700179 for partition in manifest.partitions:
Sen Jiange6e0f042018-05-24 15:40:41 -0700180 last_ext = None
Amin Hassani55c75412019-10-07 11:20:39 -0700181 for curr_op in partition.operations:
Sen Jiange6e0f042018-05-24 15:40:41 -0700182 read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents])
183 written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents])
184 for curr_ext in curr_op.dst_extents:
185 # See if the extent is contiguous with the last extent seen.
186 if last_ext and (curr_ext.start_block !=
187 last_ext.start_block + last_ext.num_blocks):
188 num_write_seeks += 1
189 last_ext = curr_ext
190
Amin Hassani55c75412019-10-07 11:20:39 -0700191 # Old and new partitions are read once during verification.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800192 read_blocks += partition.old_partition_info.size // manifest.block_size
193 read_blocks += partition.new_partition_info.size // manifest.block_size
Amin Hassani55c75412019-10-07 11:20:39 -0700194
Sen Jiange6e0f042018-05-24 15:40:41 -0700195 stats = {'read_blocks': read_blocks,
196 'written_blocks': written_blocks,
197 'num_write_seeks': num_write_seeks}
198 return stats
199
200 def _DisplayStats(self, manifest):
201 stats = self._GetStats(manifest)
202 DisplayValue('Blocks read', stats['read_blocks'])
203 DisplayValue('Blocks written', stats['written_blocks'])
204 DisplayValue('Seeks when writing', stats['num_write_seeks'])
205
206 def Run(self):
207 """Parse the update payload and display information from it."""
208 self.payload = update_payload.Payload(self.options.payload_file)
209 self.payload.Init()
210 self._DisplayHeader()
211 self._DisplayManifest()
212 if self.options.signatures:
213 self._DisplaySignatures()
214 if self.options.stats:
215 self._DisplayStats(self.payload.manifest)
216 if self.options.list_ops:
217 print()
Håkan Kvist178375a2024-02-23 10:52:24 +0100218 # pylint: disable=no-member
Amin Hassani55c75412019-10-07 11:20:39 -0700219 for partition in self.payload.manifest.partitions:
220 self._DisplayOps('%s install operations' % partition.partition_name,
221 partition.operations)
Sen Jiange6e0f042018-05-24 15:40:41 -0700222
223
224def main():
225 parser = argparse.ArgumentParser(
226 description='Show information about an update payload.')
Andrew Lassalle165843c2019-11-05 13:30:34 -0800227 parser.add_argument('payload_file', type=argparse.FileType('rb'),
Sen Jiange6e0f042018-05-24 15:40:41 -0700228 help='The update payload file.')
229 parser.add_argument('--list_ops', default=False, action='store_true',
230 help='List the install operations and their extents.')
231 parser.add_argument('--stats', default=False, action='store_true',
232 help='Show information about overall input/output.')
233 parser.add_argument('--signatures', default=False, action='store_true',
234 help='Show signatures stored in the payload.')
235 args = parser.parse_args()
236
237 PayloadCommand(args).Run()
238
Andrew Lassalle165843c2019-11-05 13:30:34 -0800239
Sen Jiange6e0f042018-05-24 15:40:41 -0700240if __name__ == '__main__':
241 sys.exit(main())