update_payload: Add zero operation support

Currenlty the applier.py and checker.py does not support ZERO
operation. This patch adds support for it. In addition, when generating
ZERO operation, we were not clearing the source length and extent
properties out of the protobuf. ZERO operation has no source length or
extent.

BUG=chromium:768461
TEST=unittest pass; scripts/paycheck.py --check payload.delta;

Change-Id: I766deaca4380686797893c2686036d59525546f4
Reviewed-on: https://chromium-review.googlesource.com/713539
Commit-Ready: Amin Hassani <ahassani@chromium.org>
Tested-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Ben Chan <benchan@chromium.org>
Reviewed-by: Sen Jiang <senj@chromium.org>
diff --git a/scripts/update_payload/applier.py b/scripts/update_payload/applier.py
index fa419bd..cc77165 100644
--- a/scripts/update_payload/applier.py
+++ b/scripts/update_payload/applier.py
@@ -301,6 +301,27 @@
     _WriteExtents(part_file, in_data, op.dst_extents, block_size,
                   '%s.dst_extents' % op_name)
 
+  def _ApplyZeroOperation(self, op, op_name, part_file):
+    """Applies a ZERO operation.
+
+    Args:
+      op: the operation object
+      op_name: name string for error reporting
+      part_file: the partition file object
+
+    Raises:
+      PayloadError if something goes wrong.
+    """
+    block_size = self.block_size
+    base_name = '%s.dst_extents' % op_name
+
+    # Iterate over the extents and write zero.
+    for ex, ex_name in common.ExtentIter(op.dst_extents, base_name):
+      # Only do actual writing if this is not a pseudo-extent.
+      if ex.start_block != common.PSEUDO_EXTENT_MARKER:
+        part_file.seek(ex.start_block * block_size)
+        part_file.write('\0' * (ex.num_blocks * block_size))
+
   def _ApplyBsdiffOperation(self, op, op_name, patch_data, new_part_file):
     """Applies a BSDIFF operation.
 
@@ -463,6 +484,8 @@
         self._ApplyReplaceOperation(op, op_name, data, new_part_file, part_size)
       elif op.type == common.OpType.MOVE:
         self._ApplyMoveOperation(op, op_name, new_part_file)
+      elif op.type == common.OpType.ZERO:
+        self._ApplyZeroOperation(op, op_name, new_part_file)
       elif op.type == common.OpType.BSDIFF:
         self._ApplyBsdiffOperation(op, op_name, data, new_part_file)
       elif op.type == common.OpType.SOURCE_COPY:
diff --git a/scripts/update_payload/checker.py b/scripts/update_payload/checker.py
index 7404408..c613642 100644
--- a/scripts/update_payload/checker.py
+++ b/scripts/update_payload/checker.py
@@ -815,6 +815,24 @@
     if dst_extent:
       raise error.PayloadError('%s: excess dst blocks.' % op_name)
 
+  def _CheckZeroOperation(self, op, op_name):
+    """Specific checks for ZERO operations.
+
+    Args:
+      op: The operation object from the manifest.
+      op_name: Operation name for error reporting.
+
+    Raises:
+      error.PayloadError if any check fails.
+    """
+    # Check: Does not contain src extents, data_length and data_offset.
+    if op.src_extents:
+      raise error.PayloadError('%s: contains src_extents.' % op_name)
+    if op.data_length:
+      raise error.PayloadError('%s: contains data_length.' % op_name)
+    if op.data_offset:
+      raise error.PayloadError('%s: contains data_offset.' % op_name)
+
   def _CheckAnyDiffOperation(self, data_length, total_dst_blocks, op_name):
     """Specific checks for BSDIFF, SOURCE_BSDIFF and PUFFDIFF operations.
 
@@ -972,6 +990,8 @@
     elif op.type == common.OpType.MOVE and self.minor_version == 1:
       self._CheckMoveOperation(op, data_offset, total_src_blocks,
                                total_dst_blocks, op_name)
+    elif op.type == common.OpType.ZERO and self.minor_version >= 4:
+      self._CheckZeroOperation(op, op_name)
     elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
       self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
     elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
@@ -1039,6 +1059,7 @@
         common.OpType.REPLACE: 0,
         common.OpType.REPLACE_BZ: 0,
         common.OpType.MOVE: 0,
+        common.OpType.ZERO: 0,
         common.OpType.BSDIFF: 0,
         common.OpType.SOURCE_COPY: 0,
         common.OpType.SOURCE_BSDIFF: 0,
diff --git a/scripts/update_payload/checker_unittest.py b/scripts/update_payload/checker_unittest.py
index 2e1efeb..d79b00b 100755
--- a/scripts/update_payload/checker_unittest.py
+++ b/scripts/update_payload/checker_unittest.py
@@ -872,6 +872,8 @@
       payload_checker.minor_version = 2 if fail_bad_minor_version else 1
     elif op_type in (common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
       payload_checker.minor_version = 1 if fail_bad_minor_version else 2
+    elif op_type in (common.OpType.ZERO, common.OpType.DISCARD):
+      payload_checker.minor_version = 3 if fail_bad_minor_version else 4
 
     if op_type not in (common.OpType.MOVE, common.OpType.SOURCE_COPY):
       if not fail_mismatched_data_offset_length: