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=b:34284069
TEST=Added unittests.
Change-Id: I9ed52b429a54a2b4d6edaba051284b7dcd8a9525
(cherry picked from commit a48f630400429ca010c5462967607985f2ffa7e4)
Reviewed-on: https://chromium-review.googlesource.com/641958
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/Android.mk b/Android.mk
index 07d5998..96b4a90 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 \
@@ -924,6 +925,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 aadbef0..9643df3 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -47,6 +47,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
@@ -1053,25 +1054,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,
@@ -1117,57 +1099,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 c828a49..7a1ad95 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',
@@ -532,6 +533,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',