Loading fs_mgr/libdm/dm.cpp +14 −0 Original line number Diff line number Diff line Loading @@ -210,6 +210,20 @@ bool DeviceMapper::GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets) { return true; } bool DeviceMapper::GetTargetByName(const std::string& name, DmTargetTypeInfo* info) { std::vector<DmTargetTypeInfo> targets; if (!GetAvailableTargets(&targets)) { return false; } for (const auto& target : targets) { if (target.name() == name) { if (info) *info = target; return true; } } return false; } bool DeviceMapper::GetAvailableDevices(std::vector<DmBlockDevice>* devices) { devices->clear(); Loading fs_mgr/libdm/dm_target.cpp +78 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <android-base/logging.h> #include <android-base/macros.h> #include <android-base/parseint.h> #include <android-base/strings.h> #include <libdm/dm.h> Loading Loading @@ -115,5 +116,82 @@ std::string DmTargetAndroidVerity::GetParameterString() const { return keyid_ + " " + block_device_; } std::string DmTargetSnapshot::name() const { if (mode_ == SnapshotStorageMode::Merge) { return "snapshot-merge"; } return "snapshot"; } std::string DmTargetSnapshot::GetParameterString() const { std::string mode; switch (mode_) { case SnapshotStorageMode::Persistent: case SnapshotStorageMode::Merge: // Note: "O" lets us query for overflow in the status message. This // is only supported on kernels 4.4+. On earlier kernels, an overflow // will be reported as "Invalid" in the status string. mode = "P"; if (ReportsOverflow(name())) { mode += "O"; } break; case SnapshotStorageMode::Transient: mode = "N"; break; default: LOG(ERROR) << "DmTargetSnapshot unknown mode"; break; } return base_device_ + " " + cow_device_ + " " + mode + " " + std::to_string(chunk_size_); } bool DmTargetSnapshot::ReportsOverflow(const std::string& target_type) { DeviceMapper& dm = DeviceMapper::Instance(); DmTargetTypeInfo info; if (!dm.GetTargetByName(target_type, &info)) { return false; } if (target_type == "snapshot") { return info.IsAtLeast(1, 15, 0); } if (target_type == "snapshot-merge") { return info.IsAtLeast(1, 4, 0); } return false; } bool DmTargetSnapshot::ParseStatusText(const std::string& text, Status* status) { auto sections = android::base::Split(text, " "); if (sections.size() == 1) { // This is probably an error code, "Invalid" is possible as is "Overflow" // on 4.4+. status->error = text; return true; } if (sections.size() != 2) { LOG(ERROR) << "snapshot status should have two components"; return false; } auto sector_info = android::base::Split(sections[0], "/"); if (sector_info.size() != 2) { LOG(ERROR) << "snapshot sector info should have two components"; return false; } if (!android::base::ParseUint(sections[1], &status->metadata_sectors)) { LOG(ERROR) << "could not parse metadata sectors"; return false; } if (!android::base::ParseUint(sector_info[0], &status->sectors_allocated)) { LOG(ERROR) << "could not parse sectors allocated"; return false; } if (!android::base::ParseUint(sector_info[1], &status->total_sectors)) { LOG(ERROR) << "could not parse total sectors"; return false; } return true; } } // namespace dm } // namespace android fs_mgr/libdm/dm_test.cpp +247 −10 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ #include <chrono> #include <ctime> #include <iostream> #include <map> #include <thread> Loading @@ -35,21 +36,15 @@ #include "test_util.h" using namespace std; using namespace std::chrono_literals; using namespace android::dm; using unique_fd = android::base::unique_fd; TEST(libdm, HasMinimumTargets) { DeviceMapper& dm = DeviceMapper::Instance(); vector<DmTargetTypeInfo> targets; ASSERT_TRUE(dm.GetAvailableTargets(&targets)); map<string, DmTargetTypeInfo> by_name; for (const auto& target : targets) { by_name[target.name()] = target; } DmTargetTypeInfo info; auto iter = by_name.find("linear"); EXPECT_NE(iter, by_name.end()); DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.GetTargetByName("linear", &info)); } // Helper to ensure that device mapper devices are released. Loading Loading @@ -201,3 +196,245 @@ TEST(libdm, DmVerityArgsAvb2) { "2 fec_blocks 126955 fec_start 126955 restart_on_corruption ignore_zero_blocks"; EXPECT_EQ(target.GetParameterString(), expected); } TEST(libdm, DmSnapshotArgs) { DmTargetSnapshot target1(0, 512, "base", "cow", SnapshotStorageMode::Persistent, 8); if (DmTargetSnapshot::ReportsOverflow("snapshot")) { EXPECT_EQ(target1.GetParameterString(), "base cow PO 8"); } else { EXPECT_EQ(target1.GetParameterString(), "base cow P 8"); } EXPECT_EQ(target1.name(), "snapshot"); DmTargetSnapshot target2(0, 512, "base", "cow", SnapshotStorageMode::Transient, 8); EXPECT_EQ(target2.GetParameterString(), "base cow N 8"); EXPECT_EQ(target2.name(), "snapshot"); DmTargetSnapshot target3(0, 512, "base", "cow", SnapshotStorageMode::Merge, 8); if (DmTargetSnapshot::ReportsOverflow("snapshot-merge")) { EXPECT_EQ(target3.GetParameterString(), "base cow PO 8"); } else { EXPECT_EQ(target3.GetParameterString(), "base cow P 8"); } EXPECT_EQ(target3.name(), "snapshot-merge"); } TEST(libdm, DmSnapshotOriginArgs) { DmTargetSnapshotOrigin target(0, 512, "base"); EXPECT_EQ(target.GetParameterString(), "base"); EXPECT_EQ(target.name(), "snapshot-origin"); } class SnapshotTestHarness final { public: bool Setup(); bool Merge(); std::string origin_dev() const { return origin_dev_->path(); } std::string snapshot_dev() const { return snapshot_dev_->path(); } int base_fd() const { return base_fd_; } static const uint64_t kBaseDeviceSize = 1024 * 1024; static const uint64_t kCowDeviceSize = 1024 * 64; static const uint64_t kSectorSize = 512; private: void SetupImpl(); void MergeImpl(); unique_fd base_fd_; unique_fd cow_fd_; unique_ptr<LoopDevice> base_loop_; unique_ptr<LoopDevice> cow_loop_; unique_ptr<TempDevice> origin_dev_; unique_ptr<TempDevice> snapshot_dev_; bool setup_ok_ = false; bool merge_ok_ = false; }; bool SnapshotTestHarness::Setup() { SetupImpl(); return setup_ok_; } void SnapshotTestHarness::SetupImpl() { base_fd_ = CreateTempFile("base_device", kBaseDeviceSize); ASSERT_GE(base_fd_, 0); cow_fd_ = CreateTempFile("cow_device", kCowDeviceSize); ASSERT_GE(cow_fd_, 0); base_loop_ = std::make_unique<LoopDevice>(base_fd_); ASSERT_TRUE(base_loop_->valid()); cow_loop_ = std::make_unique<LoopDevice>(cow_fd_); ASSERT_TRUE(cow_loop_->valid()); DmTable origin_table; ASSERT_TRUE(origin_table.AddTarget(make_unique<DmTargetSnapshotOrigin>( 0, kBaseDeviceSize / kSectorSize, base_loop_->device()))); ASSERT_TRUE(origin_table.valid()); origin_dev_ = std::make_unique<TempDevice>("libdm-test-dm-snapshot-origin", origin_table); ASSERT_TRUE(origin_dev_->valid()); ASSERT_FALSE(origin_dev_->path().empty()); ASSERT_TRUE(origin_dev_->WaitForUdev()); // chunk size = 4K blocks. DmTable snap_table; ASSERT_TRUE(snap_table.AddTarget(make_unique<DmTargetSnapshot>( 0, kBaseDeviceSize / kSectorSize, base_loop_->device(), cow_loop_->device(), SnapshotStorageMode::Persistent, 8))); ASSERT_TRUE(snap_table.valid()); snapshot_dev_ = std::make_unique<TempDevice>("libdm-test-dm-snapshot", snap_table); ASSERT_TRUE(snapshot_dev_->valid()); ASSERT_FALSE(snapshot_dev_->path().empty()); ASSERT_TRUE(snapshot_dev_->WaitForUdev()); setup_ok_ = true; } bool SnapshotTestHarness::Merge() { MergeImpl(); return merge_ok_; } void SnapshotTestHarness::MergeImpl() { DmTable merge_table; ASSERT_TRUE(merge_table.AddTarget( make_unique<DmTargetSnapshot>(0, kBaseDeviceSize / kSectorSize, base_loop_->device(), cow_loop_->device(), SnapshotStorageMode::Merge, 8))); ASSERT_TRUE(merge_table.valid()); DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.LoadTableAndActivate("libdm-test-dm-snapshot", merge_table)); while (true) { vector<DeviceMapper::TargetInfo> status; ASSERT_TRUE(dm.GetTableStatus("libdm-test-dm-snapshot", &status)); ASSERT_EQ(status.size(), 1); ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")), 0); DmTargetSnapshot::Status merge_status; ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(status[0].data, &merge_status)); ASSERT_TRUE(merge_status.error.empty()); if (merge_status.sectors_allocated == merge_status.metadata_sectors) { break; } std::this_thread::sleep_for(250ms); } merge_ok_ = true; } bool CheckSnapshotAvailability() { DmTargetTypeInfo info; DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.GetTargetByName("snapshot", &info)) { cout << "snapshot module not enabled; skipping test" << std::endl; return false; } if (!dm.GetTargetByName("snapshot-merge", &info)) { cout << "snapshot-merge module not enabled; skipping test" << std::endl; return false; } if (!dm.GetTargetByName("snapshot-origin", &info)) { cout << "snapshot-origin module not enabled; skipping test" << std::endl; return false; } return true; } TEST(libdm, DmSnapshot) { if (!CheckSnapshotAvailability()) { return; } SnapshotTestHarness harness; ASSERT_TRUE(harness.Setup()); // Open the dm devices. unique_fd origin_fd(open(harness.origin_dev().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_GE(origin_fd, 0); unique_fd snapshot_fd(open(harness.snapshot_dev().c_str(), O_RDWR | O_CLOEXEC | O_SYNC)); ASSERT_GE(snapshot_fd, 0); // Write to the first block of the snapshot device. std::string data("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); ASSERT_TRUE(android::base::WriteFully(snapshot_fd, data.data(), data.size())); ASSERT_EQ(lseek(snapshot_fd, 0, SEEK_SET), 0); // We should get the same data back from the snapshot device. std::string read(data.size(), '\0'); ASSERT_TRUE(android::base::ReadFully(snapshot_fd, read.data(), read.size())); ASSERT_EQ(read, data); // We should see the original data from the origin device. std::string zeroes(data.size(), '\0'); ASSERT_TRUE(android::base::ReadFully(origin_fd, read.data(), read.size())); ASSERT_EQ(lseek(snapshot_fd, 0, SEEK_SET), 0); ASSERT_EQ(read, zeroes); // We should also see the original data from the base device. ASSERT_TRUE(android::base::ReadFully(harness.base_fd(), read.data(), read.size())); ASSERT_EQ(lseek(harness.base_fd(), 0, SEEK_SET), 0); ASSERT_EQ(read, zeroes); // Now, perform the merge and wait. ASSERT_TRUE(harness.Merge()); // Reading from the base device should give us the modified data. ASSERT_TRUE(android::base::ReadFully(harness.base_fd(), read.data(), read.size())); ASSERT_EQ(lseek(harness.base_fd(), 0, SEEK_SET), 0); ASSERT_EQ(read, data); } TEST(libdm, DmSnapshotOverflow) { if (!CheckSnapshotAvailability()) { return; } SnapshotTestHarness harness; ASSERT_TRUE(harness.Setup()); // Open the dm devices. unique_fd snapshot_fd(open(harness.snapshot_dev().c_str(), O_RDWR | O_CLOEXEC)); ASSERT_GE(snapshot_fd, 0); // Fill the copy-on-write device until it overflows. uint64_t bytes_remaining = SnapshotTestHarness::kCowDeviceSize; uint8_t byte = 1; while (bytes_remaining) { std::string data(4096, char(byte)); if (!android::base::WriteFully(snapshot_fd, data.data(), data.size())) { ASSERT_EQ(errno, EIO); break; } bytes_remaining -= data.size(); } // If writes succeed (because they are buffered), then we should expect an // fsync to fail with EIO. if (!bytes_remaining) { ASSERT_EQ(fsync(snapshot_fd), -1); ASSERT_EQ(errno, EIO); } DeviceMapper& dm = DeviceMapper::Instance(); vector<DeviceMapper::TargetInfo> target_status; ASSERT_TRUE(dm.GetTableStatus("libdm-test-dm-snapshot", &target_status)); ASSERT_EQ(target_status.size(), 1); ASSERT_EQ(strncmp(target_status[0].spec.target_type, "snapshot", strlen("snapshot")), 0); DmTargetSnapshot::Status status; ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(target_status[0].data, &status)); if (DmTargetSnapshot::ReportsOverflow("snapshot")) { ASSERT_EQ(status.error, "Overflow"); } else { ASSERT_EQ(status.error, "Invalid"); } } fs_mgr/libdm/include/libdm/dm.h +4 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,10 @@ class DeviceMapper final { // successfully read and stored in 'targets'. Returns 'false' otherwise. bool GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets); // Finds a target by name and returns its information if found. |info| may // be null to check for the existence of a target. bool GetTargetByName(const std::string& name, DmTargetTypeInfo* info); // Return 'true' if it can successfully read the list of device mapper block devices // currently created. 'devices' will be empty if the kernel interactions // were successful and there are no block devices at the moment. Returns Loading fs_mgr/libdm/include/libdm/dm_target.h +71 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,18 @@ class DmTargetTypeInfo { return std::to_string(major_) + "." + std::to_string(minor_) + "." + std::to_string(patch_); } uint32_t major_version() const { return major_; } uint32_t minor_version() const { return minor_; } uint32_t patch_level() const { return patch_; } bool IsAtLeast(uint32_t major, uint32_t minor, uint32_t patch) const { if (major_ > major) return true; if (major_ < major) return false; if (minor_ > minor) return true; if (minor_ < minor) return false; return patch_ >= patch; } private: std::string name_; uint32_t major_; Loading Loading @@ -170,6 +182,65 @@ class DmTargetBow final : public DmTarget { std::string target_string_; }; enum class SnapshotStorageMode { // The snapshot will be persisted to the COW device. Persistent, // The snapshot will be lost on reboot. Transient, // The snapshot will be merged from the COW device into the base device, // in the background. Merge }; // Writes to a snapshot device will be written to the given COW device. Reads // will read from the COW device or base device. The chunk size is specified // in sectors. class DmTargetSnapshot final : public DmTarget { public: DmTargetSnapshot(uint64_t start, uint64_t length, const std::string& base_device, const std::string& cow_device, SnapshotStorageMode mode, uint64_t chunk_size) : DmTarget(start, length), base_device_(base_device), cow_device_(cow_device), mode_(mode), chunk_size_(chunk_size) {} std::string name() const override; std::string GetParameterString() const override; bool Valid() const override { return true; } struct Status { uint64_t sectors_allocated; uint64_t total_sectors; uint64_t metadata_sectors; std::string error; }; static bool ParseStatusText(const std::string& text, Status* status); static bool ReportsOverflow(const std::string& target_type); private: std::string base_device_; std::string cow_device_; SnapshotStorageMode mode_; uint64_t chunk_size_; }; // snapshot-origin will read/write directly to the backing device, updating any // snapshot devices with a matching origin. class DmTargetSnapshotOrigin final : public DmTarget { public: DmTargetSnapshotOrigin(uint64_t start, uint64_t length, const std::string& device) : DmTarget(start, length), device_(device) {} std::string name() const override { return "snapshot-origin"; } std::string GetParameterString() const override { return device_; } bool Valid() const override { return true; } private: std::string device_; }; } // namespace dm } // namespace android Loading Loading
fs_mgr/libdm/dm.cpp +14 −0 Original line number Diff line number Diff line Loading @@ -210,6 +210,20 @@ bool DeviceMapper::GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets) { return true; } bool DeviceMapper::GetTargetByName(const std::string& name, DmTargetTypeInfo* info) { std::vector<DmTargetTypeInfo> targets; if (!GetAvailableTargets(&targets)) { return false; } for (const auto& target : targets) { if (target.name() == name) { if (info) *info = target; return true; } } return false; } bool DeviceMapper::GetAvailableDevices(std::vector<DmBlockDevice>* devices) { devices->clear(); Loading
fs_mgr/libdm/dm_target.cpp +78 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <android-base/logging.h> #include <android-base/macros.h> #include <android-base/parseint.h> #include <android-base/strings.h> #include <libdm/dm.h> Loading Loading @@ -115,5 +116,82 @@ std::string DmTargetAndroidVerity::GetParameterString() const { return keyid_ + " " + block_device_; } std::string DmTargetSnapshot::name() const { if (mode_ == SnapshotStorageMode::Merge) { return "snapshot-merge"; } return "snapshot"; } std::string DmTargetSnapshot::GetParameterString() const { std::string mode; switch (mode_) { case SnapshotStorageMode::Persistent: case SnapshotStorageMode::Merge: // Note: "O" lets us query for overflow in the status message. This // is only supported on kernels 4.4+. On earlier kernels, an overflow // will be reported as "Invalid" in the status string. mode = "P"; if (ReportsOverflow(name())) { mode += "O"; } break; case SnapshotStorageMode::Transient: mode = "N"; break; default: LOG(ERROR) << "DmTargetSnapshot unknown mode"; break; } return base_device_ + " " + cow_device_ + " " + mode + " " + std::to_string(chunk_size_); } bool DmTargetSnapshot::ReportsOverflow(const std::string& target_type) { DeviceMapper& dm = DeviceMapper::Instance(); DmTargetTypeInfo info; if (!dm.GetTargetByName(target_type, &info)) { return false; } if (target_type == "snapshot") { return info.IsAtLeast(1, 15, 0); } if (target_type == "snapshot-merge") { return info.IsAtLeast(1, 4, 0); } return false; } bool DmTargetSnapshot::ParseStatusText(const std::string& text, Status* status) { auto sections = android::base::Split(text, " "); if (sections.size() == 1) { // This is probably an error code, "Invalid" is possible as is "Overflow" // on 4.4+. status->error = text; return true; } if (sections.size() != 2) { LOG(ERROR) << "snapshot status should have two components"; return false; } auto sector_info = android::base::Split(sections[0], "/"); if (sector_info.size() != 2) { LOG(ERROR) << "snapshot sector info should have two components"; return false; } if (!android::base::ParseUint(sections[1], &status->metadata_sectors)) { LOG(ERROR) << "could not parse metadata sectors"; return false; } if (!android::base::ParseUint(sector_info[0], &status->sectors_allocated)) { LOG(ERROR) << "could not parse sectors allocated"; return false; } if (!android::base::ParseUint(sector_info[1], &status->total_sectors)) { LOG(ERROR) << "could not parse total sectors"; return false; } return true; } } // namespace dm } // namespace android
fs_mgr/libdm/dm_test.cpp +247 −10 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ #include <chrono> #include <ctime> #include <iostream> #include <map> #include <thread> Loading @@ -35,21 +36,15 @@ #include "test_util.h" using namespace std; using namespace std::chrono_literals; using namespace android::dm; using unique_fd = android::base::unique_fd; TEST(libdm, HasMinimumTargets) { DeviceMapper& dm = DeviceMapper::Instance(); vector<DmTargetTypeInfo> targets; ASSERT_TRUE(dm.GetAvailableTargets(&targets)); map<string, DmTargetTypeInfo> by_name; for (const auto& target : targets) { by_name[target.name()] = target; } DmTargetTypeInfo info; auto iter = by_name.find("linear"); EXPECT_NE(iter, by_name.end()); DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.GetTargetByName("linear", &info)); } // Helper to ensure that device mapper devices are released. Loading Loading @@ -201,3 +196,245 @@ TEST(libdm, DmVerityArgsAvb2) { "2 fec_blocks 126955 fec_start 126955 restart_on_corruption ignore_zero_blocks"; EXPECT_EQ(target.GetParameterString(), expected); } TEST(libdm, DmSnapshotArgs) { DmTargetSnapshot target1(0, 512, "base", "cow", SnapshotStorageMode::Persistent, 8); if (DmTargetSnapshot::ReportsOverflow("snapshot")) { EXPECT_EQ(target1.GetParameterString(), "base cow PO 8"); } else { EXPECT_EQ(target1.GetParameterString(), "base cow P 8"); } EXPECT_EQ(target1.name(), "snapshot"); DmTargetSnapshot target2(0, 512, "base", "cow", SnapshotStorageMode::Transient, 8); EXPECT_EQ(target2.GetParameterString(), "base cow N 8"); EXPECT_EQ(target2.name(), "snapshot"); DmTargetSnapshot target3(0, 512, "base", "cow", SnapshotStorageMode::Merge, 8); if (DmTargetSnapshot::ReportsOverflow("snapshot-merge")) { EXPECT_EQ(target3.GetParameterString(), "base cow PO 8"); } else { EXPECT_EQ(target3.GetParameterString(), "base cow P 8"); } EXPECT_EQ(target3.name(), "snapshot-merge"); } TEST(libdm, DmSnapshotOriginArgs) { DmTargetSnapshotOrigin target(0, 512, "base"); EXPECT_EQ(target.GetParameterString(), "base"); EXPECT_EQ(target.name(), "snapshot-origin"); } class SnapshotTestHarness final { public: bool Setup(); bool Merge(); std::string origin_dev() const { return origin_dev_->path(); } std::string snapshot_dev() const { return snapshot_dev_->path(); } int base_fd() const { return base_fd_; } static const uint64_t kBaseDeviceSize = 1024 * 1024; static const uint64_t kCowDeviceSize = 1024 * 64; static const uint64_t kSectorSize = 512; private: void SetupImpl(); void MergeImpl(); unique_fd base_fd_; unique_fd cow_fd_; unique_ptr<LoopDevice> base_loop_; unique_ptr<LoopDevice> cow_loop_; unique_ptr<TempDevice> origin_dev_; unique_ptr<TempDevice> snapshot_dev_; bool setup_ok_ = false; bool merge_ok_ = false; }; bool SnapshotTestHarness::Setup() { SetupImpl(); return setup_ok_; } void SnapshotTestHarness::SetupImpl() { base_fd_ = CreateTempFile("base_device", kBaseDeviceSize); ASSERT_GE(base_fd_, 0); cow_fd_ = CreateTempFile("cow_device", kCowDeviceSize); ASSERT_GE(cow_fd_, 0); base_loop_ = std::make_unique<LoopDevice>(base_fd_); ASSERT_TRUE(base_loop_->valid()); cow_loop_ = std::make_unique<LoopDevice>(cow_fd_); ASSERT_TRUE(cow_loop_->valid()); DmTable origin_table; ASSERT_TRUE(origin_table.AddTarget(make_unique<DmTargetSnapshotOrigin>( 0, kBaseDeviceSize / kSectorSize, base_loop_->device()))); ASSERT_TRUE(origin_table.valid()); origin_dev_ = std::make_unique<TempDevice>("libdm-test-dm-snapshot-origin", origin_table); ASSERT_TRUE(origin_dev_->valid()); ASSERT_FALSE(origin_dev_->path().empty()); ASSERT_TRUE(origin_dev_->WaitForUdev()); // chunk size = 4K blocks. DmTable snap_table; ASSERT_TRUE(snap_table.AddTarget(make_unique<DmTargetSnapshot>( 0, kBaseDeviceSize / kSectorSize, base_loop_->device(), cow_loop_->device(), SnapshotStorageMode::Persistent, 8))); ASSERT_TRUE(snap_table.valid()); snapshot_dev_ = std::make_unique<TempDevice>("libdm-test-dm-snapshot", snap_table); ASSERT_TRUE(snapshot_dev_->valid()); ASSERT_FALSE(snapshot_dev_->path().empty()); ASSERT_TRUE(snapshot_dev_->WaitForUdev()); setup_ok_ = true; } bool SnapshotTestHarness::Merge() { MergeImpl(); return merge_ok_; } void SnapshotTestHarness::MergeImpl() { DmTable merge_table; ASSERT_TRUE(merge_table.AddTarget( make_unique<DmTargetSnapshot>(0, kBaseDeviceSize / kSectorSize, base_loop_->device(), cow_loop_->device(), SnapshotStorageMode::Merge, 8))); ASSERT_TRUE(merge_table.valid()); DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.LoadTableAndActivate("libdm-test-dm-snapshot", merge_table)); while (true) { vector<DeviceMapper::TargetInfo> status; ASSERT_TRUE(dm.GetTableStatus("libdm-test-dm-snapshot", &status)); ASSERT_EQ(status.size(), 1); ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")), 0); DmTargetSnapshot::Status merge_status; ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(status[0].data, &merge_status)); ASSERT_TRUE(merge_status.error.empty()); if (merge_status.sectors_allocated == merge_status.metadata_sectors) { break; } std::this_thread::sleep_for(250ms); } merge_ok_ = true; } bool CheckSnapshotAvailability() { DmTargetTypeInfo info; DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.GetTargetByName("snapshot", &info)) { cout << "snapshot module not enabled; skipping test" << std::endl; return false; } if (!dm.GetTargetByName("snapshot-merge", &info)) { cout << "snapshot-merge module not enabled; skipping test" << std::endl; return false; } if (!dm.GetTargetByName("snapshot-origin", &info)) { cout << "snapshot-origin module not enabled; skipping test" << std::endl; return false; } return true; } TEST(libdm, DmSnapshot) { if (!CheckSnapshotAvailability()) { return; } SnapshotTestHarness harness; ASSERT_TRUE(harness.Setup()); // Open the dm devices. unique_fd origin_fd(open(harness.origin_dev().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_GE(origin_fd, 0); unique_fd snapshot_fd(open(harness.snapshot_dev().c_str(), O_RDWR | O_CLOEXEC | O_SYNC)); ASSERT_GE(snapshot_fd, 0); // Write to the first block of the snapshot device. std::string data("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); ASSERT_TRUE(android::base::WriteFully(snapshot_fd, data.data(), data.size())); ASSERT_EQ(lseek(snapshot_fd, 0, SEEK_SET), 0); // We should get the same data back from the snapshot device. std::string read(data.size(), '\0'); ASSERT_TRUE(android::base::ReadFully(snapshot_fd, read.data(), read.size())); ASSERT_EQ(read, data); // We should see the original data from the origin device. std::string zeroes(data.size(), '\0'); ASSERT_TRUE(android::base::ReadFully(origin_fd, read.data(), read.size())); ASSERT_EQ(lseek(snapshot_fd, 0, SEEK_SET), 0); ASSERT_EQ(read, zeroes); // We should also see the original data from the base device. ASSERT_TRUE(android::base::ReadFully(harness.base_fd(), read.data(), read.size())); ASSERT_EQ(lseek(harness.base_fd(), 0, SEEK_SET), 0); ASSERT_EQ(read, zeroes); // Now, perform the merge and wait. ASSERT_TRUE(harness.Merge()); // Reading from the base device should give us the modified data. ASSERT_TRUE(android::base::ReadFully(harness.base_fd(), read.data(), read.size())); ASSERT_EQ(lseek(harness.base_fd(), 0, SEEK_SET), 0); ASSERT_EQ(read, data); } TEST(libdm, DmSnapshotOverflow) { if (!CheckSnapshotAvailability()) { return; } SnapshotTestHarness harness; ASSERT_TRUE(harness.Setup()); // Open the dm devices. unique_fd snapshot_fd(open(harness.snapshot_dev().c_str(), O_RDWR | O_CLOEXEC)); ASSERT_GE(snapshot_fd, 0); // Fill the copy-on-write device until it overflows. uint64_t bytes_remaining = SnapshotTestHarness::kCowDeviceSize; uint8_t byte = 1; while (bytes_remaining) { std::string data(4096, char(byte)); if (!android::base::WriteFully(snapshot_fd, data.data(), data.size())) { ASSERT_EQ(errno, EIO); break; } bytes_remaining -= data.size(); } // If writes succeed (because they are buffered), then we should expect an // fsync to fail with EIO. if (!bytes_remaining) { ASSERT_EQ(fsync(snapshot_fd), -1); ASSERT_EQ(errno, EIO); } DeviceMapper& dm = DeviceMapper::Instance(); vector<DeviceMapper::TargetInfo> target_status; ASSERT_TRUE(dm.GetTableStatus("libdm-test-dm-snapshot", &target_status)); ASSERT_EQ(target_status.size(), 1); ASSERT_EQ(strncmp(target_status[0].spec.target_type, "snapshot", strlen("snapshot")), 0); DmTargetSnapshot::Status status; ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(target_status[0].data, &status)); if (DmTargetSnapshot::ReportsOverflow("snapshot")) { ASSERT_EQ(status.error, "Overflow"); } else { ASSERT_EQ(status.error, "Invalid"); } }
fs_mgr/libdm/include/libdm/dm.h +4 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,10 @@ class DeviceMapper final { // successfully read and stored in 'targets'. Returns 'false' otherwise. bool GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets); // Finds a target by name and returns its information if found. |info| may // be null to check for the existence of a target. bool GetTargetByName(const std::string& name, DmTargetTypeInfo* info); // Return 'true' if it can successfully read the list of device mapper block devices // currently created. 'devices' will be empty if the kernel interactions // were successful and there are no block devices at the moment. Returns Loading
fs_mgr/libdm/include/libdm/dm_target.h +71 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,18 @@ class DmTargetTypeInfo { return std::to_string(major_) + "." + std::to_string(minor_) + "." + std::to_string(patch_); } uint32_t major_version() const { return major_; } uint32_t minor_version() const { return minor_; } uint32_t patch_level() const { return patch_; } bool IsAtLeast(uint32_t major, uint32_t minor, uint32_t patch) const { if (major_ > major) return true; if (major_ < major) return false; if (minor_ > minor) return true; if (minor_ < minor) return false; return patch_ >= patch; } private: std::string name_; uint32_t major_; Loading Loading @@ -170,6 +182,65 @@ class DmTargetBow final : public DmTarget { std::string target_string_; }; enum class SnapshotStorageMode { // The snapshot will be persisted to the COW device. Persistent, // The snapshot will be lost on reboot. Transient, // The snapshot will be merged from the COW device into the base device, // in the background. Merge }; // Writes to a snapshot device will be written to the given COW device. Reads // will read from the COW device or base device. The chunk size is specified // in sectors. class DmTargetSnapshot final : public DmTarget { public: DmTargetSnapshot(uint64_t start, uint64_t length, const std::string& base_device, const std::string& cow_device, SnapshotStorageMode mode, uint64_t chunk_size) : DmTarget(start, length), base_device_(base_device), cow_device_(cow_device), mode_(mode), chunk_size_(chunk_size) {} std::string name() const override; std::string GetParameterString() const override; bool Valid() const override { return true; } struct Status { uint64_t sectors_allocated; uint64_t total_sectors; uint64_t metadata_sectors; std::string error; }; static bool ParseStatusText(const std::string& text, Status* status); static bool ReportsOverflow(const std::string& target_type); private: std::string base_device_; std::string cow_device_; SnapshotStorageMode mode_; uint64_t chunk_size_; }; // snapshot-origin will read/write directly to the backing device, updating any // snapshot devices with a matching origin. class DmTargetSnapshotOrigin final : public DmTarget { public: DmTargetSnapshotOrigin(uint64_t start, uint64_t length, const std::string& device) : DmTarget(start, length), device_(device) {} std::string name() const override { return "snapshot-origin"; } std::string GetParameterString() const override { return device_; } bool Valid() const override { return true; } private: std::string device_; }; } // namespace dm } // namespace android Loading