applier.py revision 16416600a92a60294cd57aceec170a13ed72ed19
1553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold# Use of this source code is governed by a BSD-style license that can be
3553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold# found in the LICENSE file.
4553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
5553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold"""Applying a Chrome OS update payload.
6553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
7553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad ArnoldThis module is used internally by the main Payload class for applying an update
8553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldpayload. The interface for invoking the applier is as follows:
9553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
10553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  applier = PayloadApplier(payload)
11553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  applier.Run(...)
12553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
13553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold"""
14553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
15553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport array
16553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport bz2
17553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport hashlib
18553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport os
19553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport shutil
20553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport subprocess
21553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport sys
22553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport tempfile
23553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
24553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldimport common
25553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldfrom error import PayloadError
26553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
27553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
28553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold#
29553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold# Helper functions.
30553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold#
31382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnolddef _VerifySha256(file_obj, expected_hash, name, length=-1):
32553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  """Verifies the SHA256 hash of a file.
33553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
34553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  Args:
35553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    file_obj: file object to read
36553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    expected_hash: the hash digest we expect to be getting
37553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    name: name string of this hash, for error reporting
38382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold    length: precise length of data to verify (optional)
39553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  Raises:
40382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold    PayloadError if computed hash doesn't match expected one, or if fails to
41382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold    read the specified length of data.
42553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
43553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  """
44553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  # pylint: disable=E1101
45553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  hasher = hashlib.sha256()
46553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  block_length = 1024 * 1024
47382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold  max_length = length if length >= 0 else sys.maxint
48553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
49382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold  while max_length > 0:
50553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    read_length = min(max_length, block_length)
51553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    data = file_obj.read(read_length)
52553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    if not data:
53553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      break
54553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    max_length -= len(data)
55553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    hasher.update(data)
56553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
57382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold  if length >= 0 and max_length > 0:
58382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold    raise PayloadError(
59382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold        'insufficient data (%d instead of %d) when verifying %s' %
60382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold        (length - max_length, length, name))
61382df5ce2f4b67bf0998b01c6fedcdb5c35ebef9Gilad Arnold
62553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  actual_hash = hasher.digest()
63553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  if actual_hash != expected_hash:
64553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    raise PayloadError('%s hash (%s) not as expected (%s)' %
659640537ac1b6abfae866424a11e6869228fb7cacGilad Arnold                       (name, common.FormatSha256(actual_hash),
669640537ac1b6abfae866424a11e6869228fb7cacGilad Arnold                        common.FormatSha256(expected_hash)))
67553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
68553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
69553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnolddef _ReadExtents(file_obj, extents, block_size, max_length=-1):
70553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  """Reads data from file as defined by extent sequence.
71553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
72553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  This tries to be efficient by not copying data as it is read in chunks.
73553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
74553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  Args:
75553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    file_obj: file object
76553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    extents: sequence of block extents (offset and length)
77553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    block_size: size of each block
78553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    max_length: maximum length to read (optional)
79553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  Returns:
80553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    A character array containing the concatenated read data.
81553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
82553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  """
83553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  data = array.array('c')
84553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  for ex in extents:
85553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    if max_length == 0:
86553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      break
87553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    file_obj.seek(ex.start_block * block_size)
88553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    read_length = ex.num_blocks * block_size
89553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    if max_length > 0:
90553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      read_length = min(max_length, read_length)
91553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      max_length -= read_length
92553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    data.fromfile(file_obj, read_length)
93553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  return data
94553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
95553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
96553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnolddef _WriteExtents(file_obj, data, extents, block_size, base_name):
97553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  """Write data to file as defined by extent sequence.
98553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
99553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  This tries to be efficient by not copy data as it is written in chunks.
100553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
101553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  Args:
102553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    file_obj: file object
103553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    data: data to write
104553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    extents: sequence of block extents (offset and length)
105553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    block_size: size of each block
106553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    base_name: name string of extent block for error reporting
107553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  Raises:
108553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    PayloadError when things don't add up.
109553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
110553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  """
111553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  data_offset = 0
112553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  data_length = len(data)
113553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  for ex, ex_name in common.ExtentIter(extents, base_name):
114553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    if data_offset == data_length:
115553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      raise PayloadError('%s: more write extents than data' % ex_name)
116553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    write_length = min(data_length - data_offset, ex.num_blocks * block_size)
117553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    file_obj.seek(ex.start_block * block_size)
118553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    data_view = buffer(data, data_offset, write_length)
119553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    file_obj.write(data_view)
120553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    data_offset += write_length
121553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
122553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  if data_offset < data_length:
123553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    raise PayloadError('%s: more data than write extents' % base_name)
124553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
125553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
126553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold#
127553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold# Payload application.
128553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold#
129553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnoldclass PayloadApplier(object):
130553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  """Applying an update payload.
131553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
132553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  This is a short-lived object whose purpose is to isolate the logic used for
133553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  applying an update payload.
134553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
135553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  """
136553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
137553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  def __init__(self, payload):
138553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    assert payload.is_init, 'uninitialized update payload'
139553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    self.payload = payload
140553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    self.block_size = payload.manifest.block_size
141553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
142553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  def _ApplyReplaceOperation(self, op, op_name, out_data, part_file, part_size):
143553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """Applies a REPLACE{,_BZ} operation.
144553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
145553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Args:
146553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      op: the operation object
147553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      op_name: name string for error reporting
148553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      out_data: the data to be written
149553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      part_file: the partition file object
150553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      part_size: the size of the partition
151553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Raises:
152553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      PayloadError if something goes wrong.
153553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
154553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """
155553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    block_size = self.block_size
156553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    data_length = len(out_data)
157553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
158553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Decompress data if needed.
159553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    if op.type == common.OpType.REPLACE_BZ:
160553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      out_data = bz2.decompress(out_data)
161553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      data_length = len(out_data)
162553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
163553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Write data to blocks specified in dst extents.
164553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    data_start = 0
165553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    for ex, ex_name in common.ExtentIter(op.dst_extents,
166553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold                                         '%s.dst_extents' % op_name):
167553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      start_block = ex.start_block
168553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      num_blocks = ex.num_blocks
169553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      count = num_blocks * block_size
170553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
171553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      # Make sure it's not a fake (signature) operation.
172553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      if start_block != common.PSEUDO_EXTENT_MARKER:
173553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        data_end = data_start + count
174553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
175553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        # Make sure we're not running past partition boundary.
176553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        if (start_block + num_blocks) * block_size > part_size:
177553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold          raise PayloadError(
178553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold              '%s: extent (%s) exceeds partition size (%d)' %
179553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold              (ex_name, common.FormatExtent(ex, block_size),
180553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold               part_size))
181553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
182553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        # Make sure that we have enough data to write.
183553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        if data_end >= data_length + block_size:
184553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold          raise PayloadError(
185553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold              '%s: more dst blocks than data (even with padding)')
186553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
187553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        # Pad with zeros if necessary.
188553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        if data_end > data_length:
189553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold          padding = data_end - data_length
190553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold          out_data += '\0' * padding
191553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
192553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        self.payload.payload_file.seek(start_block * block_size)
193553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        part_file.seek(start_block * block_size)
194553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        part_file.write(out_data[data_start:data_end])
195553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
196553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      data_start += count
197553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
198553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Make sure we wrote all data.
199553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    if data_start < data_length:
200553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      raise PayloadError('%s: wrote fewer bytes (%d) than expected (%d)' %
201553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold                         (op_name, data_start, data_length))
202553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
203553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  def _ApplyMoveOperation(self, op, op_name, part_file):
204553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """Applies a MOVE operation.
205553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
206553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Args:
207553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      op: the operation object
208553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      op_name: name string for error reporting
209553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      part_file: the partition file object
210553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Raises:
211553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      PayloadError if something goes wrong.
212553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
213553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """
214553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    block_size = self.block_size
215553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
216553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Gather input raw data from src extents.
217553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    in_data = _ReadExtents(part_file, op.src_extents, block_size)
218553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
219553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Dump extracted data to dst extents.
220553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    _WriteExtents(part_file, in_data, op.dst_extents, block_size,
221553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold                  '%s.dst_extents' % op_name)
222553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
223553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  def _ApplyBsdiffOperation(self, op, op_name, patch_data, part_file):
224553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """Applies a BSDIFF operation.
225553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
226553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Args:
227553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      op: the operation object
228553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      op_name: name string for error reporting
229553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      patch_data: the binary patch content
230553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      part_file: the partition file object
231553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Raises:
232553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      PayloadError if something goes wrong.
233553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
234553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """
235553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    block_size = self.block_size
236553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
237553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Gather input raw data and write to a temp file.
238553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    in_data = _ReadExtents(part_file, op.src_extents, block_size,
239553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold                           max_length=op.src_length)
240553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    with tempfile.NamedTemporaryFile(delete=False) as in_file:
241553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      in_file_name = in_file.name
242553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      in_file.write(in_data)
243553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
244553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Dump patch data to file.
245553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    with tempfile.NamedTemporaryFile(delete=False) as patch_file:
246553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      patch_file_name = patch_file.name
247553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      patch_file.write(patch_data)
248553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
249553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Allocate tepmorary output file.
250553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    with tempfile.NamedTemporaryFile(delete=False) as out_file:
251553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      out_file_name = out_file.name
252553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
253553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Invoke bspatch.
254553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    bspatch_cmd = ['bspatch', in_file_name, out_file_name, patch_file_name]
255553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    subprocess.check_call(bspatch_cmd)
256553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
257553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Read output.
258553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    with open(out_file_name, 'rb') as out_file:
259553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      out_data = out_file.read()
260553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      if len(out_data) != op.dst_length:
261553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        raise PayloadError(
262553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold            '%s: actual patched data length (%d) not as expected (%d)' %
263553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold            (op_name, len(out_data), op.dst_length))
264553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
265553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Write output back to partition, with padding.
266553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    unaligned_out_len = len(out_data) % block_size
267553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    if unaligned_out_len:
268553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      out_data += '\0' * (block_size - unaligned_out_len)
269553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    _WriteExtents(part_file, out_data, op.dst_extents, block_size,
270553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold                  '%s.dst_extents' % op_name)
271553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
272553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Delete all temporary files.
273553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    os.remove(in_file_name)
274553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    os.remove(out_file_name)
275553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    os.remove(patch_file_name)
276553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
277553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  def _ApplyOperations(self, operations, base_name, part_file, part_size):
278553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """Applies a sequence of update operations to a partition.
279553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
280553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    This assumes an in-place update semantics, namely all reads are performed
281553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    first, then the data is processed and written back to the same file.
282553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
283553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Args:
284553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      operations: the sequence of operations
285553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      base_name: the name of the operation sequence
286553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      part_file: the partition file object, open for reading/writing
287553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      part_size: the partition size
288553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Raises:
289553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      PayloadError if anything goes wrong while processing the payload.
290553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
291553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """
292553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    for op, op_name in common.OperationIter(operations, base_name):
293553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      # Read data blob.
294553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      data = self.payload.ReadDataBlob(op.data_offset, op.data_length)
295553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
296553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
297553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        self._ApplyReplaceOperation(op, op_name, data, part_file, part_size)
298553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      elif op.type == common.OpType.MOVE:
299553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        self._ApplyMoveOperation(op, op_name, part_file)
300553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      elif op.type == common.OpType.BSDIFF:
301553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        self._ApplyBsdiffOperation(op, op_name, data, part_file)
302553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      else:
303553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        raise PayloadError('%s: unknown operation type (%d)' %
304553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold                           (op_name, op.type))
305553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
306553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold  def _ApplyToPartition(self, operations, part_name, base_name,
30716416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold                        new_part_file_name, new_part_info,
30816416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold                        old_part_file_name=None, old_part_info=None):
309553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """Applies an update to a partition.
310553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
311553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Args:
312553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      operations: the sequence of update operations to apply
313553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      part_name: the name of the partition, for error reporting
314553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      base_name: the name of the operation sequence
31516416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      new_part_file_name: file name to write partition data to
31616416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      new_part_info: size and expected hash of dest partition
31716416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      old_part_file_name: file name of source partition (optional)
31816416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      old_part_info: size and expected hash of source partition (optional)
319553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Raises:
320553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      PayloadError if anything goes wrong with the update.
321553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
322553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """
323553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Do we have a source partition?
32416416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold    if old_part_file_name:
325553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      # Verify the source partition.
32616416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      with open(old_part_file_name, 'rb') as old_part_file:
32716416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold        _VerifySha256(old_part_file, old_part_info.hash, part_name,
32816416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold                      length=old_part_info.size)
329553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
330553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      # Copy the src partition to the dst one.
33116416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      shutil.copyfile(old_part_file_name, new_part_file_name)
332553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    else:
333553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      # Preallocate the dst partition file.
334553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      subprocess.check_call(
33516416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold          ['fallocate', '-l', str(new_part_info.size), new_part_file_name])
336553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
337553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Apply operations.
33816416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold    with open(new_part_file_name, 'r+b') as new_part_file:
33916416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      self._ApplyOperations(operations, base_name, new_part_file,
34016416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold                            new_part_info.size)
341553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
342553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Verify the resulting partition.
34316416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold    with open(new_part_file_name, 'rb') as new_part_file:
34416416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      _VerifySha256(new_part_file, new_part_info.hash, part_name,
34516416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold                    length=new_part_info.size)
346553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
34716416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold  def Run(self, new_kernel_part, new_rootfs_part, old_kernel_part=None,
34816416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold          old_rootfs_part=None):
349553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """Applier entry point, invoking all update operations.
350553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
351553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Args:
35216416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      new_kernel_part: name of dest kernel partition file
35316416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      new_rootfs_part: name of dest rootfs partition file
35416416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      old_kernel_part: name of source kernel partition file (optional)
35516416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold      old_rootfs_part: name of source rootfs partition file (optional)
356553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    Raises:
357553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      PayloadError if payload application failed.
358553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
359553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    """
360553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    self.payload.ResetFile()
361553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
362553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Make sure the arguments are sane and match the payload.
36316416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold    if not (new_kernel_part and new_rootfs_part):
364553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      raise PayloadError('missing dst {kernel,rootfs} partitions')
365553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
36616416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold    if not (old_kernel_part or old_rootfs_part):
367553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      if not self.payload.IsFull():
368553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        raise PayloadError('trying to apply a non-full update without src '
369553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold                           '{kernel,rootfs} partitions')
37016416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold    elif old_kernel_part and old_rootfs_part:
371553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      if not self.payload.IsDelta():
372553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        raise PayloadError('trying to apply a non-delta update onto src '
373553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold                           '{kernel,rootfs} partitions')
374553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    else:
375553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold      raise PayloadError('not all src partitions provided')
376553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
377553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Apply update to rootfs.
378553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    self._ApplyToPartition(
379553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        self.payload.manifest.install_operations, 'rootfs',
38016416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold        'install_operations', new_rootfs_part,
38116416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold        self.payload.manifest.new_rootfs_info, old_rootfs_part,
382553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        self.payload.manifest.old_rootfs_info)
383553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold
384553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    # Apply update to kernel update.
385553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold    self._ApplyToPartition(
386553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        self.payload.manifest.kernel_install_operations, 'kernel',
38716416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold        'kernel_install_operations', new_kernel_part,
38816416600a92a60294cd57aceec170a13ed72ed19Gilad Arnold        self.payload.manifest.new_kernel_info, old_kernel_part,
389553b0ec49bc64fc4b7df4358cd31396a87276d2bGilad Arnold        self.payload.manifest.old_kernel_info)
390