Move extent copy and hash logic to a new file.

The SOURCE_COPY operation used to copy the source blocks one by one to
the target partition. This process is sub-optimal if there are several
consecutive blocks. This patch moves this copy and hash logic to a new
file and adds several unittests for it. The new logic copies in chunks
of up to 1MiB when the source and target data is contiguous.

Bug: 34284069
Test: Added unittests.

Change-Id: I9ed52b429a54a2b4d6edaba051284b7dcd8a9525
diff --git a/Android.mk b/Android.mk
index a26e779..37a754d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -129,6 +129,7 @@
     payload_consumer/download_action.cc \
     payload_consumer/extent_writer.cc \
     payload_consumer/file_descriptor.cc \
+    payload_consumer/file_descriptor_utils.cc \
     payload_consumer/file_writer.cc \
     payload_consumer/filesystem_verifier_action.cc \
     payload_consumer/install_plan.cc \
@@ -907,6 +908,8 @@
     payload_consumer/delta_performer_integration_test.cc \
     payload_consumer/delta_performer_unittest.cc \
     payload_consumer/extent_writer_unittest.cc \
+    payload_consumer/fake_file_descriptor.cc \
+    payload_consumer/file_descriptor_utils_unittest.cc \
     payload_consumer/file_writer_unittest.cc \
     payload_consumer/filesystem_verifier_action_unittest.cc \
     payload_consumer/postinstall_runner_action_unittest.cc \
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index b8904cb..d6ac16c 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -44,6 +44,7 @@
 #include "update_engine/payload_consumer/bzip_extent_writer.h"
 #include "update_engine/payload_consumer/download_action.h"
 #include "update_engine/payload_consumer/extent_writer.h"
+#include "update_engine/payload_consumer/file_descriptor_utils.h"
 #if USE_MTD
 #include "update_engine/payload_consumer/mtd_file_descriptor.h"
 #endif
