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