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
diff --git a/delta_performer.h b/delta_performer.h
index 8345174..34cb137 100644
--- a/delta_performer.h
+++ b/delta_performer.h
@@ -279,6 +279,8 @@
   // These perform a specific type of operation and return true on success.
   bool PerformReplaceOperation(const InstallOperation& operation,
                                bool is_kernel_partition);
+  bool PerformZeroOrDiscardOperation(const InstallOperation& operation,
+                                     bool is_kernel_partition);
   bool PerformMoveOperation(const InstallOperation& operation,
                             bool is_kernel_partition);
   bool PerformBsdiffOperation(const InstallOperation& operation,
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index d2ec5b0..12dd1d3 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -138,14 +138,29 @@
   // Apply |payload_data| on partition specified in |source_path|.
   chromeos::Blob ApplyPayload(const chromeos::Blob& payload_data,
                               const string& source_path) {
-    install_plan_.source_path = source_path;
-    install_plan_.kernel_source_path = "/dev/null";
+    return ApplyPayloadToData(payload_data, source_path, chromeos::Blob());
+  }
 
+  // Apply the payload provided in |payload_data| reading from the |source_path|
+  // file and writing the contents to a new partition. The existing data in the
+  // new target file are set to |target_data| before applying the payload.
+  // Returns the result of the payload application.
+  chromeos::Blob ApplyPayloadToData(const chromeos::Blob& payload_data,
+                                    const string& source_path,
+                                    const chromeos::Blob& target_data) {
     string new_part;
     EXPECT_TRUE(utils::MakeTempFile("Partition-XXXXXX", &new_part, nullptr));
     ScopedPathUnlinker partition_unlinker(new_part);
+    EXPECT_TRUE(utils::WriteFile(new_part.c_str(), target_data.data(),
+                                 target_data.size()));
+
+    install_plan_.source_path = source_path;
+    install_plan_.kernel_source_path = "/dev/null";
+    install_plan_.install_path = new_part;
+    install_plan_.kernel_install_path = "/dev/null";
 
     EXPECT_EQ(0, performer_.Open(new_part.c_str(), 0, 0));
+    EXPECT_TRUE(performer_.OpenSourceRootfs(source_path.c_str()));
     EXPECT_TRUE(performer_.Write(payload_data.data(), payload_data.size()));
     EXPECT_EQ(0, performer_.Close());
 
@@ -285,7 +300,7 @@
   chromeos::Blob payload_data = GeneratePayload(expected_data, aops, false,
       kFullPayloadMinorVersion);
 
-  EXPECT_EQ(expected_data, ApplyPayload(payload_data, ""));
+  EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
 }
 
 TEST_F(DeltaPerformerTest, ReplaceOperationTest) {
@@ -348,6 +363,29 @@
   EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
 }
 
+TEST_F(DeltaPerformerTest, ZeroOperationTest) {
+  chromeos::Blob existing_data = chromeos::Blob(4096 * 10, 'a');
+  chromeos::Blob expected_data = existing_data;
+  // Blocks 4, 5 and 7 should have zeros instead of 'a' after the operation is
+  // applied.
+  std::fill(expected_data.data() + 4096 * 4, expected_data.data() + 4096 * 6,
+            0);
+  std::fill(expected_data.data() + 4096 * 7, expected_data.data() + 4096 * 8,
+            0);
+
+  AnnotatedOperation aop;
+  *(aop.op.add_dst_extents()) = ExtentForRange(4, 2);
+  *(aop.op.add_dst_extents()) = ExtentForRange(7, 1);
+  aop.op.set_type(InstallOperation::ZERO);
+  vector<AnnotatedOperation> aops = {aop};
+
+  chromeos::Blob payload_data = GeneratePayload(chromeos::Blob(), aops, false,
+                                                kSourceMinorPayloadVersion);
+
+  EXPECT_EQ(expected_data,
+            ApplyPayloadToData(payload_data, "/dev/null", existing_data));
+}
+
 TEST_F(DeltaPerformerTest, SourceCopyOperationTest) {
   chromeos::Blob expected_data = chromeos::Blob(std::begin(kRandomString),
                                                 std::end(kRandomString));
diff --git a/file_descriptor.cc b/file_descriptor.cc
index 8b8fa72..4718528 100644
--- a/file_descriptor.cc
+++ b/file_descriptor.cc
@@ -17,6 +17,8 @@
 #include "update_engine/file_descriptor.h"
 
 #include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
@@ -63,6 +65,42 @@
   return lseek64(fd_, offset, whence);
 }
 
+bool EintrSafeFileDescriptor::BlkIoctl(int request,
+                                       uint64_t start,
+                                       uint64_t length,
+                                       int* result) {
+  DCHECK(request == BLKDISCARD || request == BLKZEROOUT ||
+         request == BLKSECDISCARD);
+  // On some devices, the BLKDISCARD will actually read back as zeros, instead
+  // of "undefined" data. The BLKDISCARDZEROES ioctl tells whether that's the
+  // case, so we issue a BLKDISCARD in those cases to speed up the writes.
+  unsigned int arg;
+  if (request == BLKZEROOUT && ioctl(fd_, BLKDISCARDZEROES, &arg) == 0 && arg)
+    request = BLKDISCARD;
+
+  // Ensure the |fd_| is in O_DIRECT mode during this operation, so the write
+  // cache for this region is invalidated. This is required since otherwise
+  // reading back this region could consume stale data from the cache.
+  int flags = fcntl(fd_, F_GETFL, 0);
+  if (flags == -1) {
+    PLOG(WARNING) << "Couldn't get flags on fd " << fd_;
+    return false;
+  }
+  if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags | O_DIRECT) == -1) {
+    PLOG(WARNING) << "Couldn't set O_DIRECT on fd " << fd_;
+    return false;
+  }
+
+  uint64_t range[2] = {start, length};
+  *result = ioctl(fd_, request, range);
+
+  if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags) == -1) {
+    PLOG(WARNING) << "Couldn't remove O_DIRECT on fd " << fd_;
+    return false;
+  }
+  return true;
+}
+
 bool EintrSafeFileDescriptor::Close() {
   CHECK_GE(fd_, 0);
   if (IGNORE_EINTR(close(fd_)))
diff --git a/file_descriptor.h b/file_descriptor.h
index 4d6e970..bf13611 100644
--- a/file_descriptor.h
+++ b/file_descriptor.h
@@ -78,6 +78,17 @@
   // may set errno accordingly.
   virtual off64_t Seek(off64_t offset, int whence) = 0;
 
+  // Runs a ioctl() on the file descriptor if supported. Returns whether
+  // the operation is supported. The |request| can be one of BLKDISCARD,
+  // BLKZEROOUT and BLKSECDISCARD to discard, write zeros or securely discard
+  // the blocks. These ioctls accept a range of bytes (|start| and |length|)
+  // over which they perform the operation. The return value from the ioctl is
+  // stored in |result|.
+  virtual bool BlkIoctl(int request,
+                        uint64_t start,
+                        uint64_t length,
+                        int* result) = 0;
+
   // Closes a file descriptor. The descriptor must be open prior to this call.
   // Returns true on success, false otherwise. Specific implementations may set
   // errno accordingly.
@@ -108,6 +119,10 @@
   ssize_t Read(void* buf, size_t count) override;
   ssize_t Write(const void* buf, size_t count) override;
   off64_t Seek(off64_t offset, int whence) override;
+  bool BlkIoctl(int request,
+                uint64_t start,
+                uint64_t length,
+                int* result) override;
   bool Close() override;
   void Reset() override;
   bool IsSettingErrno() override {
diff --git a/mtd_file_descriptor.h b/mtd_file_descriptor.h
index 1d023c4..954e5e5 100644
--- a/mtd_file_descriptor.h
+++ b/mtd_file_descriptor.h
@@ -40,6 +40,12 @@
   ssize_t Read(void* buf, size_t count) override;
   ssize_t Write(const void* buf, size_t count) override;
   off64_t Seek(off64_t offset, int whence) override;
+  bool BlkIoctl(int request,
+                uint64_t start,
+                uint64_t length,
+                int* result) override {
+    return false;
+  }
   bool Close() override;
 
  private:
@@ -69,6 +75,12 @@
   ssize_t Read(void* buf, size_t count) override;
   ssize_t Write(const void* buf, size_t count) override;
   off64_t Seek(off64_t offset, int whence) override;
+  bool BlkIoctl(int request,
+                uint64_t start,
+                uint64_t length,
+                int* result) override {
+    return false;
+  }
   bool Close() override;
 
  private: