Implement ZERO and DISCARD operations in update_engine.

ZERO and DISCARD operations are implemented using the specific ioctl()
that allow SSD to do a faster operation when writing zeros. In case of
failure, such as when they are not supported, the operation will fall
back to write the zeros directly.

Bug: 23180264
Test: Added a unittest.

Change-Id: I0f12ab9864620b50250157c1de17b7fc58c7ea1c
diff --git a/delta_performer.cc b/delta_performer.cc
index 4025b1f..96850df 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -18,6 +18,7 @@
 
 #include <endian.h>
 #include <errno.h>
+#include <linux/fs.h>
 
 #include <algorithm>
 #include <cstring>
@@ -650,6 +651,10 @@
       case InstallOperation::REPLACE_XZ:
         op_result = PerformReplaceOperation(op, is_kernel_partition);
         break;
+      case InstallOperation::ZERO:
+      case InstallOperation::DISCARD:
+        op_result = PerformZeroOrDiscardOperation(op, is_kernel_partition);
+        break;
       case InstallOperation::MOVE:
         op_result = PerformMoveOperation(op, is_kernel_partition);
         break;
@@ -739,6 +744,43 @@
   return true;
 }
 
+bool DeltaPerformer::PerformZeroOrDiscardOperation(
+    const InstallOperation& operation,
+    bool is_kernel_partition) {
+  CHECK(operation.type() == InstallOperation::DISCARD ||
+        operation.type() == InstallOperation::ZERO);
+
+  // These operations have no blob.
+  TEST_AND_RETURN_FALSE(!operation.has_data_offset());
+  TEST_AND_RETURN_FALSE(!operation.has_data_length());
+
+  int request =
+      (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD);
+
+  FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
+  bool attempt_ioctl = true;
+  chromeos::Blob zeros;
+  for (int i = 0; i < operation.dst_extents_size(); i++) {
+    Extent extent = operation.dst_extents(i);
+    const uint64_t start = extent.start_block() * block_size_;
+    const uint64_t length = extent.num_blocks() * block_size_;
+    if (attempt_ioctl) {
+      int result = 0;
+      if (fd->BlkIoctl(request, start, length, &result) && result == 0)
+        continue;
+      attempt_ioctl = false;
+      zeros.resize(16 * block_size_);
+    }
+    // In case of failure, we fall back to writing 0 to the selected region.
+    for (uint64_t offset = 0; offset < length; offset += zeros.size()) {
+      uint64_t chunk_length = min(length - offset, zeros.size());
+      TEST_AND_RETURN_FALSE(
+          utils::PWriteAll(fd, zeros.data(), chunk_length, start + offset));
+    }
+  }
+  return true;
+}
+
 bool DeltaPerformer::PerformMoveOperation(const InstallOperation& operation,
                                           bool is_kernel_partition) {
   // Calculate buffer size. Note, this function doesn't do a sliding