Add integration test for XOR ops
Test: th
Bug: 201099341
Change-Id: I36f4c2da25f23b3dcdfa0852a9dc01f896a292f1
diff --git a/aosp/dynamic_partition_control_android.h b/aosp/dynamic_partition_control_android.h
index ad3e056..d0842a9 100644
--- a/aosp/dynamic_partition_control_android.h
+++ b/aosp/dynamic_partition_control_android.h
@@ -108,6 +108,7 @@
const std::optional<std::string>&,
bool is_append = false) override;
+ bool MapAllPartitions() override;
bool UnmapAllPartitions() override;
bool IsDynamicPartition(const std::string& part_name, uint32_t slot) override;
@@ -227,8 +228,6 @@
const DeltaArchiveManifest& manifest,
bool delete_source);
- bool MapAllPartitions() override;
-
void SetSourceSlot(uint32_t slot) { source_slot_ = slot; }
void SetTargetSlot(uint32_t slot) { target_slot_ = slot; }
diff --git a/aosp/update_attempter_android_integration_test.cc b/aosp/update_attempter_android_integration_test.cc
index de42623..46f35f1 100644
--- a/aosp/update_attempter_android_integration_test.cc
+++ b/aosp/update_attempter_android_integration_test.cc
@@ -14,15 +14,18 @@
// limitations under the License.
//
+#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <sys/fcntl.h>
+#include <sys/sendfile.h>
#include <unistd.h>
#include <brillo/data_encoding.h>
#include <brillo/message_loops/fake_message_loop.h>
+#include <bsdiff/bsdiff.h>
#include <gtest/gtest.h>
#include <liblp/builder.h>
#include <fs_mgr.h>
@@ -39,6 +42,7 @@
#include "update_engine/common/fake_hardware.h"
#include "update_engine/common/test_utils.h"
#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
#include "update_engine/payload_consumer/payload_constants.h"
#include "update_engine/payload_consumer/install_plan.h"
#include "update_engine/payload_generator/delta_diff_generator.h"
@@ -72,7 +76,26 @@
builder_->RemovePartition("fake_b");
builder_->RemoveGroupAndPartitions("fake_group");
}
+
+ // Fill partition |path| with arbitrary data.
+ void FillPartition(const std::string& path, const bool is_source) {
+ std::array<uint8_t, kBlockSize> data;
+ EintrSafeFileDescriptor fd;
+ fd.Open(path.c_str(), O_RDWR);
+ for (size_t i = 0; i < kFakePartitionSize / kBlockSize; i++) {
+ if (is_source) {
+ std::fill(data.begin(), data.end(), i);
+ } else {
+ std::fill(
+ data.begin(), data.end(), kFakePartitionSize / kBlockSize - i - 1);
+ }
+ fd.Write(data.data(), kBlockSize);
+ }
+ }
+
void SetUp() override {
+ FillPartition(old_part_.path(), true);
+ FillPartition(new_part_.path(), false);
ASSERT_TRUE(boot_control_.Init());
if (!DynamicPartitionEnabled()) {
return;
@@ -99,6 +122,7 @@
manifest_.mutable_dynamic_partition_metadata()->set_cow_version(
android::snapshot::kCowVersionMajor);
}
+
void TearDown() override {
if (!builder_ || !DynamicPartitionEnabled()) {
return;
@@ -114,10 +138,23 @@
void CreateFakePartition() {
// Create a fake partition for testing purposes
- auto partition_a = builder_->AddPartition("fake_a", "fake_group", 0);
+ auto partition_a = builder_->AddPartition(
+ boot_control_.GetCurrentSlot() == 0 ? "fake_a" : "fake_b",
+ "fake_group",
+ 0);
ASSERT_NE(partition_a, nullptr);
ASSERT_TRUE(builder_->ResizePartition(partition_a, kFakePartitionSize));
ExportPartitionTable();
+ std::string source_part;
+ ASSERT_TRUE(
+ dynamic_control_->GetPartitionDevice("fake",
+ boot_control_.GetCurrentSlot(),
+ boot_control_.GetCurrentSlot(),
+ &source_part));
+ int out_fd = open(source_part.c_str(), O_RDWR);
+ ScopedFdCloser closer{&out_fd};
+ ASSERT_GE(out_fd, 0) << utils::ErrnoNumberAsString(errno);
+ ASSERT_TRUE(utils::SendFile(out_fd, old_part_.fd(), kFakePartitionSize));
}
void SendStatusUpdate(
@@ -166,13 +203,69 @@
total_blob_size, signature_blob_length, manifest);
}
}
+
+ // Generate blob data according to ops specified in the manifest.
+ // Also update |new_part_|'s content to match expectation of ops.
+ void HydratePayload(DeltaArchiveManifest* manifest) {
+ for (auto& partition : *manifest->mutable_partitions()) {
+ for (auto& op : *partition.mutable_operations()) {
+ if (op.type() == InstallOperation::REPLACE) {
+ ASSERT_GE(lseek64(blob_file_.fd(), op.data_offset(), SEEK_SET), 0);
+ ASSERT_TRUE(utils::SendFile(
+ new_part_.fd(), blob_file_.fd(), op.data_length()));
+ } else if (op.type() == InstallOperation::BROTLI_BSDIFF) {
+ brillo::Blob old_data;
+ ASSERT_TRUE(utils::ReadExtents(
+ old_part_.path(), op.src_extents(), &old_data, kBlockSize))
+ << "Failed to read source data: "
+ << utils::ErrnoNumberAsString(errno);
+ brillo::Blob new_data;
+ ASSERT_TRUE(utils::ReadExtents(
+ new_part_.path(), op.dst_extents(), &new_data, kBlockSize))
+ << "Failed to read target data: "
+ << utils::ErrnoNumberAsString(errno);
+ ScopedTempFile patch_file{"bspatch.XXXXXX", true};
+ ASSERT_EQ(bsdiff::bsdiff(old_data.data(),
+ old_data.size(),
+ new_data.data(),
+ new_data.size(),
+ patch_file.path().c_str(),
+ nullptr),
+ 0);
+ op.set_data_length(utils::FileSize(patch_file.fd()));
+ const auto offset = lseek64(blob_file_.fd(), 0, SEEK_CUR);
+ ASSERT_GE(offset, 0);
+ op.set_data_offset(offset);
+ brillo::Blob src_data_hash;
+ HashCalculator::RawHashOfData(old_data, &src_data_hash);
+ op.set_src_sha256_hash(src_data_hash.data(), src_data_hash.size());
+ utils::SendFile(blob_file_.fd(), patch_file.fd(), op.data_length());
+
+ } else if (op.type() == InstallOperation::ZERO) {
+ auto zero = utils::GetReadonlyZeroString(
+ utils::BlocksInExtents(op.dst_extents()) * kBlockSize);
+ for (const auto& ext : op.dst_extents()) {
+ utils::PWriteAll(new_part_.fd(),
+ zero.data(),
+ ext.num_blocks() * kBlockSize,
+ ext.start_block() * kBlockSize);
+ }
+ } else if (op.type() == InstallOperation::SOURCE_COPY) {
+ brillo::Blob data;
+ ASSERT_TRUE(utils::ReadExtents(
+ old_part_.path(), op.src_extents(), &data, kBlockSize));
+ ASSERT_TRUE(utils::WriteExtents(
+ new_part_.path(), op.dst_extents(), data, kBlockSize));
+ } else {
+ FAIL() << "Unsupported install op type: " << op.type();
+ }
+ }
+ }
+ }
+
void ApplyPayload(DeltaArchiveManifest* manifest) {
ASSERT_FALSE(manifest->partitions().empty());
- if (manifest->partitions().begin()->has_old_partition_info()) {
- // Only create fake partition if the update is incremental
- LOG(INFO) << "Creating fake partition";
- ASSERT_NO_FATAL_FAILURE(CreateFakePartition());
- }
+ ASSERT_NO_FATAL_FAILURE(HydratePayload(manifest));
const auto private_key_path =
test_utils::GetBuildArtifactsPath(kUnittestPrivateKeyPath);
ASSERT_NO_FATAL_FAILURE(
@@ -183,6 +276,19 @@
auto partition = &manifest->mutable_partitions()->at(0);
partition->mutable_new_partition_info()->set_size(kFakePartitionSize);
partition->mutable_new_partition_info()->set_hash(hash.data(), hash.size());
+ const bool source_exist =
+ std::any_of(partition->operations().begin(),
+ partition->operations().end(),
+ [](const auto& op) { return op.src_extents_size() > 0; });
+ if (source_exist) {
+ HashCalculator::RawHashOfFile(old_part_.path(), &hash);
+ partition->mutable_old_partition_info()->set_size(kFakePartitionSize);
+ partition->mutable_old_partition_info()->set_hash(hash.data(),
+ hash.size());
+ // Only create fake partition if the update is incremental
+ LOG(INFO) << "Creating fake partition";
+ ASSERT_NO_FATAL_FAILURE(CreateFakePartition());
+ }
uint64_t metadata_size = 0;
ASSERT_TRUE(PayloadFile::WritePayload(payload_file_.path(),
blob_file_.path(),
@@ -210,6 +316,36 @@
ASSERT_EQ(error, nullptr);
ASSERT_EQ(completion_code_, ErrorCode::kSuccess);
}
+
+ // Compare contents of fake_b partition to |new_part_| and print difference
+ void DumpTargetPartitionDiff() {
+ dynamic_control_->MapAllPartitions();
+ auto partition_device =
+ dynamic_control_->GetPartitionDevice("fake",
+ 1 - boot_control_.GetCurrentSlot(),
+ boot_control_.GetCurrentSlot(),
+ false);
+ if (!partition_device.has_value()) {
+ LOG(INFO) << "Failed to get target fake partition, skip diff report";
+ return;
+ }
+
+ EintrSafeFileDescriptor actual_part;
+ CHECK(actual_part.Open(partition_device->readonly_device_path.c_str(),
+ O_RDONLY));
+ EintrSafeFileDescriptor expected_part;
+ CHECK(expected_part.Open(new_part_.path().c_str(), O_RDONLY));
+
+ std::array<uint8_t, kBlockSize> actual_block;
+ std::array<uint8_t, kBlockSize> expected_block;
+ for (size_t i = 0; i < kFakePartitionSize / kBlockSize; i++) {
+ actual_part.Read(actual_block.data(), actual_block.size());
+ expected_part.Read(expected_block.data(), expected_block.size());
+ if (actual_block != expected_block) {
+ LOG(ERROR) << "Block " << i << " differs.";
+ }
+ }
+ }
// use 25MB max to avoid super not having enough space
static constexpr size_t kFakePartitionSize = 1024 * 1024 * 25;
static_assert(kFakePartitionSize % kBlockSize == 0);
@@ -219,9 +355,12 @@
std::string super_device_;
FakeHardware hardware_;
ScopedTempFile payload_file_;
- ScopedTempFile blob_file_;
- // Contains expected data for old and new partition
+ ScopedTempFile blob_file_{"blob_file.XXXXXX", true};
+ // Contains expected data for old partition. Will be copied to fake_a on test
+ // start.
ScopedTempFile old_part_{"old_part.XXXXXX", true, kFakePartitionSize};
+ // Expected data for new partition, will be compared against actual data in
+ // fake_b once test finishes.
ScopedTempFile new_part_{"new_part.XXXXXX", true, kFakePartitionSize};
DaemonStateAndroid daemon_state_;
MemoryPrefs prefs_;
@@ -259,6 +398,57 @@
}
ApplyPayload(&manifest_);
+ if (completion_code_ == ErrorCode::kNewRootfsVerificationError) {
+ DumpTargetPartitionDiff();
+ }
+}
+
+TEST_F(UpdateAttempterAndroidIntegrationTest, XorOpsTest) {
+ if (!DynamicPartitionEnabled()) {
+ return;
+ }
+ auto partition = manifest_.add_partitions();
+ partition->set_partition_name("fake");
+ partition->set_estimate_cow_size(kFakePartitionSize);
+ {
+ auto op = partition->add_operations();
+ op->set_type(InstallOperation::BROTLI_BSDIFF);
+ *op->add_src_extents() = ExtentForRange(0, 10);
+ *op->add_dst_extents() = ExtentForRange(0, 10);
+ }
+ {
+ auto op = partition->add_operations();
+ op->set_type(InstallOperation::BROTLI_BSDIFF);
+ *op->add_src_extents() = ExtentForRange(10, 10);
+ *op->add_dst_extents() = ExtentForRange(10, 10);
+ }
+ {
+ auto op = partition->add_operations();
+ op->set_type(InstallOperation::SOURCE_COPY);
+ *op->add_src_extents() =
+ ExtentForRange(20, kFakePartitionSize / kBlockSize - 20);
+ *op->add_dst_extents() =
+ ExtentForRange(20, kFakePartitionSize / kBlockSize - 20);
+ }
+ {
+ auto op = partition->add_merge_operations();
+ op->set_type(CowMergeOperation::COW_XOR);
+ op->set_src_offset(123);
+ *op->mutable_src_extent() = ExtentForRange(2, 8);
+ *op->mutable_dst_extent() = ExtentForRange(0, 8);
+ }
+ {
+ auto op = partition->add_merge_operations();
+ op->set_type(CowMergeOperation::COW_XOR);
+ op->set_src_offset(456);
+ *op->mutable_src_extent() = ExtentForRange(10, 8);
+ *op->mutable_dst_extent() = ExtentForRange(12, 8);
+ }
+
+ ApplyPayload(&manifest_);
+ if (completion_code_ == ErrorCode::kNewRootfsVerificationError) {
+ DumpTargetPartitionDiff();
+ }
}
} // namespace
diff --git a/common/utils.cc b/common/utils.cc
index 7a4a836..e47b70b 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -397,6 +397,19 @@
return size;
}
+bool SendFile(int out_fd, int in_fd, size_t count) {
+ off64_t offset = lseek(in_fd, 0, SEEK_CUR);
+ TEST_AND_RETURN_FALSE_ERRNO(offset >= 0);
+ constexpr size_t BUFFER_SIZE = 4096;
+ while (count > 0) {
+ const auto bytes_written =
+ sendfile(out_fd, in_fd, &offset, std::min(count, BUFFER_SIZE));
+ TEST_AND_RETURN_FALSE_ERRNO(bytes_written > 0);
+ count -= bytes_written;
+ }
+ return true;
+}
+
void HexDumpArray(const uint8_t* const arr, const size_t length) {
LOG(INFO) << "Logging array of length: " << length;
const unsigned int bytes_per_line = 16;
@@ -892,6 +905,34 @@
return false;
}
+bool ReadExtents(const std::string& path,
+ const google::protobuf::RepeatedPtrField<Extent>& extents,
+ brillo::Blob* out_data,
+ size_t block_size) {
+ return ReadExtents(path,
+ {extents.begin(), extents.end()},
+ out_data,
+ utils::BlocksInExtents(extents) * block_size,
+ block_size);
+}
+
+bool WriteExtents(const std::string& path,
+ const google::protobuf::RepeatedPtrField<Extent>& extents,
+ const brillo::Blob& data,
+ size_t block_size) {
+ EintrSafeFileDescriptor fd;
+ TEST_AND_RETURN_FALSE(fd.Open(path.c_str(), O_RDWR));
+ size_t bytes_written = 0;
+ for (const auto& ext : extents) {
+ TEST_AND_RETURN_FALSE_ERRNO(
+ fd.Seek(ext.start_block() * block_size, SEEK_SET));
+ TEST_AND_RETURN_FALSE_ERRNO(
+ fd.Write(data.data() + bytes_written, ext.num_blocks() * block_size));
+ bytes_written += ext.num_blocks() * block_size;
+ }
+ return true;
+}
+
bool ReadExtents(const string& path,
const vector<Extent>& extents,
brillo::Blob* out_data,
@@ -906,7 +947,7 @@
for (const Extent& extent : extents) {
ssize_t bytes_read_this_iteration = 0;
ssize_t bytes = extent.num_blocks() * block_size;
- TEST_AND_RETURN_FALSE(bytes_read + bytes <= out_data_size);
+ TEST_LE(bytes_read + bytes, out_data_size);
TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
&data[bytes_read],
bytes,
diff --git a/common/utils.h b/common/utils.h
index 50b6cb1..4ff4050 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -38,6 +38,7 @@
#include <brillo/secure_blob.h>
#include "android-base/mapped_file.h"
+#include "google/protobuf/repeated_field.h"
#include "update_engine/common/action.h"
#include "update_engine/common/action_processor.h"
#include "update_engine/common/constants.h"
@@ -137,6 +138,8 @@
off_t FileSize(const std::string& path);
off_t FileSize(int fd);
+bool SendFile(int out_fd, int in_fd, size_t count);
+
std::string ErrnoNumberAsString(int err);
// Returns true if the file exists for sure. Returns false if it doesn't exist,
@@ -308,6 +311,27 @@
ssize_t out_data_size,
size_t block_size);
+bool WriteExtents(const std::string& path,
+ const google::protobuf::RepeatedPtrField<Extent>& extents,
+ const brillo::Blob& data,
+ size_t block_size);
+
+constexpr bool ReadExtents(const std::string& path,
+ const std::vector<Extent>& extents,
+ brillo::Blob* out_data,
+ size_t block_size) {
+ return ReadExtents(path,
+ extents,
+ out_data,
+ utils::BlocksInExtents(extents) * block_size,
+ block_size);
+}
+
+bool ReadExtents(const std::string& path,
+ const google::protobuf::RepeatedPtrField<Extent>& extents,
+ brillo::Blob* out_data,
+ size_t block_size);
+
// Read the current boot identifier and store it in |boot_id|. This identifier
// is constants during the same boot of the kernel and is regenerated after
// reboot. Returns whether it succeeded getting the boot_id.
@@ -485,6 +509,11 @@
std::string HexEncode(const brillo::Blob& blob) noexcept;
std::string HexEncode(const std::string_view blob) noexcept;
+template <size_t kSize>
+std::string HexEncode(const std::array<uint8_t, kSize> blob) noexcept {
+ return base::HexEncode(blob.data(), blob.size());
+}
+
} // namespace chromeos_update_engine
#define TEST_AND_RETURN_FALSE_ERRNO(_x) \
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index cc36a4e..2ac783a 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -498,7 +498,10 @@
// |num_total_operations_| limit yet.
if (next_operation_num_ >= acc_num_operations_[current_partition_]) {
if (partition_writer_) {
- TEST_AND_RETURN_FALSE(partition_writer_->FinishedInstallOps());
+ if (!partition_writer_->FinishedInstallOps()) {
+ *error = ErrorCode::kDownloadWriteError;
+ return false;
+ }
}
CloseCurrentPartition();
// Skip until there are operations for current_partition_.
diff --git a/payload_consumer/vabc_partition_writer.cc b/payload_consumer/vabc_partition_writer.cc
index a11bdf4..9db88a9 100644
--- a/payload_consumer/vabc_partition_writer.cc
+++ b/payload_consumer/vabc_partition_writer.cc
@@ -314,7 +314,8 @@
TEST_AND_RETURN_FALSE(cow_writer_ != nullptr);
TEST_AND_RETURN_FALSE(cow_writer_->AddLabel(kEndOfInstallLabel));
TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
- return cow_writer_->VerifyMergeOps();
+ TEST_AND_RETURN_FALSE(cow_writer_->VerifyMergeOps());
+ return true;
}
VABCPartitionWriter::~VABCPartitionWriter() {