blob: ed087c1d2038f5b358fe1094874e3400294a86c5 [file] [log] [blame]
Doug Zongker424296a2014-09-02 08:53:09 -07001# Copyright (C) 2014 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerfc44a512014-08-26 13:10:25 -070015from __future__ import print_function
16
Doug Zongker6ab2a502016-02-09 08:28:09 -080017import array
Tao Bao8dcf7382015-05-21 14:09:49 -070018import common
Doug Zongker6ab2a502016-02-09 08:28:09 -080019import functools
Doug Zongker62338182014-09-08 08:29:55 -070020import heapq
Doug Zongkerfc44a512014-08-26 13:10:25 -070021import itertools
22import multiprocessing
23import os
Tao Bao3a2e3502016-12-28 09:14:39 -080024import os.path
Doug Zongkerfc44a512014-08-26 13:10:25 -070025import re
26import subprocess
Tao Bao183e56e2017-03-05 17:05:09 -080027import sys
Doug Zongkerfc44a512014-08-26 13:10:25 -070028import threading
Doug Zongkerfc44a512014-08-26 13:10:25 -070029
Tao Bao3a2e3502016-12-28 09:14:39 -080030from collections import deque, OrderedDict
31from hashlib import sha1
Dan Albert8b72aef2015-03-23 19:13:21 -070032from rangelib import RangeSet
33
Doug Zongkerfc44a512014-08-26 13:10:25 -070034
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070035__all__ = ["EmptyImage", "DataImage", "BlockImageDiff"]
36
Dan Albert8b72aef2015-03-23 19:13:21 -070037
Tao Bao183e56e2017-03-05 17:05:09 -080038def compute_patch(srcfile, tgtfile, imgdiff=False):
39 patchfile = common.MakeTempFile(prefix="patch-")
Doug Zongkerfc44a512014-08-26 13:10:25 -070040
Tao Bao183e56e2017-03-05 17:05:09 -080041 if imgdiff:
42 p = subprocess.call(
43 ["imgdiff", "-z", srcfile, tgtfile, patchfile],
44 stdout=open(os.devnull, 'w'),
45 stderr=subprocess.STDOUT)
46 else:
47 p = subprocess.call(
48 ["bsdiff", srcfile, tgtfile, patchfile],
49 stdout=open(os.devnull, 'w'),
50 stderr=subprocess.STDOUT)
Doug Zongkerfc44a512014-08-26 13:10:25 -070051
Tao Bao183e56e2017-03-05 17:05:09 -080052 if p:
53 raise ValueError("diff failed: " + str(p))
Doug Zongkerfc44a512014-08-26 13:10:25 -070054
Tao Bao183e56e2017-03-05 17:05:09 -080055 with open(patchfile, "rb") as f:
56 return f.read()
Doug Zongkerfc44a512014-08-26 13:10:25 -070057
Dan Albert8b72aef2015-03-23 19:13:21 -070058
59class Image(object):
Tao Bao183e56e2017-03-05 17:05:09 -080060 def RangeSha1(self, ranges):
61 raise NotImplementedError
62
Dan Albert8b72aef2015-03-23 19:13:21 -070063 def ReadRangeSet(self, ranges):
64 raise NotImplementedError
65
Tao Bao68658c02015-06-01 13:40:49 -070066 def TotalSha1(self, include_clobbered_blocks=False):
Dan Albert8b72aef2015-03-23 19:13:21 -070067 raise NotImplementedError
68
Tao Bao183e56e2017-03-05 17:05:09 -080069 def WriteRangeDataToFd(self, ranges, fd):
70 raise NotImplementedError
71
Dan Albert8b72aef2015-03-23 19:13:21 -070072
73class EmptyImage(Image):
Doug Zongkerfc44a512014-08-26 13:10:25 -070074 """A zero-length image."""
Tao Bao183e56e2017-03-05 17:05:09 -080075
76 def __init__(self):
77 self.blocksize = 4096
78 self.care_map = RangeSet()
79 self.clobbered_blocks = RangeSet()
80 self.extended = RangeSet()
81 self.total_blocks = 0
82 self.file_map = {}
83
84 def RangeSha1(self, ranges):
85 return sha1().hexdigest()
86
Doug Zongkerfc44a512014-08-26 13:10:25 -070087 def ReadRangeSet(self, ranges):
88 return ()
Tao Bao183e56e2017-03-05 17:05:09 -080089
Tao Bao68658c02015-06-01 13:40:49 -070090 def TotalSha1(self, include_clobbered_blocks=False):
91 # EmptyImage always carries empty clobbered_blocks, so
92 # include_clobbered_blocks can be ignored.
93 assert self.clobbered_blocks.size() == 0
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070094 return sha1().hexdigest()
95
Tao Bao183e56e2017-03-05 17:05:09 -080096 def WriteRangeDataToFd(self, ranges, fd):
97 raise ValueError("Can't write data from EmptyImage to file")
98
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070099
Dan Albert8b72aef2015-03-23 19:13:21 -0700100class DataImage(Image):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700101 """An image wrapped around a single string of data."""
102
103 def __init__(self, data, trim=False, pad=False):
104 self.data = data
105 self.blocksize = 4096
106
107 assert not (trim and pad)
108
109 partial = len(self.data) % self.blocksize
Tao Bao7589e962015-09-05 20:35:32 -0700110 padded = False
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700111 if partial > 0:
112 if trim:
113 self.data = self.data[:-partial]
114 elif pad:
115 self.data += '\0' * (self.blocksize - partial)
Tao Bao7589e962015-09-05 20:35:32 -0700116 padded = True
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700117 else:
118 raise ValueError(("data for DataImage must be multiple of %d bytes "
119 "unless trim or pad is specified") %
120 (self.blocksize,))
121
122 assert len(self.data) % self.blocksize == 0
123
124 self.total_blocks = len(self.data) / self.blocksize
125 self.care_map = RangeSet(data=(0, self.total_blocks))
Tao Bao7589e962015-09-05 20:35:32 -0700126 # When the last block is padded, we always write the whole block even for
127 # incremental OTAs. Because otherwise the last block may get skipped if
128 # unchanged for an incremental, but would fail the post-install
129 # verification if it has non-zero contents in the padding bytes.
130 # Bug: 23828506
131 if padded:
Tao Bao42206c32015-09-08 13:39:40 -0700132 clobbered_blocks = [self.total_blocks-1, self.total_blocks]
Tao Bao7589e962015-09-05 20:35:32 -0700133 else:
Tao Bao42206c32015-09-08 13:39:40 -0700134 clobbered_blocks = []
135 self.clobbered_blocks = clobbered_blocks
Tao Baoe9b61912015-07-09 17:37:49 -0700136 self.extended = RangeSet()
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700137
138 zero_blocks = []
139 nonzero_blocks = []
140 reference = '\0' * self.blocksize
141
Tao Bao7589e962015-09-05 20:35:32 -0700142 for i in range(self.total_blocks-1 if padded else self.total_blocks):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700143 d = self.data[i*self.blocksize : (i+1)*self.blocksize]
144 if d == reference:
145 zero_blocks.append(i)
146 zero_blocks.append(i+1)
147 else:
148 nonzero_blocks.append(i)
149 nonzero_blocks.append(i+1)
150
Tao Bao42206c32015-09-08 13:39:40 -0700151 assert zero_blocks or nonzero_blocks or clobbered_blocks
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700152
Tao Bao42206c32015-09-08 13:39:40 -0700153 self.file_map = dict()
154 if zero_blocks:
155 self.file_map["__ZERO"] = RangeSet(data=zero_blocks)
156 if nonzero_blocks:
157 self.file_map["__NONZERO"] = RangeSet(data=nonzero_blocks)
158 if clobbered_blocks:
159 self.file_map["__COPY"] = RangeSet(data=clobbered_blocks)
Tao Bao7589e962015-09-05 20:35:32 -0700160
Tao Bao183e56e2017-03-05 17:05:09 -0800161 def _GetRangeData(self, ranges):
162 for s, e in ranges:
163 yield self.data[s*self.blocksize:e*self.blocksize]
164
165 def RangeSha1(self, ranges):
166 h = sha1()
167 for data in self._GetRangeData(ranges):
168 h.update(data)
169 return h.hexdigest()
170
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700171 def ReadRangeSet(self, ranges):
Tao Bao183e56e2017-03-05 17:05:09 -0800172 return [self._GetRangeData(ranges)]
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700173
Tao Bao68658c02015-06-01 13:40:49 -0700174 def TotalSha1(self, include_clobbered_blocks=False):
Tao Bao7589e962015-09-05 20:35:32 -0700175 if not include_clobbered_blocks:
Tao Bao183e56e2017-03-05 17:05:09 -0800176 return self.RangeSha1(self.care_map.subtract(self.clobbered_blocks))
Tao Bao7589e962015-09-05 20:35:32 -0700177 else:
178 return sha1(self.data).hexdigest()
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700179
Tao Bao183e56e2017-03-05 17:05:09 -0800180 def WriteRangeDataToFd(self, ranges, fd):
181 for data in self._GetRangeData(ranges):
182 fd.write(data)
183
Doug Zongkerfc44a512014-08-26 13:10:25 -0700184
185class Transfer(object):
Tao Bao183e56e2017-03-05 17:05:09 -0800186 def __init__(self, tgt_name, src_name, tgt_ranges, src_ranges, tgt_sha1,
187 src_sha1, style, by_id):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700188 self.tgt_name = tgt_name
189 self.src_name = src_name
190 self.tgt_ranges = tgt_ranges
191 self.src_ranges = src_ranges
Tao Bao183e56e2017-03-05 17:05:09 -0800192 self.tgt_sha1 = tgt_sha1
193 self.src_sha1 = src_sha1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700194 self.style = style
195 self.intact = (getattr(tgt_ranges, "monotonic", False) and
196 getattr(src_ranges, "monotonic", False))
Tao Baob8c87172015-03-19 19:42:12 -0700197
198 # We use OrderedDict rather than dict so that the output is repeatable;
199 # otherwise it would depend on the hash values of the Transfer objects.
200 self.goes_before = OrderedDict()
201 self.goes_after = OrderedDict()
Doug Zongkerfc44a512014-08-26 13:10:25 -0700202
Doug Zongker62338182014-09-08 08:29:55 -0700203 self.stash_before = []
204 self.use_stash = []
205
Doug Zongkerfc44a512014-08-26 13:10:25 -0700206 self.id = len(by_id)
207 by_id.append(self)
208
Doug Zongker62338182014-09-08 08:29:55 -0700209 def NetStashChange(self):
210 return (sum(sr.size() for (_, sr) in self.stash_before) -
211 sum(sr.size() for (_, sr) in self.use_stash))
212
Tao Bao82c47982015-08-17 09:45:13 -0700213 def ConvertToNew(self):
214 assert self.style != "new"
215 self.use_stash = []
216 self.style = "new"
217 self.src_ranges = RangeSet()
218
Doug Zongkerfc44a512014-08-26 13:10:25 -0700219 def __str__(self):
220 return (str(self.id) + ": <" + str(self.src_ranges) + " " + self.style +
221 " to " + str(self.tgt_ranges) + ">")
222
223
Doug Zongker6ab2a502016-02-09 08:28:09 -0800224@functools.total_ordering
225class HeapItem(object):
226 def __init__(self, item):
227 self.item = item
228 # Negate the score since python's heap is a min-heap and we want
229 # the maximum score.
230 self.score = -item.score
231 def clear(self):
232 self.item = None
233 def __bool__(self):
234 return self.item is None
235 def __eq__(self, other):
236 return self.score == other.score
237 def __le__(self, other):
238 return self.score <= other.score
239
240
Doug Zongkerfc44a512014-08-26 13:10:25 -0700241# BlockImageDiff works on two image objects. An image object is
242# anything that provides the following attributes:
243#
244# blocksize: the size in bytes of a block, currently must be 4096.
245#
246# total_blocks: the total size of the partition/image, in blocks.
247#
248# care_map: a RangeSet containing which blocks (in the range [0,
249# total_blocks) we actually care about; i.e. which blocks contain
250# data.
251#
252# file_map: a dict that partitions the blocks contained in care_map
253# into smaller domains that are useful for doing diffs on.
254# (Typically a domain is a file, and the key in file_map is the
255# pathname.)
256#
Tao Baoff777812015-05-12 11:42:31 -0700257# clobbered_blocks: a RangeSet containing which blocks contain data
258# but may be altered by the FS. They need to be excluded when
259# verifying the partition integrity.
260#
Doug Zongkerfc44a512014-08-26 13:10:25 -0700261# ReadRangeSet(): a function that takes a RangeSet and returns the
262# data contained in the image blocks of that RangeSet. The data
263# is returned as a list or tuple of strings; concatenating the
264# elements together should produce the requested data.
265# Implementations are free to break up the data into list/tuple
266# elements in any way that is convenient.
267#
Tao Bao183e56e2017-03-05 17:05:09 -0800268# RangeSha1(): a function that returns (as a hex string) the SHA-1
269# hash of all the data in the specified range.
270#
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700271# TotalSha1(): a function that returns (as a hex string) the SHA-1
272# hash of all the data in the image (ie, all the blocks in the
Tao Bao68658c02015-06-01 13:40:49 -0700273# care_map minus clobbered_blocks, or including the clobbered
274# blocks if include_clobbered_blocks is True).
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700275#
Doug Zongkerfc44a512014-08-26 13:10:25 -0700276# When creating a BlockImageDiff, the src image may be None, in which
277# case the list of transfers produced will never read from the
278# original image.
279
280class BlockImageDiff(object):
Tao Bao293fd132016-06-11 12:19:23 -0700281 def __init__(self, tgt, src=None, threads=None, version=4,
282 disable_imgdiff=False):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700283 if threads is None:
284 threads = multiprocessing.cpu_count() // 2
Dan Albert8b72aef2015-03-23 19:13:21 -0700285 if threads == 0:
286 threads = 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700287 self.threads = threads
Doug Zongker62338182014-09-08 08:29:55 -0700288 self.version = version
Dan Albert8b72aef2015-03-23 19:13:21 -0700289 self.transfers = []
290 self.src_basenames = {}
291 self.src_numpatterns = {}
Tao Baob4cfca52016-02-04 14:26:02 -0800292 self._max_stashed_size = 0
Tao Baod522bdc2016-04-12 15:53:16 -0700293 self.touched_src_ranges = RangeSet()
294 self.touched_src_sha1 = None
Tao Bao293fd132016-06-11 12:19:23 -0700295 self.disable_imgdiff = disable_imgdiff
Doug Zongker62338182014-09-08 08:29:55 -0700296
Tao Bao8fad03e2017-03-01 14:36:26 -0800297 assert version in (3, 4)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700298
299 self.tgt = tgt
300 if src is None:
301 src = EmptyImage()
302 self.src = src
303
304 # The updater code that installs the patch always uses 4k blocks.
305 assert tgt.blocksize == 4096
306 assert src.blocksize == 4096
307
308 # The range sets in each filemap should comprise a partition of
309 # the care map.
310 self.AssertPartition(src.care_map, src.file_map.values())
311 self.AssertPartition(tgt.care_map, tgt.file_map.values())
312
Tao Baob4cfca52016-02-04 14:26:02 -0800313 @property
314 def max_stashed_size(self):
315 return self._max_stashed_size
316
Doug Zongkerfc44a512014-08-26 13:10:25 -0700317 def Compute(self, prefix):
318 # When looking for a source file to use as the diff input for a
319 # target file, we try:
320 # 1) an exact path match if available, otherwise
321 # 2) a exact basename match if available, otherwise
322 # 3) a basename match after all runs of digits are replaced by
323 # "#" if available, otherwise
324 # 4) we have no source for this target.
325 self.AbbreviateSourceNames()
326 self.FindTransfers()
327
328 # Find the ordering dependencies among transfers (this is O(n^2)
329 # in the number of transfers).
330 self.GenerateDigraph()
331 # Find a sequence of transfers that satisfies as many ordering
332 # dependencies as possible (heuristically).
333 self.FindVertexSequence()
334 # Fix up the ordering dependencies that the sequence didn't
335 # satisfy.
Tao Bao8fad03e2017-03-01 14:36:26 -0800336 self.ReverseBackwardEdges()
337 self.ImproveVertexSequence()
Doug Zongker62338182014-09-08 08:29:55 -0700338
Tao Bao82c47982015-08-17 09:45:13 -0700339 # Ensure the runtime stash size is under the limit.
Tao Bao8fad03e2017-03-01 14:36:26 -0800340 if common.OPTIONS.cache_size is not None:
Tao Bao82c47982015-08-17 09:45:13 -0700341 self.ReviseStashSize()
342
Doug Zongkerfc44a512014-08-26 13:10:25 -0700343 # Double-check our work.
344 self.AssertSequenceGood()
345
346 self.ComputePatches(prefix)
347 self.WriteTransfers(prefix)
348
349 def WriteTransfers(self, prefix):
Tianjie Xu37e29302016-06-23 16:10:35 -0700350 def WriteSplitTransfers(out, style, target_blocks):
351 """Limit the size of operand in command 'new' and 'zero' to 1024 blocks.
Tianjie Xubb848c52016-06-21 15:54:09 -0700352
353 This prevents the target size of one command from being too large; and
354 might help to avoid fsync errors on some devices."""
355
Tao Bao3a2e3502016-12-28 09:14:39 -0800356 assert style == "new" or style == "zero"
Tianjie Xu37e29302016-06-23 16:10:35 -0700357 blocks_limit = 1024
Tianjie Xubb848c52016-06-21 15:54:09 -0700358 total = 0
Tianjie Xu37e29302016-06-23 16:10:35 -0700359 while target_blocks:
360 blocks_to_write = target_blocks.first(blocks_limit)
361 out.append("%s %s\n" % (style, blocks_to_write.to_string_raw()))
362 total += blocks_to_write.size()
363 target_blocks = target_blocks.subtract(blocks_to_write)
Tianjie Xubb848c52016-06-21 15:54:09 -0700364 return total
365
Doug Zongkerfc44a512014-08-26 13:10:25 -0700366 out = []
Doug Zongkerfc44a512014-08-26 13:10:25 -0700367 total = 0
Doug Zongkerfc44a512014-08-26 13:10:25 -0700368
Tao Bao3a2e3502016-12-28 09:14:39 -0800369 # In BBOTA v3+, it uses the hash of the stashed blocks as the stash slot
370 # id. 'stashes' records the map from 'hash' to the ref count. The stash
371 # will be freed only if the count decrements to zero.
Doug Zongker62338182014-09-08 08:29:55 -0700372 stashes = {}
373 stashed_blocks = 0
374 max_stashed_blocks = 0
375
Doug Zongkerfc44a512014-08-26 13:10:25 -0700376 for xf in self.transfers:
377
Tao Bao8fad03e2017-03-01 14:36:26 -0800378 for _, sr in xf.stash_before:
379 sh = self.src.RangeSha1(sr)
380 if sh in stashes:
381 stashes[sh] += 1
Sami Tolvanendd67a292014-12-09 16:40:34 +0000382 else:
Tao Bao8fad03e2017-03-01 14:36:26 -0800383 stashes[sh] = 1
384 stashed_blocks += sr.size()
385 self.touched_src_ranges = self.touched_src_ranges.union(sr)
386 out.append("stash %s %s\n" % (sh, sr.to_string_raw()))
Doug Zongker62338182014-09-08 08:29:55 -0700387
388 if stashed_blocks > max_stashed_blocks:
389 max_stashed_blocks = stashed_blocks
390
Jesse Zhao7b985f62015-03-02 16:53:08 -0800391 free_string = []
caozhiyuan21b37d82015-10-21 15:14:03 +0800392 free_size = 0
Jesse Zhao7b985f62015-03-02 16:53:08 -0800393
Tao Bao8fad03e2017-03-01 14:36:26 -0800394 # <# blocks> <src ranges>
395 # OR
396 # <# blocks> <src ranges> <src locs> <stash refs...>
397 # OR
398 # <# blocks> - <stash refs...>
Doug Zongker62338182014-09-08 08:29:55 -0700399
Tao Bao8fad03e2017-03-01 14:36:26 -0800400 size = xf.src_ranges.size()
401 src_str = [str(size)]
Doug Zongker62338182014-09-08 08:29:55 -0700402
Tao Bao8fad03e2017-03-01 14:36:26 -0800403 unstashed_src_ranges = xf.src_ranges
404 mapped_stashes = []
405 for _, sr in xf.use_stash:
406 unstashed_src_ranges = unstashed_src_ranges.subtract(sr)
407 sh = self.src.RangeSha1(sr)
408 sr = xf.src_ranges.map_within(sr)
409 mapped_stashes.append(sr)
410 assert sh in stashes
411 src_str.append("%s:%s" % (sh, sr.to_string_raw()))
412 stashes[sh] -= 1
413 if stashes[sh] == 0:
414 free_string.append("free %s\n" % (sh,))
415 free_size += sr.size()
416 stashes.pop(sh)
Doug Zongker62338182014-09-08 08:29:55 -0700417
Tao Bao8fad03e2017-03-01 14:36:26 -0800418 if unstashed_src_ranges:
419 src_str.insert(1, unstashed_src_ranges.to_string_raw())
420 if xf.use_stash:
421 mapped_unstashed = xf.src_ranges.map_within(unstashed_src_ranges)
422 src_str.insert(2, mapped_unstashed.to_string_raw())
423 mapped_stashes.append(mapped_unstashed)
Doug Zongker62338182014-09-08 08:29:55 -0700424 self.AssertPartition(RangeSet(data=(0, size)), mapped_stashes)
Tao Bao8fad03e2017-03-01 14:36:26 -0800425 else:
426 src_str.insert(1, "-")
427 self.AssertPartition(RangeSet(data=(0, size)), mapped_stashes)
Doug Zongker62338182014-09-08 08:29:55 -0700428
Tao Bao8fad03e2017-03-01 14:36:26 -0800429 src_str = " ".join(src_str)
Doug Zongker62338182014-09-08 08:29:55 -0700430
Tao Bao8fad03e2017-03-01 14:36:26 -0800431 # version 3+:
Doug Zongker62338182014-09-08 08:29:55 -0700432 # zero <rangeset>
433 # new <rangeset>
434 # erase <rangeset>
Dan Albert8b72aef2015-03-23 19:13:21 -0700435 # bsdiff patchstart patchlen srchash tgthash <tgt rangeset> <src_str>
436 # imgdiff patchstart patchlen srchash tgthash <tgt rangeset> <src_str>
437 # move hash <tgt rangeset> <src_str>
Doug Zongkerfc44a512014-08-26 13:10:25 -0700438
439 tgt_size = xf.tgt_ranges.size()
440
441 if xf.style == "new":
442 assert xf.tgt_ranges
Tianjie Xu37e29302016-06-23 16:10:35 -0700443 assert tgt_size == WriteSplitTransfers(out, xf.style, xf.tgt_ranges)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700444 total += tgt_size
445 elif xf.style == "move":
Doug Zongkerfc44a512014-08-26 13:10:25 -0700446 assert xf.tgt_ranges
447 assert xf.src_ranges.size() == tgt_size
448 if xf.src_ranges != xf.tgt_ranges:
Sami Tolvanen29f529f2015-04-17 16:28:08 +0100449 # take into account automatic stashing of overlapping blocks
450 if xf.src_ranges.overlaps(xf.tgt_ranges):
Tao Baoe9b61912015-07-09 17:37:49 -0700451 temp_stash_usage = stashed_blocks + xf.src_ranges.size()
Sami Tolvanen29f529f2015-04-17 16:28:08 +0100452 if temp_stash_usage > max_stashed_blocks:
453 max_stashed_blocks = temp_stash_usage
454
Tao Baod522bdc2016-04-12 15:53:16 -0700455 self.touched_src_ranges = self.touched_src_ranges.union(
456 xf.src_ranges)
457
Tao Bao8fad03e2017-03-01 14:36:26 -0800458 out.append("%s %s %s %s\n" % (
Sami Tolvanendd67a292014-12-09 16:40:34 +0000459 xf.style,
Tao Bao183e56e2017-03-05 17:05:09 -0800460 xf.tgt_sha1,
Dan Albert8b72aef2015-03-23 19:13:21 -0700461 xf.tgt_ranges.to_string_raw(), src_str))
Tao Bao8fad03e2017-03-01 14:36:26 -0800462 total += tgt_size
463 elif xf.style in ("bsdiff", "imgdiff"):
464 assert xf.tgt_ranges
465 assert xf.src_ranges
466 # take into account automatic stashing of overlapping blocks
467 if xf.src_ranges.overlaps(xf.tgt_ranges):
468 temp_stash_usage = stashed_blocks + xf.src_ranges.size()
469 if temp_stash_usage > max_stashed_blocks:
470 max_stashed_blocks = temp_stash_usage
471
472 self.touched_src_ranges = self.touched_src_ranges.union(xf.src_ranges)
473
474 out.append("%s %d %d %s %s %s %s\n" % (
475 xf.style,
476 xf.patch_start, xf.patch_len,
477 xf.src_sha1,
478 xf.tgt_sha1,
479 xf.tgt_ranges.to_string_raw(), src_str))
Doug Zongkerfc44a512014-08-26 13:10:25 -0700480 total += tgt_size
481 elif xf.style == "zero":
482 assert xf.tgt_ranges
483 to_zero = xf.tgt_ranges.subtract(xf.src_ranges)
Tianjie Xu37e29302016-06-23 16:10:35 -0700484 assert WriteSplitTransfers(out, xf.style, to_zero) == to_zero.size()
Tianjie Xubb848c52016-06-21 15:54:09 -0700485 total += to_zero.size()
Doug Zongkerfc44a512014-08-26 13:10:25 -0700486 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700487 raise ValueError("unknown transfer style '%s'\n" % xf.style)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700488
Sami Tolvanendd67a292014-12-09 16:40:34 +0000489 if free_string:
490 out.append("".join(free_string))
caozhiyuan21b37d82015-10-21 15:14:03 +0800491 stashed_blocks -= free_size
Sami Tolvanendd67a292014-12-09 16:40:34 +0000492
Tao Bao8fad03e2017-03-01 14:36:26 -0800493 if common.OPTIONS.cache_size is not None:
Tao Bao8dcf7382015-05-21 14:09:49 -0700494 # Sanity check: abort if we're going to need more stash space than
495 # the allowed size (cache_size * threshold). There are two purposes
496 # of having a threshold here. a) Part of the cache may have been
497 # occupied by some recovery logs. b) It will buy us some time to deal
498 # with the oversize issue.
499 cache_size = common.OPTIONS.cache_size
500 stash_threshold = common.OPTIONS.stash_threshold
501 max_allowed = cache_size * stash_threshold
Tao Baoe8c68a02017-02-26 10:48:11 -0800502 assert max_stashed_blocks * self.tgt.blocksize <= max_allowed, \
Tao Bao8dcf7382015-05-21 14:09:49 -0700503 'Stash size %d (%d * %d) exceeds the limit %d (%d * %.2f)' % (
504 max_stashed_blocks * self.tgt.blocksize, max_stashed_blocks,
505 self.tgt.blocksize, max_allowed, cache_size,
506 stash_threshold)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700507
Tao Bao8fad03e2017-03-01 14:36:26 -0800508 self.touched_src_sha1 = self.src.RangeSha1(self.touched_src_ranges)
Tao Baod522bdc2016-04-12 15:53:16 -0700509
Tao Baoe9b61912015-07-09 17:37:49 -0700510 # Zero out extended blocks as a workaround for bug 20881595.
511 if self.tgt.extended:
Tianjie Xu37e29302016-06-23 16:10:35 -0700512 assert (WriteSplitTransfers(out, "zero", self.tgt.extended) ==
Tianjie Xubb848c52016-06-21 15:54:09 -0700513 self.tgt.extended.size())
Tao Baob32d56e2015-09-09 11:55:01 -0700514 total += self.tgt.extended.size()
Tao Baoe9b61912015-07-09 17:37:49 -0700515
516 # We erase all the blocks on the partition that a) don't contain useful
Tao Bao66f1fa62016-05-03 10:02:01 -0700517 # data in the new image; b) will not be touched by dm-verity. Out of those
518 # blocks, we erase the ones that won't be used in this update at the
519 # beginning of an update. The rest would be erased at the end. This is to
520 # work around the eMMC issue observed on some devices, which may otherwise
521 # get starving for clean blocks and thus fail the update. (b/28347095)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700522 all_tgt = RangeSet(data=(0, self.tgt.total_blocks))
Tao Baoe9b61912015-07-09 17:37:49 -0700523 all_tgt_minus_extended = all_tgt.subtract(self.tgt.extended)
524 new_dontcare = all_tgt_minus_extended.subtract(self.tgt.care_map)
Tao Bao66f1fa62016-05-03 10:02:01 -0700525
526 erase_first = new_dontcare.subtract(self.touched_src_ranges)
527 if erase_first:
528 out.insert(0, "erase %s\n" % (erase_first.to_string_raw(),))
529
530 erase_last = new_dontcare.subtract(erase_first)
531 if erase_last:
532 out.append("erase %s\n" % (erase_last.to_string_raw(),))
Doug Zongkere985f6f2014-09-09 12:38:47 -0700533
534 out.insert(0, "%d\n" % (self.version,)) # format version number
Tao Baob32d56e2015-09-09 11:55:01 -0700535 out.insert(1, "%d\n" % (total,))
Tao Bao8fad03e2017-03-01 14:36:26 -0800536 # v3+: the number of stash slots is unused.
537 out.insert(2, "0\n")
538 out.insert(3, str(max_stashed_blocks) + "\n")
Doug Zongkerfc44a512014-08-26 13:10:25 -0700539
540 with open(prefix + ".transfer.list", "wb") as f:
541 for i in out:
542 f.write(i)
543
Tao Bao8fad03e2017-03-01 14:36:26 -0800544 self._max_stashed_size = max_stashed_blocks * self.tgt.blocksize
545 OPTIONS = common.OPTIONS
546 if OPTIONS.cache_size is not None:
547 max_allowed = OPTIONS.cache_size * OPTIONS.stash_threshold
548 print("max stashed blocks: %d (%d bytes), "
549 "limit: %d bytes (%.2f%%)\n" % (
550 max_stashed_blocks, self._max_stashed_size, max_allowed,
551 self._max_stashed_size * 100.0 / max_allowed))
552 else:
553 print("max stashed blocks: %d (%d bytes), limit: <unknown>\n" % (
554 max_stashed_blocks, self._max_stashed_size))
Doug Zongker62338182014-09-08 08:29:55 -0700555
Tao Bao82c47982015-08-17 09:45:13 -0700556 def ReviseStashSize(self):
557 print("Revising stash size...")
Tao Baoe27acfd2016-12-16 11:13:55 -0800558 stash_map = {}
Tao Bao82c47982015-08-17 09:45:13 -0700559
560 # Create the map between a stash and its def/use points. For example, for a
Tao Bao3c5a16d2017-02-13 11:42:50 -0800561 # given stash of (raw_id, sr), stash_map[raw_id] = (sr, def_cmd, use_cmd).
Tao Bao82c47982015-08-17 09:45:13 -0700562 for xf in self.transfers:
563 # Command xf defines (stores) all the stashes in stash_before.
Tao Bao3a2e3502016-12-28 09:14:39 -0800564 for stash_raw_id, sr in xf.stash_before:
565 stash_map[stash_raw_id] = (sr, xf)
Tao Bao82c47982015-08-17 09:45:13 -0700566
567 # Record all the stashes command xf uses.
Tao Bao3a2e3502016-12-28 09:14:39 -0800568 for stash_raw_id, _ in xf.use_stash:
569 stash_map[stash_raw_id] += (xf,)
Tao Bao82c47982015-08-17 09:45:13 -0700570
571 # Compute the maximum blocks available for stash based on /cache size and
572 # the threshold.
573 cache_size = common.OPTIONS.cache_size
574 stash_threshold = common.OPTIONS.stash_threshold
575 max_allowed = cache_size * stash_threshold / self.tgt.blocksize
576
Tao Bao3a2e3502016-12-28 09:14:39 -0800577 # See the comments for 'stashes' in WriteTransfers().
Tao Baoe27acfd2016-12-16 11:13:55 -0800578 stashes = {}
Tao Bao82c47982015-08-17 09:45:13 -0700579 stashed_blocks = 0
Tao Bao9a5caf22015-08-25 15:10:10 -0700580 new_blocks = 0
Tao Bao82c47982015-08-17 09:45:13 -0700581
582 # Now go through all the commands. Compute the required stash size on the
583 # fly. If a command requires excess stash than available, it deletes the
584 # stash by replacing the command that uses the stash with a "new" command
585 # instead.
586 for xf in self.transfers:
587 replaced_cmds = []
588
589 # xf.stash_before generates explicit stash commands.
Tao Bao3a2e3502016-12-28 09:14:39 -0800590 for stash_raw_id, sr in xf.stash_before:
Tao Baoe27acfd2016-12-16 11:13:55 -0800591 # Check the post-command stashed_blocks.
592 stashed_blocks_after = stashed_blocks
Tao Bao8fad03e2017-03-01 14:36:26 -0800593 sh = self.src.RangeSha1(sr)
594 if sh not in stashes:
Tao Baoe27acfd2016-12-16 11:13:55 -0800595 stashed_blocks_after += sr.size()
Tao Baoe27acfd2016-12-16 11:13:55 -0800596
597 if stashed_blocks_after > max_allowed:
Tao Bao82c47982015-08-17 09:45:13 -0700598 # We cannot stash this one for a later command. Find out the command
599 # that will use this stash and replace the command with "new".
Tao Bao3a2e3502016-12-28 09:14:39 -0800600 use_cmd = stash_map[stash_raw_id][2]
Tao Bao82c47982015-08-17 09:45:13 -0700601 replaced_cmds.append(use_cmd)
Tao Bao9a5caf22015-08-25 15:10:10 -0700602 print("%10d %9s %s" % (sr.size(), "explicit", use_cmd))
Tao Bao82c47982015-08-17 09:45:13 -0700603 else:
Tao Bao3c5a16d2017-02-13 11:42:50 -0800604 # Update the stashes map.
Tao Bao8fad03e2017-03-01 14:36:26 -0800605 if sh in stashes:
606 stashes[sh] += 1
Tao Bao3c5a16d2017-02-13 11:42:50 -0800607 else:
Tao Bao8fad03e2017-03-01 14:36:26 -0800608 stashes[sh] = 1
Tao Baoe27acfd2016-12-16 11:13:55 -0800609 stashed_blocks = stashed_blocks_after
Tao Bao82c47982015-08-17 09:45:13 -0700610
611 # "move" and "diff" may introduce implicit stashes in BBOTA v3. Prior to
612 # ComputePatches(), they both have the style of "diff".
Tao Bao8fad03e2017-03-01 14:36:26 -0800613 if xf.style == "diff":
Tao Bao82c47982015-08-17 09:45:13 -0700614 assert xf.tgt_ranges and xf.src_ranges
615 if xf.src_ranges.overlaps(xf.tgt_ranges):
616 if stashed_blocks + xf.src_ranges.size() > max_allowed:
617 replaced_cmds.append(xf)
Tao Bao9a5caf22015-08-25 15:10:10 -0700618 print("%10d %9s %s" % (xf.src_ranges.size(), "implicit", xf))
Tao Bao82c47982015-08-17 09:45:13 -0700619
620 # Replace the commands in replaced_cmds with "new"s.
621 for cmd in replaced_cmds:
622 # It no longer uses any commands in "use_stash". Remove the def points
623 # for all those stashes.
Tao Bao3a2e3502016-12-28 09:14:39 -0800624 for stash_raw_id, sr in cmd.use_stash:
625 def_cmd = stash_map[stash_raw_id][1]
626 assert (stash_raw_id, sr) in def_cmd.stash_before
627 def_cmd.stash_before.remove((stash_raw_id, sr))
Tao Bao82c47982015-08-17 09:45:13 -0700628
Tianjie Xuebe39a02016-01-14 14:12:26 -0800629 # Add up blocks that violates space limit and print total number to
630 # screen later.
631 new_blocks += cmd.tgt_ranges.size()
Tao Bao82c47982015-08-17 09:45:13 -0700632 cmd.ConvertToNew()
633
Tao Bao3a2e3502016-12-28 09:14:39 -0800634 # xf.use_stash may generate free commands.
Tao Bao8fad03e2017-03-01 14:36:26 -0800635 for _, sr in xf.use_stash:
636 sh = self.src.RangeSha1(sr)
637 assert sh in stashes
638 stashes[sh] -= 1
639 if stashes[sh] == 0:
Tao Baoe27acfd2016-12-16 11:13:55 -0800640 stashed_blocks -= sr.size()
Tao Bao8fad03e2017-03-01 14:36:26 -0800641 stashes.pop(sh)
Tao Baoe27acfd2016-12-16 11:13:55 -0800642
Tianjie Xuebe39a02016-01-14 14:12:26 -0800643 num_of_bytes = new_blocks * self.tgt.blocksize
644 print(" Total %d blocks (%d bytes) are packed as new blocks due to "
645 "insufficient cache size." % (new_blocks, num_of_bytes))
Tao Bao304ee272016-12-19 11:01:38 -0800646 return new_blocks
Tao Bao9a5caf22015-08-25 15:10:10 -0700647
Doug Zongkerfc44a512014-08-26 13:10:25 -0700648 def ComputePatches(self, prefix):
649 print("Reticulating splines...")
Tao Bao183e56e2017-03-05 17:05:09 -0800650 diff_queue = []
Doug Zongkerfc44a512014-08-26 13:10:25 -0700651 patch_num = 0
652 with open(prefix + ".new.dat", "wb") as new_f:
Tao Bao183e56e2017-03-05 17:05:09 -0800653 for index, xf in enumerate(self.transfers):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700654 if xf.style == "zero":
Tao Bao08c85832016-09-19 22:26:30 -0700655 tgt_size = xf.tgt_ranges.size() * self.tgt.blocksize
656 print("%10d %10d (%6.2f%%) %7s %s %s" % (
657 tgt_size, tgt_size, 100.0, xf.style, xf.tgt_name,
658 str(xf.tgt_ranges)))
659
Doug Zongkerfc44a512014-08-26 13:10:25 -0700660 elif xf.style == "new":
Tao Bao183e56e2017-03-05 17:05:09 -0800661 self.tgt.WriteRangeDataToFd(xf.tgt_ranges, new_f)
Tao Bao08c85832016-09-19 22:26:30 -0700662 tgt_size = xf.tgt_ranges.size() * self.tgt.blocksize
663 print("%10d %10d (%6.2f%%) %7s %s %s" % (
664 tgt_size, tgt_size, 100.0, xf.style,
665 xf.tgt_name, str(xf.tgt_ranges)))
666
Doug Zongkerfc44a512014-08-26 13:10:25 -0700667 elif xf.style == "diff":
Doug Zongkerfc44a512014-08-26 13:10:25 -0700668 # We can't compare src and tgt directly because they may have
669 # the same content but be broken up into blocks differently, eg:
670 #
671 # ["he", "llo"] vs ["h", "ello"]
672 #
673 # We want those to compare equal, ideally without having to
674 # actually concatenate the strings (these may be tens of
675 # megabytes).
Tao Bao183e56e2017-03-05 17:05:09 -0800676 if xf.src_sha1 == xf.tgt_sha1:
Doug Zongkerfc44a512014-08-26 13:10:25 -0700677 # These are identical; we don't need to generate a patch,
678 # just issue copy commands on the device.
679 xf.style = "move"
Tao Bao183e56e2017-03-05 17:05:09 -0800680 tgt_size = xf.tgt_ranges.size() * self.tgt.blocksize
Tao Bao08c85832016-09-19 22:26:30 -0700681 if xf.src_ranges != xf.tgt_ranges:
682 print("%10d %10d (%6.2f%%) %7s %s %s (from %s)" % (
683 tgt_size, tgt_size, 100.0, xf.style,
684 xf.tgt_name if xf.tgt_name == xf.src_name else (
685 xf.tgt_name + " (from " + xf.src_name + ")"),
686 str(xf.tgt_ranges), str(xf.src_ranges)))
Doug Zongkerfc44a512014-08-26 13:10:25 -0700687 else:
688 # For files in zip format (eg, APKs, JARs, etc.) we would
689 # like to use imgdiff -z if possible (because it usually
690 # produces significantly smaller patches than bsdiff).
691 # This is permissible if:
692 #
Tao Bao293fd132016-06-11 12:19:23 -0700693 # - imgdiff is not disabled, and
Doug Zongkerfc44a512014-08-26 13:10:25 -0700694 # - the source and target files are monotonic (ie, the
695 # data is stored with blocks in increasing order), and
696 # - we haven't removed any blocks from the source set.
697 #
698 # If these conditions are satisfied then appending all the
699 # blocks in the set together in order will produce a valid
700 # zip file (plus possibly extra zeros in the last block),
701 # which is what imgdiff needs to operate. (imgdiff is
702 # fine with extra zeros at the end of the file.)
Tao Bao293fd132016-06-11 12:19:23 -0700703 imgdiff = (not self.disable_imgdiff and xf.intact and
Doug Zongkerfc44a512014-08-26 13:10:25 -0700704 xf.tgt_name.split(".")[-1].lower()
705 in ("apk", "jar", "zip"))
706 xf.style = "imgdiff" if imgdiff else "bsdiff"
Tao Bao183e56e2017-03-05 17:05:09 -0800707 diff_queue.append((index, imgdiff, patch_num))
Doug Zongkerfc44a512014-08-26 13:10:25 -0700708 patch_num += 1
709
710 else:
711 assert False, "unknown style " + xf.style
712
Tao Bao183e56e2017-03-05 17:05:09 -0800713 if diff_queue:
Doug Zongkerfc44a512014-08-26 13:10:25 -0700714 if self.threads > 1:
715 print("Computing patches (using %d threads)..." % (self.threads,))
716 else:
717 print("Computing patches...")
Doug Zongkerfc44a512014-08-26 13:10:25 -0700718
Tao Bao183e56e2017-03-05 17:05:09 -0800719 diff_total = len(diff_queue)
720 patches = [None] * diff_total
Doug Zongkerfc44a512014-08-26 13:10:25 -0700721
Tao Bao183e56e2017-03-05 17:05:09 -0800722 # Using multiprocessing doesn't give additional benefits, due to the
723 # pattern of the code. The diffing work is done by subprocess.call, which
724 # already runs in a separate process (not affected much by the GIL -
725 # Global Interpreter Lock). Using multiprocess also requires either a)
726 # writing the diff input files in the main process before forking, or b)
727 # reopening the image file (SparseImage) in the worker processes. Doing
728 # neither of them further improves the performance.
Doug Zongkerfc44a512014-08-26 13:10:25 -0700729 lock = threading.Lock()
730 def diff_worker():
731 while True:
732 with lock:
Tao Bao183e56e2017-03-05 17:05:09 -0800733 if not diff_queue:
Dan Albert8b72aef2015-03-23 19:13:21 -0700734 return
Tao Bao183e56e2017-03-05 17:05:09 -0800735 xf_index, imgdiff, patch_index = diff_queue.pop()
736
737 xf = self.transfers[xf_index]
738 src_ranges = xf.src_ranges
739 tgt_ranges = xf.tgt_ranges
740
741 # Needs lock since WriteRangeDataToFd() is stateful (calling seek).
Doug Zongkerfc44a512014-08-26 13:10:25 -0700742 with lock:
Tao Bao183e56e2017-03-05 17:05:09 -0800743 src_file = common.MakeTempFile(prefix="src-")
744 with open(src_file, "wb") as fd:
745 self.src.WriteRangeDataToFd(src_ranges, fd)
746
747 tgt_file = common.MakeTempFile(prefix="tgt-")
748 with open(tgt_file, "wb") as fd:
749 self.tgt.WriteRangeDataToFd(tgt_ranges, fd)
750
751 try:
752 patch = compute_patch(src_file, tgt_file, imgdiff)
753 except ValueError as e:
754 raise ValueError(
755 "Failed to generate diff for %s: src=%s, tgt=%s: %s" % (
756 xf.tgt_name, xf.src_ranges, xf.tgt_ranges, e.message))
757
758 with lock:
759 patches[patch_index] = (xf_index, patch)
760 if sys.stdout.isatty():
761 progress = len(patches) * 100 / diff_total
762 # '\033[K' is to clear to EOL.
763 print(' [%d%%] %s\033[K' % (progress, xf.tgt_name), end='\r')
764 sys.stdout.flush()
Doug Zongkerfc44a512014-08-26 13:10:25 -0700765
766 threads = [threading.Thread(target=diff_worker)
Dan Albert8b72aef2015-03-23 19:13:21 -0700767 for _ in range(self.threads)]
Doug Zongkerfc44a512014-08-26 13:10:25 -0700768 for th in threads:
769 th.start()
770 while threads:
771 threads.pop().join()
Tao Bao183e56e2017-03-05 17:05:09 -0800772
773 if sys.stdout.isatty():
774 print('\n')
Doug Zongkerfc44a512014-08-26 13:10:25 -0700775 else:
776 patches = []
777
Tao Bao183e56e2017-03-05 17:05:09 -0800778 offset = 0
779 with open(prefix + ".patch.dat", "wb") as patch_fd:
780 for index, patch in patches:
781 xf = self.transfers[index]
Doug Zongkerfc44a512014-08-26 13:10:25 -0700782 xf.patch_len = len(patch)
Tao Bao183e56e2017-03-05 17:05:09 -0800783 xf.patch_start = offset
784 offset += xf.patch_len
785 patch_fd.write(patch)
786
787 if common.OPTIONS.verbose:
788 tgt_size = xf.tgt_ranges.size() * self.tgt.blocksize
789 print("%10d %10d (%6.2f%%) %7s %s %s %s" % (
790 xf.patch_len, tgt_size, xf.patch_len * 100.0 / tgt_size,
791 xf.style,
792 xf.tgt_name if xf.tgt_name == xf.src_name else (
793 xf.tgt_name + " (from " + xf.src_name + ")"),
794 xf.tgt_ranges, xf.src_ranges))
Doug Zongkerfc44a512014-08-26 13:10:25 -0700795
796 def AssertSequenceGood(self):
797 # Simulate the sequences of transfers we will output, and check that:
798 # - we never read a block after writing it, and
799 # - we write every block we care about exactly once.
800
801 # Start with no blocks having been touched yet.
Doug Zongker6ab2a502016-02-09 08:28:09 -0800802 touched = array.array("B", "\0" * self.tgt.total_blocks)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700803
804 # Imagine processing the transfers in order.
805 for xf in self.transfers:
806 # Check that the input blocks for this transfer haven't yet been touched.
Doug Zongker62338182014-09-08 08:29:55 -0700807
808 x = xf.src_ranges
Tao Bao8fad03e2017-03-01 14:36:26 -0800809 for _, sr in xf.use_stash:
810 x = x.subtract(sr)
Doug Zongker62338182014-09-08 08:29:55 -0700811
Doug Zongker6ab2a502016-02-09 08:28:09 -0800812 for s, e in x:
Tao Baoff75c232016-03-04 15:23:34 -0800813 # Source image could be larger. Don't check the blocks that are in the
814 # source image only. Since they are not in 'touched', and won't ever
815 # be touched.
816 for i in range(s, min(e, self.tgt.total_blocks)):
Doug Zongker6ab2a502016-02-09 08:28:09 -0800817 assert touched[i] == 0
818
819 # Check that the output blocks for this transfer haven't yet
820 # been touched, and touch all the blocks written by this
821 # transfer.
822 for s, e in xf.tgt_ranges:
823 for i in range(s, e):
824 assert touched[i] == 0
825 touched[i] = 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700826
827 # Check that we've written every target block.
Doug Zongker6ab2a502016-02-09 08:28:09 -0800828 for s, e in self.tgt.care_map:
829 for i in range(s, e):
830 assert touched[i] == 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700831
Doug Zongker62338182014-09-08 08:29:55 -0700832 def ImproveVertexSequence(self):
833 print("Improving vertex order...")
834
835 # At this point our digraph is acyclic; we reversed any edges that
836 # were backwards in the heuristically-generated sequence. The
837 # previously-generated order is still acceptable, but we hope to
838 # find a better order that needs less memory for stashed data.
839 # Now we do a topological sort to generate a new vertex order,
840 # using a greedy algorithm to choose which vertex goes next
841 # whenever we have a choice.
842
843 # Make a copy of the edge set; this copy will get destroyed by the
844 # algorithm.
845 for xf in self.transfers:
846 xf.incoming = xf.goes_after.copy()
847 xf.outgoing = xf.goes_before.copy()
848
849 L = [] # the new vertex order
850
851 # S is the set of sources in the remaining graph; we always choose
852 # the one that leaves the least amount of stashed data after it's
853 # executed.
854 S = [(u.NetStashChange(), u.order, u) for u in self.transfers
855 if not u.incoming]
856 heapq.heapify(S)
857
858 while S:
859 _, _, xf = heapq.heappop(S)
860 L.append(xf)
861 for u in xf.outgoing:
862 del u.incoming[xf]
863 if not u.incoming:
864 heapq.heappush(S, (u.NetStashChange(), u.order, u))
865
866 # if this fails then our graph had a cycle.
867 assert len(L) == len(self.transfers)
868
869 self.transfers = L
870 for i, xf in enumerate(L):
871 xf.order = i
872
Doug Zongkerfc44a512014-08-26 13:10:25 -0700873 def RemoveBackwardEdges(self):
874 print("Removing backward edges...")
875 in_order = 0
876 out_of_order = 0
877 lost_source = 0
878
879 for xf in self.transfers:
Doug Zongkerfc44a512014-08-26 13:10:25 -0700880 lost = 0
881 size = xf.src_ranges.size()
882 for u in xf.goes_before:
883 # xf should go before u
884 if xf.order < u.order:
885 # it does, hurray!
Doug Zongker62338182014-09-08 08:29:55 -0700886 in_order += 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700887 else:
888 # it doesn't, boo. trim the blocks that u writes from xf's
889 # source, so that xf can go after u.
Doug Zongker62338182014-09-08 08:29:55 -0700890 out_of_order += 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700891 assert xf.src_ranges.overlaps(u.tgt_ranges)
892 xf.src_ranges = xf.src_ranges.subtract(u.tgt_ranges)
893 xf.intact = False
894
895 if xf.style == "diff" and not xf.src_ranges:
896 # nothing left to diff from; treat as new data
897 xf.style = "new"
898
899 lost = size - xf.src_ranges.size()
900 lost_source += lost
Doug Zongkerfc44a512014-08-26 13:10:25 -0700901
902 print((" %d/%d dependencies (%.2f%%) were violated; "
903 "%d source blocks removed.") %
904 (out_of_order, in_order + out_of_order,
905 (out_of_order * 100.0 / (in_order + out_of_order))
906 if (in_order + out_of_order) else 0.0,
907 lost_source))
908
Doug Zongker62338182014-09-08 08:29:55 -0700909 def ReverseBackwardEdges(self):
Tao Bao3a2e3502016-12-28 09:14:39 -0800910 """Reverse unsatisfying edges and compute pairs of stashed blocks.
911
912 For each transfer, make sure it properly stashes the blocks it touches and
913 will be used by later transfers. It uses pairs of (stash_raw_id, range) to
914 record the blocks to be stashed. 'stash_raw_id' is an id that uniquely
915 identifies each pair. Note that for the same range (e.g. RangeSet("1-5")),
916 it is possible to have multiple pairs with different 'stash_raw_id's. Each
917 'stash_raw_id' will be consumed by one transfer. In BBOTA v3+, identical
918 blocks will be written to the same stash slot in WriteTransfers().
919 """
920
Doug Zongker62338182014-09-08 08:29:55 -0700921 print("Reversing backward edges...")
922 in_order = 0
923 out_of_order = 0
Tao Bao3a2e3502016-12-28 09:14:39 -0800924 stash_raw_id = 0
Doug Zongker62338182014-09-08 08:29:55 -0700925 stash_size = 0
926
927 for xf in self.transfers:
Doug Zongker62338182014-09-08 08:29:55 -0700928 for u in xf.goes_before.copy():
929 # xf should go before u
930 if xf.order < u.order:
931 # it does, hurray!
932 in_order += 1
933 else:
934 # it doesn't, boo. modify u to stash the blocks that it
935 # writes that xf wants to read, and then require u to go
936 # before xf.
937 out_of_order += 1
938
939 overlap = xf.src_ranges.intersect(u.tgt_ranges)
940 assert overlap
941
Tao Bao3a2e3502016-12-28 09:14:39 -0800942 u.stash_before.append((stash_raw_id, overlap))
943 xf.use_stash.append((stash_raw_id, overlap))
944 stash_raw_id += 1
Doug Zongker62338182014-09-08 08:29:55 -0700945 stash_size += overlap.size()
946
947 # reverse the edge direction; now xf must go after u
948 del xf.goes_before[u]
949 del u.goes_after[xf]
950 xf.goes_after[u] = None # value doesn't matter
951 u.goes_before[xf] = None
952
953 print((" %d/%d dependencies (%.2f%%) were violated; "
954 "%d source blocks stashed.") %
955 (out_of_order, in_order + out_of_order,
956 (out_of_order * 100.0 / (in_order + out_of_order))
957 if (in_order + out_of_order) else 0.0,
958 stash_size))
959
Doug Zongkerfc44a512014-08-26 13:10:25 -0700960 def FindVertexSequence(self):
961 print("Finding vertex sequence...")
962
963 # This is based on "A Fast & Effective Heuristic for the Feedback
964 # Arc Set Problem" by P. Eades, X. Lin, and W.F. Smyth. Think of
965 # it as starting with the digraph G and moving all the vertices to
966 # be on a horizontal line in some order, trying to minimize the
967 # number of edges that end up pointing to the left. Left-pointing
968 # edges will get removed to turn the digraph into a DAG. In this
969 # case each edge has a weight which is the number of source blocks
970 # we'll lose if that edge is removed; we try to minimize the total
971 # weight rather than just the number of edges.
972
973 # Make a copy of the edge set; this copy will get destroyed by the
974 # algorithm.
975 for xf in self.transfers:
976 xf.incoming = xf.goes_after.copy()
977 xf.outgoing = xf.goes_before.copy()
Doug Zongker6ab2a502016-02-09 08:28:09 -0800978 xf.score = sum(xf.outgoing.values()) - sum(xf.incoming.values())
Doug Zongkerfc44a512014-08-26 13:10:25 -0700979
980 # We use an OrderedDict instead of just a set so that the output
981 # is repeatable; otherwise it would depend on the hash values of
982 # the transfer objects.
983 G = OrderedDict()
984 for xf in self.transfers:
985 G[xf] = None
986 s1 = deque() # the left side of the sequence, built from left to right
987 s2 = deque() # the right side of the sequence, built from right to left
988
Doug Zongker6ab2a502016-02-09 08:28:09 -0800989 heap = []
990 for xf in self.transfers:
991 xf.heap_item = HeapItem(xf)
992 heap.append(xf.heap_item)
993 heapq.heapify(heap)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700994
Tao Bao33482282016-10-24 16:49:08 -0700995 # Use OrderedDict() instead of set() to preserve the insertion order. Need
996 # to use 'sinks[key] = None' to add key into the set. sinks will look like
997 # { key1: None, key2: None, ... }.
998 sinks = OrderedDict.fromkeys(u for u in G if not u.outgoing)
999 sources = OrderedDict.fromkeys(u for u in G if not u.incoming)
Doug Zongker6ab2a502016-02-09 08:28:09 -08001000
1001 def adjust_score(iu, delta):
1002 iu.score += delta
1003 iu.heap_item.clear()
1004 iu.heap_item = HeapItem(iu)
1005 heapq.heappush(heap, iu.heap_item)
1006
1007 while G:
Doug Zongkerfc44a512014-08-26 13:10:25 -07001008 # Put all sinks at the end of the sequence.
Doug Zongker6ab2a502016-02-09 08:28:09 -08001009 while sinks:
Tao Bao33482282016-10-24 16:49:08 -07001010 new_sinks = OrderedDict()
Doug Zongkerfc44a512014-08-26 13:10:25 -07001011 for u in sinks:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001012 if u not in G: continue
Doug Zongkerfc44a512014-08-26 13:10:25 -07001013 s2.appendleft(u)
1014 del G[u]
1015 for iu in u.incoming:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001016 adjust_score(iu, -iu.outgoing.pop(u))
Tao Bao33482282016-10-24 16:49:08 -07001017 if not iu.outgoing:
1018 new_sinks[iu] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001019 sinks = new_sinks
Doug Zongkerfc44a512014-08-26 13:10:25 -07001020
1021 # Put all the sources at the beginning of the sequence.
Doug Zongker6ab2a502016-02-09 08:28:09 -08001022 while sources:
Tao Bao33482282016-10-24 16:49:08 -07001023 new_sources = OrderedDict()
Doug Zongkerfc44a512014-08-26 13:10:25 -07001024 for u in sources:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001025 if u not in G: continue
Doug Zongkerfc44a512014-08-26 13:10:25 -07001026 s1.append(u)
1027 del G[u]
1028 for iu in u.outgoing:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001029 adjust_score(iu, +iu.incoming.pop(u))
Tao Bao33482282016-10-24 16:49:08 -07001030 if not iu.incoming:
1031 new_sources[iu] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001032 sources = new_sources
Doug Zongkerfc44a512014-08-26 13:10:25 -07001033
Doug Zongker6ab2a502016-02-09 08:28:09 -08001034 if not G: break
Doug Zongkerfc44a512014-08-26 13:10:25 -07001035
1036 # Find the "best" vertex to put next. "Best" is the one that
1037 # maximizes the net difference in source blocks saved we get by
1038 # pretending it's a source rather than a sink.
1039
Doug Zongker6ab2a502016-02-09 08:28:09 -08001040 while True:
1041 u = heapq.heappop(heap)
1042 if u and u.item in G:
1043 u = u.item
1044 break
Doug Zongkerfc44a512014-08-26 13:10:25 -07001045
Doug Zongkerfc44a512014-08-26 13:10:25 -07001046 s1.append(u)
1047 del G[u]
1048 for iu in u.outgoing:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001049 adjust_score(iu, +iu.incoming.pop(u))
Tao Bao33482282016-10-24 16:49:08 -07001050 if not iu.incoming:
1051 sources[iu] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001052
Doug Zongkerfc44a512014-08-26 13:10:25 -07001053 for iu in u.incoming:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001054 adjust_score(iu, -iu.outgoing.pop(u))
Tao Bao33482282016-10-24 16:49:08 -07001055 if not iu.outgoing:
1056 sinks[iu] = None
Doug Zongkerfc44a512014-08-26 13:10:25 -07001057
1058 # Now record the sequence in the 'order' field of each transfer,
1059 # and by rearranging self.transfers to be in the chosen sequence.
1060
1061 new_transfers = []
1062 for x in itertools.chain(s1, s2):
1063 x.order = len(new_transfers)
1064 new_transfers.append(x)
1065 del x.incoming
1066 del x.outgoing
1067
1068 self.transfers = new_transfers
1069
1070 def GenerateDigraph(self):
1071 print("Generating digraph...")
Doug Zongker6ab2a502016-02-09 08:28:09 -08001072
1073 # Each item of source_ranges will be:
1074 # - None, if that block is not used as a source,
Tao Bao33482282016-10-24 16:49:08 -07001075 # - an ordered set of transfers.
Doug Zongker6ab2a502016-02-09 08:28:09 -08001076 source_ranges = []
1077 for b in self.transfers:
1078 for s, e in b.src_ranges:
1079 if e > len(source_ranges):
1080 source_ranges.extend([None] * (e-len(source_ranges)))
1081 for i in range(s, e):
1082 if source_ranges[i] is None:
Tao Bao33482282016-10-24 16:49:08 -07001083 source_ranges[i] = OrderedDict.fromkeys([b])
Doug Zongker6ab2a502016-02-09 08:28:09 -08001084 else:
Tao Bao33482282016-10-24 16:49:08 -07001085 source_ranges[i][b] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001086
Doug Zongkerfc44a512014-08-26 13:10:25 -07001087 for a in self.transfers:
Tao Bao33482282016-10-24 16:49:08 -07001088 intersections = OrderedDict()
Doug Zongker6ab2a502016-02-09 08:28:09 -08001089 for s, e in a.tgt_ranges:
1090 for i in range(s, e):
1091 if i >= len(source_ranges): break
Tao Bao33482282016-10-24 16:49:08 -07001092 # Add all the Transfers in source_ranges[i] to the (ordered) set.
1093 if source_ranges[i] is not None:
1094 for j in source_ranges[i]:
1095 intersections[j] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001096
1097 for b in intersections:
1098 if a is b: continue
Doug Zongkerfc44a512014-08-26 13:10:25 -07001099
1100 # If the blocks written by A are read by B, then B needs to go before A.
1101 i = a.tgt_ranges.intersect(b.src_ranges)
1102 if i:
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001103 if b.src_name == "__ZERO":
1104 # the cost of removing source blocks for the __ZERO domain
1105 # is (nearly) zero.
1106 size = 0
1107 else:
1108 size = i.size()
Doug Zongkerfc44a512014-08-26 13:10:25 -07001109 b.goes_before[a] = size
1110 a.goes_after[b] = size
1111
1112 def FindTransfers(self):
Tao Bao9a5caf22015-08-25 15:10:10 -07001113 """Parse the file_map to generate all the transfers."""
1114
Tao Bao08c85832016-09-19 22:26:30 -07001115 def AddSplitTransfers(tgt_name, src_name, tgt_ranges, src_ranges,
1116 style, by_id):
1117 """Add one or multiple Transfer()s by splitting large files.
Tao Bao9a5caf22015-08-25 15:10:10 -07001118
1119 For BBOTA v3, we need to stash source blocks for resumable feature.
1120 However, with the growth of file size and the shrink of the cache
1121 partition source blocks are too large to be stashed. If a file occupies
Tao Bao08c85832016-09-19 22:26:30 -07001122 too many blocks, we split it into smaller pieces by getting multiple
1123 Transfer()s.
Tao Bao9a5caf22015-08-25 15:10:10 -07001124
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001125 The downside is that after splitting, we may increase the package size
1126 since the split pieces don't align well. According to our experiments,
1127 1/8 of the cache size as the per-piece limit appears to be optimal.
1128 Compared to the fixed 1024-block limit, it reduces the overall package
Tao Bao08c85832016-09-19 22:26:30 -07001129 size by 30% for volantis, and 20% for angler and bullhead."""
Tao Bao9a5caf22015-08-25 15:10:10 -07001130
Tao Bao08c85832016-09-19 22:26:30 -07001131 # Possibly split large files into smaller chunks.
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001132 pieces = 0
1133 cache_size = common.OPTIONS.cache_size
1134 split_threshold = 0.125
1135 max_blocks_per_transfer = int(cache_size * split_threshold /
1136 self.tgt.blocksize)
1137
Tao Bao9a5caf22015-08-25 15:10:10 -07001138 # Change nothing for small files.
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001139 if (tgt_ranges.size() <= max_blocks_per_transfer and
1140 src_ranges.size() <= max_blocks_per_transfer):
Tao Bao183e56e2017-03-05 17:05:09 -08001141 Transfer(tgt_name, src_name, tgt_ranges, src_ranges,
1142 self.tgt.RangeSha1(tgt_ranges), self.src.RangeSha1(src_ranges),
1143 style, by_id)
Tao Bao9a5caf22015-08-25 15:10:10 -07001144 return
1145
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001146 while (tgt_ranges.size() > max_blocks_per_transfer and
1147 src_ranges.size() > max_blocks_per_transfer):
Tao Bao9a5caf22015-08-25 15:10:10 -07001148 tgt_split_name = "%s-%d" % (tgt_name, pieces)
1149 src_split_name = "%s-%d" % (src_name, pieces)
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001150 tgt_first = tgt_ranges.first(max_blocks_per_transfer)
1151 src_first = src_ranges.first(max_blocks_per_transfer)
1152
Tao Bao183e56e2017-03-05 17:05:09 -08001153 Transfer(tgt_split_name, src_split_name, tgt_first, src_first,
1154 self.tgt.RangeSha1(tgt_first), self.src.RangeSha1(src_first),
1155 style, by_id)
Tao Bao9a5caf22015-08-25 15:10:10 -07001156
1157 tgt_ranges = tgt_ranges.subtract(tgt_first)
1158 src_ranges = src_ranges.subtract(src_first)
1159 pieces += 1
1160
1161 # Handle remaining blocks.
1162 if tgt_ranges.size() or src_ranges.size():
1163 # Must be both non-empty.
1164 assert tgt_ranges.size() and src_ranges.size()
1165 tgt_split_name = "%s-%d" % (tgt_name, pieces)
1166 src_split_name = "%s-%d" % (src_name, pieces)
Tao Bao183e56e2017-03-05 17:05:09 -08001167 Transfer(tgt_split_name, src_split_name, tgt_ranges, src_ranges,
1168 self.tgt.RangeSha1(tgt_ranges), self.src.RangeSha1(src_ranges),
1169 style, by_id)
Tao Bao9a5caf22015-08-25 15:10:10 -07001170
Tao Bao08c85832016-09-19 22:26:30 -07001171 def AddTransfer(tgt_name, src_name, tgt_ranges, src_ranges, style, by_id,
1172 split=False):
1173 """Wrapper function for adding a Transfer()."""
1174
1175 # We specialize diff transfers only (which covers bsdiff/imgdiff/move);
1176 # otherwise add the Transfer() as is.
1177 if style != "diff" or not split:
Tao Bao183e56e2017-03-05 17:05:09 -08001178 Transfer(tgt_name, src_name, tgt_ranges, src_ranges,
1179 self.tgt.RangeSha1(tgt_ranges), self.src.RangeSha1(src_ranges),
1180 style, by_id)
Tao Bao08c85832016-09-19 22:26:30 -07001181 return
1182
1183 # Handle .odex files specially to analyze the block-wise difference. If
1184 # most of the blocks are identical with only few changes (e.g. header),
1185 # we will patch the changed blocks only. This avoids stashing unchanged
1186 # blocks while patching. We limit the analysis to files without size
1187 # changes only. This is to avoid sacrificing the OTA generation cost too
1188 # much.
1189 if (tgt_name.split(".")[-1].lower() == 'odex' and
1190 tgt_ranges.size() == src_ranges.size()):
1191
1192 # 0.5 threshold can be further tuned. The tradeoff is: if only very
1193 # few blocks remain identical, we lose the opportunity to use imgdiff
1194 # that may have better compression ratio than bsdiff.
1195 crop_threshold = 0.5
1196
1197 tgt_skipped = RangeSet()
1198 src_skipped = RangeSet()
1199 tgt_size = tgt_ranges.size()
1200 tgt_changed = 0
1201 for src_block, tgt_block in zip(src_ranges.next_item(),
1202 tgt_ranges.next_item()):
1203 src_rs = RangeSet(str(src_block))
1204 tgt_rs = RangeSet(str(tgt_block))
1205 if self.src.ReadRangeSet(src_rs) == self.tgt.ReadRangeSet(tgt_rs):
1206 tgt_skipped = tgt_skipped.union(tgt_rs)
1207 src_skipped = src_skipped.union(src_rs)
1208 else:
1209 tgt_changed += tgt_rs.size()
1210
1211 # Terminate early if no clear sign of benefits.
1212 if tgt_changed > tgt_size * crop_threshold:
1213 break
1214
1215 if tgt_changed < tgt_size * crop_threshold:
1216 assert tgt_changed + tgt_skipped.size() == tgt_size
1217 print('%10d %10d (%6.2f%%) %s' % (tgt_skipped.size(), tgt_size,
1218 tgt_skipped.size() * 100.0 / tgt_size, tgt_name))
1219 AddSplitTransfers(
1220 "%s-skipped" % (tgt_name,),
1221 "%s-skipped" % (src_name,),
1222 tgt_skipped, src_skipped, style, by_id)
1223
1224 # Intentionally change the file extension to avoid being imgdiff'd as
1225 # the files are no longer in their original format.
1226 tgt_name = "%s-cropped" % (tgt_name,)
1227 src_name = "%s-cropped" % (src_name,)
1228 tgt_ranges = tgt_ranges.subtract(tgt_skipped)
1229 src_ranges = src_ranges.subtract(src_skipped)
1230
1231 # Possibly having no changed blocks.
1232 if not tgt_ranges:
1233 return
1234
1235 # Add the transfer(s).
1236 AddSplitTransfers(
1237 tgt_name, src_name, tgt_ranges, src_ranges, style, by_id)
1238
1239 print("Finding transfers...")
1240
Doug Zongkerfc44a512014-08-26 13:10:25 -07001241 empty = RangeSet()
1242 for tgt_fn, tgt_ranges in self.tgt.file_map.items():
1243 if tgt_fn == "__ZERO":
1244 # the special "__ZERO" domain is all the blocks not contained
1245 # in any file and that are filled with zeros. We have a
1246 # special transfer style for zero blocks.
1247 src_ranges = self.src.file_map.get("__ZERO", empty)
Tao Bao9a5caf22015-08-25 15:10:10 -07001248 AddTransfer(tgt_fn, "__ZERO", tgt_ranges, src_ranges,
1249 "zero", self.transfers)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001250 continue
1251
Tao Baoff777812015-05-12 11:42:31 -07001252 elif tgt_fn == "__COPY":
1253 # "__COPY" domain includes all the blocks not contained in any
1254 # file and that need to be copied unconditionally to the target.
Tao Bao9a5caf22015-08-25 15:10:10 -07001255 AddTransfer(tgt_fn, None, tgt_ranges, empty, "new", self.transfers)
Tao Baoff777812015-05-12 11:42:31 -07001256 continue
1257
Doug Zongkerfc44a512014-08-26 13:10:25 -07001258 elif tgt_fn in self.src.file_map:
1259 # Look for an exact pathname match in the source.
Tao Bao9a5caf22015-08-25 15:10:10 -07001260 AddTransfer(tgt_fn, tgt_fn, tgt_ranges, self.src.file_map[tgt_fn],
Tao Bao8fad03e2017-03-01 14:36:26 -08001261 "diff", self.transfers, True)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001262 continue
1263
1264 b = os.path.basename(tgt_fn)
1265 if b in self.src_basenames:
1266 # Look for an exact basename match in the source.
1267 src_fn = self.src_basenames[b]
Tao Bao9a5caf22015-08-25 15:10:10 -07001268 AddTransfer(tgt_fn, src_fn, tgt_ranges, self.src.file_map[src_fn],
Tao Bao8fad03e2017-03-01 14:36:26 -08001269 "diff", self.transfers, True)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001270 continue
1271
1272 b = re.sub("[0-9]+", "#", b)
1273 if b in self.src_numpatterns:
1274 # Look for a 'number pattern' match (a basename match after
1275 # all runs of digits are replaced by "#"). (This is useful
1276 # for .so files that contain version numbers in the filename
1277 # that get bumped.)
1278 src_fn = self.src_numpatterns[b]
Tao Bao9a5caf22015-08-25 15:10:10 -07001279 AddTransfer(tgt_fn, src_fn, tgt_ranges, self.src.file_map[src_fn],
Tao Bao8fad03e2017-03-01 14:36:26 -08001280 "diff", self.transfers, True)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001281 continue
1282
Tao Bao9a5caf22015-08-25 15:10:10 -07001283 AddTransfer(tgt_fn, None, tgt_ranges, empty, "new", self.transfers)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001284
1285 def AbbreviateSourceNames(self):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001286 for k in self.src.file_map.keys():
1287 b = os.path.basename(k)
1288 self.src_basenames[b] = k
1289 b = re.sub("[0-9]+", "#", b)
1290 self.src_numpatterns[b] = k
1291
1292 @staticmethod
1293 def AssertPartition(total, seq):
1294 """Assert that all the RangeSets in 'seq' form a partition of the
1295 'total' RangeSet (ie, they are nonintersecting and their union
1296 equals 'total')."""
Doug Zongker6ab2a502016-02-09 08:28:09 -08001297
Doug Zongkerfc44a512014-08-26 13:10:25 -07001298 so_far = RangeSet()
1299 for i in seq:
1300 assert not so_far.overlaps(i)
1301 so_far = so_far.union(i)
1302 assert so_far == total