blob: eeb2e42411e9f53cece08442de61b36ee2dd8805 [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
Amin Hassanif1d6cea2017-12-07 12:13:03 -080021try:
22 import lzma
23except ImportError:
24 from backports import lzma
Gilad Arnold553b0ec2013-01-26 01:00:39 -080025import os
26import shutil
27import subprocess
28import sys
29import tempfile
30
31import common
32from error import PayloadError
33
34
35#
36# Helper functions.
37#
Gilad Arnold382df5c2013-05-03 12:49:28 -070038def _VerifySha256(file_obj, expected_hash, name, length=-1):
Gilad Arnold553b0ec2013-01-26 01:00:39 -080039 """Verifies the SHA256 hash of a file.
40
41 Args:
42 file_obj: file object to read
43 expected_hash: the hash digest we expect to be getting
44 name: name string of this hash, for error reporting
Gilad Arnold382df5c2013-05-03 12:49:28 -070045 length: precise length of data to verify (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -070046
Gilad Arnold553b0ec2013-01-26 01:00:39 -080047 Raises:
Gilad Arnold382df5c2013-05-03 12:49:28 -070048 PayloadError if computed hash doesn't match expected one, or if fails to
49 read the specified length of data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080050 """
51 # pylint: disable=E1101
52 hasher = hashlib.sha256()
53 block_length = 1024 * 1024
Gilad Arnold382df5c2013-05-03 12:49:28 -070054 max_length = length if length >= 0 else sys.maxint
Gilad Arnold553b0ec2013-01-26 01:00:39 -080055
Gilad Arnold382df5c2013-05-03 12:49:28 -070056 while max_length > 0:
Gilad Arnold553b0ec2013-01-26 01:00:39 -080057 read_length = min(max_length, block_length)
58 data = file_obj.read(read_length)
59 if not data:
60 break
61 max_length -= len(data)
62 hasher.update(data)
63
Gilad Arnold382df5c2013-05-03 12:49:28 -070064 if length >= 0 and max_length > 0:
65 raise PayloadError(
66 'insufficient data (%d instead of %d) when verifying %s' %
67 (length - max_length, length, name))
68
Gilad Arnold553b0ec2013-01-26 01:00:39 -080069 actual_hash = hasher.digest()
70 if actual_hash != expected_hash:
71 raise PayloadError('%s hash (%s) not as expected (%s)' %
Gilad Arnold96405372013-05-04 00:24:58 -070072 (name, common.FormatSha256(actual_hash),
73 common.FormatSha256(expected_hash)))
Gilad Arnold553b0ec2013-01-26 01:00:39 -080074
75
76def _ReadExtents(file_obj, extents, block_size, max_length=-1):
77 """Reads data from file as defined by extent sequence.
78
79 This tries to be efficient by not copying data as it is read in chunks.
80
81 Args:
82 file_obj: file object
83 extents: sequence of block extents (offset and length)
84 block_size: size of each block
85 max_length: maximum length to read (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -070086
Gilad Arnold553b0ec2013-01-26 01:00:39 -080087 Returns:
88 A character array containing the concatenated read data.
Gilad Arnold553b0ec2013-01-26 01:00:39 -080089 """
90 data = array.array('c')
Gilad Arnold272a4992013-05-08 13:12:53 -070091 if max_length < 0:
92 max_length = sys.maxint
Gilad Arnold553b0ec2013-01-26 01:00:39 -080093 for ex in extents:
94 if max_length == 0:
95 break
Gilad Arnold272a4992013-05-08 13:12:53 -070096 read_length = min(max_length, ex.num_blocks * block_size)
Gilad Arnold658185a2013-05-08 17:57:54 -070097
98 # Fill with zeros or read from file, depending on the type of extent.
99 if ex.start_block == common.PSEUDO_EXTENT_MARKER:
100 data.extend(itertools.repeat('\0', read_length))
101 else:
102 file_obj.seek(ex.start_block * block_size)
103 data.fromfile(file_obj, read_length)
104
Gilad Arnold272a4992013-05-08 13:12:53 -0700105 max_length -= read_length
Gilad Arnold658185a2013-05-08 17:57:54 -0700106
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800107 return data
108
109
110def _WriteExtents(file_obj, data, extents, block_size, base_name):
Gilad Arnold272a4992013-05-08 13:12:53 -0700111 """Writes data to file as defined by extent sequence.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800112
113 This tries to be efficient by not copy data as it is written in chunks.
114
115 Args:
116 file_obj: file object
117 data: data to write
118 extents: sequence of block extents (offset and length)
119 block_size: size of each block
Gilad Arnold272a4992013-05-08 13:12:53 -0700120 base_name: name string of extent sequence for error reporting
Allie Wood12f59aa2015-04-06 11:05:12 -0700121
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800122 Raises:
123 PayloadError when things don't add up.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800124 """
125 data_offset = 0
126 data_length = len(data)
127 for ex, ex_name in common.ExtentIter(extents, base_name):
Gilad Arnold272a4992013-05-08 13:12:53 -0700128 if not data_length:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800129 raise PayloadError('%s: more write extents than data' % ex_name)
Gilad Arnold272a4992013-05-08 13:12:53 -0700130 write_length = min(data_length, ex.num_blocks * block_size)
Gilad Arnold658185a2013-05-08 17:57:54 -0700131
132 # Only do actual writing if this is not a pseudo-extent.
133 if ex.start_block != common.PSEUDO_EXTENT_MARKER:
134 file_obj.seek(ex.start_block * block_size)
135 data_view = buffer(data, data_offset, write_length)
136 file_obj.write(data_view)
137
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800138 data_offset += write_length
Gilad Arnold272a4992013-05-08 13:12:53 -0700139 data_length -= write_length
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800140
Gilad Arnold272a4992013-05-08 13:12:53 -0700141 if data_length:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800142 raise PayloadError('%s: more data than write extents' % base_name)
143
144
Gilad Arnold272a4992013-05-08 13:12:53 -0700145def _ExtentsToBspatchArg(extents, block_size, base_name, data_length=-1):
146 """Translates an extent sequence into a bspatch-compatible string argument.
147
148 Args:
149 extents: sequence of block extents (offset and length)
150 block_size: size of each block
151 base_name: name string of extent sequence for error reporting
152 data_length: the actual total length of the data in bytes (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -0700153
Gilad Arnold272a4992013-05-08 13:12:53 -0700154 Returns:
155 A tuple consisting of (i) a string of the form
156 "off_1:len_1,...,off_n:len_n", (ii) an offset where zero padding is needed
157 for filling the last extent, (iii) the length of the padding (zero means no
158 padding is needed and the extents cover the full length of data).
Allie Wood12f59aa2015-04-06 11:05:12 -0700159
Gilad Arnold272a4992013-05-08 13:12:53 -0700160 Raises:
161 PayloadError if data_length is too short or too long.
Gilad Arnold272a4992013-05-08 13:12:53 -0700162 """
163 arg = ''
164 pad_off = pad_len = 0
165 if data_length < 0:
166 data_length = sys.maxint
167 for ex, ex_name in common.ExtentIter(extents, base_name):
168 if not data_length:
169 raise PayloadError('%s: more extents than total data length' % ex_name)
Gilad Arnold658185a2013-05-08 17:57:54 -0700170
171 is_pseudo = ex.start_block == common.PSEUDO_EXTENT_MARKER
172 start_byte = -1 if is_pseudo else ex.start_block * block_size
Gilad Arnold272a4992013-05-08 13:12:53 -0700173 num_bytes = ex.num_blocks * block_size
174 if data_length < num_bytes:
Gilad Arnold658185a2013-05-08 17:57:54 -0700175 # We're only padding a real extent.
176 if not is_pseudo:
177 pad_off = start_byte + data_length
178 pad_len = num_bytes - data_length
179
Gilad Arnold272a4992013-05-08 13:12:53 -0700180 num_bytes = data_length
Gilad Arnold658185a2013-05-08 17:57:54 -0700181
Gilad Arnold272a4992013-05-08 13:12:53 -0700182 arg += '%s%d:%d' % (arg and ',', start_byte, num_bytes)
183 data_length -= num_bytes
184
185 if data_length:
186 raise PayloadError('%s: extents not covering full data length' % base_name)
187
188 return arg, pad_off, pad_len
189
190
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800191#
192# Payload application.
193#
194class PayloadApplier(object):
195 """Applying an update payload.
196
197 This is a short-lived object whose purpose is to isolate the logic used for
198 applying an update payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800199 """
200
Gilad Arnold21a02502013-08-22 16:59:48 -0700201 def __init__(self, payload, bsdiff_in_place=True, bspatch_path=None,
Amin Hassani5ef5d452017-08-04 13:10:59 -0700202 puffpatch_path=None, truncate_to_expected_size=True):
Gilad Arnold272a4992013-05-08 13:12:53 -0700203 """Initialize the applier.
204
205 Args:
206 payload: the payload object to check
207 bsdiff_in_place: whether to perform BSDIFF operation in-place (optional)
Gilad Arnold21a02502013-08-22 16:59:48 -0700208 bspatch_path: path to the bspatch binary (optional)
Amin Hassani5ef5d452017-08-04 13:10:59 -0700209 puffpatch_path: path to the puffpatch binary (optional)
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700210 truncate_to_expected_size: whether to truncate the resulting partitions
211 to their expected sizes, as specified in the
212 payload (optional)
Gilad Arnold272a4992013-05-08 13:12:53 -0700213 """
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800214 assert payload.is_init, 'uninitialized update payload'
215 self.payload = payload
216 self.block_size = payload.manifest.block_size
Allie Wood12f59aa2015-04-06 11:05:12 -0700217 self.minor_version = payload.manifest.minor_version
Gilad Arnold272a4992013-05-08 13:12:53 -0700218 self.bsdiff_in_place = bsdiff_in_place
Gilad Arnold21a02502013-08-22 16:59:48 -0700219 self.bspatch_path = bspatch_path or 'bspatch'
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700220 self.puffpatch_path = puffpatch_path or 'puffin'
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700221 self.truncate_to_expected_size = truncate_to_expected_size
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800222
223 def _ApplyReplaceOperation(self, op, op_name, out_data, part_file, part_size):
Amin Hassanif1d6cea2017-12-07 12:13:03 -0800224 """Applies a REPLACE{,_BZ,_XZ} operation.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800225
226 Args:
227 op: the operation object
228 op_name: name string for error reporting
229 out_data: the data to be written
230 part_file: the partition file object
231 part_size: the size of the partition
Allie Wood12f59aa2015-04-06 11:05:12 -0700232
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800233 Raises:
234 PayloadError if something goes wrong.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800235 """
236 block_size = self.block_size
237 data_length = len(out_data)
238
239 # Decompress data if needed.
240 if op.type == common.OpType.REPLACE_BZ:
241 out_data = bz2.decompress(out_data)
242 data_length = len(out_data)
Amin Hassanif1d6cea2017-12-07 12:13:03 -0800243 elif op.type == common.OpType.REPLACE_XZ:
244 out_data = lzma.decompress(out_data)
245 data_length = len(out_data)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800246
247 # Write data to blocks specified in dst extents.
248 data_start = 0
249 for ex, ex_name in common.ExtentIter(op.dst_extents,
250 '%s.dst_extents' % op_name):
251 start_block = ex.start_block
252 num_blocks = ex.num_blocks
253 count = num_blocks * block_size
254
255 # Make sure it's not a fake (signature) operation.
256 if start_block != common.PSEUDO_EXTENT_MARKER:
257 data_end = data_start + count
258
259 # Make sure we're not running past partition boundary.
260 if (start_block + num_blocks) * block_size > part_size:
261 raise PayloadError(
262 '%s: extent (%s) exceeds partition size (%d)' %
263 (ex_name, common.FormatExtent(ex, block_size),
264 part_size))
265
266 # Make sure that we have enough data to write.
267 if data_end >= data_length + block_size:
268 raise PayloadError(
269 '%s: more dst blocks than data (even with padding)')
270
271 # Pad with zeros if necessary.
272 if data_end > data_length:
273 padding = data_end - data_length
274 out_data += '\0' * padding
275
276 self.payload.payload_file.seek(start_block * block_size)
277 part_file.seek(start_block * block_size)
278 part_file.write(out_data[data_start:data_end])
279
280 data_start += count
281
282 # Make sure we wrote all data.
283 if data_start < data_length:
284 raise PayloadError('%s: wrote fewer bytes (%d) than expected (%d)' %
285 (op_name, data_start, data_length))
286
287 def _ApplyMoveOperation(self, op, op_name, part_file):
288 """Applies a MOVE operation.
289
Gilad Arnold658185a2013-05-08 17:57:54 -0700290 Note that this operation must read the whole block data from the input and
291 only then dump it, due to our in-place update semantics; otherwise, it
292 might clobber data midway through.
293
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800294 Args:
295 op: the operation object
296 op_name: name string for error reporting
297 part_file: the partition file object
Allie Wood12f59aa2015-04-06 11:05:12 -0700298
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800299 Raises:
300 PayloadError if something goes wrong.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800301 """
302 block_size = self.block_size
303
304 # Gather input raw data from src extents.
305 in_data = _ReadExtents(part_file, op.src_extents, block_size)
306
307 # Dump extracted data to dst extents.
308 _WriteExtents(part_file, in_data, op.dst_extents, block_size,
309 '%s.dst_extents' % op_name)
310
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700311 def _ApplyZeroOperation(self, op, op_name, part_file):
312 """Applies a ZERO operation.
313
314 Args:
315 op: the operation object
316 op_name: name string for error reporting
317 part_file: the partition file object
318
319 Raises:
320 PayloadError if something goes wrong.
321 """
322 block_size = self.block_size
323 base_name = '%s.dst_extents' % op_name
324
325 # Iterate over the extents and write zero.
326 for ex, ex_name in common.ExtentIter(op.dst_extents, base_name):
327 # Only do actual writing if this is not a pseudo-extent.
328 if ex.start_block != common.PSEUDO_EXTENT_MARKER:
329 part_file.seek(ex.start_block * block_size)
330 part_file.write('\0' * (ex.num_blocks * block_size))
331
Allie Wood12f59aa2015-04-06 11:05:12 -0700332 def _ApplySourceCopyOperation(self, op, op_name, old_part_file,
333 new_part_file):
334 """Applies a SOURCE_COPY operation.
335
336 Args:
337 op: the operation object
338 op_name: name string for error reporting
339 old_part_file: the old partition file object
340 new_part_file: the new partition file object
341
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800342 Raises:
343 PayloadError if something goes wrong.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800344 """
Allie Wood12f59aa2015-04-06 11:05:12 -0700345 if not old_part_file:
346 raise PayloadError(
347 '%s: no source partition file provided for operation type (%d)' %
348 (op_name, op.type))
349
350 block_size = self.block_size
351
352 # Gather input raw data from src extents.
353 in_data = _ReadExtents(old_part_file, op.src_extents, block_size)
354
355 # Dump extracted data to dst extents.
356 _WriteExtents(new_part_file, in_data, op.dst_extents, block_size,
357 '%s.dst_extents' % op_name)
358
Amin Hassaniefa62d92017-11-09 13:46:56 -0800359 def _BytesInExtents(self, extents, base_name):
360 """Counts the length of extents in bytes.
361
362 Args:
363 extents: The list of Extents.
364 base_name: For error reporting.
365
366 Returns:
367 The number of bytes in extents.
368 """
369
370 length = 0
371 for ex, ex_name in common.ExtentIter(extents, base_name):
372 length += ex.num_blocks * self.block_size
373 return length
374
Sen Jiang92161a72016-06-28 16:09:38 -0700375 def _ApplyDiffOperation(self, op, op_name, patch_data, old_part_file,
376 new_part_file):
Amin Hassaniefa62d92017-11-09 13:46:56 -0800377 """Applies a SOURCE_BSDIFF, BROTLI_BSDIFF or PUFFDIFF operation.
Allie Wood12f59aa2015-04-06 11:05:12 -0700378
379 Args:
380 op: the operation object
381 op_name: name string for error reporting
382 patch_data: the binary patch content
383 old_part_file: the source partition file object
384 new_part_file: the target partition file object
385
386 Raises:
387 PayloadError if something goes wrong.
Allie Wood12f59aa2015-04-06 11:05:12 -0700388 """
389 if not old_part_file:
390 raise PayloadError(
391 '%s: no source partition file provided for operation type (%d)' %
392 (op_name, op.type))
393
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800394 block_size = self.block_size
395
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800396 # Dump patch data to file.
397 with tempfile.NamedTemporaryFile(delete=False) as patch_file:
398 patch_file_name = patch_file.name
399 patch_file.write(patch_data)
400
Allie Wood12f59aa2015-04-06 11:05:12 -0700401 if (hasattr(new_part_file, 'fileno') and
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700402 ((not old_part_file) or hasattr(old_part_file, 'fileno'))):
Gilad Arnold272a4992013-05-08 13:12:53 -0700403 # Construct input and output extents argument for bspatch.
Amin Hassaniefa62d92017-11-09 13:46:56 -0800404
Gilad Arnold272a4992013-05-08 13:12:53 -0700405 in_extents_arg, _, _ = _ExtentsToBspatchArg(
406 op.src_extents, block_size, '%s.src_extents' % op_name,
Amin Hassaniefa62d92017-11-09 13:46:56 -0800407 data_length=op.src_length if op.src_length else
408 self._BytesInExtents(op.src_extents, "%s.src_extents"))
Gilad Arnold272a4992013-05-08 13:12:53 -0700409 out_extents_arg, pad_off, pad_len = _ExtentsToBspatchArg(
410 op.dst_extents, block_size, '%s.dst_extents' % op_name,
Amin Hassaniefa62d92017-11-09 13:46:56 -0800411 data_length=op.dst_length if op.dst_length else
412 self._BytesInExtents(op.dst_extents, "%s.dst_extents"))
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800413
Allie Wood12f59aa2015-04-06 11:05:12 -0700414 new_file_name = '/dev/fd/%d' % new_part_file.fileno()
415 # Diff from source partition.
416 old_file_name = '/dev/fd/%d' % old_part_file.fileno()
417
Amin Hassaniefa62d92017-11-09 13:46:56 -0800418 if op.type in (common.OpType.BSDIFF, common.OpType.SOURCE_BSDIFF,
419 common.OpType.BROTLI_BSDIFF):
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700420 # Invoke bspatch on partition file with extents args.
421 bspatch_cmd = [self.bspatch_path, old_file_name, new_file_name,
422 patch_file_name, in_extents_arg, out_extents_arg]
423 subprocess.check_call(bspatch_cmd)
424 elif op.type == common.OpType.PUFFDIFF:
425 # Invoke puffpatch on partition file with extents args.
426 puffpatch_cmd = [self.puffpatch_path,
427 "--operation=puffpatch",
428 "--src_file=%s" % old_file_name,
429 "--dst_file=%s" % new_file_name,
430 "--patch_file=%s" % patch_file_name,
431 "--src_extents=%s" % in_extents_arg,
432 "--dst_extents=%s" % out_extents_arg]
433 subprocess.check_call(puffpatch_cmd)
434 else:
435 raise PayloadError("Unknown operation %s", op.type)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800436
Gilad Arnold272a4992013-05-08 13:12:53 -0700437 # Pad with zeros past the total output length.
438 if pad_len:
Allie Wood12f59aa2015-04-06 11:05:12 -0700439 new_part_file.seek(pad_off)
440 new_part_file.write('\0' * pad_len)
Gilad Arnold272a4992013-05-08 13:12:53 -0700441 else:
442 # Gather input raw data and write to a temp file.
Allie Wood12f59aa2015-04-06 11:05:12 -0700443 input_part_file = old_part_file if old_part_file else new_part_file
444 in_data = _ReadExtents(input_part_file, op.src_extents, block_size,
Amin Hassaniefa62d92017-11-09 13:46:56 -0800445 max_length=op.src_length if op.src_length else
446 self._BytesInExtents(op.src_extents,
447 "%s.src_extents"))
Gilad Arnold272a4992013-05-08 13:12:53 -0700448 with tempfile.NamedTemporaryFile(delete=False) as in_file:
449 in_file_name = in_file.name
450 in_file.write(in_data)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800451
Allie Wood12f59aa2015-04-06 11:05:12 -0700452 # Allocate temporary output file.
Gilad Arnold272a4992013-05-08 13:12:53 -0700453 with tempfile.NamedTemporaryFile(delete=False) as out_file:
454 out_file_name = out_file.name
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800455
Amin Hassaniefa62d92017-11-09 13:46:56 -0800456 if op.type in (common.OpType.BSDIFF, common.OpType.SOURCE_BSDIFF,
457 common.OpType.BROTLI_BSDIFF):
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700458 # Invoke bspatch.
459 bspatch_cmd = [self.bspatch_path, in_file_name, out_file_name,
460 patch_file_name]
461 subprocess.check_call(bspatch_cmd)
462 elif op.type == common.OpType.PUFFDIFF:
463 # Invoke puffpatch.
464 puffpatch_cmd = [self.puffpatch_path,
465 "--operation=puffpatch",
466 "--src_file=%s" % in_file_name,
467 "--dst_file=%s" % out_file_name,
468 "--patch_file=%s" % patch_file_name]
469 subprocess.check_call(puffpatch_cmd)
470 else:
471 raise PayloadError("Unknown operation %s", op.type)
Gilad Arnold272a4992013-05-08 13:12:53 -0700472
473 # Read output.
474 with open(out_file_name, 'rb') as out_file:
475 out_data = out_file.read()
476 if len(out_data) != op.dst_length:
477 raise PayloadError(
478 '%s: actual patched data length (%d) not as expected (%d)' %
479 (op_name, len(out_data), op.dst_length))
480
481 # Write output back to partition, with padding.
482 unaligned_out_len = len(out_data) % block_size
483 if unaligned_out_len:
484 out_data += '\0' * (block_size - unaligned_out_len)
Allie Wood12f59aa2015-04-06 11:05:12 -0700485 _WriteExtents(new_part_file, out_data, op.dst_extents, block_size,
Gilad Arnold272a4992013-05-08 13:12:53 -0700486 '%s.dst_extents' % op_name)
487
488 # Delete input/output files.
489 os.remove(in_file_name)
490 os.remove(out_file_name)
491
492 # Delete patch file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800493 os.remove(patch_file_name)
494
Allie Wood12f59aa2015-04-06 11:05:12 -0700495 def _ApplyOperations(self, operations, base_name, old_part_file,
496 new_part_file, part_size):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800497 """Applies a sequence of update operations to a partition.
498
Allie Wood12f59aa2015-04-06 11:05:12 -0700499 This assumes an in-place update semantics for MOVE and BSDIFF, namely all
500 reads are performed first, then the data is processed and written back to
501 the same file.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800502
503 Args:
504 operations: the sequence of operations
505 base_name: the name of the operation sequence
Allie Wood12f59aa2015-04-06 11:05:12 -0700506 old_part_file: the old partition file object, open for reading/writing
507 new_part_file: the new partition file object, open for reading/writing
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800508 part_size: the partition size
Allie Wood12f59aa2015-04-06 11:05:12 -0700509
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800510 Raises:
511 PayloadError if anything goes wrong while processing the payload.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800512 """
513 for op, op_name in common.OperationIter(operations, base_name):
514 # Read data blob.
515 data = self.payload.ReadDataBlob(op.data_offset, op.data_length)
516
Amin Hassanif1d6cea2017-12-07 12:13:03 -0800517 if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ,
518 common.OpType.REPLACE_XZ):
Allie Wood12f59aa2015-04-06 11:05:12 -0700519 self._ApplyReplaceOperation(op, op_name, data, new_part_file, part_size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800520 elif op.type == common.OpType.MOVE:
Allie Wood12f59aa2015-04-06 11:05:12 -0700521 self._ApplyMoveOperation(op, op_name, new_part_file)
Amin Hassani8ad22ba2017-10-11 10:15:11 -0700522 elif op.type == common.OpType.ZERO:
523 self._ApplyZeroOperation(op, op_name, new_part_file)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800524 elif op.type == common.OpType.BSDIFF:
Amin Hassanicdeb6e62017-10-11 10:15:11 -0700525 self._ApplyDiffOperation(op, op_name, data, new_part_file,
526 new_part_file)
Allie Wood12f59aa2015-04-06 11:05:12 -0700527 elif op.type == common.OpType.SOURCE_COPY:
528 self._ApplySourceCopyOperation(op, op_name, old_part_file,
529 new_part_file)
Amin Hassaniefa62d92017-11-09 13:46:56 -0800530 elif op.type in (common.OpType.SOURCE_BSDIFF, common.OpType.PUFFDIFF,
531 common.OpType.BROTLI_BSDIFF):
Sen Jiang92161a72016-06-28 16:09:38 -0700532 self._ApplyDiffOperation(op, op_name, data, old_part_file,
533 new_part_file)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800534 else:
535 raise PayloadError('%s: unknown operation type (%d)' %
536 (op_name, op.type))
537
538 def _ApplyToPartition(self, operations, part_name, base_name,
Gilad Arnold16416602013-05-04 21:40:39 -0700539 new_part_file_name, new_part_info,
540 old_part_file_name=None, old_part_info=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800541 """Applies an update to a partition.
542
543 Args:
544 operations: the sequence of update operations to apply
545 part_name: the name of the partition, for error reporting
546 base_name: the name of the operation sequence
Gilad Arnold16416602013-05-04 21:40:39 -0700547 new_part_file_name: file name to write partition data to
548 new_part_info: size and expected hash of dest partition
549 old_part_file_name: file name of source partition (optional)
550 old_part_info: size and expected hash of source partition (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -0700551
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800552 Raises:
553 PayloadError if anything goes wrong with the update.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800554 """
555 # Do we have a source partition?
Gilad Arnold16416602013-05-04 21:40:39 -0700556 if old_part_file_name:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800557 # Verify the source partition.
Gilad Arnold16416602013-05-04 21:40:39 -0700558 with open(old_part_file_name, 'rb') as old_part_file:
Gilad Arnold4b8f4c22015-07-16 11:45:39 -0700559 _VerifySha256(old_part_file, old_part_info.hash,
560 'old ' + part_name, length=old_part_info.size)
Gilad Arnoldf69065c2013-05-27 16:54:59 -0700561 new_part_file_mode = 'r+b'
Allie Wood12f59aa2015-04-06 11:05:12 -0700562 if self.minor_version == common.INPLACE_MINOR_PAYLOAD_VERSION:
563 # Copy the src partition to the dst one; make sure we don't truncate it.
564 shutil.copyfile(old_part_file_name, new_part_file_name)
Sen Jiangd6122bb2015-12-11 10:27:04 -0800565 elif (self.minor_version == common.SOURCE_MINOR_PAYLOAD_VERSION or
Sen Jiang92161a72016-06-28 16:09:38 -0700566 self.minor_version == common.OPSRCHASH_MINOR_PAYLOAD_VERSION or
Amin Hassani5ef5d452017-08-04 13:10:59 -0700567 self.minor_version == common.PUFFDIFF_MINOR_PAYLOAD_VERSION):
Sen Jiangd6122bb2015-12-11 10:27:04 -0800568 # In minor version >= 2, we don't want to copy the partitions, so
569 # instead just make the new partition file.
Allie Wood12f59aa2015-04-06 11:05:12 -0700570 open(new_part_file_name, 'w').close()
571 else:
572 raise PayloadError("Unknown minor version: %d" % self.minor_version)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800573 else:
Gilad Arnoldf69065c2013-05-27 16:54:59 -0700574 # We need to create/truncate the dst partition file.
575 new_part_file_mode = 'w+b'
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800576
577 # Apply operations.
Gilad Arnoldf69065c2013-05-27 16:54:59 -0700578 with open(new_part_file_name, new_part_file_mode) as new_part_file:
Allie Wood12f59aa2015-04-06 11:05:12 -0700579 old_part_file = (open(old_part_file_name, 'r+b')
580 if old_part_file_name else None)
581 try:
582 self._ApplyOperations(operations, base_name, old_part_file,
583 new_part_file, new_part_info.size)
584 finally:
585 if old_part_file:
586 old_part_file.close()
587
Gilad Arnolde5fdf182013-05-23 16:13:38 -0700588 # Truncate the result, if so instructed.
589 if self.truncate_to_expected_size:
590 new_part_file.seek(0, 2)
591 if new_part_file.tell() > new_part_info.size:
592 new_part_file.seek(new_part_info.size)
593 new_part_file.truncate()
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800594
595 # Verify the resulting partition.
Gilad Arnold16416602013-05-04 21:40:39 -0700596 with open(new_part_file_name, 'rb') as new_part_file:
Gilad Arnold4b8f4c22015-07-16 11:45:39 -0700597 _VerifySha256(new_part_file, new_part_info.hash,
598 'new ' + part_name, length=new_part_info.size)
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800599
Gilad Arnold16416602013-05-04 21:40:39 -0700600 def Run(self, new_kernel_part, new_rootfs_part, old_kernel_part=None,
601 old_rootfs_part=None):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800602 """Applier entry point, invoking all update operations.
603
604 Args:
Gilad Arnold16416602013-05-04 21:40:39 -0700605 new_kernel_part: name of dest kernel partition file
606 new_rootfs_part: name of dest rootfs partition file
607 old_kernel_part: name of source kernel partition file (optional)
608 old_rootfs_part: name of source rootfs partition file (optional)
Allie Wood12f59aa2015-04-06 11:05:12 -0700609
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800610 Raises:
611 PayloadError if payload application failed.
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800612 """
613 self.payload.ResetFile()
614
615 # Make sure the arguments are sane and match the payload.
Gilad Arnold16416602013-05-04 21:40:39 -0700616 if not (new_kernel_part and new_rootfs_part):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800617 raise PayloadError('missing dst {kernel,rootfs} partitions')
618
Gilad Arnold16416602013-05-04 21:40:39 -0700619 if not (old_kernel_part or old_rootfs_part):
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800620 if not self.payload.IsFull():
621 raise PayloadError('trying to apply a non-full update without src '
622 '{kernel,rootfs} partitions')
Gilad Arnold16416602013-05-04 21:40:39 -0700623 elif old_kernel_part and old_rootfs_part:
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800624 if not self.payload.IsDelta():
625 raise PayloadError('trying to apply a non-delta update onto src '
626 '{kernel,rootfs} partitions')
627 else:
628 raise PayloadError('not all src partitions provided')
629
630 # Apply update to rootfs.
631 self._ApplyToPartition(
632 self.payload.manifest.install_operations, 'rootfs',
Gilad Arnold16416602013-05-04 21:40:39 -0700633 'install_operations', new_rootfs_part,
634 self.payload.manifest.new_rootfs_info, old_rootfs_part,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800635 self.payload.manifest.old_rootfs_info)
636
637 # Apply update to kernel update.
638 self._ApplyToPartition(
639 self.payload.manifest.kernel_install_operations, 'kernel',
Gilad Arnold16416602013-05-04 21:40:39 -0700640 'kernel_install_operations', new_kernel_part,
641 self.payload.manifest.new_kernel_info, old_kernel_part,
Gilad Arnold553b0ec2013-01-26 01:00:39 -0800642 self.payload.manifest.old_kernel_info)