blob: d10cb241be0e934214414bb2f53c794966cecd46 [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
30MAJOR_PAYLOAD_VERSION_CHROMEOS = 1
31MAJOR_PAYLOAD_VERSION_BRILLO = 2
32
33def DisplayValue(key, value):
34 """Print out a key, value pair with values left-aligned."""
35 if value != None:
36 print('%-*s %s' % (28, key + ':', value))
37 else:
38 raise ValueError('Cannot display an empty value.')
39
40
41def DisplayHexData(data, indent=0):
42 """Print out binary data as a hex values."""
43 for off in range(0, len(data), 16):
44 chunk = data[off:off + 16]
45 print(' ' * indent +
46 ' '.join('%.2x' % ord(c) for c in chunk) +
47 ' ' * (16 - len(chunk)) +
48 ' | ' +
49 ''.join(c if 32 <= ord(c) < 127 else '.' for c in chunk))
50
51
52class PayloadCommand(object):
53 """Show basic information about an update payload.
54
55 This command parses an update payload and displays information from
56 its header and manifest.
57 """
58
59 def __init__(self, options):
60 self.options = options
61 self.payload = None
62
63 def _DisplayHeader(self):
64 """Show information from the payload header."""
65 header = self.payload.header
66 DisplayValue('Payload version', header.version)
67 DisplayValue('Manifest length', header.manifest_len)
68
69 def _DisplayManifest(self):
70 """Show information from the payload manifest."""
71 manifest = self.payload.manifest
72 if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO:
73 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 else:
78 DisplayValue('Number of operations', len(manifest.install_operations))
79 DisplayValue('Number of kernel ops',
80 len(manifest.kernel_install_operations))
81 DisplayValue('Block size', manifest.block_size)
82 DisplayValue('Minor version', manifest.minor_version)
83
84 def _DisplaySignatures(self):
85 """Show information about the signatures from the manifest."""
86 header = self.payload.header
87 if header.metadata_signature_len:
88 offset = header.size + header.manifest_len
89 DisplayValue('Metadata signatures blob',
90 'file_offset=%d (%d bytes)' %
91 (offset, header.metadata_signature_len))
92 # pylint: disable=invalid-unary-operand-type
93 signatures_blob = self.payload.ReadDataBlob(
94 -header.metadata_signature_len,
95 header.metadata_signature_len)
96 self._DisplaySignaturesBlob('Metadata', signatures_blob)
97 else:
98 print('No metadata signatures stored in the payload')
99
100 manifest = self.payload.manifest
101 if manifest.HasField('signatures_offset'):
102 signature_msg = 'blob_offset=%d' % manifest.signatures_offset
103 if manifest.signatures_size:
104 signature_msg += ' (%d bytes)' % manifest.signatures_size
105 DisplayValue('Payload signatures blob', signature_msg)
106 signatures_blob = self.payload.ReadDataBlob(manifest.signatures_offset,
107 manifest.signatures_size)
108 self._DisplaySignaturesBlob('Payload', signatures_blob)
109 else:
110 print('No payload signatures stored in the payload')
111
112 @staticmethod
113 def _DisplaySignaturesBlob(signature_name, signatures_blob):
114 """Show information about the signatures blob."""
115 signatures = update_payload.update_metadata_pb2.Signatures()
116 signatures.ParseFromString(signatures_blob)
117 print('%s signatures: (%d entries)' %
118 (signature_name, len(signatures.signatures)))
119 for signature in signatures.signatures:
120 print(' version=%s, hex_data: (%d bytes)' %
121 (signature.version if signature.HasField('version') else None,
122 len(signature.data)))
123 DisplayHexData(signature.data, indent=4)
124
125
126 def _DisplayOps(self, name, operations):
127 """Show information about the install operations from the manifest.
128
129 The list shown includes operation type, data offset, data length, source
130 extents, source length, destination extents, and destinations length.
131
132 Args:
133 name: The name you want displayed above the operation table.
134 operations: The install_operations object that you want to display
135 information about.
136 """
137 def _DisplayExtents(extents, name):
138 """Show information about extents."""
139 num_blocks = sum([ext.num_blocks for ext in extents])
140 ext_str = ' '.join(
141 '(%s,%s)' % (ext.start_block, ext.num_blocks) for ext in extents)
142 # Make extent list wrap around at 80 chars.
143 ext_str = '\n '.join(textwrap.wrap(ext_str, 74))
144 extent_plural = 's' if len(extents) > 1 else ''
145 block_plural = 's' if num_blocks > 1 else ''
146 print(' %s: %d extent%s (%d block%s)' %
147 (name, len(extents), extent_plural, num_blocks, block_plural))
148 print(' %s' % ext_str)
149
150 op_dict = update_payload.common.OpType.NAMES
151 print('%s:' % name)
152 for op, op_count in itertools.izip(operations, itertools.count()):
153 print(' %d: %s' % (op_count, op_dict[op.type]))
154 if op.HasField('data_offset'):
155 print(' Data offset: %s' % op.data_offset)
156 if op.HasField('data_length'):
157 print(' Data length: %s' % op.data_length)
158 if op.src_extents:
159 _DisplayExtents(op.src_extents, 'Source')
160 if op.dst_extents:
161 _DisplayExtents(op.dst_extents, 'Destination')
162
163 def _GetStats(self, manifest):
164 """Returns various statistics about a payload file.
165
166 Returns a dictionary containing the number of blocks read during payload
167 application, the number of blocks written, and the number of seeks done
168 when writing during operation application.
169 """
170 read_blocks = 0
171 written_blocks = 0
172 num_write_seeks = 0
173 if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO:
174 partitions_operations = [part.operations for part in manifest.partitions]
175 else:
176 partitions_operations = [manifest.install_operations,
177 manifest.kernel_install_operations]
178 for operations in partitions_operations:
179 last_ext = None
180 for curr_op in operations:
181 read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents])
182 written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents])
183 for curr_ext in curr_op.dst_extents:
184 # See if the extent is contiguous with the last extent seen.
185 if last_ext and (curr_ext.start_block !=
186 last_ext.start_block + last_ext.num_blocks):
187 num_write_seeks += 1
188 last_ext = curr_ext
189
Sen Jiange6e0f042018-05-24 15:40:41 -0700190 # Old and new rootfs and kernel are read once during verification
191 read_blocks += manifest.old_rootfs_info.size / manifest.block_size
192 read_blocks += manifest.old_kernel_info.size / manifest.block_size
193 read_blocks += manifest.new_rootfs_info.size / manifest.block_size
194 read_blocks += manifest.new_kernel_info.size / manifest.block_size
195 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()
218 if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO:
219 for partition in self.payload.manifest.partitions:
220 self._DisplayOps('%s install operations' % partition.partition_name,
221 partition.operations)
222 else:
223 self._DisplayOps('Install operations',
224 self.payload.manifest.install_operations)
225 self._DisplayOps('Kernel install operations',
226 self.payload.manifest.kernel_install_operations)
227
228
229def main():
230 parser = argparse.ArgumentParser(
231 description='Show information about an update payload.')
232 parser.add_argument('payload_file', type=file,
233 help='The update payload file.')
234 parser.add_argument('--list_ops', default=False, action='store_true',
235 help='List the install operations and their extents.')
236 parser.add_argument('--stats', default=False, action='store_true',
237 help='Show information about overall input/output.')
238 parser.add_argument('--signatures', default=False, action='store_true',
239 help='Show signatures stored in the payload.')
240 args = parser.parse_args()
241
242 PayloadCommand(args).Run()
243
244if __name__ == '__main__':
245 sys.exit(main())