@@ -1034,25 +1035,6 @@
 
 namespace {
 
-// Takes |extents| and fills an empty vector |blocks| with a block index for
-// each block in |extents|. For example, [(3, 2), (8, 1)] would give [3, 4, 8].
-void ExtentsToBlocks(const RepeatedPtrField<Extent>& extents,
-                     vector<uint64_t>* blocks) {
-  for (const Extent& ext : extents) {
-    for (uint64_t j = 0; j < ext.num_blocks(); j++)
-      blocks->push_back(ext.start_block() + j);
-  }
-}
-
-// Takes |extents| and returns the number of blocks in those extents.
-uint64_t GetBlockCount(const RepeatedPtrField<Extent>& extents) {
-  uint64_t sum = 0;
-  for (const Extent& ext : extents) {
-    sum += ext.num_blocks();
-  }
-  return sum;
-}
-
 // Compare |calculated_hash| with source hash in |operation|, return false and
 // dump hash and set |error| if don't match.
 bool ValidateSourceHash(const brillo::Blob& calculated_hash,
@@ -1098,57 +1080,18 @@
   if (operation.has_dst_length())
     TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
 
-  uint64_t blocks_to_read = GetBlockCount(operation.src_extents());
-  uint64_t blocks_to_write = GetBlockCount(operation.dst_extents());
-  TEST_AND_RETURN_FALSE(blocks_to_write ==  blocks_to_read);
-
-  // Create vectors of all the individual src/dst blocks.
-  vector<uint64_t> src_blocks;
-  vector<uint64_t> dst_blocks;
-  ExtentsToBlocks(operation.src_extents(), &src_blocks);
-  ExtentsToBlocks(operation.dst_extents(), &dst_blocks);
-  DCHECK_EQ(src_blocks.size(), blocks_to_read);
-  DCHECK_EQ(src_blocks.size(), dst_blocks.size());
-
-  brillo::Blob buf(block_size_);
-  ssize_t bytes_read = 0;
-  HashCalculator source_hasher;
-  // Read/write one block at a time.
-  for (uint64_t i = 0; i < blocks_to_read; i++) {
-    ssize_t bytes_read_this_iteration = 0;
-    uint64_t src_block = src_blocks[i];
-    uint64_t dst_block = dst_blocks[i];
-
-    // Read in bytes.
-    TEST_AND_RETURN_FALSE(
-        utils::PReadAll(source_fd_,
-                        buf.data(),
-                        block_size_,
-                        src_block * block_size_,
-                        &bytes_read_this_iteration));
-
-    // Write bytes out.
-    TEST_AND_RETURN_FALSE(
-        utils::PWriteAll(target_fd_,
-                         buf.data(),
-                         block_size_,
-                         dst_block * block_size_));
-
-    bytes_read += bytes_read_this_iteration;
-    TEST_AND_RETURN_FALSE(bytes_read_this_iteration ==
-                          static_cast<ssize_t>(block_size_));
-
-    if (operation.has_src_sha256_hash())
-      TEST_AND_RETURN_FALSE(source_hasher.Update(buf.data(), buf.size()));
-  }
+  brillo::Blob source_hash;
+  TEST_AND_RETURN_FALSE(fd_utils::CopyAndHashExtents(source_fd_,
+                                                     operation.src_extents(),
+                                                     target_fd_,
+                                                     operation.dst_extents(),
+                                                     block_size_,
+                                                     &source_hash));
 
   if (operation.has_src_sha256_hash()) {
-    TEST_AND_RETURN_FALSE(source_hasher.Finalize());
-    TEST_AND_RETURN_FALSE(
-        ValidateSourceHash(source_hasher.raw_hash(), operation, error));
+    TEST_AND_RETURN_FALSE(ValidateSourceHash(source_hash, operation, error));
   }
 
-  DCHECK_EQ(bytes_read, static_cast<ssize_t>(blocks_to_read * block_size_));
   return true;
 }
 
diff --git a/payload_consumer/fake_file_descriptor.cc b/payload_consumer/fake_file_descriptor.cc
new file mode 100644
index 0000000..09bd2c9
--- /dev/null
+++ b/payload_consumer/fake_file_descriptor.cc
@@ -0,0 +1,76 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/fake_file_descriptor.h"
+
+namespace chromeos_update_engine {
+
+ssize_t FakeFileDescriptor::Read(void* buf, size_t count) {
+  // Record the read operation so it can later be inspected.
+  read_ops_.emplace_back(offset_, count);
+
+  // Check for the EOF condition first to avoid reporting it as a failure.
+  if (offset_ >= static_cast<uint64_t>(size_) || !count)
+    return 0;
+  // Find the first offset greater or equal than the current position where a
+  // failure will occur. This will mark the end of the read chunk.
+  uint64_t first_failure = size_;
+  for (const auto& failure : failure_ranges_) {
+    // A failure range that includes the current offset results in an
+    // immediate failure to read any bytes.
+    if (failure.first <= offset_ && offset_ < failure.first + failure.second) {
+      errno = EIO;
+      return -1;
+    }
+    if (failure.first > offset_)
+      first_failure = std::min(first_failure, failure.first);
+  }
+  count = std::min(static_cast<uint64_t>(count), first_failure - offset_);
+  static const char kHexChars[] = "0123456789ABCDEF";
+  for (size_t i = 0; i < count; ++i) {
+    // Encode the 16-bit number "offset_ / 4" as a hex digit in big-endian.
+    uint16_t current_num = offset_ / 4;
+    uint8_t current_digit = (current_num >> (4 * (3 - offset_ % 4))) & 0x0f;
+
+    static_cast<uint8_t*>(buf)[i] = kHexChars[current_digit];
+    offset_++;
+  }
+
+  return count;
+}
+
+off64_t FakeFileDescriptor::Seek(off64_t offset, int whence) {
+  switch (whence) {
+    case SEEK_SET:
+      offset_ = offset;
+      break;
+    case SEEK_CUR:
+      offset_ += offset;
+      break;
+    case SEEK_END:
+      if (offset > size_)
+        offset_ = 0;
+      else
+        offset_ = size_ - offset_;
+      break;
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+  return offset_;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/fake_file_descriptor.h b/payload_consumer/fake_file_descriptor.h
new file mode 100644
index 0000000..ad49373
--- /dev/null
+++ b/payload_consumer/fake_file_descriptor.h
@@ -0,0 +1,122 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_FILE_DESCRIPTOR_H_
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "update_engine/payload_consumer/file_descriptor.h"
+
+namespace chromeos_update_engine {
+
+// A fake file descriptor with configurable errors. The file descriptor always
+// reads a fixed sequence of bytes, consisting on the concatenation of the
+// numbers 0, 1, 2... each one encoded in 4 bytes as the big-endian 16-bit
+// number encoded in hexadecimal. For example, the beginning of the stream in
+// ASCII is 0000000100020003... which corresponds to the numbers 0, 1, 2 and 3.
+class FakeFileDescriptor : public FileDescriptor {
+ public:
+  FakeFileDescriptor() = default;
+  ~FakeFileDescriptor() = default;
+
+  // FileDescriptor override methods.
+  bool Open(const char* path, int flags, mode_t mode) override {
+    if (open_)
+      return false;
+    open_ = true;
+    return true;
+  }
+
+  bool Open(const char* path, int flags) override {
+    return Open(path, flags, 0);
+  }
+
+  ssize_t Read(void* buf, size_t count) override;
+
+  ssize_t Write(const void* buf, size_t count) override {
+    // Read-only block device.
+    errno = EROFS;
+    return -1;
+  }
+
+  off64_t Seek(off64_t offset, int whence) override;
+
+  uint64_t BlockDevSize() override { return size_; }
+
+  bool BlkIoctl(int request,
+                uint64_t start,
+                uint64_t length,
+                int* result) override {
+    return false;
+  }
+
+  bool Close() override {
+    if (!open_)
+      return false;
+    open_ = false;
+    return true;
+  }
+
+  bool IsSettingErrno() override { return true; }
+
+  bool IsOpen() override { return open_; }
+
+  // Fake class configuration methods.
+
+  // Set the size of the file.
+  void SetFileSize(uint64_t size) { size_ = size; }
+
+  // Marks the range starting from |offset| bytes into the file and |length|
+  // size as a failure range. Reads from this range will always fail.
+  void AddFailureRange(uint64_t offset, uint64_t length) {
+    if (!length)
+      return;
+    failure_ranges_.emplace_back(offset, length);
+  }
+
+  // Return the list of ranges of bytes requested with a Read() as (offset,
+  // length), regardless of the Read() return value.
+  std::vector<std::pair<uint64_t, uint64_t>> GetReadOps() const {
+    return read_ops_;
+  }
+
+ private:
+  // Whether the fake file is open.
+  bool open_{false};
+
+  // The current file pointer offset into the fake file.
+  uint64_t offset_{0};
+
+  // The size of the file. Reads beyond |max_size_| will an EOF condition.
+  off64_t size_{std::numeric_limits<off64_t>::max()};
+
+  // The list of ranges represented as (start, length) in bytes where reads will
+  // always fail.
+  std::vector<std::pair<uint64_t, uint64_t>> failure_ranges_;
+
+  // List of reads performed as (offset, length) of the read request.
+  std::vector<std::pair<uint64_t, uint64_t>> read_ops_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeFileDescriptor);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_FILE_DESCRIPTOR_H_
diff --git a/payload_consumer/file_descriptor_utils.cc b/payload_consumer/file_descriptor_utils.cc
new file mode 100644
index 0000000..f7f61a5
--- /dev/null
+++ b/payload_consumer/file_descriptor_utils.cc
@@ -0,0 +1,115 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/file_descriptor_utils.h"
+
+#include <algorithm>
+
+#include <base/logging.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+
+using google::protobuf::RepeatedPtrField;
+using std::min;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Size of the buffer used to copy blocks.
+const int kMaxCopyBufferSize = 1024 * 1024;
+
+// Return the total number of blocks in the passed |extents| list.
+uint64_t GetBlockCount(const RepeatedPtrField<Extent>& extents) {
+  uint64_t sum = 0;
+  for (const Extent& ext : extents) {
+    sum += ext.num_blocks();
+  }
+  return sum;
+}
+
+}  // namespace
+
+namespace fd_utils {
+
+bool CopyAndHashExtents(FileDescriptorPtr source,
+                        const RepeatedPtrField<Extent>& src_extents,
+                        FileDescriptorPtr target,
+                        const RepeatedPtrField<Extent>& tgt_extents,
+                        uint32_t block_size,
+                        brillo::Blob* hash_out) {
+  HashCalculator source_hasher;
+
+  uint64_t buffer_blocks = kMaxCopyBufferSize / block_size;
+  // Ensure we copy at least one block at a time.
+  if (buffer_blocks < 1)
+    buffer_blocks = 1;
+
+  uint64_t total_blocks = GetBlockCount(src_extents);
+  TEST_AND_RETURN_FALSE(total_blocks == GetBlockCount(tgt_extents));
+
+  brillo::Blob buf(buffer_blocks * block_size);
+
+  DirectExtentWriter writer;
+  std::vector<Extent> vec_tgt_extents;
+  vec_tgt_extents.reserve(tgt_extents.size());
+  for (const auto& ext : tgt_extents) {
+    vec_tgt_extents.push_back(ext);
+  }
+  TEST_AND_RETURN_FALSE(writer.Init(target, vec_tgt_extents, block_size));
+
+  for (const Extent& src_ext : src_extents) {
+    for (uint64_t src_ext_block = 0; src_ext_block < src_ext.num_blocks();
+         src_ext_block += buffer_blocks) {
+      uint64_t iteration_blocks =
+          min(buffer_blocks,
+              static_cast<uint64_t>(src_ext.num_blocks() - src_ext_block));
+      uint64_t src_start_block = src_ext.start_block() + src_ext_block;
+
+      ssize_t bytes_read_this_iteration;
+      TEST_AND_RETURN_FALSE(utils::PReadAll(source,
+                                            buf.data(),
+                                            iteration_blocks * block_size,
+                                            src_start_block * block_size,
+                                            &bytes_read_this_iteration));
+
+      TEST_AND_RETURN_FALSE(
+          bytes_read_this_iteration ==
+          static_cast<ssize_t>(iteration_blocks * block_size));
+
+      TEST_AND_RETURN_FALSE(
+          writer.Write(buf.data(), iteration_blocks * block_size));
+
+      if (hash_out) {
+        TEST_AND_RETURN_FALSE(
+            source_hasher.Update(buf.data(), iteration_blocks * block_size));
+      }
+    }
+  }
+  TEST_AND_RETURN_FALSE(writer.End());
+
+  if (hash_out) {
+    TEST_AND_RETURN_FALSE(source_hasher.Finalize());
+    *hash_out = source_hasher.raw_hash();
+  }
+  return true;
+}
+
+}  // namespace fd_utils
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/file_descriptor_utils.h b/payload_consumer/file_descriptor_utils.h
new file mode 100644
index 0000000..b73defb
--- /dev/null
+++ b/payload_consumer/file_descriptor_utils.h
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_UTILS_H_
+
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+namespace fd_utils {
+
+// Copy a blocks from the |source| file to the |target| file and hash the
+// contents. The blocks to copy from the |source| to the |target| files are
+// specified by the |src_extents| and |tgt_extents| list of Extents, which
+// must have the same length in number of blocks. Stores the hash of the
+// copied blocks in Blob pointed by |hash_out| if not null. The block size
+// is passed as |block_size|. In case of error reading or writing, returns
+// false and the value pointed by |hash_out| is undefined.
+// The |source| and |target| files must be different, or otherwise |src_extents|
+// and |tgt_extents| must not overlap.
+bool CopyAndHashExtents(
+    FileDescriptorPtr source,
+    const google::protobuf::RepeatedPtrField<Extent>& src_extents,
+    FileDescriptorPtr target,
+    const google::protobuf::RepeatedPtrField<Extent>& tgt_extents,
+    uint32_t block_size,
+    brillo::Blob* hash_out);
+
+}  // namespace fd_utils
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_UTILS_H_
diff --git a/payload_consumer/file_descriptor_utils_unittest.cc b/payload_consumer/file_descriptor_utils_unittest.cc
new file mode 100644
index 0000000..9910239
--- /dev/null
+++ b/payload_consumer/file_descriptor_utils_unittest.cc
@@ -0,0 +1,167 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/file_descriptor_utils.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <brillo/data_encoding.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/fake_file_descriptor.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+
+namespace chromeos_update_engine {
+
+namespace {
+
+::google::protobuf::RepeatedPtrField<Extent> CreateExtentList(
+    const std::vector<std::pair<uint64_t, uint64_t>>& lst) {
+  ::google::protobuf::RepeatedPtrField<Extent> result;
+  for (const auto& item : lst) {
+    *result.Add() = ExtentForRange(item.first, item.second);
+  }
+  return result;
+}
+
+}  // namespace
+
+class FileDescriptorUtilsTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    EXPECT_TRUE(utils::MakeTempFile("fd_tgt.XXXXXX", &tgt_path_, nullptr));
+    EXPECT_TRUE(target_->Open(tgt_path_.c_str(), O_RDWR));
+  }
+
+  // Check that the |target_| file contains |expected_contents|.
+  void ExpectTarget(const std::string& expected_contents) {
+    std::string target_contents;
+    EXPECT_TRUE(utils::ReadFile(tgt_path_, &target_contents));
+    EXPECT_EQ(expected_contents.size(), target_contents.size());
+    if (target_contents != expected_contents) {
+      ADD_FAILURE() << "Contents don't match.";
+      LOG(INFO) << "Expected contents:";
+      utils::HexDumpString(expected_contents);
+      LOG(INFO) << "Actual contents:";
+      utils::HexDumpString(target_contents);
+    }
+  }
+
+  // Path to the target temporary file.
+  std::string tgt_path_;
+
+  // Source and target file descriptor used for testing the tools.
+  FakeFileDescriptor* fake_source_{new FakeFileDescriptor()};
+  FileDescriptorPtr source_{fake_source_};
+  FileDescriptorPtr target_{new EintrSafeFileDescriptor()};
+};
+
+// Source and target extents should have the same number of blocks.
+TEST_F(FileDescriptorUtilsTest, CopyAndHashExtentsMismatchBlocksTest) {
+  auto src_extents = CreateExtentList({{1, 4}});
+  auto tgt_extents = CreateExtentList({{0, 5}});
+
+  EXPECT_FALSE(fd_utils::CopyAndHashExtents(
+      source_, src_extents, target_, tgt_extents, 4, nullptr));
+}
+
+// Failing to read from the source should fail the copy.
+TEST_F(FileDescriptorUtilsTest, CopyAndHashExtentsReadFailureTest) {
+  auto extents = CreateExtentList({{0, 5}});
+  fake_source_->AddFailureRange(10, 5);
+
+  EXPECT_FALSE(fd_utils::CopyAndHashExtents(
+      source_, extents, target_, extents, 4, nullptr));
+}
+
+// Failing to write to the target should fail the copy.
+TEST_F(FileDescriptorUtilsTest, CopyAndHashExtentsWriteFailureTest) {
+  auto src_extents = CreateExtentList({{0, 2}});
+  auto tgt_extents = CreateExtentList({{5, 2}});
+  fake_source_->AddFailureRange(5 * 4, 10);
+
+  // Note that we pass |source_| as the target as well, which should fail to
+  // write.
+  EXPECT_FALSE(fd_utils::CopyAndHashExtents(
+      source_, src_extents, source_, tgt_extents, 4, nullptr));
+}
+
+// Test that we can copy extents without hashing them, allowing a nullptr
+// pointer as hash_out.
+TEST_F(FileDescriptorUtilsTest, CopyAndHashExtentsWithoutHashingTest) {
+  auto extents = CreateExtentList({{0, 5}});
+
+  EXPECT_TRUE(fd_utils::CopyAndHashExtents(
+      source_, extents, target_, extents, 4, nullptr));
+  ExpectTarget("00000001000200030004");
+}
+
+// CopyAndHash() can take different number of extents in the source and target
+// files, as long as the number of blocks is the same. Test that it handles it
+// properly.
+TEST_F(FileDescriptorUtilsTest, CopyAndHashExtentsManyToOneTest) {
+  brillo::Blob hash_out;
+  // Reorder the input as 1 4 2 3 0.
+  auto src_extents = CreateExtentList({{1, 1}, {4, 1}, {2, 2}, {0, 1}});
+  auto tgt_extents = CreateExtentList({{0, 5}});
+
+  EXPECT_TRUE(fd_utils::CopyAndHashExtents(
+      source_, src_extents, target_, tgt_extents, 4, &hash_out));
+  const char kExpectedResult[] = "00010004000200030000";
+  ExpectTarget(kExpectedResult);
+
+  brillo::Blob expected_hash;
+  EXPECT_TRUE(HashCalculator::RawHashOfBytes(
+      kExpectedResult, strlen(kExpectedResult), &expected_hash));
+  EXPECT_EQ(expected_hash, hash_out);
+}
+
+TEST_F(FileDescriptorUtilsTest, CopyAndHashExtentsManyToManyTest) {
+  brillo::Blob hash_out;
+  auto src_extents = CreateExtentList({{1, 1}, {4, 1}, {2, 2}, {0, 1}});
+  auto tgt_extents = CreateExtentList({{2, 3}, {0, 2}});
+
+  EXPECT_TRUE(fd_utils::CopyAndHashExtents(
+      source_, src_extents, target_, tgt_extents, 4, &hash_out));
+  // The reads always match the source extent list of blocks (up to the
+  // internal buffer size).
+  std::vector<std::pair<uint64_t, uint64_t>> kExpectedOps = {
+      {4, 4}, {16, 4}, {8, 8}, {0, 4}};
+  EXPECT_EQ(kExpectedOps, fake_source_->GetReadOps());
+
+  // The output here is as in the previous test but the first 3 4-byte blocks
+  // are at the end of the stream. The expected hash is as in the previous
+  // example anyway since the hash doesn't depend on the order of the target
+  // blocks.
+  const char kExpectedResult[] = "00030000000100040002";
+  ExpectTarget(kExpectedResult);
+
+  // The data in the order that the reader processes (and hashes) it.
+  const char kExpectedOrderedData[] = "00010004000200030000";
+  brillo::Blob expected_hash;
+  EXPECT_TRUE(HashCalculator::RawHashOfBytes(
+      kExpectedOrderedData, strlen(kExpectedOrderedData), &expected_hash));
+  EXPECT_EQ(expected_hash, hash_out);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine.gyp b/update_engine.gyp
index f3b199e..467000e 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -189,6 +189,7 @@
         'payload_consumer/download_action.cc',
         'payload_consumer/extent_writer.cc',
         'payload_consumer/file_descriptor.cc',
+        'payload_consumer/file_descriptor_utils.cc',
         'payload_consumer/file_writer.cc',
         'payload_consumer/filesystem_verifier_action.cc',
         'payload_consumer/install_plan.cc',
@@ -533,6 +534,8 @@
             'payload_consumer/delta_performer_unittest.cc',
             'payload_consumer/download_action_unittest.cc',
             'payload_consumer/extent_writer_unittest.cc',
+            'payload_consumer/fake_file_descriptor.cc',
+            'payload_consumer/file_descriptor_utils_unittest.cc',
             'payload_consumer/file_writer_unittest.cc',
             'payload_consumer/filesystem_verifier_action_unittest.cc',
             'payload_consumer/postinstall_runner_action_unittest.cc',