blob: c204c90b2ef524b9a6862134e36130b441066bf0 [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
Doug Zongkerfc44a512014-08-26 13:10:25 -070027import threading
28import tempfile
29
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
Doug Zongkerfc44a512014-08-26 13:10:25 -070038def compute_patch(src, tgt, imgdiff=False):
39 srcfd, srcfile = tempfile.mkstemp(prefix="src-")
40 tgtfd, tgtfile = tempfile.mkstemp(prefix="tgt-")
41 patchfd, patchfile = tempfile.mkstemp(prefix="patch-")
42 os.close(patchfd)
43
44 try:
45 with os.fdopen(srcfd, "wb") as f_src:
46 for p in src:
47 f_src.write(p)
48
49 with os.fdopen(tgtfd, "wb") as f_tgt:
50 for p in tgt:
51 f_tgt.write(p)
52 try:
53 os.unlink(patchfile)
54 except OSError:
55 pass
56 if imgdiff:
57 p = subprocess.call(["imgdiff", "-z", srcfile, tgtfile, patchfile],
58 stdout=open("/dev/null", "a"),
59 stderr=subprocess.STDOUT)
60 else:
61 p = subprocess.call(["bsdiff", srcfile, tgtfile, patchfile])
62
63 if p:
64 raise ValueError("diff failed: " + str(p))
65
66 with open(patchfile, "rb") as f:
67 return f.read()
68 finally:
69 try:
70 os.unlink(srcfile)
71 os.unlink(tgtfile)
72 os.unlink(patchfile)
73 except OSError:
74 pass
75
Dan Albert8b72aef2015-03-23 19:13:21 -070076
77class Image(object):
78 def ReadRangeSet(self, ranges):
79 raise NotImplementedError
80
Tao Bao68658c02015-06-01 13:40:49 -070081 def TotalSha1(self, include_clobbered_blocks=False):
Dan Albert8b72aef2015-03-23 19:13:21 -070082 raise NotImplementedError
83
84
85class EmptyImage(Image):
Doug Zongkerfc44a512014-08-26 13:10:25 -070086 """A zero-length image."""
87 blocksize = 4096
88 care_map = RangeSet()
Tao Baoff777812015-05-12 11:42:31 -070089 clobbered_blocks = RangeSet()
Tao Baoe9b61912015-07-09 17:37:49 -070090 extended = RangeSet()
Doug Zongkerfc44a512014-08-26 13:10:25 -070091 total_blocks = 0
92 file_map = {}
93 def ReadRangeSet(self, ranges):
94 return ()
Tao Bao68658c02015-06-01 13:40:49 -070095 def TotalSha1(self, include_clobbered_blocks=False):
96 # EmptyImage always carries empty clobbered_blocks, so
97 # include_clobbered_blocks can be ignored.
98 assert self.clobbered_blocks.size() == 0
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070099 return sha1().hexdigest()
100
101
Dan Albert8b72aef2015-03-23 19:13:21 -0700102class DataImage(Image):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700103 """An image wrapped around a single string of data."""
104
105 def __init__(self, data, trim=False, pad=False):
106 self.data = data
107 self.blocksize = 4096
108
109 assert not (trim and pad)
110
111 partial = len(self.data) % self.blocksize
Tao Bao7589e962015-09-05 20:35:32 -0700112 padded = False
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700113 if partial > 0:
114 if trim:
115 self.data = self.data[:-partial]
116 elif pad:
117 self.data += '\0' * (self.blocksize - partial)
Tao Bao7589e962015-09-05 20:35:32 -0700118 padded = True
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700119 else:
120 raise ValueError(("data for DataImage must be multiple of %d bytes "
121 "unless trim or pad is specified") %
122 (self.blocksize,))
123
124 assert len(self.data) % self.blocksize == 0
125
126 self.total_blocks = len(self.data) / self.blocksize
127 self.care_map = RangeSet(data=(0, self.total_blocks))
Tao Bao7589e962015-09-05 20:35:32 -0700128 # When the last block is padded, we always write the whole block even for
129 # incremental OTAs. Because otherwise the last block may get skipped if
130 # unchanged for an incremental, but would fail the post-install
131 # verification if it has non-zero contents in the padding bytes.
132 # Bug: 23828506
133 if padded:
Tao Bao42206c32015-09-08 13:39:40 -0700134 clobbered_blocks = [self.total_blocks-1, self.total_blocks]
Tao Bao7589e962015-09-05 20:35:32 -0700135 else:
Tao Bao42206c32015-09-08 13:39:40 -0700136 clobbered_blocks = []
137 self.clobbered_blocks = clobbered_blocks
Tao Baoe9b61912015-07-09 17:37:49 -0700138 self.extended = RangeSet()
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700139
140 zero_blocks = []
141 nonzero_blocks = []
142 reference = '\0' * self.blocksize
143
Tao Bao7589e962015-09-05 20:35:32 -0700144 for i in range(self.total_blocks-1 if padded else self.total_blocks):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700145 d = self.data[i*self.blocksize : (i+1)*self.blocksize]
146 if d == reference:
147 zero_blocks.append(i)
148 zero_blocks.append(i+1)
149 else:
150 nonzero_blocks.append(i)
151 nonzero_blocks.append(i+1)
152
Tao Bao42206c32015-09-08 13:39:40 -0700153 assert zero_blocks or nonzero_blocks or clobbered_blocks
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700154
Tao Bao42206c32015-09-08 13:39:40 -0700155 self.file_map = dict()
156 if zero_blocks:
157 self.file_map["__ZERO"] = RangeSet(data=zero_blocks)
158 if nonzero_blocks:
159 self.file_map["__NONZERO"] = RangeSet(data=nonzero_blocks)
160 if clobbered_blocks:
161 self.file_map["__COPY"] = RangeSet(data=clobbered_blocks)
Tao Bao7589e962015-09-05 20:35:32 -0700162
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700163 def ReadRangeSet(self, ranges):
164 return [self.data[s*self.blocksize:e*self.blocksize] for (s, e) in ranges]
165
Tao Bao68658c02015-06-01 13:40:49 -0700166 def TotalSha1(self, include_clobbered_blocks=False):
Tao Bao7589e962015-09-05 20:35:32 -0700167 if not include_clobbered_blocks:
168 ranges = self.care_map.subtract(self.clobbered_blocks)
169 return sha1(self.ReadRangeSet(ranges)).hexdigest()
170 else:
171 return sha1(self.data).hexdigest()
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700172
Doug Zongkerfc44a512014-08-26 13:10:25 -0700173
174class Transfer(object):
175 def __init__(self, tgt_name, src_name, tgt_ranges, src_ranges, style, by_id):
176 self.tgt_name = tgt_name
177 self.src_name = src_name
178 self.tgt_ranges = tgt_ranges
179 self.src_ranges = src_ranges
180 self.style = style
181 self.intact = (getattr(tgt_ranges, "monotonic", False) and
182 getattr(src_ranges, "monotonic", False))
Tao Baob8c87172015-03-19 19:42:12 -0700183
184 # We use OrderedDict rather than dict so that the output is repeatable;
185 # otherwise it would depend on the hash values of the Transfer objects.
186 self.goes_before = OrderedDict()
187 self.goes_after = OrderedDict()
Doug Zongkerfc44a512014-08-26 13:10:25 -0700188
Doug Zongker62338182014-09-08 08:29:55 -0700189 self.stash_before = []
190 self.use_stash = []
191
Doug Zongkerfc44a512014-08-26 13:10:25 -0700192 self.id = len(by_id)
193 by_id.append(self)
194
Doug Zongker62338182014-09-08 08:29:55 -0700195 def NetStashChange(self):
196 return (sum(sr.size() for (_, sr) in self.stash_before) -
197 sum(sr.size() for (_, sr) in self.use_stash))
198
Tao Bao82c47982015-08-17 09:45:13 -0700199 def ConvertToNew(self):
200 assert self.style != "new"
201 self.use_stash = []
202 self.style = "new"
203 self.src_ranges = RangeSet()
204
Doug Zongkerfc44a512014-08-26 13:10:25 -0700205 def __str__(self):
206 return (str(self.id) + ": <" + str(self.src_ranges) + " " + self.style +
207 " to " + str(self.tgt_ranges) + ">")
208
209
Doug Zongker6ab2a502016-02-09 08:28:09 -0800210@functools.total_ordering
211class HeapItem(object):
212 def __init__(self, item):
213 self.item = item
214 # Negate the score since python's heap is a min-heap and we want
215 # the maximum score.
216 self.score = -item.score
217 def clear(self):
218 self.item = None
219 def __bool__(self):
220 return self.item is None
221 def __eq__(self, other):
222 return self.score == other.score
223 def __le__(self, other):
224 return self.score <= other.score
225
226
Doug Zongkerfc44a512014-08-26 13:10:25 -0700227# BlockImageDiff works on two image objects. An image object is
228# anything that provides the following attributes:
229#
230# blocksize: the size in bytes of a block, currently must be 4096.
231#
232# total_blocks: the total size of the partition/image, in blocks.
233#
234# care_map: a RangeSet containing which blocks (in the range [0,
235# total_blocks) we actually care about; i.e. which blocks contain
236# data.
237#
238# file_map: a dict that partitions the blocks contained in care_map
239# into smaller domains that are useful for doing diffs on.
240# (Typically a domain is a file, and the key in file_map is the
241# pathname.)
242#
Tao Baoff777812015-05-12 11:42:31 -0700243# clobbered_blocks: a RangeSet containing which blocks contain data
244# but may be altered by the FS. They need to be excluded when
245# verifying the partition integrity.
246#
Doug Zongkerfc44a512014-08-26 13:10:25 -0700247# ReadRangeSet(): a function that takes a RangeSet and returns the
248# data contained in the image blocks of that RangeSet. The data
249# is returned as a list or tuple of strings; concatenating the
250# elements together should produce the requested data.
251# Implementations are free to break up the data into list/tuple
252# elements in any way that is convenient.
253#
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700254# TotalSha1(): a function that returns (as a hex string) the SHA-1
255# hash of all the data in the image (ie, all the blocks in the
Tao Bao68658c02015-06-01 13:40:49 -0700256# care_map minus clobbered_blocks, or including the clobbered
257# blocks if include_clobbered_blocks is True).
Doug Zongkerab7ca1d2014-08-26 10:40:28 -0700258#
Doug Zongkerfc44a512014-08-26 13:10:25 -0700259# When creating a BlockImageDiff, the src image may be None, in which
260# case the list of transfers produced will never read from the
261# original image.
262
263class BlockImageDiff(object):
Tao Bao293fd132016-06-11 12:19:23 -0700264 def __init__(self, tgt, src=None, threads=None, version=4,
265 disable_imgdiff=False):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700266 if threads is None:
267 threads = multiprocessing.cpu_count() // 2
Dan Albert8b72aef2015-03-23 19:13:21 -0700268 if threads == 0:
269 threads = 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700270 self.threads = threads
Doug Zongker62338182014-09-08 08:29:55 -0700271 self.version = version
Dan Albert8b72aef2015-03-23 19:13:21 -0700272 self.transfers = []
273 self.src_basenames = {}
274 self.src_numpatterns = {}
Tao Baob4cfca52016-02-04 14:26:02 -0800275 self._max_stashed_size = 0
Tao Baod522bdc2016-04-12 15:53:16 -0700276 self.touched_src_ranges = RangeSet()
277 self.touched_src_sha1 = None
Tao Bao293fd132016-06-11 12:19:23 -0700278 self.disable_imgdiff = disable_imgdiff
Doug Zongker62338182014-09-08 08:29:55 -0700279
Tao Baoeba409c2015-10-21 13:30:43 -0700280 assert version in (1, 2, 3, 4)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700281
282 self.tgt = tgt
283 if src is None:
284 src = EmptyImage()
285 self.src = src
286
287 # The updater code that installs the patch always uses 4k blocks.
288 assert tgt.blocksize == 4096
289 assert src.blocksize == 4096
290
291 # The range sets in each filemap should comprise a partition of
292 # the care map.
293 self.AssertPartition(src.care_map, src.file_map.values())
294 self.AssertPartition(tgt.care_map, tgt.file_map.values())
295
Tao Baob4cfca52016-02-04 14:26:02 -0800296 @property
297 def max_stashed_size(self):
298 return self._max_stashed_size
299
Doug Zongkerfc44a512014-08-26 13:10:25 -0700300 def Compute(self, prefix):
301 # When looking for a source file to use as the diff input for a
302 # target file, we try:
303 # 1) an exact path match if available, otherwise
304 # 2) a exact basename match if available, otherwise
305 # 3) a basename match after all runs of digits are replaced by
306 # "#" if available, otherwise
307 # 4) we have no source for this target.
308 self.AbbreviateSourceNames()
309 self.FindTransfers()
310
311 # Find the ordering dependencies among transfers (this is O(n^2)
312 # in the number of transfers).
313 self.GenerateDigraph()
314 # Find a sequence of transfers that satisfies as many ordering
315 # dependencies as possible (heuristically).
316 self.FindVertexSequence()
317 # Fix up the ordering dependencies that the sequence didn't
318 # satisfy.
Doug Zongker62338182014-09-08 08:29:55 -0700319 if self.version == 1:
320 self.RemoveBackwardEdges()
321 else:
322 self.ReverseBackwardEdges()
323 self.ImproveVertexSequence()
324
Tao Bao82c47982015-08-17 09:45:13 -0700325 # Ensure the runtime stash size is under the limit.
326 if self.version >= 2 and common.OPTIONS.cache_size is not None:
327 self.ReviseStashSize()
328
Doug Zongkerfc44a512014-08-26 13:10:25 -0700329 # Double-check our work.
330 self.AssertSequenceGood()
331
332 self.ComputePatches(prefix)
333 self.WriteTransfers(prefix)
334
Dan Albert8b72aef2015-03-23 19:13:21 -0700335 def HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +0000336 data = source.ReadRangeSet(ranges)
337 ctx = sha1()
338
339 for p in data:
340 ctx.update(p)
341
342 return ctx.hexdigest()
343
Doug Zongkerfc44a512014-08-26 13:10:25 -0700344 def WriteTransfers(self, prefix):
Tianjie Xu37e29302016-06-23 16:10:35 -0700345 def WriteSplitTransfers(out, style, target_blocks):
346 """Limit the size of operand in command 'new' and 'zero' to 1024 blocks.
Tianjie Xubb848c52016-06-21 15:54:09 -0700347
348 This prevents the target size of one command from being too large; and
349 might help to avoid fsync errors on some devices."""
350
Tao Bao3a2e3502016-12-28 09:14:39 -0800351 assert style == "new" or style == "zero"
Tianjie Xu37e29302016-06-23 16:10:35 -0700352 blocks_limit = 1024
Tianjie Xubb848c52016-06-21 15:54:09 -0700353 total = 0
Tianjie Xu37e29302016-06-23 16:10:35 -0700354 while target_blocks:
355 blocks_to_write = target_blocks.first(blocks_limit)
356 out.append("%s %s\n" % (style, blocks_to_write.to_string_raw()))
357 total += blocks_to_write.size()
358 target_blocks = target_blocks.subtract(blocks_to_write)
Tianjie Xubb848c52016-06-21 15:54:09 -0700359 return total
360
Doug Zongkerfc44a512014-08-26 13:10:25 -0700361 out = []
Doug Zongkerfc44a512014-08-26 13:10:25 -0700362 total = 0
Doug Zongkerfc44a512014-08-26 13:10:25 -0700363
Tao Bao3a2e3502016-12-28 09:14:39 -0800364 # In BBOTA v2, 'stashes' records the map from 'stash_raw_id' to 'stash_id'
365 # (aka 'sid', which is the stash slot id). The stash in a 'stash_id' will
366 # be freed immediately after its use. So unlike 'stash_raw_id' (which
367 # uniquely identifies each pair of stashed blocks), the same 'stash_id'
368 # may be reused during the life cycle of an update (maintained by
369 # 'free_stash_ids' heap and 'next_stash_id').
370 #
371 # In BBOTA v3+, it uses the hash of the stashed blocks as the stash slot
372 # id. 'stashes' records the map from 'hash' to the ref count. The stash
373 # will be freed only if the count decrements to zero.
Doug Zongker62338182014-09-08 08:29:55 -0700374 stashes = {}
375 stashed_blocks = 0
376 max_stashed_blocks = 0
377
Tao Bao3a2e3502016-12-28 09:14:39 -0800378 if self.version == 2:
379 free_stash_ids = []
380 next_stash_id = 0
Doug Zongker62338182014-09-08 08:29:55 -0700381
Doug Zongkerfc44a512014-08-26 13:10:25 -0700382 for xf in self.transfers:
383
Doug Zongker62338182014-09-08 08:29:55 -0700384 if self.version < 2:
385 assert not xf.stash_before
386 assert not xf.use_stash
387
Tao Bao3a2e3502016-12-28 09:14:39 -0800388 for stash_raw_id, sr in xf.stash_before:
Sami Tolvanendd67a292014-12-09 16:40:34 +0000389 if self.version == 2:
Tao Bao3a2e3502016-12-28 09:14:39 -0800390 assert stash_raw_id not in stashes
391 if free_stash_ids:
392 sid = heapq.heappop(free_stash_ids)
393 else:
394 sid = next_stash_id
395 next_stash_id += 1
396 stashes[stash_raw_id] = sid
caozhiyuan21b37d82015-10-21 15:14:03 +0800397 stashed_blocks += sr.size()
Sami Tolvanendd67a292014-12-09 16:40:34 +0000398 out.append("stash %d %s\n" % (sid, sr.to_string_raw()))
399 else:
400 sh = self.HashBlocks(self.src, sr)
401 if sh in stashes:
402 stashes[sh] += 1
403 else:
404 stashes[sh] = 1
caozhiyuan21b37d82015-10-21 15:14:03 +0800405 stashed_blocks += sr.size()
Tao Baod522bdc2016-04-12 15:53:16 -0700406 self.touched_src_ranges = self.touched_src_ranges.union(sr)
Sami Tolvanendd67a292014-12-09 16:40:34 +0000407 out.append("stash %s %s\n" % (sh, sr.to_string_raw()))
Doug Zongker62338182014-09-08 08:29:55 -0700408
409 if stashed_blocks > max_stashed_blocks:
410 max_stashed_blocks = stashed_blocks
411
Jesse Zhao7b985f62015-03-02 16:53:08 -0800412 free_string = []
caozhiyuan21b37d82015-10-21 15:14:03 +0800413 free_size = 0
Jesse Zhao7b985f62015-03-02 16:53:08 -0800414
Doug Zongker62338182014-09-08 08:29:55 -0700415 if self.version == 1:
Tao Bao4fcb77e2015-10-21 13:36:01 -0700416 src_str = xf.src_ranges.to_string_raw() if xf.src_ranges else ""
Sami Tolvanendd67a292014-12-09 16:40:34 +0000417 elif self.version >= 2:
Doug Zongker62338182014-09-08 08:29:55 -0700418
419 # <# blocks> <src ranges>
420 # OR
421 # <# blocks> <src ranges> <src locs> <stash refs...>
422 # OR
423 # <# blocks> - <stash refs...>
424
425 size = xf.src_ranges.size()
Dan Albert8b72aef2015-03-23 19:13:21 -0700426 src_str = [str(size)]
Doug Zongker62338182014-09-08 08:29:55 -0700427
428 unstashed_src_ranges = xf.src_ranges
429 mapped_stashes = []
Tao Bao3a2e3502016-12-28 09:14:39 -0800430 for stash_raw_id, sr in xf.use_stash:
Doug Zongker62338182014-09-08 08:29:55 -0700431 unstashed_src_ranges = unstashed_src_ranges.subtract(sr)
Sami Tolvanendd67a292014-12-09 16:40:34 +0000432 sh = self.HashBlocks(self.src, sr)
Doug Zongker62338182014-09-08 08:29:55 -0700433 sr = xf.src_ranges.map_within(sr)
434 mapped_stashes.append(sr)
Sami Tolvanendd67a292014-12-09 16:40:34 +0000435 if self.version == 2:
Tao Bao3a2e3502016-12-28 09:14:39 -0800436 sid = stashes.pop(stash_raw_id)
Dan Albert8b72aef2015-03-23 19:13:21 -0700437 src_str.append("%d:%s" % (sid, sr.to_string_raw()))
Tao Baobb625d22015-08-13 14:44:15 -0700438 # A stash will be used only once. We need to free the stash
439 # immediately after the use, instead of waiting for the automatic
440 # clean-up at the end. Because otherwise it may take up extra space
441 # and lead to OTA failures.
442 # Bug: 23119955
443 free_string.append("free %d\n" % (sid,))
caozhiyuan21b37d82015-10-21 15:14:03 +0800444 free_size += sr.size()
Tao Bao3a2e3502016-12-28 09:14:39 -0800445 heapq.heappush(free_stash_ids, sid)
Sami Tolvanendd67a292014-12-09 16:40:34 +0000446 else:
447 assert sh in stashes
Dan Albert8b72aef2015-03-23 19:13:21 -0700448 src_str.append("%s:%s" % (sh, sr.to_string_raw()))
Sami Tolvanendd67a292014-12-09 16:40:34 +0000449 stashes[sh] -= 1
450 if stashes[sh] == 0:
Tao Baoe27acfd2016-12-16 11:13:55 -0800451 free_string.append("free %s\n" % (sh,))
Tao Bao3a2e3502016-12-28 09:14:39 -0800452 free_size += sr.size()
Sami Tolvanendd67a292014-12-09 16:40:34 +0000453 stashes.pop(sh)
Doug Zongker62338182014-09-08 08:29:55 -0700454
455 if unstashed_src_ranges:
Dan Albert8b72aef2015-03-23 19:13:21 -0700456 src_str.insert(1, unstashed_src_ranges.to_string_raw())
Doug Zongker62338182014-09-08 08:29:55 -0700457 if xf.use_stash:
458 mapped_unstashed = xf.src_ranges.map_within(unstashed_src_ranges)
Dan Albert8b72aef2015-03-23 19:13:21 -0700459 src_str.insert(2, mapped_unstashed.to_string_raw())
Doug Zongker62338182014-09-08 08:29:55 -0700460 mapped_stashes.append(mapped_unstashed)
461 self.AssertPartition(RangeSet(data=(0, size)), mapped_stashes)
462 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700463 src_str.insert(1, "-")
Doug Zongker62338182014-09-08 08:29:55 -0700464 self.AssertPartition(RangeSet(data=(0, size)), mapped_stashes)
465
Dan Albert8b72aef2015-03-23 19:13:21 -0700466 src_str = " ".join(src_str)
Doug Zongker62338182014-09-08 08:29:55 -0700467
Sami Tolvanendd67a292014-12-09 16:40:34 +0000468 # all versions:
Doug Zongker62338182014-09-08 08:29:55 -0700469 # zero <rangeset>
470 # new <rangeset>
471 # erase <rangeset>
472 #
473 # version 1:
474 # bsdiff patchstart patchlen <src rangeset> <tgt rangeset>
475 # imgdiff patchstart patchlen <src rangeset> <tgt rangeset>
476 # move <src rangeset> <tgt rangeset>
477 #
478 # version 2:
Dan Albert8b72aef2015-03-23 19:13:21 -0700479 # bsdiff patchstart patchlen <tgt rangeset> <src_str>
480 # imgdiff patchstart patchlen <tgt rangeset> <src_str>
481 # move <tgt rangeset> <src_str>
Sami Tolvanendd67a292014-12-09 16:40:34 +0000482 #
483 # version 3:
Dan Albert8b72aef2015-03-23 19:13:21 -0700484 # bsdiff patchstart patchlen srchash tgthash <tgt rangeset> <src_str>
485 # imgdiff patchstart patchlen srchash tgthash <tgt rangeset> <src_str>
486 # move hash <tgt rangeset> <src_str>
Doug Zongkerfc44a512014-08-26 13:10:25 -0700487
488 tgt_size = xf.tgt_ranges.size()
489
490 if xf.style == "new":
491 assert xf.tgt_ranges
Tianjie Xu37e29302016-06-23 16:10:35 -0700492 assert tgt_size == WriteSplitTransfers(out, xf.style, xf.tgt_ranges)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700493 total += tgt_size
494 elif xf.style == "move":
Doug Zongkerfc44a512014-08-26 13:10:25 -0700495 assert xf.tgt_ranges
496 assert xf.src_ranges.size() == tgt_size
497 if xf.src_ranges != xf.tgt_ranges:
Doug Zongker62338182014-09-08 08:29:55 -0700498 if self.version == 1:
499 out.append("%s %s %s\n" % (
500 xf.style,
501 xf.src_ranges.to_string_raw(), xf.tgt_ranges.to_string_raw()))
502 elif self.version == 2:
503 out.append("%s %s %s\n" % (
504 xf.style,
Dan Albert8b72aef2015-03-23 19:13:21 -0700505 xf.tgt_ranges.to_string_raw(), src_str))
Sami Tolvanendd67a292014-12-09 16:40:34 +0000506 elif self.version >= 3:
Sami Tolvanen29f529f2015-04-17 16:28:08 +0100507 # take into account automatic stashing of overlapping blocks
508 if xf.src_ranges.overlaps(xf.tgt_ranges):
Tao Baoe9b61912015-07-09 17:37:49 -0700509 temp_stash_usage = stashed_blocks + xf.src_ranges.size()
Sami Tolvanen29f529f2015-04-17 16:28:08 +0100510 if temp_stash_usage > max_stashed_blocks:
511 max_stashed_blocks = temp_stash_usage
512
Tao Baod522bdc2016-04-12 15:53:16 -0700513 self.touched_src_ranges = self.touched_src_ranges.union(
514 xf.src_ranges)
515
Sami Tolvanendd67a292014-12-09 16:40:34 +0000516 out.append("%s %s %s %s\n" % (
517 xf.style,
518 self.HashBlocks(self.tgt, xf.tgt_ranges),
Dan Albert8b72aef2015-03-23 19:13:21 -0700519 xf.tgt_ranges.to_string_raw(), src_str))
Doug Zongkerfc44a512014-08-26 13:10:25 -0700520 total += tgt_size
521 elif xf.style in ("bsdiff", "imgdiff"):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700522 assert xf.tgt_ranges
523 assert xf.src_ranges
Doug Zongker62338182014-09-08 08:29:55 -0700524 if self.version == 1:
525 out.append("%s %d %d %s %s\n" % (
526 xf.style, xf.patch_start, xf.patch_len,
527 xf.src_ranges.to_string_raw(), xf.tgt_ranges.to_string_raw()))
528 elif self.version == 2:
529 out.append("%s %d %d %s %s\n" % (
530 xf.style, xf.patch_start, xf.patch_len,
Dan Albert8b72aef2015-03-23 19:13:21 -0700531 xf.tgt_ranges.to_string_raw(), src_str))
Sami Tolvanendd67a292014-12-09 16:40:34 +0000532 elif self.version >= 3:
Sami Tolvanen29f529f2015-04-17 16:28:08 +0100533 # take into account automatic stashing of overlapping blocks
534 if xf.src_ranges.overlaps(xf.tgt_ranges):
Tao Baoe9b61912015-07-09 17:37:49 -0700535 temp_stash_usage = stashed_blocks + xf.src_ranges.size()
Sami Tolvanen29f529f2015-04-17 16:28:08 +0100536 if temp_stash_usage > max_stashed_blocks:
537 max_stashed_blocks = temp_stash_usage
538
Tao Baod522bdc2016-04-12 15:53:16 -0700539 self.touched_src_ranges = self.touched_src_ranges.union(
540 xf.src_ranges)
541
Sami Tolvanendd67a292014-12-09 16:40:34 +0000542 out.append("%s %d %d %s %s %s %s\n" % (
543 xf.style,
544 xf.patch_start, xf.patch_len,
545 self.HashBlocks(self.src, xf.src_ranges),
546 self.HashBlocks(self.tgt, xf.tgt_ranges),
Dan Albert8b72aef2015-03-23 19:13:21 -0700547 xf.tgt_ranges.to_string_raw(), src_str))
Doug Zongkerfc44a512014-08-26 13:10:25 -0700548 total += tgt_size
549 elif xf.style == "zero":
550 assert xf.tgt_ranges
551 to_zero = xf.tgt_ranges.subtract(xf.src_ranges)
Tianjie Xu37e29302016-06-23 16:10:35 -0700552 assert WriteSplitTransfers(out, xf.style, to_zero) == to_zero.size()
Tianjie Xubb848c52016-06-21 15:54:09 -0700553 total += to_zero.size()
Doug Zongkerfc44a512014-08-26 13:10:25 -0700554 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700555 raise ValueError("unknown transfer style '%s'\n" % xf.style)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700556
Sami Tolvanendd67a292014-12-09 16:40:34 +0000557 if free_string:
558 out.append("".join(free_string))
caozhiyuan21b37d82015-10-21 15:14:03 +0800559 stashed_blocks -= free_size
Sami Tolvanendd67a292014-12-09 16:40:34 +0000560
Tao Bao575d68a2015-08-07 19:49:45 -0700561 if self.version >= 2 and common.OPTIONS.cache_size is not None:
Tao Bao8dcf7382015-05-21 14:09:49 -0700562 # Sanity check: abort if we're going to need more stash space than
563 # the allowed size (cache_size * threshold). There are two purposes
564 # of having a threshold here. a) Part of the cache may have been
565 # occupied by some recovery logs. b) It will buy us some time to deal
566 # with the oversize issue.
567 cache_size = common.OPTIONS.cache_size
568 stash_threshold = common.OPTIONS.stash_threshold
569 max_allowed = cache_size * stash_threshold
Tao Baoe8c68a02017-02-26 10:48:11 -0800570 assert max_stashed_blocks * self.tgt.blocksize <= max_allowed, \
Tao Bao8dcf7382015-05-21 14:09:49 -0700571 'Stash size %d (%d * %d) exceeds the limit %d (%d * %.2f)' % (
572 max_stashed_blocks * self.tgt.blocksize, max_stashed_blocks,
573 self.tgt.blocksize, max_allowed, cache_size,
574 stash_threshold)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700575
Tao Baod522bdc2016-04-12 15:53:16 -0700576 if self.version >= 3:
577 self.touched_src_sha1 = self.HashBlocks(
578 self.src, self.touched_src_ranges)
579
Tao Baoe9b61912015-07-09 17:37:49 -0700580 # Zero out extended blocks as a workaround for bug 20881595.
581 if self.tgt.extended:
Tianjie Xu37e29302016-06-23 16:10:35 -0700582 assert (WriteSplitTransfers(out, "zero", self.tgt.extended) ==
Tianjie Xubb848c52016-06-21 15:54:09 -0700583 self.tgt.extended.size())
Tao Baob32d56e2015-09-09 11:55:01 -0700584 total += self.tgt.extended.size()
Tao Baoe9b61912015-07-09 17:37:49 -0700585
586 # We erase all the blocks on the partition that a) don't contain useful
Tao Bao66f1fa62016-05-03 10:02:01 -0700587 # data in the new image; b) will not be touched by dm-verity. Out of those
588 # blocks, we erase the ones that won't be used in this update at the
589 # beginning of an update. The rest would be erased at the end. This is to
590 # work around the eMMC issue observed on some devices, which may otherwise
591 # get starving for clean blocks and thus fail the update. (b/28347095)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700592 all_tgt = RangeSet(data=(0, self.tgt.total_blocks))
Tao Baoe9b61912015-07-09 17:37:49 -0700593 all_tgt_minus_extended = all_tgt.subtract(self.tgt.extended)
594 new_dontcare = all_tgt_minus_extended.subtract(self.tgt.care_map)
Tao Bao66f1fa62016-05-03 10:02:01 -0700595
596 erase_first = new_dontcare.subtract(self.touched_src_ranges)
597 if erase_first:
598 out.insert(0, "erase %s\n" % (erase_first.to_string_raw(),))
599
600 erase_last = new_dontcare.subtract(erase_first)
601 if erase_last:
602 out.append("erase %s\n" % (erase_last.to_string_raw(),))
Doug Zongkere985f6f2014-09-09 12:38:47 -0700603
604 out.insert(0, "%d\n" % (self.version,)) # format version number
Tao Baob32d56e2015-09-09 11:55:01 -0700605 out.insert(1, "%d\n" % (total,))
Tao Bao3a2e3502016-12-28 09:14:39 -0800606 if self.version == 2:
607 # v2 only: after the total block count, we give the number of stash slots
608 # needed, and the maximum size needed (in blocks).
Doug Zongkere985f6f2014-09-09 12:38:47 -0700609 out.insert(2, str(next_stash_id) + "\n")
610 out.insert(3, str(max_stashed_blocks) + "\n")
Tao Bao3a2e3502016-12-28 09:14:39 -0800611 elif self.version >= 3:
612 # v3+: the number of stash slots is unused.
613 out.insert(2, "0\n")
614 out.insert(3, str(max_stashed_blocks) + "\n")
Doug Zongkerfc44a512014-08-26 13:10:25 -0700615
616 with open(prefix + ".transfer.list", "wb") as f:
617 for i in out:
618 f.write(i)
619
Doug Zongker62338182014-09-08 08:29:55 -0700620 if self.version >= 2:
Tao Baob4cfca52016-02-04 14:26:02 -0800621 self._max_stashed_size = max_stashed_blocks * self.tgt.blocksize
Tao Bao575d68a2015-08-07 19:49:45 -0700622 OPTIONS = common.OPTIONS
623 if OPTIONS.cache_size is not None:
624 max_allowed = OPTIONS.cache_size * OPTIONS.stash_threshold
625 print("max stashed blocks: %d (%d bytes), "
626 "limit: %d bytes (%.2f%%)\n" % (
Tao Baob4cfca52016-02-04 14:26:02 -0800627 max_stashed_blocks, self._max_stashed_size, max_allowed,
628 self._max_stashed_size * 100.0 / max_allowed))
Tao Bao575d68a2015-08-07 19:49:45 -0700629 else:
630 print("max stashed blocks: %d (%d bytes), limit: <unknown>\n" % (
Tao Baob4cfca52016-02-04 14:26:02 -0800631 max_stashed_blocks, self._max_stashed_size))
Doug Zongker62338182014-09-08 08:29:55 -0700632
Tao Bao82c47982015-08-17 09:45:13 -0700633 def ReviseStashSize(self):
634 print("Revising stash size...")
Tao Baoe27acfd2016-12-16 11:13:55 -0800635 stash_map = {}
Tao Bao82c47982015-08-17 09:45:13 -0700636
637 # Create the map between a stash and its def/use points. For example, for a
Tao Bao3c5a16d2017-02-13 11:42:50 -0800638 # given stash of (raw_id, sr), stash_map[raw_id] = (sr, def_cmd, use_cmd).
Tao Bao82c47982015-08-17 09:45:13 -0700639 for xf in self.transfers:
640 # Command xf defines (stores) all the stashes in stash_before.
Tao Bao3a2e3502016-12-28 09:14:39 -0800641 for stash_raw_id, sr in xf.stash_before:
642 stash_map[stash_raw_id] = (sr, xf)
Tao Bao82c47982015-08-17 09:45:13 -0700643
644 # Record all the stashes command xf uses.
Tao Bao3a2e3502016-12-28 09:14:39 -0800645 for stash_raw_id, _ in xf.use_stash:
646 stash_map[stash_raw_id] += (xf,)
Tao Bao82c47982015-08-17 09:45:13 -0700647
648 # Compute the maximum blocks available for stash based on /cache size and
649 # the threshold.
650 cache_size = common.OPTIONS.cache_size
651 stash_threshold = common.OPTIONS.stash_threshold
652 max_allowed = cache_size * stash_threshold / self.tgt.blocksize
653
Tao Bao3a2e3502016-12-28 09:14:39 -0800654 # See the comments for 'stashes' in WriteTransfers().
Tao Baoe27acfd2016-12-16 11:13:55 -0800655 stashes = {}
Tao Bao82c47982015-08-17 09:45:13 -0700656 stashed_blocks = 0
Tao Bao9a5caf22015-08-25 15:10:10 -0700657 new_blocks = 0
Tao Bao82c47982015-08-17 09:45:13 -0700658
Tao Bao3a2e3502016-12-28 09:14:39 -0800659 if self.version == 2:
660 free_stash_ids = []
661 next_stash_id = 0
Tao Baoe27acfd2016-12-16 11:13:55 -0800662
Tao Bao82c47982015-08-17 09:45:13 -0700663 # Now go through all the commands. Compute the required stash size on the
664 # fly. If a command requires excess stash than available, it deletes the
665 # stash by replacing the command that uses the stash with a "new" command
666 # instead.
667 for xf in self.transfers:
668 replaced_cmds = []
669
670 # xf.stash_before generates explicit stash commands.
Tao Bao3a2e3502016-12-28 09:14:39 -0800671 for stash_raw_id, sr in xf.stash_before:
Tao Baoe27acfd2016-12-16 11:13:55 -0800672 # Check the post-command stashed_blocks.
673 stashed_blocks_after = stashed_blocks
674 if self.version == 2:
675 stashed_blocks_after += sr.size()
676 else:
677 sh = self.HashBlocks(self.src, sr)
Tao Bao3c5a16d2017-02-13 11:42:50 -0800678 if sh not in stashes:
Tao Baoe27acfd2016-12-16 11:13:55 -0800679 stashed_blocks_after += sr.size()
680
681 if stashed_blocks_after > max_allowed:
Tao Bao82c47982015-08-17 09:45:13 -0700682 # We cannot stash this one for a later command. Find out the command
683 # that will use this stash and replace the command with "new".
Tao Bao3a2e3502016-12-28 09:14:39 -0800684 use_cmd = stash_map[stash_raw_id][2]
Tao Bao82c47982015-08-17 09:45:13 -0700685 replaced_cmds.append(use_cmd)
Tao Bao9a5caf22015-08-25 15:10:10 -0700686 print("%10d %9s %s" % (sr.size(), "explicit", use_cmd))
Tao Bao82c47982015-08-17 09:45:13 -0700687 else:
Tao Bao3c5a16d2017-02-13 11:42:50 -0800688 # Update the stashes map.
689 if self.version == 2:
690 assert stash_raw_id not in stashes
691 if free_stash_ids:
692 sid = heapq.heappop(free_stash_ids)
693 else:
694 sid = next_stash_id
695 next_stash_id += 1
696 stashes[stash_raw_id] = sid
697 else:
698 if sh in stashes:
699 stashes[sh] += 1
700 else:
701 stashes[sh] = 1
Tao Baoe27acfd2016-12-16 11:13:55 -0800702 stashed_blocks = stashed_blocks_after
Tao Bao82c47982015-08-17 09:45:13 -0700703
704 # "move" and "diff" may introduce implicit stashes in BBOTA v3. Prior to
705 # ComputePatches(), they both have the style of "diff".
706 if xf.style == "diff" and self.version >= 3:
707 assert xf.tgt_ranges and xf.src_ranges
708 if xf.src_ranges.overlaps(xf.tgt_ranges):
709 if stashed_blocks + xf.src_ranges.size() > max_allowed:
710 replaced_cmds.append(xf)
Tao Bao9a5caf22015-08-25 15:10:10 -0700711 print("%10d %9s %s" % (xf.src_ranges.size(), "implicit", xf))
Tao Bao82c47982015-08-17 09:45:13 -0700712
713 # Replace the commands in replaced_cmds with "new"s.
714 for cmd in replaced_cmds:
715 # It no longer uses any commands in "use_stash". Remove the def points
716 # for all those stashes.
Tao Bao3a2e3502016-12-28 09:14:39 -0800717 for stash_raw_id, sr in cmd.use_stash:
718 def_cmd = stash_map[stash_raw_id][1]
719 assert (stash_raw_id, sr) in def_cmd.stash_before
720 def_cmd.stash_before.remove((stash_raw_id, sr))
Tao Bao82c47982015-08-17 09:45:13 -0700721
Tianjie Xuebe39a02016-01-14 14:12:26 -0800722 # Add up blocks that violates space limit and print total number to
723 # screen later.
724 new_blocks += cmd.tgt_ranges.size()
Tao Bao82c47982015-08-17 09:45:13 -0700725 cmd.ConvertToNew()
726
Tao Bao3a2e3502016-12-28 09:14:39 -0800727 # xf.use_stash may generate free commands.
728 for stash_raw_id, sr in xf.use_stash:
Tao Baoe27acfd2016-12-16 11:13:55 -0800729 if self.version == 2:
Tao Bao3a2e3502016-12-28 09:14:39 -0800730 sid = stashes.pop(stash_raw_id)
Tao Baoe27acfd2016-12-16 11:13:55 -0800731 stashed_blocks -= sr.size()
Tao Bao3a2e3502016-12-28 09:14:39 -0800732 heapq.heappush(free_stash_ids, sid)
Tao Baoe27acfd2016-12-16 11:13:55 -0800733 else:
734 sh = self.HashBlocks(self.src, sr)
735 assert sh in stashes
736 stashes[sh] -= 1
737 if stashes[sh] == 0:
738 stashed_blocks -= sr.size()
739 stashes.pop(sh)
Tao Baoe27acfd2016-12-16 11:13:55 -0800740
Tianjie Xuebe39a02016-01-14 14:12:26 -0800741 num_of_bytes = new_blocks * self.tgt.blocksize
742 print(" Total %d blocks (%d bytes) are packed as new blocks due to "
743 "insufficient cache size." % (new_blocks, num_of_bytes))
Tao Bao304ee272016-12-19 11:01:38 -0800744 return new_blocks
Tao Bao9a5caf22015-08-25 15:10:10 -0700745
Doug Zongkerfc44a512014-08-26 13:10:25 -0700746 def ComputePatches(self, prefix):
747 print("Reticulating splines...")
748 diff_q = []
749 patch_num = 0
750 with open(prefix + ".new.dat", "wb") as new_f:
751 for xf in self.transfers:
752 if xf.style == "zero":
Tao Bao08c85832016-09-19 22:26:30 -0700753 tgt_size = xf.tgt_ranges.size() * self.tgt.blocksize
754 print("%10d %10d (%6.2f%%) %7s %s %s" % (
755 tgt_size, tgt_size, 100.0, xf.style, xf.tgt_name,
756 str(xf.tgt_ranges)))
757
Doug Zongkerfc44a512014-08-26 13:10:25 -0700758 elif xf.style == "new":
759 for piece in self.tgt.ReadRangeSet(xf.tgt_ranges):
760 new_f.write(piece)
Tao Bao08c85832016-09-19 22:26:30 -0700761 tgt_size = xf.tgt_ranges.size() * self.tgt.blocksize
762 print("%10d %10d (%6.2f%%) %7s %s %s" % (
763 tgt_size, tgt_size, 100.0, xf.style,
764 xf.tgt_name, str(xf.tgt_ranges)))
765
Doug Zongkerfc44a512014-08-26 13:10:25 -0700766 elif xf.style == "diff":
767 src = self.src.ReadRangeSet(xf.src_ranges)
768 tgt = self.tgt.ReadRangeSet(xf.tgt_ranges)
769
770 # We can't compare src and tgt directly because they may have
771 # the same content but be broken up into blocks differently, eg:
772 #
773 # ["he", "llo"] vs ["h", "ello"]
774 #
775 # We want those to compare equal, ideally without having to
776 # actually concatenate the strings (these may be tens of
777 # megabytes).
778
779 src_sha1 = sha1()
780 for p in src:
781 src_sha1.update(p)
782 tgt_sha1 = sha1()
783 tgt_size = 0
784 for p in tgt:
785 tgt_sha1.update(p)
786 tgt_size += len(p)
787
788 if src_sha1.digest() == tgt_sha1.digest():
789 # These are identical; we don't need to generate a patch,
790 # just issue copy commands on the device.
791 xf.style = "move"
Tao Bao08c85832016-09-19 22:26:30 -0700792 if xf.src_ranges != xf.tgt_ranges:
793 print("%10d %10d (%6.2f%%) %7s %s %s (from %s)" % (
794 tgt_size, tgt_size, 100.0, xf.style,
795 xf.tgt_name if xf.tgt_name == xf.src_name else (
796 xf.tgt_name + " (from " + xf.src_name + ")"),
797 str(xf.tgt_ranges), str(xf.src_ranges)))
Doug Zongkerfc44a512014-08-26 13:10:25 -0700798 else:
799 # For files in zip format (eg, APKs, JARs, etc.) we would
800 # like to use imgdiff -z if possible (because it usually
801 # produces significantly smaller patches than bsdiff).
802 # This is permissible if:
803 #
Tao Bao293fd132016-06-11 12:19:23 -0700804 # - imgdiff is not disabled, and
Doug Zongkerfc44a512014-08-26 13:10:25 -0700805 # - the source and target files are monotonic (ie, the
806 # data is stored with blocks in increasing order), and
807 # - we haven't removed any blocks from the source set.
808 #
809 # If these conditions are satisfied then appending all the
810 # blocks in the set together in order will produce a valid
811 # zip file (plus possibly extra zeros in the last block),
812 # which is what imgdiff needs to operate. (imgdiff is
813 # fine with extra zeros at the end of the file.)
Tao Bao293fd132016-06-11 12:19:23 -0700814 imgdiff = (not self.disable_imgdiff and xf.intact and
Doug Zongkerfc44a512014-08-26 13:10:25 -0700815 xf.tgt_name.split(".")[-1].lower()
816 in ("apk", "jar", "zip"))
817 xf.style = "imgdiff" if imgdiff else "bsdiff"
818 diff_q.append((tgt_size, src, tgt, xf, patch_num))
819 patch_num += 1
820
821 else:
822 assert False, "unknown style " + xf.style
823
824 if diff_q:
825 if self.threads > 1:
826 print("Computing patches (using %d threads)..." % (self.threads,))
827 else:
828 print("Computing patches...")
829 diff_q.sort()
830
831 patches = [None] * patch_num
832
Dan Albert8b72aef2015-03-23 19:13:21 -0700833 # TODO: Rewrite with multiprocessing.ThreadPool?
Doug Zongkerfc44a512014-08-26 13:10:25 -0700834 lock = threading.Lock()
835 def diff_worker():
836 while True:
837 with lock:
Dan Albert8b72aef2015-03-23 19:13:21 -0700838 if not diff_q:
839 return
Doug Zongkerfc44a512014-08-26 13:10:25 -0700840 tgt_size, src, tgt, xf, patchnum = diff_q.pop()
841 patch = compute_patch(src, tgt, imgdiff=(xf.style == "imgdiff"))
842 size = len(patch)
843 with lock:
844 patches[patchnum] = (patch, xf)
Tao Bao08c85832016-09-19 22:26:30 -0700845 print("%10d %10d (%6.2f%%) %7s %s %s %s" % (
Doug Zongkerfc44a512014-08-26 13:10:25 -0700846 size, tgt_size, size * 100.0 / tgt_size, xf.style,
847 xf.tgt_name if xf.tgt_name == xf.src_name else (
Tao Bao08c85832016-09-19 22:26:30 -0700848 xf.tgt_name + " (from " + xf.src_name + ")"),
849 str(xf.tgt_ranges), str(xf.src_ranges)))
Doug Zongkerfc44a512014-08-26 13:10:25 -0700850
851 threads = [threading.Thread(target=diff_worker)
Dan Albert8b72aef2015-03-23 19:13:21 -0700852 for _ in range(self.threads)]
Doug Zongkerfc44a512014-08-26 13:10:25 -0700853 for th in threads:
854 th.start()
855 while threads:
856 threads.pop().join()
857 else:
858 patches = []
859
860 p = 0
861 with open(prefix + ".patch.dat", "wb") as patch_f:
862 for patch, xf in patches:
863 xf.patch_start = p
864 xf.patch_len = len(patch)
865 patch_f.write(patch)
866 p += len(patch)
867
868 def AssertSequenceGood(self):
869 # Simulate the sequences of transfers we will output, and check that:
870 # - we never read a block after writing it, and
871 # - we write every block we care about exactly once.
872
873 # Start with no blocks having been touched yet.
Doug Zongker6ab2a502016-02-09 08:28:09 -0800874 touched = array.array("B", "\0" * self.tgt.total_blocks)
Doug Zongkerfc44a512014-08-26 13:10:25 -0700875
876 # Imagine processing the transfers in order.
877 for xf in self.transfers:
878 # Check that the input blocks for this transfer haven't yet been touched.
Doug Zongker62338182014-09-08 08:29:55 -0700879
880 x = xf.src_ranges
881 if self.version >= 2:
882 for _, sr in xf.use_stash:
883 x = x.subtract(sr)
884
Doug Zongker6ab2a502016-02-09 08:28:09 -0800885 for s, e in x:
Tao Baoff75c232016-03-04 15:23:34 -0800886 # Source image could be larger. Don't check the blocks that are in the
887 # source image only. Since they are not in 'touched', and won't ever
888 # be touched.
889 for i in range(s, min(e, self.tgt.total_blocks)):
Doug Zongker6ab2a502016-02-09 08:28:09 -0800890 assert touched[i] == 0
891
892 # Check that the output blocks for this transfer haven't yet
893 # been touched, and touch all the blocks written by this
894 # transfer.
895 for s, e in xf.tgt_ranges:
896 for i in range(s, e):
897 assert touched[i] == 0
898 touched[i] = 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700899
900 # Check that we've written every target block.
Doug Zongker6ab2a502016-02-09 08:28:09 -0800901 for s, e in self.tgt.care_map:
902 for i in range(s, e):
903 assert touched[i] == 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700904
Doug Zongker62338182014-09-08 08:29:55 -0700905 def ImproveVertexSequence(self):
906 print("Improving vertex order...")
907
908 # At this point our digraph is acyclic; we reversed any edges that
909 # were backwards in the heuristically-generated sequence. The
910 # previously-generated order is still acceptable, but we hope to
911 # find a better order that needs less memory for stashed data.
912 # Now we do a topological sort to generate a new vertex order,
913 # using a greedy algorithm to choose which vertex goes next
914 # whenever we have a choice.
915
916 # Make a copy of the edge set; this copy will get destroyed by the
917 # algorithm.
918 for xf in self.transfers:
919 xf.incoming = xf.goes_after.copy()
920 xf.outgoing = xf.goes_before.copy()
921
922 L = [] # the new vertex order
923
924 # S is the set of sources in the remaining graph; we always choose
925 # the one that leaves the least amount of stashed data after it's
926 # executed.
927 S = [(u.NetStashChange(), u.order, u) for u in self.transfers
928 if not u.incoming]
929 heapq.heapify(S)
930
931 while S:
932 _, _, xf = heapq.heappop(S)
933 L.append(xf)
934 for u in xf.outgoing:
935 del u.incoming[xf]
936 if not u.incoming:
937 heapq.heappush(S, (u.NetStashChange(), u.order, u))
938
939 # if this fails then our graph had a cycle.
940 assert len(L) == len(self.transfers)
941
942 self.transfers = L
943 for i, xf in enumerate(L):
944 xf.order = i
945
Doug Zongkerfc44a512014-08-26 13:10:25 -0700946 def RemoveBackwardEdges(self):
947 print("Removing backward edges...")
948 in_order = 0
949 out_of_order = 0
950 lost_source = 0
951
952 for xf in self.transfers:
Doug Zongkerfc44a512014-08-26 13:10:25 -0700953 lost = 0
954 size = xf.src_ranges.size()
955 for u in xf.goes_before:
956 # xf should go before u
957 if xf.order < u.order:
958 # it does, hurray!
Doug Zongker62338182014-09-08 08:29:55 -0700959 in_order += 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700960 else:
961 # it doesn't, boo. trim the blocks that u writes from xf's
962 # source, so that xf can go after u.
Doug Zongker62338182014-09-08 08:29:55 -0700963 out_of_order += 1
Doug Zongkerfc44a512014-08-26 13:10:25 -0700964 assert xf.src_ranges.overlaps(u.tgt_ranges)
965 xf.src_ranges = xf.src_ranges.subtract(u.tgt_ranges)
966 xf.intact = False
967
968 if xf.style == "diff" and not xf.src_ranges:
969 # nothing left to diff from; treat as new data
970 xf.style = "new"
971
972 lost = size - xf.src_ranges.size()
973 lost_source += lost
Doug Zongkerfc44a512014-08-26 13:10:25 -0700974
975 print((" %d/%d dependencies (%.2f%%) were violated; "
976 "%d source blocks removed.") %
977 (out_of_order, in_order + out_of_order,
978 (out_of_order * 100.0 / (in_order + out_of_order))
979 if (in_order + out_of_order) else 0.0,
980 lost_source))
981
Doug Zongker62338182014-09-08 08:29:55 -0700982 def ReverseBackwardEdges(self):
Tao Bao3a2e3502016-12-28 09:14:39 -0800983 """Reverse unsatisfying edges and compute pairs of stashed blocks.
984
985 For each transfer, make sure it properly stashes the blocks it touches and
986 will be used by later transfers. It uses pairs of (stash_raw_id, range) to
987 record the blocks to be stashed. 'stash_raw_id' is an id that uniquely
988 identifies each pair. Note that for the same range (e.g. RangeSet("1-5")),
989 it is possible to have multiple pairs with different 'stash_raw_id's. Each
990 'stash_raw_id' will be consumed by one transfer. In BBOTA v3+, identical
991 blocks will be written to the same stash slot in WriteTransfers().
992 """
993
Doug Zongker62338182014-09-08 08:29:55 -0700994 print("Reversing backward edges...")
995 in_order = 0
996 out_of_order = 0
Tao Bao3a2e3502016-12-28 09:14:39 -0800997 stash_raw_id = 0
Doug Zongker62338182014-09-08 08:29:55 -0700998 stash_size = 0
999
1000 for xf in self.transfers:
Doug Zongker62338182014-09-08 08:29:55 -07001001 for u in xf.goes_before.copy():
1002 # xf should go before u
1003 if xf.order < u.order:
1004 # it does, hurray!
1005 in_order += 1
1006 else:
1007 # it doesn't, boo. modify u to stash the blocks that it
1008 # writes that xf wants to read, and then require u to go
1009 # before xf.
1010 out_of_order += 1
1011
1012 overlap = xf.src_ranges.intersect(u.tgt_ranges)
1013 assert overlap
1014
Tao Bao3a2e3502016-12-28 09:14:39 -08001015 u.stash_before.append((stash_raw_id, overlap))
1016 xf.use_stash.append((stash_raw_id, overlap))
1017 stash_raw_id += 1
Doug Zongker62338182014-09-08 08:29:55 -07001018 stash_size += overlap.size()
1019
1020 # reverse the edge direction; now xf must go after u
1021 del xf.goes_before[u]
1022 del u.goes_after[xf]
1023 xf.goes_after[u] = None # value doesn't matter
1024 u.goes_before[xf] = None
1025
1026 print((" %d/%d dependencies (%.2f%%) were violated; "
1027 "%d source blocks stashed.") %
1028 (out_of_order, in_order + out_of_order,
1029 (out_of_order * 100.0 / (in_order + out_of_order))
1030 if (in_order + out_of_order) else 0.0,
1031 stash_size))
1032
Doug Zongkerfc44a512014-08-26 13:10:25 -07001033 def FindVertexSequence(self):
1034 print("Finding vertex sequence...")
1035
1036 # This is based on "A Fast & Effective Heuristic for the Feedback
1037 # Arc Set Problem" by P. Eades, X. Lin, and W.F. Smyth. Think of
1038 # it as starting with the digraph G and moving all the vertices to
1039 # be on a horizontal line in some order, trying to minimize the
1040 # number of edges that end up pointing to the left. Left-pointing
1041 # edges will get removed to turn the digraph into a DAG. In this
1042 # case each edge has a weight which is the number of source blocks
1043 # we'll lose if that edge is removed; we try to minimize the total
1044 # weight rather than just the number of edges.
1045
1046 # Make a copy of the edge set; this copy will get destroyed by the
1047 # algorithm.
1048 for xf in self.transfers:
1049 xf.incoming = xf.goes_after.copy()
1050 xf.outgoing = xf.goes_before.copy()
Doug Zongker6ab2a502016-02-09 08:28:09 -08001051 xf.score = sum(xf.outgoing.values()) - sum(xf.incoming.values())
Doug Zongkerfc44a512014-08-26 13:10:25 -07001052
1053 # We use an OrderedDict instead of just a set so that the output
1054 # is repeatable; otherwise it would depend on the hash values of
1055 # the transfer objects.
1056 G = OrderedDict()
1057 for xf in self.transfers:
1058 G[xf] = None
1059 s1 = deque() # the left side of the sequence, built from left to right
1060 s2 = deque() # the right side of the sequence, built from right to left
1061
Doug Zongker6ab2a502016-02-09 08:28:09 -08001062 heap = []
1063 for xf in self.transfers:
1064 xf.heap_item = HeapItem(xf)
1065 heap.append(xf.heap_item)
1066 heapq.heapify(heap)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001067
Tao Bao33482282016-10-24 16:49:08 -07001068 # Use OrderedDict() instead of set() to preserve the insertion order. Need
1069 # to use 'sinks[key] = None' to add key into the set. sinks will look like
1070 # { key1: None, key2: None, ... }.
1071 sinks = OrderedDict.fromkeys(u for u in G if not u.outgoing)
1072 sources = OrderedDict.fromkeys(u for u in G if not u.incoming)
Doug Zongker6ab2a502016-02-09 08:28:09 -08001073
1074 def adjust_score(iu, delta):
1075 iu.score += delta
1076 iu.heap_item.clear()
1077 iu.heap_item = HeapItem(iu)
1078 heapq.heappush(heap, iu.heap_item)
1079
1080 while G:
Doug Zongkerfc44a512014-08-26 13:10:25 -07001081 # Put all sinks at the end of the sequence.
Doug Zongker6ab2a502016-02-09 08:28:09 -08001082 while sinks:
Tao Bao33482282016-10-24 16:49:08 -07001083 new_sinks = OrderedDict()
Doug Zongkerfc44a512014-08-26 13:10:25 -07001084 for u in sinks:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001085 if u not in G: continue
Doug Zongkerfc44a512014-08-26 13:10:25 -07001086 s2.appendleft(u)
1087 del G[u]
1088 for iu in u.incoming:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001089 adjust_score(iu, -iu.outgoing.pop(u))
Tao Bao33482282016-10-24 16:49:08 -07001090 if not iu.outgoing:
1091 new_sinks[iu] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001092 sinks = new_sinks
Doug Zongkerfc44a512014-08-26 13:10:25 -07001093
1094 # Put all the sources at the beginning of the sequence.
Doug Zongker6ab2a502016-02-09 08:28:09 -08001095 while sources:
Tao Bao33482282016-10-24 16:49:08 -07001096 new_sources = OrderedDict()
Doug Zongkerfc44a512014-08-26 13:10:25 -07001097 for u in sources:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001098 if u not in G: continue
Doug Zongkerfc44a512014-08-26 13:10:25 -07001099 s1.append(u)
1100 del G[u]
1101 for iu in u.outgoing:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001102 adjust_score(iu, +iu.incoming.pop(u))
Tao Bao33482282016-10-24 16:49:08 -07001103 if not iu.incoming:
1104 new_sources[iu] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001105 sources = new_sources
Doug Zongkerfc44a512014-08-26 13:10:25 -07001106
Doug Zongker6ab2a502016-02-09 08:28:09 -08001107 if not G: break
Doug Zongkerfc44a512014-08-26 13:10:25 -07001108
1109 # Find the "best" vertex to put next. "Best" is the one that
1110 # maximizes the net difference in source blocks saved we get by
1111 # pretending it's a source rather than a sink.
1112
Doug Zongker6ab2a502016-02-09 08:28:09 -08001113 while True:
1114 u = heapq.heappop(heap)
1115 if u and u.item in G:
1116 u = u.item
1117 break
Doug Zongkerfc44a512014-08-26 13:10:25 -07001118
Doug Zongkerfc44a512014-08-26 13:10:25 -07001119 s1.append(u)
1120 del G[u]
1121 for iu in u.outgoing:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001122 adjust_score(iu, +iu.incoming.pop(u))
Tao Bao33482282016-10-24 16:49:08 -07001123 if not iu.incoming:
1124 sources[iu] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001125
Doug Zongkerfc44a512014-08-26 13:10:25 -07001126 for iu in u.incoming:
Doug Zongker6ab2a502016-02-09 08:28:09 -08001127 adjust_score(iu, -iu.outgoing.pop(u))
Tao Bao33482282016-10-24 16:49:08 -07001128 if not iu.outgoing:
1129 sinks[iu] = None
Doug Zongkerfc44a512014-08-26 13:10:25 -07001130
1131 # Now record the sequence in the 'order' field of each transfer,
1132 # and by rearranging self.transfers to be in the chosen sequence.
1133
1134 new_transfers = []
1135 for x in itertools.chain(s1, s2):
1136 x.order = len(new_transfers)
1137 new_transfers.append(x)
1138 del x.incoming
1139 del x.outgoing
1140
1141 self.transfers = new_transfers
1142
1143 def GenerateDigraph(self):
1144 print("Generating digraph...")
Doug Zongker6ab2a502016-02-09 08:28:09 -08001145
1146 # Each item of source_ranges will be:
1147 # - None, if that block is not used as a source,
Tao Bao33482282016-10-24 16:49:08 -07001148 # - an ordered set of transfers.
Doug Zongker6ab2a502016-02-09 08:28:09 -08001149 source_ranges = []
1150 for b in self.transfers:
1151 for s, e in b.src_ranges:
1152 if e > len(source_ranges):
1153 source_ranges.extend([None] * (e-len(source_ranges)))
1154 for i in range(s, e):
1155 if source_ranges[i] is None:
Tao Bao33482282016-10-24 16:49:08 -07001156 source_ranges[i] = OrderedDict.fromkeys([b])
Doug Zongker6ab2a502016-02-09 08:28:09 -08001157 else:
Tao Bao33482282016-10-24 16:49:08 -07001158 source_ranges[i][b] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001159
Doug Zongkerfc44a512014-08-26 13:10:25 -07001160 for a in self.transfers:
Tao Bao33482282016-10-24 16:49:08 -07001161 intersections = OrderedDict()
Doug Zongker6ab2a502016-02-09 08:28:09 -08001162 for s, e in a.tgt_ranges:
1163 for i in range(s, e):
1164 if i >= len(source_ranges): break
Tao Bao33482282016-10-24 16:49:08 -07001165 # Add all the Transfers in source_ranges[i] to the (ordered) set.
1166 if source_ranges[i] is not None:
1167 for j in source_ranges[i]:
1168 intersections[j] = None
Doug Zongker6ab2a502016-02-09 08:28:09 -08001169
1170 for b in intersections:
1171 if a is b: continue
Doug Zongkerfc44a512014-08-26 13:10:25 -07001172
1173 # If the blocks written by A are read by B, then B needs to go before A.
1174 i = a.tgt_ranges.intersect(b.src_ranges)
1175 if i:
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001176 if b.src_name == "__ZERO":
1177 # the cost of removing source blocks for the __ZERO domain
1178 # is (nearly) zero.
1179 size = 0
1180 else:
1181 size = i.size()
Doug Zongkerfc44a512014-08-26 13:10:25 -07001182 b.goes_before[a] = size
1183 a.goes_after[b] = size
1184
1185 def FindTransfers(self):
Tao Bao9a5caf22015-08-25 15:10:10 -07001186 """Parse the file_map to generate all the transfers."""
1187
Tao Bao08c85832016-09-19 22:26:30 -07001188 def AddSplitTransfers(tgt_name, src_name, tgt_ranges, src_ranges,
1189 style, by_id):
1190 """Add one or multiple Transfer()s by splitting large files.
Tao Bao9a5caf22015-08-25 15:10:10 -07001191
1192 For BBOTA v3, we need to stash source blocks for resumable feature.
1193 However, with the growth of file size and the shrink of the cache
1194 partition source blocks are too large to be stashed. If a file occupies
Tao Bao08c85832016-09-19 22:26:30 -07001195 too many blocks, we split it into smaller pieces by getting multiple
1196 Transfer()s.
Tao Bao9a5caf22015-08-25 15:10:10 -07001197
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001198 The downside is that after splitting, we may increase the package size
1199 since the split pieces don't align well. According to our experiments,
1200 1/8 of the cache size as the per-piece limit appears to be optimal.
1201 Compared to the fixed 1024-block limit, it reduces the overall package
Tao Bao08c85832016-09-19 22:26:30 -07001202 size by 30% for volantis, and 20% for angler and bullhead."""
Tao Bao9a5caf22015-08-25 15:10:10 -07001203
Tao Bao08c85832016-09-19 22:26:30 -07001204 # Possibly split large files into smaller chunks.
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001205 pieces = 0
1206 cache_size = common.OPTIONS.cache_size
1207 split_threshold = 0.125
1208 max_blocks_per_transfer = int(cache_size * split_threshold /
1209 self.tgt.blocksize)
1210
Tao Bao9a5caf22015-08-25 15:10:10 -07001211 # Change nothing for small files.
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001212 if (tgt_ranges.size() <= max_blocks_per_transfer and
1213 src_ranges.size() <= max_blocks_per_transfer):
Tao Bao9a5caf22015-08-25 15:10:10 -07001214 Transfer(tgt_name, src_name, tgt_ranges, src_ranges, style, by_id)
1215 return
1216
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001217 while (tgt_ranges.size() > max_blocks_per_transfer and
1218 src_ranges.size() > max_blocks_per_transfer):
Tao Bao9a5caf22015-08-25 15:10:10 -07001219 tgt_split_name = "%s-%d" % (tgt_name, pieces)
1220 src_split_name = "%s-%d" % (src_name, pieces)
Tianjie Xubb86e1d2016-01-13 16:14:10 -08001221 tgt_first = tgt_ranges.first(max_blocks_per_transfer)
1222 src_first = src_ranges.first(max_blocks_per_transfer)
1223
Tao Bao9a5caf22015-08-25 15:10:10 -07001224 Transfer(tgt_split_name, src_split_name, tgt_first, src_first, style,
1225 by_id)
1226
1227 tgt_ranges = tgt_ranges.subtract(tgt_first)
1228 src_ranges = src_ranges.subtract(src_first)
1229 pieces += 1
1230
1231 # Handle remaining blocks.
1232 if tgt_ranges.size() or src_ranges.size():
1233 # Must be both non-empty.
1234 assert tgt_ranges.size() and src_ranges.size()
1235 tgt_split_name = "%s-%d" % (tgt_name, pieces)
1236 src_split_name = "%s-%d" % (src_name, pieces)
1237 Transfer(tgt_split_name, src_split_name, tgt_ranges, src_ranges, style,
1238 by_id)
1239
Tao Bao08c85832016-09-19 22:26:30 -07001240 def AddTransfer(tgt_name, src_name, tgt_ranges, src_ranges, style, by_id,
1241 split=False):
1242 """Wrapper function for adding a Transfer()."""
1243
1244 # We specialize diff transfers only (which covers bsdiff/imgdiff/move);
1245 # otherwise add the Transfer() as is.
1246 if style != "diff" or not split:
1247 Transfer(tgt_name, src_name, tgt_ranges, src_ranges, style, by_id)
1248 return
1249
1250 # Handle .odex files specially to analyze the block-wise difference. If
1251 # most of the blocks are identical with only few changes (e.g. header),
1252 # we will patch the changed blocks only. This avoids stashing unchanged
1253 # blocks while patching. We limit the analysis to files without size
1254 # changes only. This is to avoid sacrificing the OTA generation cost too
1255 # much.
1256 if (tgt_name.split(".")[-1].lower() == 'odex' and
1257 tgt_ranges.size() == src_ranges.size()):
1258
1259 # 0.5 threshold can be further tuned. The tradeoff is: if only very
1260 # few blocks remain identical, we lose the opportunity to use imgdiff
1261 # that may have better compression ratio than bsdiff.
1262 crop_threshold = 0.5
1263
1264 tgt_skipped = RangeSet()
1265 src_skipped = RangeSet()
1266 tgt_size = tgt_ranges.size()
1267 tgt_changed = 0
1268 for src_block, tgt_block in zip(src_ranges.next_item(),
1269 tgt_ranges.next_item()):
1270 src_rs = RangeSet(str(src_block))
1271 tgt_rs = RangeSet(str(tgt_block))
1272 if self.src.ReadRangeSet(src_rs) == self.tgt.ReadRangeSet(tgt_rs):
1273 tgt_skipped = tgt_skipped.union(tgt_rs)
1274 src_skipped = src_skipped.union(src_rs)
1275 else:
1276 tgt_changed += tgt_rs.size()
1277
1278 # Terminate early if no clear sign of benefits.
1279 if tgt_changed > tgt_size * crop_threshold:
1280 break
1281
1282 if tgt_changed < tgt_size * crop_threshold:
1283 assert tgt_changed + tgt_skipped.size() == tgt_size
1284 print('%10d %10d (%6.2f%%) %s' % (tgt_skipped.size(), tgt_size,
1285 tgt_skipped.size() * 100.0 / tgt_size, tgt_name))
1286 AddSplitTransfers(
1287 "%s-skipped" % (tgt_name,),
1288 "%s-skipped" % (src_name,),
1289 tgt_skipped, src_skipped, style, by_id)
1290
1291 # Intentionally change the file extension to avoid being imgdiff'd as
1292 # the files are no longer in their original format.
1293 tgt_name = "%s-cropped" % (tgt_name,)
1294 src_name = "%s-cropped" % (src_name,)
1295 tgt_ranges = tgt_ranges.subtract(tgt_skipped)
1296 src_ranges = src_ranges.subtract(src_skipped)
1297
1298 # Possibly having no changed blocks.
1299 if not tgt_ranges:
1300 return
1301
1302 # Add the transfer(s).
1303 AddSplitTransfers(
1304 tgt_name, src_name, tgt_ranges, src_ranges, style, by_id)
1305
1306 print("Finding transfers...")
1307
Doug Zongkerfc44a512014-08-26 13:10:25 -07001308 empty = RangeSet()
1309 for tgt_fn, tgt_ranges in self.tgt.file_map.items():
1310 if tgt_fn == "__ZERO":
1311 # the special "__ZERO" domain is all the blocks not contained
1312 # in any file and that are filled with zeros. We have a
1313 # special transfer style for zero blocks.
1314 src_ranges = self.src.file_map.get("__ZERO", empty)
Tao Bao9a5caf22015-08-25 15:10:10 -07001315 AddTransfer(tgt_fn, "__ZERO", tgt_ranges, src_ranges,
1316 "zero", self.transfers)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001317 continue
1318
Tao Baoff777812015-05-12 11:42:31 -07001319 elif tgt_fn == "__COPY":
1320 # "__COPY" domain includes all the blocks not contained in any
1321 # file and that need to be copied unconditionally to the target.
Tao Bao9a5caf22015-08-25 15:10:10 -07001322 AddTransfer(tgt_fn, None, tgt_ranges, empty, "new", self.transfers)
Tao Baoff777812015-05-12 11:42:31 -07001323 continue
1324
Doug Zongkerfc44a512014-08-26 13:10:25 -07001325 elif tgt_fn in self.src.file_map:
1326 # Look for an exact pathname match in the source.
Tao Bao9a5caf22015-08-25 15:10:10 -07001327 AddTransfer(tgt_fn, tgt_fn, tgt_ranges, self.src.file_map[tgt_fn],
1328 "diff", self.transfers, self.version >= 3)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001329 continue
1330
1331 b = os.path.basename(tgt_fn)
1332 if b in self.src_basenames:
1333 # Look for an exact basename match in the source.
1334 src_fn = self.src_basenames[b]
Tao Bao9a5caf22015-08-25 15:10:10 -07001335 AddTransfer(tgt_fn, src_fn, tgt_ranges, self.src.file_map[src_fn],
1336 "diff", self.transfers, self.version >= 3)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001337 continue
1338
1339 b = re.sub("[0-9]+", "#", b)
1340 if b in self.src_numpatterns:
1341 # Look for a 'number pattern' match (a basename match after
1342 # all runs of digits are replaced by "#"). (This is useful
1343 # for .so files that contain version numbers in the filename
1344 # that get bumped.)
1345 src_fn = self.src_numpatterns[b]
Tao Bao9a5caf22015-08-25 15:10:10 -07001346 AddTransfer(tgt_fn, src_fn, tgt_ranges, self.src.file_map[src_fn],
1347 "diff", self.transfers, self.version >= 3)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001348 continue
1349
Tao Bao9a5caf22015-08-25 15:10:10 -07001350 AddTransfer(tgt_fn, None, tgt_ranges, empty, "new", self.transfers)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001351
1352 def AbbreviateSourceNames(self):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001353 for k in self.src.file_map.keys():
1354 b = os.path.basename(k)
1355 self.src_basenames[b] = k
1356 b = re.sub("[0-9]+", "#", b)
1357 self.src_numpatterns[b] = k
1358
1359 @staticmethod
1360 def AssertPartition(total, seq):
1361 """Assert that all the RangeSets in 'seq' form a partition of the
1362 'total' RangeSet (ie, they are nonintersecting and their union
1363 equals 'total')."""
Doug Zongker6ab2a502016-02-09 08:28:09 -08001364
Doug Zongkerfc44a512014-08-26 13:10:25 -07001365 so_far = RangeSet()
1366 for i in seq:
1367 assert not so_far.overlaps(i)
1368 so_far = so_far.union(i)
1369 assert so_far == total