1#! /usr/bin/env python 2 3# Copyright (C) 2012 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from __future__ import print_function 18import csv 19import getopt 20import hashlib 21import posixpath 22import signal 23import struct 24import sys 25 26 27def usage(argv0): 28 print(""" 29Usage: %s [-v] [-s] [-c <filename>] sparse_image_file ... 30 -v verbose output 31 -s show sha1sum of data blocks 32 -c <filename> save .csv file of blocks 33""" % (argv0)) 34 sys.exit(2) 35 36 37def main(): 38 signal.signal(signal.SIGPIPE, signal.SIG_DFL) 39 40 me = posixpath.basename(sys.argv[0]) 41 42 # Parse the command line 43 verbose = 0 # -v 44 showhash = 0 # -s 45 csvfilename = None # -c 46 try: 47 opts, args = getopt.getopt(sys.argv[1:], 48 "vsc:", 49 ["verbose", "showhash", "csvfile"]) 50 except getopt.GetoptError, e: 51 print(e) 52 usage(me) 53 for o, a in opts: 54 if o in ("-v", "--verbose"): 55 verbose += 1 56 elif o in ("-s", "--showhash"): 57 showhash = True 58 elif o in ("-c", "--csvfile"): 59 csvfilename = a 60 else: 61 print("Unrecognized option \"%s\"" % (o)) 62 usage(me) 63 64 if not args: 65 print("No sparse_image_file specified") 66 usage(me) 67 68 if csvfilename: 69 csvfile = open(csvfilename, "wb") 70 csvwriter = csv.writer(csvfile) 71 72 output = verbose or csvfilename or showhash 73 74 for path in args: 75 FH = open(path, "rb") 76 header_bin = FH.read(28) 77 header = struct.unpack("<I4H4I", header_bin) 78 79 magic = header[0] 80 major_version = header[1] 81 minor_version = header[2] 82 file_hdr_sz = header[3] 83 chunk_hdr_sz = header[4] 84 blk_sz = header[5] 85 total_blks = header[6] 86 total_chunks = header[7] 87 image_checksum = header[8] 88 89 if magic != 0xED26FF3A: 90 print("%s: %s: Magic should be 0xED26FF3A but is 0x%08X" 91 % (me, path, magic)) 92 continue 93 if major_version != 1 or minor_version != 0: 94 print("%s: %s: I only know about version 1.0, but this is version %u.%u" 95 % (me, path, major_version, minor_version)) 96 continue 97 if file_hdr_sz != 28: 98 print("%s: %s: The file header size was expected to be 28, but is %u." 99 % (me, path, file_hdr_sz)) 100 continue 101 if chunk_hdr_sz != 12: 102 print("%s: %s: The chunk header size was expected to be 12, but is %u." 103 % (me, path, chunk_hdr_sz)) 104 continue 105 106 print("%s: Total of %u %u-byte output blocks in %u input chunks." 107 % (path, total_blks, blk_sz, total_chunks)) 108 109 if image_checksum != 0: 110 print("checksum=0x%08X" % (image_checksum)) 111 112 if not output: 113 continue 114 115 if verbose > 0: 116 print(" input_bytes output_blocks") 117 print("chunk offset number offset number") 118 119 if csvfilename: 120 csvwriter.writerow(["chunk", "input offset", "input bytes", 121 "output offset", "output blocks", "type", "hash"]) 122 123 offset = 0 124 for i in xrange(1, total_chunks + 1): 125 header_bin = FH.read(12) 126 header = struct.unpack("<2H2I", header_bin) 127 chunk_type = header[0] 128 chunk_sz = header[2] 129 total_sz = header[3] 130 data_sz = total_sz - 12 131 curhash = "" 132 curtype = "" 133 curpos = FH.tell() 134 135 if verbose > 0: 136 print("%4u %10u %10u %7u %7u" % (i, curpos, data_sz, offset, chunk_sz), 137 end=" ") 138 139 if chunk_type == 0xCAC1: 140 if data_sz != (chunk_sz * blk_sz): 141 print("Raw chunk input size (%u) does not match output size (%u)" 142 % (data_sz, chunk_sz * blk_sz)) 143 break 144 else: 145 curtype = "Raw data" 146 data = FH.read(data_sz) 147 if showhash: 148 h = hashlib.sha1() 149 h.update(data) 150 curhash = h.hexdigest() 151 elif chunk_type == 0xCAC2: 152 if data_sz != 4: 153 print("Fill chunk should have 4 bytes of fill, but this has %u" 154 % (data_sz)) 155 break 156 else: 157 fill_bin = FH.read(4) 158 fill = struct.unpack("<I", fill_bin) 159 curtype = format("Fill with 0x%08X" % (fill)) 160 if showhash: 161 h = hashlib.sha1() 162 data = fill_bin * (blk_sz / 4); 163 for block in xrange(chunk_sz): 164 h.update(data) 165 curhash = h.hexdigest() 166 elif chunk_type == 0xCAC3: 167 if data_sz != 0: 168 print("Don't care chunk input size is non-zero (%u)" % (data_sz)) 169 break 170 else: 171 curtype = "Don't care" 172 elif chunk_type == 0xCAC4: 173 if data_sz != 4: 174 print("CRC32 chunk should have 4 bytes of CRC, but this has %u" 175 % (data_sz)) 176 break 177 else: 178 crc_bin = FH.read(4) 179 crc = struct.unpack("<I", crc_bin) 180 curtype = format("Unverified CRC32 0x%08X" % (crc)) 181 else: 182 print("Unknown chunk type 0x%04X" % (chunk_type)) 183 break 184 185 if verbose > 0: 186 print("%-18s" % (curtype), end=" ") 187 188 if verbose > 1: 189 header = struct.unpack("<12B", header_bin) 190 print(" (%02X%02X %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X)" 191 % (header[0], header[1], header[2], header[3], 192 header[4], header[5], header[6], header[7], 193 header[8], header[9], header[10], header[11]), end=" ") 194 195 print(curhash) 196 197 if csvfilename: 198 csvwriter.writerow([i, curpos, data_sz, offset, chunk_sz, curtype, 199 curhash]) 200 201 offset += chunk_sz 202 203 if verbose > 0: 204 print(" %10u %7u End" % (FH.tell(), offset)) 205 206 if total_blks != offset: 207 print("The header said we should have %u output blocks, but we saw %u" 208 % (total_blks, offset)) 209 210 junk_len = len(FH.read()) 211 if junk_len: 212 print("There were %u bytes of extra data at the end of the file." 213 % (junk_len)) 214 215 if csvfilename: 216 csvfile.close() 217 218 sys.exit(0) 219 220if __name__ == "__main__": 221 main() 222