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