blob: 965bb76f8c51bdefee83e0449538c56ff7e9ed66 [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))
77
Sen Jiange6e0f042018-05-24 15:40:41 -070078 DisplayValue('Block size', manifest.block_size)
79 DisplayValue('Minor version', manifest.minor_version)
80
81 def _DisplaySignatures(self):
82 """Show information about the signatures from the manifest."""
83 header = self.payload.header
84 if header.metadata_signature_len:
85 offset = header.size + header.manifest_len
86 DisplayValue('Metadata signatures blob',
87 'file_offset=%d (%d bytes)' %
88 (offset, header.metadata_signature_len))
89 # pylint: disable=invalid-unary-operand-type
90 signatures_blob = self.payload.ReadDataBlob(
91 -header.metadata_signature_len,
92 header.metadata_signature_len)
93 self._DisplaySignaturesBlob('Metadata', signatures_blob)
94 else:
95 print('No metadata signatures stored in the payload')
96
97 manifest = self.payload.manifest
98 if manifest.HasField('signatures_offset'):
99 signature_msg = 'blob_offset=%d' % manifest.signatures_offset
100 if manifest.signatures_size:
101 signature_msg += ' (%d bytes)' % manifest.signatures_size
102 DisplayValue('Payload signatures blob', signature_msg)
103 signatures_blob = self.payload.ReadDataBlob(manifest.signatures_offset,
104 manifest.signatures_size)
105 self._DisplaySignaturesBlob('Payload', signatures_blob)
106 else:
107 print('No payload signatures stored in the payload')
108
109 @staticmethod
110 def _DisplaySignaturesBlob(signature_name, signatures_blob):
111 """Show information about the signatures blob."""
112 signatures = update_payload.update_metadata_pb2.Signatures()
113 signatures.ParseFromString(signatures_blob)
114 print('%s signatures: (%d entries)' %
115 (signature_name, len(signatures.signatures)))
116 for signature in signatures.signatures:
117 print(' version=%s, hex_data: (%d bytes)' %
118 (signature.version if signature.HasField('version') else None,
119 len(signature.data)))
120 DisplayHexData(signature.data, indent=4)
121
122
123 def _DisplayOps(self, name, operations):
124 """Show information about the install operations from the manifest.
125
126 The list shown includes operation type, data offset, data length, source
127 extents, source length, destination extents, and destinations length.
128
129 Args:
130 name: The name you want displayed above the operation table.
Amin Hassani55c75412019-10-07 11:20:39 -0700131 operations: The operations object that you want to display information
132 about.
Sen Jiange6e0f042018-05-24 15:40:41 -0700133 """
134 def _DisplayExtents(extents, name):
135 """Show information about extents."""
136 num_blocks = sum([ext.num_blocks for ext in extents])
137 ext_str = ' '.join(
138 '(%s,%s)' % (ext.start_block, ext.num_blocks) for ext in extents)
139 # Make extent list wrap around at 80 chars.
140 ext_str = '\n '.join(textwrap.wrap(ext_str, 74))
141 extent_plural = 's' if len(extents) > 1 else ''
142 block_plural = 's' if num_blocks > 1 else ''
143 print(' %s: %d extent%s (%d block%s)' %
144 (name, len(extents), extent_plural, num_blocks, block_plural))
145 print(' %s' % ext_str)
146
147 op_dict = update_payload.common.OpType.NAMES
148 print('%s:' % name)
Andrew Lassalle165843c2019-11-05 13:30:34 -0800149 for op_count, op in enumerate(operations):
Sen Jiange6e0f042018-05-24 15:40:41 -0700150 print(' %d: %s' % (op_count, op_dict[op.type]))
151 if op.HasField('data_offset'):
152 print(' Data offset: %s' % op.data_offset)
153 if op.HasField('data_length'):
154 print(' Data length: %s' % op.data_length)
155 if op.src_extents:
156 _DisplayExtents(op.src_extents, 'Source')
157 if op.dst_extents:
158 _DisplayExtents(op.dst_extents, 'Destination')
159
160 def _GetStats(self, manifest):
161 """Returns various statistics about a payload file.
162
163 Returns a dictionary containing the number of blocks read during payload
164 application, the number of blocks written, and the number of seeks done
165 when writing during operation application.
166 """
167 read_blocks = 0
168 written_blocks = 0
169 num_write_seeks = 0
Amin Hassani55c75412019-10-07 11:20:39 -0700170 for partition in manifest.partitions:
Sen Jiange6e0f042018-05-24 15:40:41 -0700171 last_ext = None
Amin Hassani55c75412019-10-07 11:20:39 -0700172 for curr_op in partition.operations:
Sen Jiange6e0f042018-05-24 15:40:41 -0700173 read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents])
174 written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents])
175 for curr_ext in curr_op.dst_extents:
176 # See if the extent is contiguous with the last extent seen.
177 if last_ext and (curr_ext.start_block !=
178 last_ext.start_block + last_ext.num_blocks):
179 num_write_seeks += 1
180 last_ext = curr_ext
181
Amin Hassani55c75412019-10-07 11:20:39 -0700182 # Old and new partitions are read once during verification.
Andrew Lassalle165843c2019-11-05 13:30:34 -0800183 read_blocks += partition.old_partition_info.size // manifest.block_size
184 read_blocks += partition.new_partition_info.size // manifest.block_size
Amin Hassani55c75412019-10-07 11:20:39 -0700185
Sen Jiange6e0f042018-05-24 15:40:41 -0700186 stats = {'read_blocks': read_blocks,
187 'written_blocks': written_blocks,
188 'num_write_seeks': num_write_seeks}
189 return stats
190
191 def _DisplayStats(self, manifest):
192 stats = self._GetStats(manifest)
193 DisplayValue('Blocks read', stats['read_blocks'])
194 DisplayValue('Blocks written', stats['written_blocks'])
195 DisplayValue('Seeks when writing', stats['num_write_seeks'])
196
197 def Run(self):
198 """Parse the update payload and display information from it."""
199 self.payload = update_payload.Payload(self.options.payload_file)
200 self.payload.Init()
201 self._DisplayHeader()
202 self._DisplayManifest()
203 if self.options.signatures:
204 self._DisplaySignatures()
205 if self.options.stats:
206 self._DisplayStats(self.payload.manifest)
207 if self.options.list_ops:
208 print()
Amin Hassani55c75412019-10-07 11:20:39 -0700209 for partition in self.payload.manifest.partitions:
210 self._DisplayOps('%s install operations' % partition.partition_name,
211 partition.operations)
Sen Jiange6e0f042018-05-24 15:40:41 -0700212
213
214def main():
215 parser = argparse.ArgumentParser(
216 description='Show information about an update payload.')
Andrew Lassalle165843c2019-11-05 13:30:34 -0800217 parser.add_argument('payload_file', type=argparse.FileType('rb'),
Sen Jiange6e0f042018-05-24 15:40:41 -0700218 help='The update payload file.')
219 parser.add_argument('--list_ops', default=False, action='store_true',
220 help='List the install operations and their extents.')
221 parser.add_argument('--stats', default=False, action='store_true',
222 help='Show information about overall input/output.')
223 parser.add_argument('--signatures', default=False, action='store_true',
224 help='Show signatures stored in the payload.')
225 args = parser.parse_args()
226
227 PayloadCommand(args).Run()
228
Andrew Lassalle165843c2019-11-05 13:30:34 -0800229
Sen Jiange6e0f042018-05-24 15:40:41 -0700230if __name__ == '__main__':
231 sys.exit(main())