Add post-install verification for BBOTAs

Similar to the assertations in file-based OTA, we perform verification
for block-based OTAs (BBOTAs) after updating a partition, for both of
the incremental and full OTAs. It increases the update time (~20s on
Nexus 6), but will capture unnoticed errors right away.

Bug: 21500869
Change-Id: I89ac8fe623b855721b7affd07cf9426a23433ab0
diff --git a/tools/releasetools/blockimgdiff.py b/tools/releasetools/blockimgdiff.py
index 0a387ec..3402572 100644
--- a/tools/releasetools/blockimgdiff.py
+++ b/tools/releasetools/blockimgdiff.py
@@ -74,7 +74,7 @@
   def ReadRangeSet(self, ranges):
     raise NotImplementedError
 
-  def TotalSha1(self):
+  def TotalSha1(self, include_clobbered_blocks=False):
     raise NotImplementedError
 
 
@@ -87,7 +87,10 @@
   file_map = {}
   def ReadRangeSet(self, ranges):
     return ()
-  def TotalSha1(self):
+  def TotalSha1(self, include_clobbered_blocks=False):
+    # EmptyImage always carries empty clobbered_blocks, so
+    # include_clobbered_blocks can be ignored.
+    assert self.clobbered_blocks.size() == 0
     return sha1().hexdigest()
 
 
@@ -136,8 +139,9 @@
   def ReadRangeSet(self, ranges):
     return [self.data[s*self.blocksize:e*self.blocksize] for (s, e) in ranges]
 
-  def TotalSha1(self):
-    # DataImage always carries empty clobbered_blocks.
+  def TotalSha1(self, include_clobbered_blocks=False):
+    # DataImage always carries empty clobbered_blocks, so
+    # include_clobbered_blocks can be ignored.
     assert self.clobbered_blocks.size() == 0
     return sha1(self.data).hexdigest()
 
@@ -201,7 +205,8 @@
 #
 #    TotalSha1(): a function that returns (as a hex string) the SHA-1
 #      hash of all the data in the image (ie, all the blocks in the
-#      care_map minus clobbered_blocks).
+#      care_map minus clobbered_blocks, or including the clobbered
+#      blocks if include_clobbered_blocks is True).
 #
 # When creating a BlockImageDiff, the src image may be None, in which
 # case the list of transfers produced will never read from the
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index f54c8bd..f4e25d5 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -1182,6 +1182,7 @@
     if progress:
       script.ShowProgress(progress, 0)
     self._WriteUpdate(script, output_zip)
+    self._WritePostInstallVerifyScript(script)
 
   def WriteVerifyScript(self, script):
     partition = self.partition
@@ -1220,6 +1221,21 @@
       script.AppendExtra(('abort("%s partition has unexpected contents");\n'
                           'endif;') % (partition,))
 
+  def _WritePostInstallVerifyScript(self, script):
+    partition = self.partition
+    script.Print('Verifying the updated %s image...' % (partition,))
+    # Unlike pre-install verification, clobbered_blocks should not be ignored.
+    ranges = self.tgt.care_map
+    ranges_str = ranges.to_string_raw()
+    script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
+                       self.device, ranges_str,
+                       self.tgt.TotalSha1(include_clobbered_blocks=True)))
+    script.Print('Verified the updated %s image.' % (partition,))
+    script.AppendExtra(
+        'else\n'
+        '  abort("%s partition has unexpected contents after OTA update");\n'
+        'endif;' % (partition,))
+
   def _WriteUpdate(self, script, output_zip):
     ZipWrite(output_zip,
              '{}.transfer.list'.format(self.path),
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index c4d0c1b..b0913e4 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -917,6 +917,7 @@
 
   system_diff.WriteScript(script, output_zip,
                           progress=0.8 if vendor_diff else 0.9)
+
   if vendor_diff:
     vendor_diff.WriteScript(script, output_zip, progress=0.1)
 
diff --git a/tools/releasetools/sparse_img.py b/tools/releasetools/sparse_img.py
index 2ac97ac..51a1643 100644
--- a/tools/releasetools/sparse_img.py
+++ b/tools/releasetools/sparse_img.py
@@ -118,11 +118,16 @@
   def ReadRangeSet(self, ranges):
     return [d for d in self._GetRangeData(ranges)]
 
-  def TotalSha1(self):
-    """Return the SHA-1 hash of all data in the 'care' regions but not in
-    clobbered_blocks of this image."""
+  def TotalSha1(self, include_clobbered_blocks=False):
+    """Return the SHA-1 hash of all data in the 'care' regions.
+
+    If include_clobbered_blocks is True, it returns the hash including the
+    clobbered_blocks."""
+    ranges = self.care_map
+    if not include_clobbered_blocks:
+      ranges.subtract(self.clobbered_blocks)
     h = sha1()
-    for d in self._GetRangeData(self.care_map.subtract(self.clobbered_blocks)):
+    for d in self._GetRangeData(ranges):
       h.update(d)
     return h.hexdigest()