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