| // Copyright (C) 2018 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 <libsnapshot/snapshot.h> |
| |
| #include <fcntl.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <chrono> |
| #include <iostream> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/properties.h> |
| #include <android-base/strings.h> |
| #include <android-base/unique_fd.h> |
| #include <fs_mgr/roots.h> |
| #include <fs_mgr_dm_linear.h> |
| #include <gtest/gtest.h> |
| #include <libdm/dm.h> |
| #include <libfiemap/image_manager.h> |
| #include <liblp/builder.h> |
| #include <storage_literals/storage_literals.h> |
| |
| #include <android/snapshot/snapshot.pb.h> |
| #include "test_helpers.h" |
| #include "utility.h" |
| |
| namespace android { |
| namespace snapshot { |
| |
| using android::base::unique_fd; |
| using android::dm::DeviceMapper; |
| using android::dm::DmDeviceState; |
| using android::fiemap::IImageManager; |
| using android::fs_mgr::BlockDeviceInfo; |
| using android::fs_mgr::CreateLogicalPartitionParams; |
| using android::fs_mgr::DestroyLogicalPartition; |
| using android::fs_mgr::EnsurePathMounted; |
| using android::fs_mgr::EnsurePathUnmounted; |
| using android::fs_mgr::Extent; |
| using android::fs_mgr::Fstab; |
| using android::fs_mgr::GetPartitionGroupName; |
| using android::fs_mgr::GetPartitionName; |
| using android::fs_mgr::Interval; |
| using android::fs_mgr::MetadataBuilder; |
| using android::fs_mgr::SlotSuffixForSlotNumber; |
| using chromeos_update_engine::DeltaArchiveManifest; |
| using chromeos_update_engine::DynamicPartitionGroup; |
| using chromeos_update_engine::PartitionUpdate; |
| using namespace ::testing; |
| using namespace android::storage_literals; |
| using namespace std::chrono_literals; |
| using namespace std::string_literals; |
| |
| // Global states. See test_helpers.h. |
| std::unique_ptr<SnapshotManager> sm; |
| TestDeviceInfo* test_device = nullptr; |
| std::string fake_super; |
| |
| class SnapshotTest : public ::testing::Test { |
| public: |
| SnapshotTest() : dm_(DeviceMapper::Instance()) {} |
| |
| // This is exposed for main. |
| void Cleanup() { |
| InitializeState(); |
| CleanupTestArtifacts(); |
| } |
| |
| protected: |
| void SetUp() override { |
| SnapshotTestPropertyFetcher::SetUp(); |
| InitializeState(); |
| CleanupTestArtifacts(); |
| FormatFakeSuper(); |
| |
| ASSERT_TRUE(sm->BeginUpdate()); |
| } |
| |
| void TearDown() override { |
| lock_ = nullptr; |
| |
| CleanupTestArtifacts(); |
| SnapshotTestPropertyFetcher::TearDown(); |
| } |
| |
| void InitializeState() { |
| ASSERT_TRUE(sm->EnsureImageManager()); |
| image_manager_ = sm->image_manager(); |
| |
| test_device->set_slot_suffix("_a"); |
| } |
| |
| void CleanupTestArtifacts() { |
| // Normally cancelling inside a merge is not allowed. Since these |
| // are tests, we don't care, destroy everything that might exist. |
| // Note we hardcode this list because of an annoying quirk: when |
| // completing a merge, the snapshot stops existing, so we can't |
| // get an accurate list to remove. |
| lock_ = nullptr; |
| |
| std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a", |
| "test_partition_b"}; |
| for (const auto& snapshot : snapshots) { |
| ASSERT_TRUE(DeleteSnapshotDevice(snapshot)); |
| DeleteBackingImage(image_manager_, snapshot + "-cow-img"); |
| |
| auto status_file = sm->GetSnapshotStatusFilePath(snapshot); |
| android::base::RemoveFileIfExists(status_file); |
| } |
| |
| // Remove stale partitions in fake super. |
| std::vector<std::string> partitions = { |
| "base-device", |
| "test_partition_b", |
| "test_partition_b-base", |
| }; |
| for (const auto& partition : partitions) { |
| DeleteDevice(partition); |
| } |
| |
| if (sm->GetUpdateState() != UpdateState::None) { |
| auto state_file = sm->GetStateFilePath(); |
| unlink(state_file.c_str()); |
| } |
| } |
| |
| bool AcquireLock() { |
| lock_ = sm->OpenStateFile(O_RDWR, LOCK_EX); |
| return !!lock_; |
| } |
| |
| // This is so main() can instantiate this to invoke Cleanup. |
| virtual void TestBody() override {} |
| |
| void FormatFakeSuper() { |
| BlockDeviceInfo super_device("super", kSuperSize, 0, 0, 4096); |
| std::vector<BlockDeviceInfo> devices = {super_device}; |
| |
| auto builder = MetadataBuilder::New(devices, "super", 65536, 2); |
| ASSERT_NE(builder, nullptr); |
| |
| auto metadata = builder->Export(); |
| ASSERT_NE(metadata, nullptr); |
| |
| TestPartitionOpener opener(fake_super); |
| ASSERT_TRUE(FlashPartitionTable(opener, fake_super, *metadata.get())); |
| } |
| |
| // If |path| is non-null, the partition will be mapped after creation. |
| bool CreatePartition(const std::string& name, uint64_t size, std::string* path = nullptr) { |
| TestPartitionOpener opener(fake_super); |
| auto builder = MetadataBuilder::New(opener, "super", 0); |
| if (!builder) return false; |
| |
| auto partition = builder->AddPartition(name, 0); |
| if (!partition) return false; |
| if (!builder->ResizePartition(partition, size)) { |
| return false; |
| } |
| |
| // Update the source slot. |
| auto metadata = builder->Export(); |
| if (!metadata) return false; |
| if (!UpdatePartitionTable(opener, "super", *metadata.get(), 0)) { |
| return false; |
| } |
| |
| if (!path) return true; |
| |
| CreateLogicalPartitionParams params = { |
| .block_device = fake_super, |
| .metadata = metadata.get(), |
| .partition_name = name, |
| .force_writable = true, |
| .timeout_ms = 10s, |
| }; |
| return CreateLogicalPartition(params, path); |
| } |
| |
| bool MapUpdatePartitions() { |
| TestPartitionOpener opener(fake_super); |
| auto builder = MetadataBuilder::NewForUpdate(opener, "super", 0, 1); |
| if (!builder) return false; |
| |
| auto metadata = builder->Export(); |
| if (!metadata) return false; |
| |
| // Update the destination slot, mark it as updated. |
| if (!UpdatePartitionTable(opener, "super", *metadata.get(), 1)) { |
| return false; |
| } |
| |
| for (const auto& partition : metadata->partitions) { |
| CreateLogicalPartitionParams params = { |
| .block_device = fake_super, |
| .metadata = metadata.get(), |
| .partition = &partition, |
| .force_writable = true, |
| .timeout_ms = 10s, |
| .device_name = GetPartitionName(partition) + "-base", |
| }; |
| std::string ignore_path; |
| if (!CreateLogicalPartition(params, &ignore_path)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| AssertionResult DeleteSnapshotDevice(const std::string& snapshot) { |
| AssertionResult res = AssertionSuccess(); |
| if (!(res = DeleteDevice(snapshot))) return res; |
| if (!(res = DeleteDevice(snapshot + "-inner"))) return res; |
| if (!(res = DeleteDevice(snapshot + "-cow"))) return res; |
| if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) { |
| return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img"; |
| } |
| if (!(res = DeleteDevice(snapshot + "-base"))) return res; |
| return AssertionSuccess(); |
| } |
| |
| AssertionResult DeleteDevice(const std::string& device) { |
| if (!dm_.DeleteDeviceIfExists(device)) { |
| return AssertionFailure() << "Can't delete " << device; |
| } |
| return AssertionSuccess(); |
| } |
| |
| AssertionResult CreateCowImage(const std::string& name) { |
| if (!sm->CreateCowImage(lock_.get(), name)) { |
| return AssertionFailure() << "Cannot create COW image " << name; |
| } |
| std::string cow_device; |
| auto map_res = MapCowImage(name, 10s, &cow_device); |
| if (!map_res) { |
| return map_res; |
| } |
| if (!InitializeCow(cow_device)) { |
| return AssertionFailure() << "Cannot zero fill " << cow_device; |
| } |
| if (!sm->UnmapCowImage(name)) { |
| return AssertionFailure() << "Cannot unmap " << name << " after zero filling it"; |
| } |
| return AssertionSuccess(); |
| } |
| |
| AssertionResult MapCowImage(const std::string& name, |
| const std::chrono::milliseconds& timeout_ms, std::string* path) { |
| auto cow_image_path = sm->MapCowImage(name, timeout_ms); |
| if (!cow_image_path.has_value()) { |
| return AssertionFailure() << "Cannot map cow image " << name; |
| } |
| *path = *cow_image_path; |
| return AssertionSuccess(); |
| } |
| |
| DeviceMapper& dm_; |
| std::unique_ptr<SnapshotManager::LockedFile> lock_; |
| android::fiemap::IImageManager* image_manager_ = nullptr; |
| std::string fake_super_; |
| }; |
| |
| TEST_F(SnapshotTest, CreateSnapshot) { |
| ASSERT_TRUE(AcquireLock()); |
| |
| static const uint64_t kDeviceSize = 1024 * 1024; |
| SnapshotStatus status; |
| status.set_name("test-snapshot"); |
| status.set_device_size(kDeviceSize); |
| status.set_snapshot_size(kDeviceSize); |
| status.set_cow_file_size(kDeviceSize); |
| ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); |
| ASSERT_TRUE(CreateCowImage("test-snapshot")); |
| |
| std::vector<std::string> snapshots; |
| ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots)); |
| ASSERT_EQ(snapshots.size(), 1); |
| ASSERT_EQ(snapshots[0], "test-snapshot"); |
| |
| // Scope so delete can re-acquire the snapshot file lock. |
| { |
| SnapshotStatus status; |
| ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status)); |
| ASSERT_EQ(status.state(), SnapshotState::CREATED); |
| ASSERT_EQ(status.device_size(), kDeviceSize); |
| ASSERT_EQ(status.snapshot_size(), kDeviceSize); |
| } |
| |
| ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot")); |
| ASSERT_TRUE(sm->UnmapCowImage("test-snapshot")); |
| ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot")); |
| } |
| |
| TEST_F(SnapshotTest, MapSnapshot) { |
| ASSERT_TRUE(AcquireLock()); |
| |
| static const uint64_t kDeviceSize = 1024 * 1024; |
| SnapshotStatus status; |
| status.set_name("test-snapshot"); |
| status.set_device_size(kDeviceSize); |
| status.set_snapshot_size(kDeviceSize); |
| status.set_cow_file_size(kDeviceSize); |
| ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); |
| ASSERT_TRUE(CreateCowImage("test-snapshot")); |
| |
| std::string base_device; |
| ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device)); |
| |
| std::string cow_device; |
| ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device)); |
| |
| std::string snap_device; |
| ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s, |
| &snap_device)); |
| ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-")); |
| } |
| |
| TEST_F(SnapshotTest, MapPartialSnapshot) { |
| ASSERT_TRUE(AcquireLock()); |
| |
| static const uint64_t kSnapshotSize = 1024 * 1024; |
| static const uint64_t kDeviceSize = 1024 * 1024 * 2; |
| SnapshotStatus status; |
| status.set_name("test-snapshot"); |
| status.set_device_size(kDeviceSize); |
| status.set_snapshot_size(kSnapshotSize); |
| status.set_cow_file_size(kSnapshotSize); |
| ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); |
| ASSERT_TRUE(CreateCowImage("test-snapshot")); |
| |
| std::string base_device; |
| ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device)); |
| |
| std::string cow_device; |
| ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device)); |
| |
| std::string snap_device; |
| ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s, |
| &snap_device)); |
| ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-")); |
| } |
| |
| TEST_F(SnapshotTest, NoMergeBeforeReboot) { |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| |
| // Merge should fail, since the slot hasn't changed. |
| ASSERT_FALSE(sm->InitiateMerge()); |
| } |
| |
| TEST_F(SnapshotTest, CleanFirstStageMount) { |
| // If there's no update in progress, there should be no first-stage mount |
| // needed. |
| TestDeviceInfo* info = new TestDeviceInfo(fake_super); |
| auto sm = SnapshotManager::NewForFirstStageMount(info); |
| ASSERT_NE(sm, nullptr); |
| ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount()); |
| } |
| |
| TEST_F(SnapshotTest, FirstStageMountAfterRollback) { |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| |
| // We didn't change the slot, so we shouldn't need snapshots. |
| TestDeviceInfo* info = new TestDeviceInfo(fake_super); |
| auto sm = SnapshotManager::NewForFirstStageMount(info); |
| ASSERT_NE(sm, nullptr); |
| ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount()); |
| } |
| |
| TEST_F(SnapshotTest, Merge) { |
| ASSERT_TRUE(AcquireLock()); |
| |
| static const uint64_t kDeviceSize = 1024 * 1024; |
| |
| std::string base_device, cow_device, snap_device; |
| ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize)); |
| ASSERT_TRUE(MapUpdatePartitions()); |
| ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b-base", &base_device)); |
| SnapshotStatus status; |
| status.set_name("test_partition_b"); |
| status.set_device_size(kDeviceSize); |
| status.set_snapshot_size(kDeviceSize); |
| status.set_cow_file_size(kDeviceSize); |
| ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); |
| ASSERT_TRUE(CreateCowImage("test_partition_b")); |
| ASSERT_TRUE(MapCowImage("test_partition_b", 10s, &cow_device)); |
| ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s, |
| &snap_device)); |
| |
| std::string test_string = "This is a test string."; |
| { |
| unique_fd fd(open(snap_device.c_str(), O_RDWR | O_CLOEXEC | O_SYNC)); |
| ASSERT_GE(fd, 0); |
| ASSERT_TRUE(android::base::WriteFully(fd, test_string.data(), test_string.size())); |
| } |
| |
| // Note: we know there is no inner/outer dm device since we didn't request |
| // a linear segment. |
| DeviceMapper::TargetInfo target; |
| ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target)); |
| ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot"); |
| |
| // Release the lock. |
| lock_ = nullptr; |
| |
| // Done updating. |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| |
| test_device->set_slot_suffix("_b"); |
| ASSERT_TRUE(sm->InitiateMerge()); |
| |
| // The device should have been switched to a snapshot-merge target. |
| ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target)); |
| ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge"); |
| |
| // We should not be able to cancel an update now. |
| ASSERT_FALSE(sm->CancelUpdate()); |
| |
| ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeCompleted); |
| ASSERT_EQ(sm->GetUpdateState(), UpdateState::None); |
| |
| // The device should no longer be a snapshot or snapshot-merge. |
| ASSERT_FALSE(sm->IsSnapshotDevice("test_partition_b")); |
| |
| // Test that we can read back the string we wrote to the snapshot. Note |
| // that the base device is gone now. |snap_device| contains the correct |
| // partition. |
| unique_fd fd(open(snap_device.c_str(), O_RDONLY | O_CLOEXEC)); |
| ASSERT_GE(fd, 0); |
| |
| std::string buffer(test_string.size(), '\0'); |
| ASSERT_TRUE(android::base::ReadFully(fd, buffer.data(), buffer.size())); |
| ASSERT_EQ(test_string, buffer); |
| } |
| |
| TEST_F(SnapshotTest, FirstStageMountAndMerge) { |
| ASSERT_TRUE(AcquireLock()); |
| |
| static const uint64_t kDeviceSize = 1024 * 1024; |
| |
| ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize)); |
| ASSERT_TRUE(MapUpdatePartitions()); |
| SnapshotStatus status; |
| status.set_name("test_partition_b"); |
| status.set_device_size(kDeviceSize); |
| status.set_snapshot_size(kDeviceSize); |
| status.set_cow_file_size(kDeviceSize); |
| ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); |
| ASSERT_TRUE(CreateCowImage("test_partition_b")); |
| |
| // Simulate a reboot into the new slot. |
| lock_ = nullptr; |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base")); |
| |
| auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b")); |
| ASSERT_NE(init, nullptr); |
| ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| |
| ASSERT_TRUE(AcquireLock()); |
| |
| // Validate that we have a snapshot device. |
| ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status)); |
| ASSERT_EQ(status.state(), SnapshotState::CREATED); |
| |
| DeviceMapper::TargetInfo target; |
| auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status); |
| ASSERT_TRUE(init->IsSnapshotDevice(dm_name, &target)); |
| ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot"); |
| } |
| |
| TEST_F(SnapshotTest, FlashSuperDuringUpdate) { |
| ASSERT_TRUE(AcquireLock()); |
| |
| static const uint64_t kDeviceSize = 1024 * 1024; |
| |
| ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize)); |
| ASSERT_TRUE(MapUpdatePartitions()); |
| SnapshotStatus status; |
| status.set_name("test_partition_b"); |
| status.set_device_size(kDeviceSize); |
| status.set_snapshot_size(kDeviceSize); |
| status.set_cow_file_size(kDeviceSize); |
| ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); |
| ASSERT_TRUE(CreateCowImage("test_partition_b")); |
| |
| // Simulate a reboot into the new slot. |
| lock_ = nullptr; |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base")); |
| |
| // Reflash the super partition. |
| FormatFakeSuper(); |
| ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize)); |
| |
| auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b")); |
| ASSERT_NE(init, nullptr); |
| ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| |
| ASSERT_TRUE(AcquireLock()); |
| |
| ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status)); |
| |
| // We should not get a snapshot device now. |
| DeviceMapper::TargetInfo target; |
| auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status); |
| ASSERT_FALSE(init->IsSnapshotDevice(dm_name, &target)); |
| |
| // We should see a cancelled update as well. |
| lock_ = nullptr; |
| ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::Cancelled); |
| } |
| |
| TEST_F(SnapshotTest, FlashSuperDuringMerge) { |
| ASSERT_TRUE(AcquireLock()); |
| |
| static const uint64_t kDeviceSize = 1024 * 1024; |
| |
| ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize)); |
| ASSERT_TRUE(MapUpdatePartitions()); |
| SnapshotStatus status; |
| status.set_name("test_partition_b"); |
| status.set_device_size(kDeviceSize); |
| status.set_snapshot_size(kDeviceSize); |
| status.set_cow_file_size(kDeviceSize); |
| ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status)); |
| ASSERT_TRUE(CreateCowImage("test_partition_b")); |
| |
| // Simulate a reboot into the new slot. |
| lock_ = nullptr; |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| ASSERT_TRUE(DestroyLogicalPartition("test_partition_b-base")); |
| |
| auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b")); |
| ASSERT_NE(init, nullptr); |
| ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| ASSERT_TRUE(init->InitiateMerge()); |
| |
| // Now, reflash super. Note that we haven't called ProcessUpdateState, so the |
| // status is still Merging. |
| ASSERT_TRUE(DeleteSnapshotDevice("test_partition_b")); |
| ASSERT_TRUE(init->image_manager()->UnmapImageIfExists("test_partition_b-cow-img")); |
| FormatFakeSuper(); |
| ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize)); |
| ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| |
| // Because the status is Merging, we must call ProcessUpdateState, which should |
| // detect a cancelled update. |
| ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::Cancelled); |
| ASSERT_EQ(sm->GetUpdateState(), UpdateState::None); |
| } |
| |
| TEST_F(SnapshotTest, UpdateBootControlHal) { |
| ASSERT_TRUE(AcquireLock()); |
| |
| ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None)); |
| ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); |
| |
| ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Initiated)); |
| ASSERT_EQ(test_device->merge_status(), MergeStatus::SNAPSHOTTED); |
| |
| ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified)); |
| ASSERT_EQ(test_device->merge_status(), MergeStatus::SNAPSHOTTED); |
| |
| ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Merging)); |
| ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING); |
| |
| ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeNeedsReboot)); |
| ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); |
| |
| ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeCompleted)); |
| ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); |
| |
| ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed)); |
| ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING); |
| } |
| |
| class SnapshotUpdateTest : public SnapshotTest { |
| public: |
| void SetUp() override { |
| SnapshotTest::SetUp(); |
| Cleanup(); |
| |
| // Cleanup() changes slot suffix, so initialize it again. |
| test_device->set_slot_suffix("_a"); |
| |
| opener_ = std::make_unique<TestPartitionOpener>(fake_super); |
| |
| // Create a fake update package metadata. |
| // Not using full name "system", "vendor", "product" because these names collide with the |
| // mapped partitions on the running device. |
| // Each test modifies manifest_ slightly to indicate changes to the partition layout. |
| group_ = manifest_.mutable_dynamic_partition_metadata()->add_groups(); |
| group_->set_name("group"); |
| group_->set_size(kGroupSize); |
| group_->add_partition_names("sys"); |
| group_->add_partition_names("vnd"); |
| group_->add_partition_names("prd"); |
| sys_ = manifest_.add_partitions(); |
| sys_->set_partition_name("sys"); |
| SetSize(sys_, 3_MiB); |
| vnd_ = manifest_.add_partitions(); |
| vnd_->set_partition_name("vnd"); |
| SetSize(vnd_, 3_MiB); |
| prd_ = manifest_.add_partitions(); |
| prd_->set_partition_name("prd"); |
| SetSize(prd_, 3_MiB); |
| |
| // Initialize source partition metadata using |manifest_|. |
| src_ = MetadataBuilder::New(*opener_, "super", 0); |
| ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a")); |
| // Add sys_b which is like system_other. |
| ASSERT_TRUE(src_->AddGroup("group_b", kGroupSize)); |
| auto partition = src_->AddPartition("sys_b", "group_b", 0); |
| ASSERT_NE(nullptr, partition); |
| ASSERT_TRUE(src_->ResizePartition(partition, 1_MiB)); |
| auto metadata = src_->Export(); |
| ASSERT_NE(nullptr, metadata); |
| ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); |
| |
| // Map source partitions. Additionally, map sys_b to simulate system_other after flashing. |
| std::string path; |
| for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) { |
| ASSERT_TRUE(CreateLogicalPartition( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata_slot = 0, |
| .partition_name = name, |
| .timeout_ms = 1s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)); |
| ASSERT_TRUE(WriteRandomData(path)); |
| auto hash = GetHash(path); |
| ASSERT_TRUE(hash.has_value()); |
| hashes_[name] = *hash; |
| } |
| } |
| void TearDown() override { |
| Cleanup(); |
| SnapshotTest::TearDown(); |
| } |
| void Cleanup() { |
| if (!image_manager_) { |
| InitializeState(); |
| } |
| for (const auto& suffix : {"_a", "_b"}) { |
| test_device->set_slot_suffix(suffix); |
| EXPECT_TRUE(sm->CancelUpdate()) << suffix; |
| } |
| EXPECT_TRUE(UnmapAll()); |
| } |
| |
| AssertionResult IsPartitionUnchanged(const std::string& name) { |
| std::string path; |
| if (!dm_.GetDmDevicePathByName(name, &path)) { |
| return AssertionFailure() << "Path of " << name << " cannot be determined"; |
| } |
| auto hash = GetHash(path); |
| if (!hash.has_value()) { |
| return AssertionFailure() << "Cannot read partition " << name << ": " << path; |
| } |
| auto it = hashes_.find(name); |
| if (it == hashes_.end()) { |
| return AssertionFailure() << "No existing hash for " << name << ". Bad test code?"; |
| } |
| if (it->second != *hash) { |
| return AssertionFailure() << "Content of " << name << " has changed"; |
| } |
| return AssertionSuccess(); |
| } |
| |
| std::optional<uint64_t> GetSnapshotSize(const std::string& name) { |
| if (!AcquireLock()) { |
| return std::nullopt; |
| } |
| auto local_lock = std::move(lock_); |
| |
| SnapshotStatus status; |
| if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) { |
| return std::nullopt; |
| } |
| return status.snapshot_size(); |
| } |
| |
| AssertionResult UnmapAll() { |
| for (const auto& name : {"sys", "vnd", "prd"}) { |
| if (!dm_.DeleteDeviceIfExists(name + "_a"s)) { |
| return AssertionFailure() << "Cannot unmap " << name << "_a"; |
| } |
| if (!DeleteSnapshotDevice(name + "_b"s)) { |
| return AssertionFailure() << "Cannot delete snapshot " << name << "_b"; |
| } |
| } |
| return AssertionSuccess(); |
| } |
| |
| std::unique_ptr<TestPartitionOpener> opener_; |
| DeltaArchiveManifest manifest_; |
| std::unique_ptr<MetadataBuilder> src_; |
| std::map<std::string, std::string> hashes_; |
| |
| PartitionUpdate* sys_ = nullptr; |
| PartitionUpdate* vnd_ = nullptr; |
| PartitionUpdate* prd_ = nullptr; |
| DynamicPartitionGroup* group_ = nullptr; |
| }; |
| |
| // Test full update flow executed by update_engine. Some partitions uses super empty space, |
| // some uses images, and some uses both. |
| // Also test UnmapUpdateSnapshot unmaps everything. |
| // Also test first stage mount and merge after this. |
| TEST_F(SnapshotUpdateTest, FullUpdateFlow) { |
| // OTA client blindly unmaps all partitions that are possibly mapped. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); |
| } |
| |
| // Grow all partitions. |
| constexpr uint64_t partition_size = 3788_KiB; |
| SetSize(sys_, partition_size); |
| SetSize(vnd_, partition_size); |
| SetSize(prd_, partition_size); |
| |
| // Create fake install operations to grow the COW device size. |
| for (auto& partition : {sys_, vnd_, prd_}) { |
| auto e = partition->add_operations()->add_dst_extents(); |
| e->set_start_block(0); |
| e->set_num_blocks(GetSize(partition) / manifest_.block_size()); |
| } |
| |
| // Execute the update. |
| ASSERT_TRUE(sm->BeginUpdate()); |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| |
| // Test that partitions prioritize using space in super. |
| auto tgt = MetadataBuilder::New(*opener_, "super", 1); |
| ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow")); |
| ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow")); |
| ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow")); |
| |
| // Write some data to target partitions. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| std::string path; |
| ASSERT_TRUE(sm->MapUpdateSnapshot( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata_slot = 1, |
| .partition_name = name, |
| .timeout_ms = 10s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)) |
| << name; |
| ASSERT_TRUE(WriteRandomData(path)); |
| auto hash = GetHash(path); |
| ASSERT_TRUE(hash.has_value()); |
| hashes_[name] = *hash; |
| } |
| |
| // Assert that source partitions aren't affected. |
| for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name)); |
| } |
| |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| |
| // Simulate shutting down the device. |
| ASSERT_TRUE(UnmapAll()); |
| |
| // After reboot, init does first stage mount. |
| auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b")); |
| ASSERT_NE(init, nullptr); |
| ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| |
| // Check that the target partitions have the same content. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name)); |
| } |
| |
| // Initiate the merge and wait for it to be completed. |
| ASSERT_EQ(UpdateState::MergeCompleted, init->InitiateMergeAndWait()); |
| |
| // Check that the target partitions have the same content after the merge. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name)) |
| << "Content of " << name << " changes after the merge"; |
| } |
| } |
| |
| // Test that if new system partitions uses empty space in super, that region is not snapshotted. |
| TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) { |
| GTEST_SKIP() << "b/141889746"; |
| SetSize(sys_, 4_MiB); |
| // vnd_b and prd_b are unchanged. |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| ASSERT_EQ(3_MiB, GetSnapshotSize("sys_b").value_or(0)); |
| } |
| |
| // Test that if new system partitions uses space of old vendor partition, that region is |
| // snapshotted. |
| TEST_F(SnapshotUpdateTest, SnapshotOldPartitions) { |
| SetSize(sys_, 4_MiB); // grows |
| SetSize(vnd_, 2_MiB); // shrinks |
| // prd_b is unchanged |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0)); |
| } |
| |
| // Test that even if there seem to be empty space in target metadata, COW partition won't take |
| // it because they are used by old partitions. |
| TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) { |
| SetSize(sys_, 2_MiB); // shrinks |
| // vnd_b and prd_b are unchanged. |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| |
| auto tgt = MetadataBuilder::New(*opener_, "super", 1); |
| ASSERT_NE(nullptr, tgt); |
| auto metadata = tgt->Export(); |
| ASSERT_NE(nullptr, metadata); |
| std::vector<std::string> written; |
| // Write random data to all COW partitions in super |
| for (auto p : metadata->partitions) { |
| if (GetPartitionGroupName(metadata->groups[p.group_index]) != kCowGroupName) { |
| continue; |
| } |
| std::string path; |
| ASSERT_TRUE(CreateLogicalPartition( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata = metadata.get(), |
| .partition = &p, |
| .timeout_ms = 1s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)); |
| ASSERT_TRUE(WriteRandomData(path)); |
| written.push_back(GetPartitionName(p)); |
| } |
| ASSERT_FALSE(written.empty()) |
| << "No COW partitions are created even if there are empty space in super partition"; |
| |
| // Make sure source partitions aren't affected. |
| for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name)); |
| } |
| } |
| |
| // Test that it crashes after creating snapshot status file but before creating COW image, then |
| // calling CreateUpdateSnapshots again works. |
| TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) { |
| // Write some trash snapshot files to simulate leftovers from previous runs. |
| { |
| ASSERT_TRUE(AcquireLock()); |
| auto local_lock = std::move(lock_); |
| SnapshotStatus status; |
| status.set_name("sys_b"); |
| ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), status)); |
| ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB, |
| IImageManager::CREATE_IMAGE_DEFAULT)); |
| } |
| |
| // Redo the update. |
| ASSERT_TRUE(sm->BeginUpdate()); |
| ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b")); |
| |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| |
| // Check that target partitions can be mapped. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| std::string path; |
| EXPECT_TRUE(sm->MapUpdateSnapshot( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata_slot = 1, |
| .partition_name = name, |
| .timeout_ms = 10s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)) |
| << name; |
| } |
| } |
| |
| // Test that the old partitions are not modified. |
| TEST_F(SnapshotUpdateTest, TestRollback) { |
| // Execute the update. |
| ASSERT_TRUE(sm->BeginUpdate()); |
| ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b")); |
| |
| // Create fake install operations to grow the COW device size. |
| for (auto& partition : {sys_, vnd_, prd_}) { |
| auto e = partition->add_operations()->add_dst_extents(); |
| e->set_start_block(0); |
| e->set_num_blocks(GetSize(partition) / manifest_.block_size()); |
| } |
| |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| |
| // Write some data to target partitions. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| std::string path; |
| ASSERT_TRUE(sm->MapUpdateSnapshot( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata_slot = 1, |
| .partition_name = name, |
| .timeout_ms = 10s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)) |
| << name; |
| ASSERT_TRUE(WriteRandomData(path)); |
| auto hash = GetHash(path); |
| ASSERT_TRUE(hash.has_value()); |
| hashes_[name] = *hash; |
| } |
| |
| // Assert that source partitions aren't affected. |
| for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name)); |
| } |
| |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| |
| // Simulate shutting down the device. |
| ASSERT_TRUE(UnmapAll()); |
| |
| // After reboot, init does first stage mount. |
| auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b")); |
| ASSERT_NE(init, nullptr); |
| ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| |
| // Check that the target partitions have the same content. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name)); |
| } |
| |
| // Simulate shutting down the device again. |
| ASSERT_TRUE(UnmapAll()); |
| init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_a")); |
| ASSERT_NE(init, nullptr); |
| ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount()); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| |
| // Assert that the source partitions aren't affected. |
| for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name)); |
| } |
| } |
| |
| // Test that if an update is applied but not booted into, it can be canceled. |
| TEST_F(SnapshotUpdateTest, CancelAfterApply) { |
| ASSERT_TRUE(sm->BeginUpdate()); |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| ASSERT_TRUE(sm->CancelUpdate()); |
| } |
| |
| static std::vector<Interval> ToIntervals(const std::vector<std::unique_ptr<Extent>>& extents) { |
| std::vector<Interval> ret; |
| std::transform(extents.begin(), extents.end(), std::back_inserter(ret), |
| [](const auto& extent) { return extent->AsLinearExtent()->AsInterval(); }); |
| return ret; |
| } |
| |
| // Test that at the second update, old COW partition spaces are reclaimed. |
| TEST_F(SnapshotUpdateTest, ReclaimCow) { |
| // Execute the first update. |
| ASSERT_TRUE(sm->BeginUpdate()); |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| |
| // Simulate shutting down the device. |
| ASSERT_TRUE(UnmapAll()); |
| |
| // After reboot, init does first stage mount. |
| auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b")); |
| ASSERT_NE(init, nullptr); |
| ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| init = nullptr; |
| |
| // Initiate the merge and wait for it to be completed. |
| auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); |
| ASSERT_EQ(UpdateState::MergeCompleted, new_sm->InitiateMergeAndWait()); |
| |
| // Execute the second update. |
| ASSERT_TRUE(new_sm->BeginUpdate()); |
| ASSERT_TRUE(new_sm->CreateUpdateSnapshots(manifest_)); |
| |
| // Check that the old COW space is reclaimed and does not occupy space of mapped partitions. |
| auto src = MetadataBuilder::New(*opener_, "super", 1); |
| auto tgt = MetadataBuilder::New(*opener_, "super", 0); |
| for (const auto& cow_part_name : {"sys_a-cow", "vnd_a-cow", "prd_a-cow"}) { |
| auto* cow_part = tgt->FindPartition(cow_part_name); |
| ASSERT_NE(nullptr, cow_part) << cow_part_name << " does not exist in target metadata"; |
| auto cow_intervals = ToIntervals(cow_part->extents()); |
| for (const auto& old_part_name : {"sys_b", "vnd_b", "prd_b"}) { |
| auto* old_part = src->FindPartition(old_part_name); |
| ASSERT_NE(nullptr, old_part) << old_part_name << " does not exist in source metadata"; |
| auto old_intervals = ToIntervals(old_part->extents()); |
| |
| auto intersect = Interval::Intersect(cow_intervals, old_intervals); |
| ASSERT_TRUE(intersect.empty()) << "COW uses space of source partitions"; |
| } |
| } |
| } |
| |
| TEST_F(SnapshotUpdateTest, RetrofitAfterRegularAb) { |
| constexpr auto kRetrofitGroupSize = kGroupSize / 2; |
| |
| // Initialize device-mapper / disk |
| ASSERT_TRUE(UnmapAll()); |
| FormatFakeSuper(); |
| |
| // Setup source partition metadata to have both _a and _b partitions. |
| src_ = MetadataBuilder::New(*opener_, "super", 0); |
| ASSERT_NE(nullptr, src_); |
| for (const auto& suffix : {"_a"s, "_b"s}) { |
| ASSERT_TRUE(src_->AddGroup(group_->name() + suffix, kRetrofitGroupSize)); |
| for (const auto& name : {"sys"s, "vnd"s, "prd"s}) { |
| auto partition = src_->AddPartition(name + suffix, group_->name() + suffix, 0); |
| ASSERT_NE(nullptr, partition); |
| ASSERT_TRUE(src_->ResizePartition(partition, 2_MiB)); |
| } |
| } |
| auto metadata = src_->Export(); |
| ASSERT_NE(nullptr, metadata); |
| ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); |
| |
| // Flash source partitions |
| std::string path; |
| for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { |
| ASSERT_TRUE(CreateLogicalPartition( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata_slot = 0, |
| .partition_name = name, |
| .timeout_ms = 1s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)); |
| ASSERT_TRUE(WriteRandomData(path)); |
| auto hash = GetHash(path); |
| ASSERT_TRUE(hash.has_value()); |
| hashes_[name] = *hash; |
| } |
| |
| // Setup manifest. |
| group_->set_size(kRetrofitGroupSize); |
| for (auto* partition : {sys_, vnd_, prd_}) { |
| SetSize(partition, 2_MiB); |
| auto* e = partition->add_operations()->add_dst_extents(); |
| e->set_start_block(0); |
| e->set_num_blocks(2_MiB / manifest_.block_size()); |
| } |
| |
| ASSERT_TRUE(sm->BeginUpdate()); |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| |
| // Test that COW image should not be created for retrofit devices; super |
| // should be big enough. |
| ASSERT_FALSE(image_manager_->BackingImageExists("sys_b-cow-img")); |
| ASSERT_FALSE(image_manager_->BackingImageExists("vnd_b-cow-img")); |
| ASSERT_FALSE(image_manager_->BackingImageExists("prd_b-cow-img")); |
| |
| // Write some data to target partitions. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| std::string path; |
| ASSERT_TRUE(sm->MapUpdateSnapshot( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata_slot = 1, |
| .partition_name = name, |
| .timeout_ms = 10s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)) |
| << name; |
| ASSERT_TRUE(WriteRandomData(path)); |
| auto hash = GetHash(path); |
| ASSERT_TRUE(hash.has_value()); |
| hashes_[name] = *hash; |
| } |
| |
| // Assert that source partitions aren't affected. |
| for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name)); |
| } |
| |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| } |
| |
| TEST_F(SnapshotUpdateTest, MergeCannotRemoveCow) { |
| // Make source partitions as big as possible to force COW image to be created. |
| SetSize(sys_, 5_MiB); |
| SetSize(vnd_, 5_MiB); |
| SetSize(prd_, 5_MiB); |
| src_ = MetadataBuilder::New(*opener_, "super", 0); |
| src_->RemoveGroupAndPartitions(group_->name() + "_a"); |
| src_->RemoveGroupAndPartitions(group_->name() + "_b"); |
| ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a")); |
| auto metadata = src_->Export(); |
| ASSERT_NE(nullptr, metadata); |
| ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); |
| |
| // OTA client blindly unmaps all partitions that are possibly mapped. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); |
| } |
| |
| // Add operations for sys. The whole device is written. |
| auto e = sys_->add_operations()->add_dst_extents(); |
| e->set_start_block(0); |
| e->set_num_blocks(GetSize(sys_) / manifest_.block_size()); |
| |
| // Execute the update. |
| ASSERT_TRUE(sm->BeginUpdate()); |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| |
| // Simulate shutting down the device. |
| ASSERT_TRUE(UnmapAll()); |
| |
| // After reboot, init does first stage mount. |
| // Normally we should use NewForFirstStageMount, but if so, "gsid.mapped_image.sys_b-cow-img" |
| // won't be set. |
| auto init = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); |
| ASSERT_NE(init, nullptr); |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| |
| // Keep an open handle to the cow device. This should cause the merge to |
| // be incomplete. |
| auto cow_path = android::base::GetProperty("gsid.mapped_image.sys_b-cow-img", ""); |
| unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC)); |
| ASSERT_GE(fd, 0); |
| |
| // COW cannot be removed due to open fd, so expect a soft failure. |
| ASSERT_EQ(UpdateState::MergeNeedsReboot, init->InitiateMergeAndWait()); |
| |
| // Simulate shutting down the device. |
| fd.reset(); |
| ASSERT_TRUE(UnmapAll()); |
| |
| // init does first stage mount again. |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| |
| // sys_b should be mapped as a dm-linear device directly. |
| ASSERT_FALSE(sm->IsSnapshotDevice("sys_b", nullptr)); |
| |
| // Merge should be able to complete now. |
| ASSERT_EQ(UpdateState::MergeCompleted, init->InitiateMergeAndWait()); |
| } |
| |
| class MetadataMountedTest : public SnapshotUpdateTest { |
| public: |
| void SetUp() override { |
| metadata_dir_ = test_device->GetMetadataDir(); |
| ASSERT_TRUE(ReadDefaultFstab(&fstab_)); |
| } |
| void TearDown() override { |
| SetUp(); |
| // Remount /metadata |
| test_device->set_recovery(false); |
| EXPECT_TRUE(android::fs_mgr::EnsurePathMounted(&fstab_, metadata_dir_)); |
| } |
| AssertionResult IsMetadataMounted() { |
| Fstab mounted_fstab; |
| if (!ReadFstabFromFile("/proc/mounts", &mounted_fstab)) { |
| ADD_FAILURE() << "Failed to scan mounted volumes"; |
| return AssertionFailure() << "Failed to scan mounted volumes"; |
| } |
| |
| auto entry = GetEntryForPath(&fstab_, metadata_dir_); |
| if (entry == nullptr) { |
| return AssertionFailure() << "No mount point found in fstab for path " << metadata_dir_; |
| } |
| |
| auto mv = GetEntryForMountPoint(&mounted_fstab, entry->mount_point); |
| if (mv == nullptr) { |
| return AssertionFailure() << metadata_dir_ << " is not mounted"; |
| } |
| return AssertionSuccess() << metadata_dir_ << " is mounted"; |
| } |
| std::string metadata_dir_; |
| Fstab fstab_; |
| }; |
| |
| TEST_F(MetadataMountedTest, Android) { |
| auto device = sm->EnsureMetadataMounted(); |
| EXPECT_NE(nullptr, device); |
| device.reset(); |
| |
| EXPECT_TRUE(IsMetadataMounted()); |
| EXPECT_TRUE(sm->CancelUpdate()) << "Metadata dir should never be unmounted in Android mode"; |
| } |
| |
| TEST_F(MetadataMountedTest, Recovery) { |
| test_device->set_recovery(true); |
| metadata_dir_ = test_device->GetMetadataDir(); |
| |
| EXPECT_TRUE(android::fs_mgr::EnsurePathUnmounted(&fstab_, metadata_dir_)); |
| EXPECT_FALSE(IsMetadataMounted()); |
| |
| auto device = sm->EnsureMetadataMounted(); |
| EXPECT_NE(nullptr, device); |
| EXPECT_TRUE(IsMetadataMounted()); |
| |
| device.reset(); |
| EXPECT_FALSE(IsMetadataMounted()); |
| } |
| |
| class FlashAfterUpdateTest : public SnapshotUpdateTest, |
| public WithParamInterface<std::tuple<uint32_t, bool>> { |
| public: |
| AssertionResult InitiateMerge(const std::string& slot_suffix) { |
| auto sm = SnapshotManager::New(new TestDeviceInfo(fake_super, slot_suffix)); |
| if (!sm->CreateLogicalAndSnapshotPartitions("super")) { |
| return AssertionFailure() << "Cannot CreateLogicalAndSnapshotPartitions"; |
| } |
| if (!sm->InitiateMerge()) { |
| return AssertionFailure() << "Cannot initiate merge"; |
| } |
| return AssertionSuccess(); |
| } |
| }; |
| |
| TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) { |
| // OTA client blindly unmaps all partitions that are possibly mapped. |
| for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { |
| ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); |
| } |
| |
| // Execute the update. |
| ASSERT_TRUE(sm->BeginUpdate()); |
| ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); |
| |
| ASSERT_TRUE(sm->FinishedSnapshotWrites()); |
| |
| // Simulate shutting down the device. |
| ASSERT_TRUE(UnmapAll()); |
| |
| if (std::get<1>(GetParam()) /* merge */) { |
| ASSERT_TRUE(InitiateMerge("_b")); |
| // Simulate shutting down the device after merge has initiated. |
| ASSERT_TRUE(UnmapAll()); |
| } |
| |
| auto flashed_slot = std::get<0>(GetParam()); |
| auto flashed_slot_suffix = SlotSuffixForSlotNumber(flashed_slot); |
| |
| // Simulate flashing |flashed_slot|. This clears the UPDATED flag. |
| auto flashed_builder = MetadataBuilder::New(*opener_, "super", flashed_slot); |
| flashed_builder->RemoveGroupAndPartitions(group_->name() + flashed_slot_suffix); |
| flashed_builder->RemoveGroupAndPartitions(kCowGroupName); |
| ASSERT_TRUE(FillFakeMetadata(flashed_builder.get(), manifest_, flashed_slot_suffix)); |
| |
| // Deliberately remove a partition from this build so that |
| // InitiateMerge do not switch state to "merging". This is possible in |
| // practice because the list of dynamic partitions may change. |
| ASSERT_NE(nullptr, flashed_builder->FindPartition("prd" + flashed_slot_suffix)); |
| flashed_builder->RemovePartition("prd" + flashed_slot_suffix); |
| |
| auto flashed_metadata = flashed_builder->Export(); |
| ASSERT_NE(nullptr, flashed_metadata); |
| ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *flashed_metadata, flashed_slot)); |
| |
| std::string path; |
| for (const auto& name : {"sys", "vnd"}) { |
| ASSERT_TRUE(CreateLogicalPartition( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata_slot = flashed_slot, |
| .partition_name = name + flashed_slot_suffix, |
| .timeout_ms = 1s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)); |
| ASSERT_TRUE(WriteRandomData(path)); |
| auto hash = GetHash(path); |
| ASSERT_TRUE(hash.has_value()); |
| hashes_[name + flashed_slot_suffix] = *hash; |
| } |
| |
| // Simulate shutting down the device after flash. |
| ASSERT_TRUE(UnmapAll()); |
| |
| // Simulate reboot. After reboot, init does first stage mount. |
| auto init = SnapshotManager::NewForFirstStageMount( |
| new TestDeviceInfo(fake_super, flashed_slot_suffix)); |
| ASSERT_NE(init, nullptr); |
| if (init->NeedSnapshotsInFirstStageMount()) { |
| ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super")); |
| } else { |
| for (const auto& name : {"sys", "vnd"}) { |
| ASSERT_TRUE(CreateLogicalPartition( |
| CreateLogicalPartitionParams{ |
| .block_device = fake_super, |
| .metadata_slot = flashed_slot, |
| .partition_name = name + flashed_slot_suffix, |
| .timeout_ms = 1s, |
| .partition_opener = opener_.get(), |
| }, |
| &path)); |
| } |
| } |
| |
| // Check that the target partitions have the same content. |
| for (const auto& name : {"sys", "vnd"}) { |
| ASSERT_TRUE(IsPartitionUnchanged(name + flashed_slot_suffix)); |
| } |
| |
| // There should be no snapshot to merge. |
| auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, flashed_slot_suffix)); |
| ASSERT_EQ(UpdateState::Cancelled, new_sm->InitiateMergeAndWait()); |
| |
| // Next OTA calls CancelUpdate no matter what. |
| ASSERT_TRUE(new_sm->CancelUpdate()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(Snapshot, FlashAfterUpdateTest, Combine(Values(0, 1), Bool()), |
| [](const TestParamInfo<FlashAfterUpdateTest::ParamType>& info) { |
| return "Flash"s + (std::get<0>(info.param) ? "New"s : "Old"s) + |
| "Slot"s + (std::get<1>(info.param) ? "After"s : "Before"s) + |
| "Merge"s; |
| }); |
| |
| } // namespace snapshot |
| } // namespace android |
| |
| using namespace android::snapshot; |
| |
| bool Mkdir(const std::string& path) { |
| if (mkdir(path.c_str(), 0700) && errno != EEXIST) { |
| std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; |
| return false; |
| } |
| return true; |
| } |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| |
| std::vector<std::string> paths = { |
| // clang-format off |
| "/data/gsi/ota/test", |
| "/data/gsi/ota/test/super", |
| "/metadata/gsi/ota/test", |
| "/metadata/gsi/ota/test/super", |
| "/metadata/ota/test", |
| "/metadata/ota/test/snapshots", |
| // clang-format on |
| }; |
| for (const auto& path : paths) { |
| if (!Mkdir(path)) { |
| return 1; |
| } |
| } |
| |
| // Create this once, otherwise, gsid will start/stop between each test. |
| test_device = new TestDeviceInfo(); |
| sm = SnapshotManager::New(test_device); |
| if (!sm) { |
| std::cerr << "Could not create snapshot manager\n"; |
| return 1; |
| } |
| |
| // Clean up previous run. |
| MetadataMountedTest().TearDown(); |
| SnapshotUpdateTest().Cleanup(); |
| SnapshotTest().Cleanup(); |
| |
| // Use a separate image manager for our fake super partition. |
| auto super_images = IImageManager::Open("ota/test/super", 10s); |
| if (!super_images) { |
| std::cerr << "Could not create image manager\n"; |
| return 1; |
| } |
| |
| // Clean up any old copy. |
| DeleteBackingImage(super_images.get(), "fake-super"); |
| |
| // Create and map the fake super partition. |
| static constexpr int kImageFlags = |
| IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL; |
| if (!super_images->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) { |
| std::cerr << "Could not create fake super partition\n"; |
| return 1; |
| } |
| if (!super_images->MapImageDevice("fake-super", 10s, &fake_super)) { |
| std::cerr << "Could not map fake super partition\n"; |
| return 1; |
| } |
| test_device->set_fake_super(fake_super); |
| |
| auto result = RUN_ALL_TESTS(); |
| |
| DeleteBackingImage(super_images.get(), "fake-super"); |
| |
| return result; |
| } |