# Copyright (C) 2014 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import bisect import os import struct from hashlib import sha1 import rangelib class SparseImage(object): """Wraps a sparse image file (and optional file map) into an image object suitable for passing to BlockImageDiff.""" def __init__(self, simg_fn, file_map_fn=None): self.simg_f = f = open(simg_fn, "rb") header_bin = f.read(28) header = struct.unpack("> 2)) to_read -= this_read while to_read > 0: # continue with following chunks if this range spans multiple chunks. idx += 1 chunk_start, chunk_len, filepos, fill_data = self.offset_map[idx] this_read = min(chunk_len, to_read) if filepos is not None: f.seek(filepos, os.SEEK_SET) yield f.read(this_read * self.blocksize) else: yield fill_data * (this_read * (self.blocksize >> 2)) to_read -= this_read def LoadFileBlockMap(self, fn): remaining = self.care_map self.file_map = out = {} with open(fn) as f: for line in f: fn, ranges = line.split(None, 1) ranges = rangelib.RangeSet.parse(ranges) out[fn] = ranges assert ranges.size() == ranges.intersect(remaining).size() remaining = remaining.subtract(ranges) # For all the remaining blocks in the care_map (ie, those that # aren't part of the data for any file), divide them into blocks # that are all zero and blocks that aren't. (Zero blocks are # handled specially because (1) there are usually a lot of them # and (2) bsdiff handles files with long sequences of repeated # bytes especially poorly.) zero_blocks = [] nonzero_blocks = [] reference = '\0' * self.blocksize f = self.simg_f for s, e in remaining: for b in range(s, e): idx = bisect.bisect_right(self.offset_index, b) - 1 chunk_start, _, filepos, fill_data = self.offset_map[idx] if filepos is not None: filepos += (b-chunk_start) * self.blocksize f.seek(filepos, os.SEEK_SET) data = f.read(self.blocksize) else: if fill_data == reference[:4]: # fill with all zeros data = reference else: data = None if data == reference: zero_blocks.append(b) zero_blocks.append(b+1) else: nonzero_blocks.append(b) nonzero_blocks.append(b+1) out["__ZERO"] = rangelib.RangeSet(data=zero_blocks) out["__NONZERO"] = rangelib.RangeSet(data=nonzero_blocks) def ResetFileMap(self): """Throw away the file map and treat the entire image as undifferentiated data.""" self.file_map = {"__DATA": self.care_map}