blob: bb7f8a4100263e78099d145f11a5550429920516 [file] [log] [blame]
Sen Jiange6e0f042018-05-24 15:40:41 -07001#!/usr/bin/env python2
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
21from __future__ import print_function
22
23import argparse
24import itertools
25import sys
26import textwrap
27
28import update_payload
29
Sen Jiange6e0f042018-05-24 15:40:41 -070030MAJOR_PAYLOAD_VERSION_BRILLO = 2
31
32def DisplayValue(key, value):
33 """Print out a key, value pair with values left-aligned."""
34 if value != None:
35 print('%-*s %s' % (28, key + ':', value))
36 else:
37 raise ValueError('Cannot display an empty value.')
38
39
40def DisplayHexData(data, indent=0):
41 """Print out binary data as a hex values."""
42 for off in range(0, len(data), 16):
43 chunk = data[off:off + 16]
44 print(' ' * indent +
45 ' '.join('%.2x' % ord(c) for c in chunk) +
46 ' ' * (16 - len(chunk)) +
47 ' | ' +
48 ''.join(c if 32 <= ord(c) < 127 else '.' for c in chunk))
49
50
51class PayloadCommand(object):
52 """Show basic information about an update payload.
53
54 This command parses an update payload and displays information from
55 its header and manifest.
56 """
57
58 def __init__(self, options):
59 self.options = options
60 self.payload = None
61
62 def _DisplayHeader(self):
63 """Show information from the payload header."""
64 header = self.payload.header
65 DisplayValue('Payload version', header.version)
66 DisplayValue('Manifest length', header.manifest_len)
67
68 def _DisplayManifest(self):
69 """Show information from the payload manifest."""
70 manifest = self.payload.manifest
Amin Hassani55c75412019-10-07 11:20:39 -070071 DisplayValue('Number of partitions', len(manifest.partitions))
72 for partition in manifest.partitions:
73 DisplayValue(' Number of "%s" ops' % partition.partition_name,
74 len(partition.operations))
75
Sen Jiange6e0f042018-05-24 15:40:41 -070076 DisplayValue('Block size', manifest.block_size)
77 DisplayValue('Minor version', manifest.minor_version)
78
79 def _DisplaySignatures(self):
80 """Show information about the signatures from the manifest."""
81 header = self.payload.header
82 if header.metadata_signature_len:
83 offset = header.size + header.manifest_len
84 DisplayValue('Metadata signatures blob',
85 'file_offset=%d (%d bytes)' %
86 (offset, header.metadata_signature_len))
87 # pylint: disable=invalid-unary-operand-type
88 signatures_blob = self.payload.ReadDataBlob(
89 -header.metadata_signature_len,
90 header.metadata_signature_len)
91 self._DisplaySignaturesBlob('Metadata', signatures_blob)
92 else:
93 print('No metadata signatures stored in the payload')
94
95 manifest = self.payload.manifest
96 if manifest.HasField('signatures_offset'):
97 signature_msg = 'blob_offset=%d' % manifest.signatures_offset
98 if manifest.signatures_size:
99 signature_msg += ' (%d bytes)' % manifest.signatures_size
100 DisplayValue('Payload signatures blob', signature_msg)
101 signatures_blob = self.payload.ReadDataBlob(manifest.signatures_offset,
102 manifest.signatures_size)
103 self._DisplaySignaturesBlob('Payload', signatures_blob)
104 else:
105 print('No payload signatures stored in the payload')
106
107 @staticmethod
108 def _DisplaySignaturesBlob(signature_name, signatures_blob):
109 """Show information about the signatures blob."""
110 signatures = update_payload.update_metadata_pb2.Signatures()
111 signatures.ParseFromString(signatures_blob)
112 print('%s signatures: (%d entries)' %
113 (signature_name, len(signatures.signatures)))
114 for signature in signatures.signatures:
115 print(' version=%s, hex_data: (%d bytes)' %
116 (signature.version if signature.HasField('version') else None,
117 len(signature.data)))
118 DisplayHexData(signature.data, indent=4)
119
120
121 def _DisplayOps(self, name, operations):
122 """Show information about the install operations from the manifest.
123
124 The list shown includes operation type, data offset, data length, source
125 extents, source length, destination extents, and destinations length.
126
127 Args:
128 name: The name you want displayed above the operation table.
Amin Hassani55c75412019-10-07 11:20:39 -0700129 operations: The operations object that you want to display information
130 about.
Sen Jiange6e0f042018-05-24 15:40:41 -0700131 """
132 def _DisplayExtents(extents, name):
133 """Show information about extents."""
134 num_blocks = sum([ext.num_blocks for ext in extents])
135 ext_str = ' '.join(
136 '(%s,%s)' % (ext.start_block, ext.num_blocks) for ext in extents)
137 # Make extent list wrap around at 80 chars.
138 ext_str = '\n '.join(textwrap.wrap(ext_str, 74))
139 extent_plural = 's' if len(extents) > 1 else ''
140 block_plural = 's' if num_blocks > 1 else ''
141 print(' %s: %d extent%s (%d block%s)' %
142 (name, len(extents), extent_plural, num_blocks, block_plural))
143 print(' %s' % ext_str)
144
145 op_dict = update_payload.common.OpType.NAMES
146 print('%s:' % name)
147 for op, op_count in itertools.izip(operations, itertools.count()):
148 print(' %d: %s' % (op_count, op_dict[op.type]))
149 if op.HasField('data_offset'):
150 print(' Data offset: %s' % op.data_offset)
151 if op.HasField('data_length'):
152 print(' Data length: %s' % op.data_length)
153 if op.src_extents:
154 _DisplayExtents(op.src_extents, 'Source')
155 if op.dst_extents:
156 _DisplayExtents(op.dst_extents, 'Destination')
157
158 def _GetStats(self, manifest):
159 """Returns various statistics about a payload file.
160
161 Returns a dictionary containing the number of blocks read during payload
162 application, the number of blocks written, and the number of seeks done
163 when writing during operation application.
164 """
165 read_blocks = 0
166 written_blocks = 0
167 num_write_seeks = 0
Amin Hassani55c75412019-10-07 11:20:39 -0700168 for partition in manifest.partitions:
Sen Jiange6e0f042018-05-24 15:40:41 -0700169 last_ext = None
Amin Hassani55c75412019-10-07 11:20:39 -0700170 for curr_op in partition.operations:
Sen Jiange6e0f042018-05-24 15:40:41 -0700171 read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents])
172 written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents])
173 for curr_ext in curr_op.dst_extents:
174 # See if the extent is contiguous with the last extent seen.
175 if last_ext and (curr_ext.start_block !=
176 last_ext.start_block + last_ext.num_blocks):
177 num_write_seeks += 1
178 last_ext = curr_ext
179
Amin Hassani55c75412019-10-07 11:20:39 -0700180 # Old and new partitions are read once during verification.
181 read_blocks += partition.old_partition_info.size / manifest.block_size
182 read_blocks += partition.new_partition_info.size / manifest.block_size
183
Sen Jiange6e0f042018-05-24 15:40:41 -0700184 stats = {'read_blocks': read_blocks,
185 'written_blocks': written_blocks,
186 'num_write_seeks': num_write_seeks}
187 return stats
188
189 def _DisplayStats(self, manifest):
190 stats = self._GetStats(manifest)
191 DisplayValue('Blocks read', stats['read_blocks'])
192 DisplayValue('Blocks written', stats['written_blocks'])
193 DisplayValue('Seeks when writing', stats['num_write_seeks'])
194
195 def Run(self):
196 """Parse the update payload and display information from it."""
197 self.payload = update_payload.Payload(self.options.payload_file)
198 self.payload.Init()
199 self._DisplayHeader()
200 self._DisplayManifest()
201 if self.options.signatures:
202 self._DisplaySignatures()
203 if self.options.stats:
204 self._DisplayStats(self.payload.manifest)
205 if self.options.list_ops:
206 print()
Amin Hassani55c75412019-10-07 11:20:39 -0700207 for partition in self.payload.manifest.partitions:
208 self._DisplayOps('%s install operations' % partition.partition_name,
209 partition.operations)
Sen Jiange6e0f042018-05-24 15:40:41 -0700210
211
212def main():
213 parser = argparse.ArgumentParser(
214 description='Show information about an update payload.')
215 parser.add_argument('payload_file', type=file,
216 help='The update payload file.')
217 parser.add_argument('--list_ops', default=False, action='store_true',
218 help='List the install operations and their extents.')
219 parser.add_argument('--stats', default=False, action='store_true',
220 help='Show information about overall input/output.')
221 parser.add_argument('--signatures', default=False, action='store_true',
222 help='Show signatures stored in the payload.')
223 args = parser.parse_args()
224
225 PayloadCommand(args).Run()
226
227if __name__ == '__main__':
228 sys.exit(main())