-
Notifications
You must be signed in to change notification settings - Fork 0
/
payload_info.py
executable file
·236 lines (202 loc) · 8.96 KB
/
payload_info.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""payload_info: Show information about an update payload."""
from __future__ import absolute_import
from __future__ import print_function
import argparse
import sys
import textwrap
from six.moves import range
import update_payload
MAJOR_PAYLOAD_VERSION_BRILLO = 2
def DisplayValue(key, value):
"""Print out a key, value pair with values left-aligned."""
if value != None:
print('%-*s %s' % (28, key + ':', value))
else:
raise ValueError('Cannot display an empty value.')
def DisplayHexData(data, indent=0):
"""Print out binary data as a hex values."""
for off in range(0, len(data), 16):
chunk = bytearray(data[off:off + 16])
print(' ' * indent +
' '.join('%.2x' % c for c in chunk) +
' ' * (16 - len(chunk)) +
' | ' +
''.join(chr(c) if 32 <= c < 127 else '.' for c in chunk))
class PayloadCommand(object):
"""Show basic information about an update payload.
This command parses an update payload and displays information from
its header and manifest.
"""
def __init__(self, options):
self.options = options
self.payload = None
def _DisplayHeader(self):
"""Show information from the payload header."""
header = self.payload.header
DisplayValue('Payload version', header.version)
DisplayValue('Manifest length', header.manifest_len)
def _DisplayManifest(self):
"""Show information from the payload manifest."""
manifest = self.payload.manifest
DisplayValue('Number of partitions', len(manifest.partitions))
for partition in manifest.partitions:
DisplayValue(' Number of "%s" ops' % partition.partition_name,
len(partition.operations))
for partition in manifest.partitions:
DisplayValue(" Timestamp for " +
partition.partition_name, partition.version)
for partition in manifest.partitions:
DisplayValue(" COW Size for " +
partition.partition_name, partition.estimate_cow_size)
DisplayValue('Block size', manifest.block_size)
DisplayValue('Minor version', manifest.minor_version)
def _DisplaySignatures(self):
"""Show information about the signatures from the manifest."""
header = self.payload.header
if header.metadata_signature_len:
offset = header.size + header.manifest_len
DisplayValue('Metadata signatures blob',
'file_offset=%d (%d bytes)' %
(offset, header.metadata_signature_len))
# pylint: disable=invalid-unary-operand-type
signatures_blob = self.payload.ReadDataBlob(
-header.metadata_signature_len,
header.metadata_signature_len)
self._DisplaySignaturesBlob('Metadata', signatures_blob)
else:
print('No metadata signatures stored in the payload')
manifest = self.payload.manifest
if manifest.HasField('signatures_offset'):
signature_msg = 'blob_offset=%d' % manifest.signatures_offset
if manifest.signatures_size:
signature_msg += ' (%d bytes)' % manifest.signatures_size
DisplayValue('Payload signatures blob', signature_msg)
signatures_blob = self.payload.ReadDataBlob(manifest.signatures_offset,
manifest.signatures_size)
self._DisplaySignaturesBlob('Payload', signatures_blob)
else:
print('No payload signatures stored in the payload')
@staticmethod
def _DisplaySignaturesBlob(signature_name, signatures_blob):
"""Show information about the signatures blob."""
signatures = update_payload.update_metadata_pb2.Signatures()
signatures.ParseFromString(signatures_blob)
print('%s signatures: (%d entries)' %
(signature_name, len(signatures.signatures)))
for signature in signatures.signatures:
print(' version=%s, hex_data: (%d bytes)' %
(signature.version if signature.HasField('version') else None,
len(signature.data)))
DisplayHexData(signature.data, indent=4)
def _DisplayOps(self, name, operations):
"""Show information about the install operations from the manifest.
The list shown includes operation type, data offset, data length, source
extents, source length, destination extents, and destinations length.
Args:
name: The name you want displayed above the operation table.
operations: The operations object that you want to display information
about.
"""
def _DisplayExtents(extents, name):
"""Show information about extents."""
num_blocks = sum([ext.num_blocks for ext in extents])
ext_str = ' '.join(
'(%s,%s)' % (ext.start_block, ext.num_blocks) for ext in extents)
# Make extent list wrap around at 80 chars.
ext_str = '\n '.join(textwrap.wrap(ext_str, 74))
extent_plural = 's' if len(extents) > 1 else ''
block_plural = 's' if num_blocks > 1 else ''
print(' %s: %d extent%s (%d block%s)' %
(name, len(extents), extent_plural, num_blocks, block_plural))
print(' %s' % ext_str)
op_dict = update_payload.common.OpType.NAMES
print('%s:' % name)
for op_count, op in enumerate(operations):
print(' %d: %s' % (op_count, op_dict[op.type]))
if op.HasField('data_offset'):
print(' Data offset: %s' % op.data_offset)
if op.HasField('data_length'):
print(' Data length: %s' % op.data_length)
if op.src_extents:
_DisplayExtents(op.src_extents, 'Source')
if op.dst_extents:
_DisplayExtents(op.dst_extents, 'Destination')
def _GetStats(self, manifest):
"""Returns various statistics about a payload file.
Returns a dictionary containing the number of blocks read during payload
application, the number of blocks written, and the number of seeks done
when writing during operation application.
"""
read_blocks = 0
written_blocks = 0
num_write_seeks = 0
for partition in manifest.partitions:
last_ext = None
for curr_op in partition.operations:
read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents])
written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents])
for curr_ext in curr_op.dst_extents:
# See if the extent is contiguous with the last extent seen.
if last_ext and (curr_ext.start_block !=
last_ext.start_block + last_ext.num_blocks):
num_write_seeks += 1
last_ext = curr_ext
# Old and new partitions are read once during verification.
read_blocks += partition.old_partition_info.size // manifest.block_size
read_blocks += partition.new_partition_info.size // manifest.block_size
stats = {'read_blocks': read_blocks,
'written_blocks': written_blocks,
'num_write_seeks': num_write_seeks}
return stats
def _DisplayStats(self, manifest):
stats = self._GetStats(manifest)
DisplayValue('Blocks read', stats['read_blocks'])
DisplayValue('Blocks written', stats['written_blocks'])
DisplayValue('Seeks when writing', stats['num_write_seeks'])
def Run(self):
"""Parse the update payload and display information from it."""
self.payload = update_payload.Payload(self.options.payload_file)
self.payload.Init()
self._DisplayHeader()
self._DisplayManifest()
if self.options.signatures:
self._DisplaySignatures()
if self.options.stats:
self._DisplayStats(self.payload.manifest)
if self.options.list_ops:
print()
for partition in self.payload.manifest.partitions:
self._DisplayOps('%s install operations' % partition.partition_name,
partition.operations)
def main():
parser = argparse.ArgumentParser(
description='Show information about an update payload.')
parser.add_argument('payload_file', type=argparse.FileType('rb'),
help='The update payload file.')
parser.add_argument('--list_ops', default=False, action='store_true',
help='List the install operations and their extents.')
parser.add_argument('--stats', default=False, action='store_true',
help='Show information about overall input/output.')
parser.add_argument('--signatures', default=False, action='store_true',
help='Show signatures stored in the payload.')
args = parser.parse_args()
PayloadCommand(args).Run()
if __name__ == '__main__':
sys.exit(main())