blob: e470ac4ecdd8135f1652eac07489632e4856733c [file] [log] [blame]
Gilad Arnold553b0ec2013-01-26 01:00:39 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Applying a Chrome OS update payload.
6
7This module is used internally by the main Payload class for applying an update
8payload. The interface for invoking the applier is as follows:
9
10 applier = PayloadApplier(payload)
11 applier.Run(...)
12
13"""
14
Allie Wood12f59aa2015-04-06 11:05:12 -070015from __future__ import print_function
16
Gilad Arnold553b0ec2013-01-26 01:00:39 -080017import array
18import bz2
19import hashlib
Gilad Arnold658185a2013-05-08 17:57:54 -070020import itertools
Gilad Arnold553b0ec2013-01-26 01:00:39 -080021import os
22import shutil
23import subprocess
24import sys
25import tempfile
26
Amin Hassanib05a65a2017-12-18 15:15:32 -080027from update_payload import common
28from update_payload.error import PayloadError
Gilad Arnold553b0ec2013-01-26 01:00:39 -080029
30
31#
32# Helper functions.
33#
Gilad Arnold382df5c2013-05-03 12:49:28 -070034def _VerifySha256(file_obj, expected_hash, name, length=-1):
Gilad Arnold553b0ec2013-01-26 01:00:39 -080035 """Verifies the SHA256 hash of a file.
36
37 Args:
38 file_obj: file object to read
39 expected_hash: the hash digest we expect to be getting
40 name: name string of this hash, for error reporting
Gilad Arnold382df5c2013-05-03 12:49:28 -070041 length: precise length of data to verify (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -070042
Gilad Arnold553b0ec2013-01-26 01:00:39 -080043 Raises:
Gilad Arnold382df5c2013-05-03 12:49:28 -070044 PayloadError if computed hash doesn't match expected one, or if fails to
45 read the specified length of data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080046 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -080047 hasher = hashlib.sha256()
48 block_length = 1024 * 1024
Gilad Arnold382df5c2013-05-03 12:49:28 -070049 max_length = length if length >= 0 else sys.maxint
Gilad Arnold553b0ec2013-01-26 01:00:39 -080050
Gilad Arnold382df5c2013-05-03 12:49:28 -070051 while max_length > 0:
Gilad Arnold553b0ec2013-01-26 01:00:39 -080052 read_length = min(max_length, block_length)
53 data = file_obj.read(read_length)
54 if not data:
55 break
56 max_length -= len(data)
57 hasher.update(data)
58
Gilad Arnold382df5c2013-05-03 12:49:28 -070059 if length >= 0 and max_length > 0:
60 raise PayloadError(
61 'insufficient data (%d instead of %d) when verifying %s' %
62 (length - max_length, length, name))
63
Gilad Arnold553b0ec2013-01-26 01:00:39 -080064 actual_hash = hasher.digest()
65 if actual_hash != expected_hash:
66 raise PayloadError('%s hash (%s) not as expected (%s)' %
Gilad Arnold96405372013-05-04 00:24:58 -070067 (name, common.FormatSha256(actual_hash),
68 common.FormatSha256(expected_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -080069
70
71def _ReadExtents(file_obj, extents, block_size, max_length=-1):
72 """Reads data from file as defined by extent sequence.
73
74 This tries to be efficient by not copying data as it is read in chunks.
75
76 Args:
77 file_obj: file object
78 extents: sequence of block extents (offset and length)
79 block_size: size of each block
80 max_length: maximum length to read (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -070081
Gilad Arnold553b0ec2013-01-26 01:00:39 -080082 Returns:
83 A character array containing the concatenated read data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080084 """
85 data = array.array('c')
Gilad Arnold272a4992013-05-08 13:12:53 -070086 if max_length < 0:
87 max_length = sys.maxint
Gilad Arnold553b0ec2013-01-26 01:00:39 -080088 for ex in extents:
89 if max_length == 0:
90 break
Gilad Arnold272a4992013-05-08 13:12:53 -070091 read_length = min(max_length, ex.num_blocks * block_size)
Gilad Arnold658185a2013-05-08 17:57:54 -070092
93 # Fill with zeros or read from file, depending on the type of extent.
94 if ex.start_block == common.PSEUDO_EXTENT_MARKER:
95 data.extend(itertools.repeat('\0', read_length))
96 else:
97 file_obj.seek(ex.start_block * block_size)
98 data.fromfile(file_obj, read_length)
99
Gilad Arnold272a4992013-05-08 13:12:53 -0700100 max_length -= read_length
Gilad Arnold658185a2013-05-08 17:57:54 -0700101
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800102 return data
103
104
105def _WriteExtents(file_obj, data, extents, block_size, base_name):
Gilad Arnold272a4992013-05-08 13:12:53 -0700106 """Writes data to file as defined by extent sequence.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800107
108 This tries to be efficient by not copy data as it is written in chunks.
109
110 Args:
111 file_obj: file object
112 data: data to write
113 extents: sequence of block extents (offset and length)
114 block_size: size of each block
Gilad Arnold272a4992013-05-08 13:12:53 -0700115 base_name: name string of extent sequence for error reporting
Allie Wood12f59aa2015-04-06 11:05:12 -0700116
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800117 Raises:
118 PayloadError when things don't add up.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800119 """
120 data_offset = 0
121 data_length = len(data)
122 for ex, ex_name in common.ExtentIter(extents, base_name):
Gilad Arnold272a4992013-05-08 13:12:53 -0700123 if not data_length:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800124 raise PayloadError('%s: more write extents than data' % ex_name)
Gilad Arnold272a4992013-05-08 13:12:53 -0700125 write_length = min(data_length, ex.num_blocks * block_size)
Gilad Arnold658185a2013-05-08 17:57:54 -0700126
127 # Only do actual writing if this is not a pseudo-extent.
128 if ex.start_block != common.PSEUDO_EXTENT_MARKER:
129 file_obj.seek(ex.start_block * block_size)
130 data_view = buffer(data, data_offset, write_length)
131 file_obj.write(data_view)
132
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800133 data_offset += write_length
Gilad Arnold272a4992013-05-08 13:12:53 -0700134 data_length -= write_length
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800135
Gilad Arnold272a4992013-05-08 13:12:53 -0700136 if data_length:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800137 raise PayloadError('%s: more data than write extents' % base_name)
138
139
Gilad Arnold272a4992013-05-08 13:12:53 -0700140def _ExtentsToBspatchArg(extents, block_size, base_name, data_length=-1):
141 """Translates an extent sequence into a bspatch-compatible string argument.
142
143 Args:
144 extents: sequence of block extents (offset and length)
145 block_size: size of each block
146 base_name: name string of extent sequence for error reporting
147 data_length: the actual total length of the data in bytes (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -0700148
Gilad Arnold272a4992013-05-08 13:12:53 -0700149 Returns:
150 A tuple consisting of (i) a string of the form
151 "off_1:len_1,...,off_n:len_n", (ii) an offset where zero padding is needed
152 for filling the last extent, (iii) the length of the padding (zero means no
153 padding is needed and the extents cover the full length of data).
Allie Wood12f59aa2015-04-06 11:05:12 -0700154
Gilad Arnold272a4992013-05-08 13:12:53 -0700155 Raises:
156 PayloadError if data_length is too short or too long.
Gilad Arnold272a4992013-05-08 13:12:53 -0700157 """
158 arg = ''
159 pad_off = pad_len = 0
160 if data_length < 0:
161 data_length = sys.maxint
162 for ex, ex_name in common.ExtentIter(extents, base_name):
163 if not data_length:
164 raise PayloadError('%s: more extents than total data length' % ex_name)
Gilad Arnold658185a2013-05-08 17:57:54 -0700165
166 is_pseudo = ex.start_block == common.PSEUDO_EXTENT_MARKER
167 start_byte = -1 if is_pseudo else ex.start_block * block_size
Gilad Arnold272a4992013-05-08 13:12:53 -0700168 num_bytes = ex.num_blocks * block_size
169 if data_length < num_bytes:
Gilad Arnold658185a2013-05-08 17:57:54 -0700170 # We're only padding a real extent.
171 if not is_pseudo:
172 pad_off = start_byte + data_length
173 pad_len = num_bytes - data_length
174
Gilad Arnold272a4992013-05-08 13:12:53 -0700175 num_bytes = data_length
Gilad Arnold658185a2013-05-08 17:57:54 -0700176
Gilad Arnold272a4992013-05-08 13:12:53 -0700177 arg += '%s%d:%d' % (arg and ',', start_byte, num_bytes)
178 data_length -= num_bytes
179
180 if data_length:
181 raise PayloadError('%s: extents not covering full data length' % base_name)
182
183 return arg, pad_off, pad_len
184
185
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800186#
187# Payload application.
188#
189class PayloadApplier(object):
190 """Applying an update payload.
191
192 This is a short-lived object whose purpose is to isolate the logic used for
193 applying an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800194 """
195
Gilad Arnold21a02502013-08-22 16:59:48 -0700196 def __init__(self, payload, bsdiff_in_place=True, bspatch_path=None,
Amin Hassani5ef5d452017-08-04 13:10:59 -0700197 puffpatch_path=None, truncate_to_expected_size=True):
Gilad Arnold272a4992013-05-08 13:12:53 -0700198 """Initialize the applier.
199
200 Args:
201 payload: the payload object to check
202 bsdiff_in_place: whether to perform BSDIFF operation in-place (optional)
Gilad Arnold21a02502013-08-22 16:59:48 -0700203 bspatch_path: path to the bspatch binary (optional)
Amin Hassani5ef5d452017-08-04 13:10:59 -0700204 puffpatch_path: path to the puffpatch binary (optional)
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700205 truncate_to_expected_size: whether to truncate the resulting partitions
206 to their expected sizes, as specified in the
207 payload (optional)
Gilad Arnold272a4992013-05-08 13:12:53 -0700208 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800209 assert payload.is_init, 'uninitialized update payload'
210 self.payload = payload
211 self.block_size = payload.manifest.block_size
Allie Wood12f59aa2015-04-06 11:05:12 -0700212 self.minor_version = payload.manifest.minor_version
Gilad Arnold272a4992013-05-08 13:12:53 -0700213 self.bsdiff_in_place = bsdiff_in_place
Gilad Arnold21a02502013-08-22 16:59:48 -0700214 self.bspatch_path = bspatch_path or 'bspatch'
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700215 self.puffpatch_path = puffpatch_path or 'puffin'
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700216 self.truncate_to_expected_size = truncate_to_expected_size
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800217
218 def _ApplyReplaceOperation(self, op, op_name, out_data, part_file, part_size):
Amin Hassanib44f73b2017-12-15 17:45:49 +0000219 """Applies a REPLACE{,_BZ} operation.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800220
221 Args:
222 op: the operation object
223 op_name: name string for error reporting
224 out_data: the data to be written
225 part_file: the partition file object
226 part_size: the size of the partition
Allie Wood12f59aa2015-04-06 11:05:12 -0700227
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800228 Raises:
229 PayloadError if something goes wrong.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800230 """
231 block_size = self.block_size
232 data_length = len(out_data)
233
234 # Decompress data if needed.
235 if op.type == common.OpType.REPLACE_BZ:
236 out_data = bz2.decompress(out_data)
237 data_length = len(out_data)
238
239 # Write data to blocks specified in dst extents.
240 data_start = 0
241 for ex, ex_name in common.ExtentIter(op.dst_extents,
242 '%s.dst_extents' % op_name):
243 start_block = ex.start_block
244 num_blocks = ex.num_blocks
245 count = num_blocks * block_size
246
247 # Make sure it's not a fake (signature) operation.
248 if start_block != common.PSEUDO_EXTENT_MARKER:
249 data_end = data_start + count
250
251 # Make sure we're not running past partition boundary.
252 if (start_block + num_blocks) * block_size > part_size:
253 raise PayloadError(
254 '%s: extent (%s) exceeds partition size (%d)' %
255 (ex_name, common.FormatExtent(ex, block_size),
256 part_size))
257
258 # Make sure that we have enough data to write.
259 if data_end >= data_length + block_size:
260 raise PayloadError(
261 '%s: more dst blocks than data (even with padding)')
262
263 # Pad with zeros if necessary.
264 if data_end > data_length:
265 padding = data_end - data_length
266 out_data += '\0' * padding
267
268 self.payload.payload_file.seek(start_block * block_size)
269 part_file.seek(start_block * block_size)
270 part_file.write(out_data[data_start:data_end])
271
272 data_start += count
273
274 # Make sure we wrote all data.
275 if data_start < data_length:
276 raise PayloadError('%s: wrote fewer bytes (%d) than expected (%d)' %
277 (op_name, data_start, data_length))
278
279 def _ApplyMoveOperation(self, op, op_name, part_file):
280 """Applies a MOVE operation.
281
Gilad Arnold658185a2013-05-08 17:57:54 -0700282 Note that this operation must read the whole block data from the input and
283 only then dump it, due to our in-place update semantics; otherwise, it
284 might clobber data midway through.
285
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800286 Args:
287 op: the operation object
288 op_name: name string for error reporting
289 part_file: the partition file object
Allie Wood12f59aa2015-04-06 11:05:12 -0700290
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800291 Raises:
292 PayloadError if something goes wrong.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800293 """
294 block_size = self.block_size
295
296 # Gather input raw data from src extents.
297 in_data = _ReadExtents(part_file, op.src_extents, block_size)
298
299 # Dump extracted data to dst extents.
300 _WriteExtents(part_file, in_data, op.dst_extents, block_size,
301 '%s.dst_extents' % op_name)
302
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700303 def _ApplyZeroOperation(self, op, op_name, part_file):
304 """Applies a ZERO operation.
305
306 Args:
307 op: the operation object
308 op_name: name string for error reporting
309 part_file: the partition file object
310
311 Raises:
312 PayloadError if something goes wrong.
313 """
314 block_size = self.block_size
315 base_name = '%s.dst_extents' % op_name
316
317 # Iterate over the extents and write zero.
Amin Hassanib05a65a2017-12-18 15:15:32 -0800318 # pylint: disable=unused-variable
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700319 for ex, ex_name in common.ExtentIter(op.dst_extents, base_name):
320 # Only do actual writing if this is not a pseudo-extent.
321 if ex.start_block != common.PSEUDO_EXTENT_MARKER:
322 part_file.seek(ex.start_block * block_size)
323 part_file.write('\0' * (ex.num_blocks * block_size))
324
Allie Wood12f59aa2015-04-06 11:05:12 -0700325 def _ApplySourceCopyOperation(self, op, op_name, old_part_file,
326 new_part_file):
327 """Applies a SOURCE_COPY operation.
328
329 Args:
330 op: the operation object
331 op_name: name string for error reporting
332 old_part_file: the old partition file object
333 new_part_file: the new partition file object
334
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800335 Raises:
336 PayloadError if something goes wrong.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800337 """
Allie Wood12f59aa2015-04-06 11:05:12 -0700338 if not old_part_file:
339 raise PayloadError(
340 '%s: no source partition file provided for operation type (%d)' %
341 (op_name, op.type))
342
343 block_size = self.block_size
344
345 # Gather input raw data from src extents.
346 in_data = _ReadExtents(old_part_file, op.src_extents, block_size)
347
348 # Dump extracted data to dst extents.
349 _WriteExtents(new_part_file, in_data, op.dst_extents, block_size,
350 '%s.dst_extents' % op_name)
351
Amin Hassaniefa62d92017-11-09 13:46:56 -0800352 def _BytesInExtents(self, extents, base_name):
353 """Counts the length of extents in bytes.
354
355 Args:
356 extents: The list of Extents.
357 base_name: For error reporting.
358
359 Returns:
360 The number of bytes in extents.
361 """
362
363 length = 0
Amin Hassanib05a65a2017-12-18 15:15:32 -0800364 # pylint: disable=unused-variable
Amin Hassaniefa62d92017-11-09 13:46:56 -0800365 for ex, ex_name in common.ExtentIter(extents, base_name):
366 length += ex.num_blocks * self.block_size
367 return length
368
Sen Jiang92161a72016-06-28 16:09:38 -0700369 def _ApplyDiffOperation(self, op, op_name, patch_data, old_part_file,
370 new_part_file):
Amin Hassaniefa62d92017-11-09 13:46:56 -0800371 """Applies a SOURCE_BSDIFF, BROTLI_BSDIFF or PUFFDIFF operation.
Allie Wood12f59aa2015-04-06 11:05:12 -0700372
373 Args:
374 op: the operation object
375 op_name: name string for error reporting
376 patch_data: the binary patch content
377 old_part_file: the source partition file object
378 new_part_file: the target partition file object
379
380 Raises:
381 PayloadError if something goes wrong.
Allie Wood12f59aa2015-04-06 11:05:12 -0700382 """
383 if not old_part_file:
384 raise PayloadError(
385 '%s: no source partition file provided for operation type (%d)' %
386 (op_name, op.type))
387
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800388 block_size = self.block_size
389
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800390 # Dump patch data to file.
391 with tempfile.NamedTemporaryFile(delete=False) as patch_file:
392 patch_file_name = patch_file.name
393 patch_file.write(patch_data)
394
Allie Wood12f59aa2015-04-06 11:05:12 -0700395 if (hasattr(new_part_file, 'fileno') and
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700396 ((not old_part_file) or hasattr(old_part_file, 'fileno'))):
Gilad Arnold272a4992013-05-08 13:12:53 -0700397 # Construct input and output extents argument for bspatch.
Amin Hassaniefa62d92017-11-09 13:46:56 -0800398
Gilad Arnold272a4992013-05-08 13:12:53 -0700399 in_extents_arg, _, _ = _ExtentsToBspatchArg(
400 op.src_extents, block_size, '%s.src_extents' % op_name,
Amin Hassaniefa62d92017-11-09 13:46:56 -0800401 data_length=op.src_length if op.src_length else
402 self._BytesInExtents(op.src_extents, "%s.src_extents"))
Gilad Arnold272a4992013-05-08 13:12:53 -0700403 out_extents_arg, pad_off, pad_len = _ExtentsToBspatchArg(
404 op.dst_extents, block_size, '%s.dst_extents' % op_name,
Amin Hassaniefa62d92017-11-09 13:46:56 -0800405 data_length=op.dst_length if op.dst_length else
406 self._BytesInExtents(op.dst_extents, "%s.dst_extents"))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800407
Allie Wood12f59aa2015-04-06 11:05:12 -0700408 new_file_name = '/dev/fd/%d' % new_part_file.fileno()
409 # Diff from source partition.
410 old_file_name = '/dev/fd/%d' % old_part_file.fileno()
411
Amin Hassaniefa62d92017-11-09 13:46:56 -0800412 if op.type in (common.OpType.BSDIFF, common.OpType.SOURCE_BSDIFF,
413 common.OpType.BROTLI_BSDIFF):
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700414 # Invoke bspatch on partition file with extents args.
415 bspatch_cmd = [self.bspatch_path, old_file_name, new_file_name,
416 patch_file_name, in_extents_arg, out_extents_arg]
417 subprocess.check_call(bspatch_cmd)
418 elif op.type == common.OpType.PUFFDIFF:
419 # Invoke puffpatch on partition file with extents args.
420 puffpatch_cmd = [self.puffpatch_path,
421 "--operation=puffpatch",
422 "--src_file=%s" % old_file_name,
423 "--dst_file=%s" % new_file_name,
424 "--patch_file=%s" % patch_file_name,
425 "--src_extents=%s" % in_extents_arg,
426 "--dst_extents=%s" % out_extents_arg]
427 subprocess.check_call(puffpatch_cmd)
428 else:
429 raise PayloadError("Unknown operation %s", op.type)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800430
Gilad Arnold272a4992013-05-08 13:12:53 -0700431 # Pad with zeros past the total output length.
432 if pad_len:
Allie Wood12f59aa2015-04-06 11:05:12 -0700433 new_part_file.seek(pad_off)
434 new_part_file.write('\0' * pad_len)
Gilad Arnold272a4992013-05-08 13:12:53 -0700435 else:
436 # Gather input raw data and write to a temp file.
Allie Wood12f59aa2015-04-06 11:05:12 -0700437 input_part_file = old_part_file if old_part_file else new_part_file
438 in_data = _ReadExtents(input_part_file, op.src_extents, block_size,
Amin Hassaniefa62d92017-11-09 13:46:56 -0800439 max_length=op.src_length if op.src_length else
440 self._BytesInExtents(op.src_extents,
441 "%s.src_extents"))
Gilad Arnold272a4992013-05-08 13:12:53 -0700442 with tempfile.NamedTemporaryFile(delete=False) as in_file:
443 in_file_name = in_file.name
444 in_file.write(in_data)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800445
Allie Wood12f59aa2015-04-06 11:05:12 -0700446 # Allocate temporary output file.
Gilad Arnold272a4992013-05-08 13:12:53 -0700447 with tempfile.NamedTemporaryFile(delete=False) as out_file:
448 out_file_name = out_file.name
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800449
Amin Hassaniefa62d92017-11-09 13:46:56 -0800450 if op.type in (common.OpType.BSDIFF, common.OpType.SOURCE_BSDIFF,
451 common.OpType.BROTLI_BSDIFF):
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700452 # Invoke bspatch.
453 bspatch_cmd = [self.bspatch_path, in_file_name, out_file_name,
454 patch_file_name]
455 subprocess.check_call(bspatch_cmd)
456 elif op.type == common.OpType.PUFFDIFF:
457 # Invoke puffpatch.
458 puffpatch_cmd = [self.puffpatch_path,
459 "--operation=puffpatch",
460 "--src_file=%s" % in_file_name,
461 "--dst_file=%s" % out_file_name,
462 "--patch_file=%s" % patch_file_name]
463 subprocess.check_call(puffpatch_cmd)
464 else:
465 raise PayloadError("Unknown operation %s", op.type)
Gilad Arnold272a4992013-05-08 13:12:53 -0700466
467 # Read output.
468 with open(out_file_name, 'rb') as out_file:
469 out_data = out_file.read()
470 if len(out_data) != op.dst_length:
471 raise PayloadError(
472 '%s: actual patched data length (%d) not as expected (%d)' %
473 (op_name, len(out_data), op.dst_length))
474
475 # Write output back to partition, with padding.
476 unaligned_out_len = len(out_data) % block_size
477 if unaligned_out_len:
478 out_data += '\0' * (block_size - unaligned_out_len)
Allie Wood12f59aa2015-04-06 11:05:12 -0700479 _WriteExtents(new_part_file, out_data, op.dst_extents, block_size,
Gilad Arnold272a4992013-05-08 13:12:53 -0700480 '%s.dst_extents' % op_name)
481
482 # Delete input/output files.
483 os.remove(in_file_name)
484 os.remove(out_file_name)
485
486 # Delete patch file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800487 os.remove(patch_file_name)
488
Allie Wood12f59aa2015-04-06 11:05:12 -0700489 def _ApplyOperations(self, operations, base_name, old_part_file,
490 new_part_file, part_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800491 """Applies a sequence of update operations to a partition.
492
Allie Wood12f59aa2015-04-06 11:05:12 -0700493 This assumes an in-place update semantics for MOVE and BSDIFF, namely all
494 reads are performed first, then the data is processed and written back to
495 the same file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800496
497 Args:
498 operations: the sequence of operations
499 base_name: the name of the operation sequence
Allie Wood12f59aa2015-04-06 11:05:12 -0700500 old_part_file: the old partition file object, open for reading/writing
501 new_part_file: the new partition file object, open for reading/writing
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800502 part_size: the partition size
Allie Wood12f59aa2015-04-06 11:05:12 -0700503
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800504 Raises:
505 PayloadError if anything goes wrong while processing the payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800506 """
507 for op, op_name in common.OperationIter(operations, base_name):
508 # Read data blob.
509 data = self.payload.ReadDataBlob(op.data_offset, op.data_length)
510
Amin Hassanib44f73b2017-12-15 17:45:49 +0000511 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
Allie Wood12f59aa2015-04-06 11:05:12 -0700512 self._ApplyReplaceOperation(op, op_name, data, new_part_file, part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800513 elif op.type == common.OpType.MOVE:
Allie Wood12f59aa2015-04-06 11:05:12 -0700514 self._ApplyMoveOperation(op, op_name, new_part_file)
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700515 elif op.type == common.OpType.ZERO:
516 self._ApplyZeroOperation(op, op_name, new_part_file)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800517 elif op.type == common.OpType.BSDIFF:
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700518 self._ApplyDiffOperation(op, op_name, data, new_part_file,
519 new_part_file)
Allie Wood12f59aa2015-04-06 11:05:12 -0700520 elif op.type == common.OpType.SOURCE_COPY:
521 self._ApplySourceCopyOperation(op, op_name, old_part_file,
522 new_part_file)
Amin Hassaniefa62d92017-11-09 13:46:56 -0800523 elif op.type in (common.OpType.SOURCE_BSDIFF, common.OpType.PUFFDIFF,
524 common.OpType.BROTLI_BSDIFF):
Sen Jiang92161a72016-06-28 16:09:38 -0700525 self._ApplyDiffOperation(op, op_name, data, old_part_file,
526 new_part_file)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800527 else:
528 raise PayloadError('%s: unknown operation type (%d)' %
529 (op_name, op.type))
530
531 def _ApplyToPartition(self, operations, part_name, base_name,
Gilad Arnold16416602013-05-04 21:40:39 -0700532 new_part_file_name, new_part_info,
533 old_part_file_name=None, old_part_info=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800534 """Applies an update to a partition.
535
536 Args:
537 operations: the sequence of update operations to apply
538 part_name: the name of the partition, for error reporting
539 base_name: the name of the operation sequence
Gilad Arnold16416602013-05-04 21:40:39 -0700540 new_part_file_name: file name to write partition data to
541 new_part_info: size and expected hash of dest partition
542 old_part_file_name: file name of source partition (optional)
543 old_part_info: size and expected hash of source partition (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -0700544
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800545 Raises:
546 PayloadError if anything goes wrong with the update.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800547 """
548 # Do we have a source partition?
Gilad Arnold16416602013-05-04 21:40:39 -0700549 if old_part_file_name:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800550 # Verify the source partition.
Gilad Arnold16416602013-05-04 21:40:39 -0700551 with open(old_part_file_name, 'rb') as old_part_file:
Gilad Arnold4b8f4c22015-07-16 11:45:39 -0700552 _VerifySha256(old_part_file, old_part_info.hash,
553 'old ' + part_name, length=old_part_info.size)
Gilad Arnoldf69065c2013-05-27 16:54:59 -0700554 new_part_file_mode = 'r+b'
Allie Wood12f59aa2015-04-06 11:05:12 -0700555 if self.minor_version == common.INPLACE_MINOR_PAYLOAD_VERSION:
556 # Copy the src partition to the dst one; make sure we don't truncate it.
557 shutil.copyfile(old_part_file_name, new_part_file_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800558 elif (self.minor_version == common.SOURCE_MINOR_PAYLOAD_VERSION or
Sen Jiang92161a72016-06-28 16:09:38 -0700559 self.minor_version == common.OPSRCHASH_MINOR_PAYLOAD_VERSION or
Amin Hassani5ef5d452017-08-04 13:10:59 -0700560 self.minor_version == common.PUFFDIFF_MINOR_PAYLOAD_VERSION):
Sen Jiangd6122bb2015-12-11 10:27:04 -0800561 # In minor version >= 2, we don't want to copy the partitions, so
562 # instead just make the new partition file.
Allie Wood12f59aa2015-04-06 11:05:12 -0700563 open(new_part_file_name, 'w').close()
564 else:
565 raise PayloadError("Unknown minor version: %d" % self.minor_version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800566 else:
Gilad Arnoldf69065c2013-05-27 16:54:59 -0700567 # We need to create/truncate the dst partition file.
568 new_part_file_mode = 'w+b'
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800569
570 # Apply operations.
Gilad Arnoldf69065c2013-05-27 16:54:59 -0700571 with open(new_part_file_name, new_part_file_mode) as new_part_file:
Allie Wood12f59aa2015-04-06 11:05:12 -0700572 old_part_file = (open(old_part_file_name, 'r+b')
573 if old_part_file_name else None)
574 try:
575 self._ApplyOperations(operations, base_name, old_part_file,
576 new_part_file, new_part_info.size)
577 finally:
578 if old_part_file:
579 old_part_file.close()
580
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700581 # Truncate the result, if so instructed.
582 if self.truncate_to_expected_size:
583 new_part_file.seek(0, 2)
584 if new_part_file.tell() > new_part_info.size:
585 new_part_file.seek(new_part_info.size)
586 new_part_file.truncate()
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800587
588 # Verify the resulting partition.
Gilad Arnold16416602013-05-04 21:40:39 -0700589 with open(new_part_file_name, 'rb') as new_part_file:
Gilad Arnold4b8f4c22015-07-16 11:45:39 -0700590 _VerifySha256(new_part_file, new_part_info.hash,
591 'new ' + part_name, length=new_part_info.size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800592
Gilad Arnold16416602013-05-04 21:40:39 -0700593 def Run(self, new_kernel_part, new_rootfs_part, old_kernel_part=None,
594 old_rootfs_part=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800595 """Applier entry point, invoking all update operations.
596
597 Args:
Gilad Arnold16416602013-05-04 21:40:39 -0700598 new_kernel_part: name of dest kernel partition file
599 new_rootfs_part: name of dest rootfs partition file
600 old_kernel_part: name of source kernel partition file (optional)
601 old_rootfs_part: name of source rootfs partition file (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -0700602
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800603 Raises:
604 PayloadError if payload application failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800605 """
606 self.payload.ResetFile()
607
608 # Make sure the arguments are sane and match the payload.
Gilad Arnold16416602013-05-04 21:40:39 -0700609 if not (new_kernel_part and new_rootfs_part):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800610 raise PayloadError('missing dst {kernel,rootfs} partitions')
611
Gilad Arnold16416602013-05-04 21:40:39 -0700612 if not (old_kernel_part or old_rootfs_part):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800613 if not self.payload.IsFull():
614 raise PayloadError('trying to apply a non-full update without src '
615 '{kernel,rootfs} partitions')
Gilad Arnold16416602013-05-04 21:40:39 -0700616 elif old_kernel_part and old_rootfs_part:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800617 if not self.payload.IsDelta():
618 raise PayloadError('trying to apply a non-delta update onto src '
619 '{kernel,rootfs} partitions')
620 else:
621 raise PayloadError('not all src partitions provided')
622
623 # Apply update to rootfs.
624 self._ApplyToPartition(
625 self.payload.manifest.install_operations, 'rootfs',
Gilad Arnold16416602013-05-04 21:40:39 -0700626 'install_operations', new_rootfs_part,
627 self.payload.manifest.new_rootfs_info, old_rootfs_part,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800628 self.payload.manifest.old_rootfs_info)
629
630 # Apply update to kernel update.
631 self._ApplyToPartition(
632 self.payload.manifest.kernel_install_operations, 'kernel',
Gilad Arnold16416602013-05-04 21:40:39 -0700633 'kernel_install_operations', new_kernel_part,
634 self.payload.manifest.new_kernel_info, old_kernel_part,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800635 self.payload.manifest.old_kernel_info)