diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp index 78a9005a1..bb7d8b311 100644 --- a/fs_mgr/libdm/dm.cpp +++ b/fs_mgr/libdm/dm.cpp @@ -429,7 +429,7 @@ bool DeviceMapper::GetDmDevicePathByName(const std::string& name, std::string* p // Accepts a device mapper device name (like system_a, vendor_b etc) and // returns its UUID. -bool DeviceMapper::GetDmDeviceUUIDByName(const std::string& name, std::string* uuid) { +bool DeviceMapper::GetDmDeviceUuidByName(const std::string& name, std::string* uuid) { struct dm_ioctl io; InitIo(&io, name); if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) { diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h index 0b2553fc7..5d6db467b 100644 --- a/fs_mgr/libdm/include/libdm/dm.h +++ b/fs_mgr/libdm/include/libdm/dm.h @@ -177,7 +177,7 @@ class DeviceMapper final { // // WaitForFile() should not be used in conjunction with this call, since it // could race with ueventd. - bool GetDmDeviceUUIDByName(const std::string& name, std::string* path); + bool GetDmDeviceUuidByName(const std::string& name, std::string* path); // Returns a device's unique path as generated by ueventd. This will return // true as long as the device has been created, even if ueventd has not diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp index d767022fb..6970ec153 100644 --- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp +++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp @@ -37,52 +37,86 @@ using android::base::unique_fd; class SnapuserdTest : public ::testing::Test { protected: void SetUp() override { - cow_ = std::make_unique(); - ASSERT_GE(cow_->fd, 0) << strerror(errno); + cow_system_ = std::make_unique(); + ASSERT_GE(cow_system_->fd, 0) << strerror(errno); + + cow_product_ = std::make_unique(); + ASSERT_GE(cow_product_->fd, 0) << strerror(errno); + + size_ = 100_MiB; } - void TearDown() override { cow_ = nullptr; } + void TearDown() override { + cow_system_ = nullptr; + cow_product_ = nullptr; + } - std::unique_ptr cow_; + std::unique_ptr cow_system_; + std::unique_ptr cow_product_; + + unique_fd sys_fd_; + unique_fd product_fd_; + size_t size_; + + int system_blksize_; + int product_blksize_; + std::string system_device_name_; + std::string product_device_name_; + + std::unique_ptr random_buffer_1_; + std::unique_ptr random_buffer_2_; + std::unique_ptr zero_buffer_; + std::unique_ptr system_buffer_; + std::unique_ptr product_buffer_; + + void Init(); + void CreateCowDevice(std::unique_ptr& cow); + void CreateSystemDmUser(); + void CreateProductDmUser(); + void StartSnapuserdDaemon(); + void CreateSnapshotDevices(); + + void TestIO(unique_fd& snapshot_fd, std::unique_ptr&& buf); }; -TEST_F(SnapuserdTest, ReadWrite) { - loff_t offset = 0; - size_t size = 100_MiB; +void SnapuserdTest::Init() { unique_fd rnd_fd; - unique_fd sys_fd; - unique_fd snapshot_fd; - unique_fd system_a_fd; - std::string cmd; + loff_t offset = 0; rnd_fd.reset(open("/dev/random", O_RDONLY)); ASSERT_TRUE(rnd_fd > 0); - std::unique_ptr random_buffer_1; - std::unique_ptr random_buffer_2; - std::unique_ptr system_buffer; - - random_buffer_1 = std::make_unique(size); - - random_buffer_2 = std::make_unique(size); - - system_buffer = std::make_unique(size); + random_buffer_1_ = std::make_unique(size_); + random_buffer_2_ = std::make_unique(size_); + system_buffer_ = std::make_unique(size_); + product_buffer_ = std::make_unique(size_); + zero_buffer_ = std::make_unique(size_); // Fill random data - for (size_t j = 0; j < (size / 1_MiB); j++) { - ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1.get() + offset, 1_MiB, 0), true); + for (size_t j = 0; j < (size_ / 1_MiB); j++) { + ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0), + true); - ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_2.get() + offset, 1_MiB, 0), true); + ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_2_.get() + offset, 1_MiB, 0), + true); offset += 1_MiB; } - sys_fd.reset(open("/dev/block/mapper/system_a", O_RDONLY)); - ASSERT_TRUE(sys_fd > 0); + sys_fd_.reset(open("/dev/block/mapper/system_a", O_RDONLY)); + ASSERT_TRUE(sys_fd_ > 0); + + product_fd_.reset(open("/dev/block/mapper/product_a", O_RDONLY)); + ASSERT_TRUE(product_fd_ > 0); // Read from system partition from offset 0 of size 100MB - ASSERT_EQ(ReadFullyAtOffset(sys_fd, system_buffer.get(), size, 0), true); + ASSERT_EQ(ReadFullyAtOffset(sys_fd_, system_buffer_.get(), size_, 0), true); + // Read from system partition from offset 0 of size 100MB + ASSERT_EQ(ReadFullyAtOffset(product_fd_, product_buffer_.get(), size_, 0), true); +} + +void SnapuserdTest::CreateCowDevice(std::unique_ptr& cow) { //================Create a COW file with the following operations=========== // // Create COW file which is gz compressed @@ -96,12 +130,12 @@ TEST_F(SnapuserdTest, ReadWrite) { options.compression = "gz"; CowWriter writer(options); - ASSERT_TRUE(writer.Initialize(cow_->fd)); + ASSERT_TRUE(writer.Initialize(cow->fd)); // Write 100MB random data to COW file which is gz compressed from block 0 - ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1.get(), size)); + ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1_.get(), size_)); - size_t num_blocks = size / options.block_size; + size_t num_blocks = size_ / options.block_size; size_t blk_start_copy = num_blocks; size_t blk_end_copy = blk_start_copy + num_blocks; size_t source_blk = 0; @@ -110,7 +144,7 @@ TEST_F(SnapuserdTest, ReadWrite) { // has to read from block 0 in system_a partition // // This initializes copy operation from block 0 of size 100 MB from - // /dev/block/mapper/system_a + // /dev/block/mapper/system_a or product_a for (size_t i = blk_start_copy; i < blk_end_copy; i++) { ASSERT_TRUE(writer.AddCopy(i, source_blk)); source_blk += 1; @@ -125,14 +159,17 @@ TEST_F(SnapuserdTest, ReadWrite) { // Final 100MB filled with random data which is gz compressed size_t blk_random2_replace_start = blk_zero_copy_end; - ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_2.get(), size)); + ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_2_.get(), size_)); // Flush operations ASSERT_TRUE(writer.Finalize()); - ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); + ASSERT_EQ(lseek(cow->fd, 0, SEEK_SET), 0); +} - //================Setup dm-snapshot and start snapuserd daemon=========== +void SnapuserdTest::CreateSystemDmUser() { + unique_fd system_a_fd; + std::string cmd; // Create a COW device. Number of sectors is chosen random which can // hold at least 400MB of data @@ -140,39 +177,77 @@ TEST_F(SnapuserdTest, ReadWrite) { system_a_fd.reset(open("/dev/block/mapper/system_a", O_RDONLY)); ASSERT_TRUE(system_a_fd > 0); - int blksize; - int err = ioctl(system_a_fd.get(), BLKGETSIZE, &blksize); - if (err < 0) { - ASSERT_TRUE(0); - } + int err = ioctl(system_a_fd.get(), BLKGETSIZE, &system_blksize_); + ASSERT_GE(err, 0); + + std::string str(cow_system_->path); + std::size_t found = str.find_last_of("/\\"); + ASSERT_NE(found, std::string::npos); + system_device_name_ = str.substr(found + 1); + cmd = "dmctl create " + system_device_name_ + " user 0 " + std::to_string(system_blksize_); - cmd = "dmctl create system_cow user 0 " + std::to_string(blksize); system(cmd.c_str()); +} +void SnapuserdTest::CreateProductDmUser() { + unique_fd product_a_fd; + std::string cmd; + + // Create a COW device. Number of sectors is chosen random which can + // hold at least 400MB of data + + product_a_fd.reset(open("/dev/block/mapper/product_a", O_RDONLY)); + ASSERT_TRUE(product_a_fd > 0); + + int err = ioctl(product_a_fd.get(), BLKGETSIZE, &product_blksize_); + ASSERT_GE(err, 0); + + std::string str(cow_product_->path); + std::size_t found = str.find_last_of("/\\"); + ASSERT_NE(found, std::string::npos); + product_device_name_ = str.substr(found + 1); + cmd = "dmctl create " + product_device_name_ + " user 0 " + std::to_string(product_blksize_); + + system(cmd.c_str()); +} + +void SnapuserdTest::StartSnapuserdDaemon() { // Start the snapuserd daemon if (fork() == 0) { - const char* argv[] = {"/system/bin/snapuserd", cow_->path, "/dev/block/mapper/system_a", - nullptr}; + const char* argv[] = {"/system/bin/snapuserd", cow_system_->path, + "/dev/block/mapper/system_a", cow_product_->path, + "/dev/block/mapper/product_a", nullptr}; if (execv(argv[0], const_cast(argv))) { ASSERT_TRUE(0); } } +} + +void SnapuserdTest::CreateSnapshotDevices() { + std::string cmd; + + cmd = "dmctl create system-snapshot -ro snapshot 0 " + std::to_string(system_blksize_); + cmd += " /dev/block/mapper/system_a"; + cmd += " /dev/block/mapper/" + system_device_name_; + cmd += " P 8"; + + system(cmd.c_str()); cmd.clear(); - cmd = "dmctl create system-snapshot -ro snapshot 0 " + std::to_string(blksize); - cmd += " /dev/block/mapper/system_a /dev/block/mapper/system_cow "; - cmd += "P 8"; + cmd = "dmctl create product-snapshot -ro snapshot 0 " + std::to_string(product_blksize_); + cmd += " /dev/block/mapper/product_a"; + cmd += " /dev/block/mapper/" + product_device_name_; + cmd += " P 8"; + system(cmd.c_str()); +} - // Wait so that snapshot device is created - sleep(5); - std::unique_ptr snapuserd_buffer = std::make_unique(size); +void SnapuserdTest::TestIO(unique_fd& snapshot_fd, std::unique_ptr&& buf) { + loff_t offset = 0; + std::unique_ptr buffer = std::move(buf); - offset = 0; - - snapshot_fd.reset(open("/dev/block/mapper/system-snapshot", O_RDONLY)); - ASSERT_TRUE(snapshot_fd > 0); + std::unique_ptr snapuserd_buffer = std::make_unique(size_); //================Start IO operation on dm-snapshot device================= // This will test the following paths: @@ -189,16 +264,16 @@ TEST_F(SnapuserdTest, ReadWrite) { // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace // op)->decompress_cow->return - ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size, offset), true); + ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); // Update the offset - offset += size; + offset += size_; - // Compare data with random_buffer_1. - ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_1.get(), size), 0); + // Compare data with random_buffer_1_. + ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_1_.get(), size_), 0); // Clear the buffer - memset(snapuserd_buffer.get(), 0, size); + memset(snapuserd_buffer.get(), 0, size_); // Read from snapshot device of size 100MB from offset 100MB. This tests the // copy operation. @@ -207,13 +282,13 @@ TEST_F(SnapuserdTest, ReadWrite) { // // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_from_system_a_partition // (copy op) -> return - ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size, offset), true); + ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); // Update the offset - offset += size; + offset += size_; - // Compare data with system_buffer. - ASSERT_EQ(memcmp(snapuserd_buffer.get(), system_buffer.get(), size), 0); + // Compare data with buffer. + ASSERT_EQ(memcmp(snapuserd_buffer.get(), buffer.get(), size_), 0); // Read from snapshot device of size 100MB from offset 200MB. This tests the // zero operation. @@ -222,16 +297,13 @@ TEST_F(SnapuserdTest, ReadWrite) { // // dm-snap->dm-snap-persistent->dm-user->snapuserd->fill_memory_with_zero // (zero op) -> return - ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size, offset), true); - - // Fill the random_buffer_1 with zero as we no longer need it - memset(random_buffer_1.get(), 0, size); + ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); // Compare data with zero filled buffer - ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_1.get(), size), 0); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), zero_buffer_.get(), size_), 0); // Update the offset - offset += size; + offset += size_; // Read from snapshot device of size 100MB from offset 300MB. This tests the // final replace operation. @@ -240,10 +312,34 @@ TEST_F(SnapuserdTest, ReadWrite) { // // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace // op)->decompress_cow->return - ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size, offset), true); + ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); - // Compare data with random_buffer_2. - ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_2.get(), size), 0); + // Compare data with random_buffer_2_. + ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_2_.get(), size_), 0); +} + +TEST_F(SnapuserdTest, ReadWrite) { + unique_fd snapshot_fd; + + Init(); + + CreateCowDevice(cow_system_); + CreateCowDevice(cow_product_); + + CreateSystemDmUser(); + CreateProductDmUser(); + + StartSnapuserdDaemon(); + + CreateSnapshotDevices(); + + snapshot_fd.reset(open("/dev/block/mapper/system-snapshot", O_RDONLY)); + ASSERT_TRUE(snapshot_fd > 0); + TestIO(snapshot_fd, std::move(system_buffer_)); + + snapshot_fd.reset(open("/dev/block/mapper/product-snapshot", O_RDONLY)); + ASSERT_TRUE(snapshot_fd > 0); + TestIO(snapshot_fd, std::move(product_buffer_)); } } // namespace snapshot diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp index 605af9b9c..d3f4f70de 100644 --- a/fs_mgr/libsnapshot/snapuserd.cpp +++ b/fs_mgr/libsnapshot/snapuserd.cpp @@ -17,10 +17,12 @@ #include #include +#include #include #include #include #include +#include #include #include @@ -35,6 +37,7 @@ namespace android { namespace snapshot { +using namespace android; using namespace android::dm; using android::base::unique_fd; @@ -45,6 +48,60 @@ static constexpr size_t PAYLOAD_SIZE = (1UL << 16); static_assert(PAYLOAD_SIZE >= BLOCK_SIZE); +class Target { + public: + // Represents an already-created Target, which is referenced by UUID. + Target(std::string uuid) : uuid_(uuid) {} + + const auto& uuid() { return uuid_; } + std::string control_path() { return std::string("/dev/dm-user-") + uuid(); } + + private: + const std::string uuid_; +}; + +class Daemon { + // The Daemon class is a singleton to avoid + // instantiating more than once + public: + static Daemon& Instance() { + static Daemon instance; + return instance; + } + + bool IsRunning(); + + private: + bool is_running_; + + Daemon(); + Daemon(Daemon const&) = delete; + void operator=(Daemon const&) = delete; + + static void SignalHandler(int signal); +}; + +Daemon::Daemon() { + is_running_ = true; + signal(SIGINT, Daemon::SignalHandler); + signal(SIGTERM, Daemon::SignalHandler); +} + +bool Daemon::IsRunning() { + return is_running_; +} + +void Daemon::SignalHandler(int signal) { + LOG(DEBUG) << "Snapuserd received signal: " << signal; + switch (signal) { + case SIGINT: + case SIGTERM: { + Daemon::Instance().is_running_ = false; + break; + } + } +} + class BufferSink : public IByteSink { public: void Initialize(size_t size) { @@ -558,10 +615,26 @@ int Snapuserd::Run() { return 1; } - // TODO: use UUID to support multiple partitions - ctrl_fd_.reset(open("/dev/dm-user", O_RDWR)); + std::string str(in_cow_device_); + std::size_t found = str.find_last_of("/\\"); + CHECK(found != std::string::npos); + std::string device_name = str.substr(found + 1); + + LOG(DEBUG) << "Fetching UUID for: " << device_name; + + auto& dm = dm::DeviceMapper::Instance(); + std::string uuid; + if (!dm.GetDmDeviceUuidByName(device_name, &uuid)) { + LOG(ERROR) << "Unable to find UUID for " << in_cow_device_; + return 1; + } + + LOG(DEBUG) << "UUID: " << uuid; + Target t(uuid); + + ctrl_fd_.reset(open(t.control_path().c_str(), O_RDWR)); if (ctrl_fd_ < 0) { - LOG(ERROR) << "Unable to open /dev/dm-user"; + LOG(ERROR) << "Unable to open " << t.control_path(); return 1; } @@ -682,9 +755,30 @@ int Snapuserd::Run() { } // namespace snapshot } // namespace android +void run_thread(std::string cow_device, std::string backing_device) { + android::snapshot::Snapuserd snapd(cow_device, backing_device); + snapd.Run(); +} + int main([[maybe_unused]] int argc, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); - android::snapshot::Snapuserd snapd(argv[1], argv[2]); - return snapd.Run(); + android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance(); + + while (daemon.IsRunning()) { + // TODO: This is hardcoded wherein: + // argv[1] = system_cow, argv[2] = /dev/block/mapper/system_a + // argv[3] = product_cow, argv[4] = /dev/block/mapper/product_a + // + // This should be fixed based on some kind of IPC or setup a + // command socket and spin up the thread based when a new + // partition is visible. + std::thread system_a(run_thread, argv[1], argv[2]); + std::thread product_a(run_thread, argv[3], argv[4]); + + system_a.join(); + product_a.join(); + } + + return 0; } diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp index 1bfd9b2c5..9edcda705 100644 --- a/fs_mgr/tools/dmctl.cpp +++ b/fs_mgr/tools/dmctl.cpp @@ -38,6 +38,7 @@ #include using namespace std::literals::string_literals; +using namespace std::chrono_literals; using namespace android::dm; using DmBlockDevice = ::android::dm::DeviceMapper::DmBlockDevice; @@ -242,8 +243,9 @@ static int DmCreateCmdHandler(int argc, char** argv) { return ret; } + std::string ignore_path; DeviceMapper& dm = DeviceMapper::Instance(); - if (!dm.CreateDevice(name, table)) { + if (!dm.CreateDevice(name, table, &ignore_path, 5s)) { std::cerr << "Failed to create device-mapper device with name: " << name << std::endl; return -EIO; } @@ -392,7 +394,7 @@ static int GetPathCmdHandler(int argc, char** argv) { return 0; } -static int GetUUIDCmdHandler(int argc, char** argv) { +static int GetUuidCmdHandler(int argc, char** argv) { if (argc != 1) { std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl; return -EINVAL; @@ -400,7 +402,7 @@ static int GetUUIDCmdHandler(int argc, char** argv) { DeviceMapper& dm = DeviceMapper::Instance(); std::string uuid; - if (!dm.GetDmDeviceUUIDByName(argv[0], &uuid)) { + if (!dm.GetDmDeviceUuidByName(argv[0], &uuid)) { std::cerr << "Could not query uuid of device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } @@ -521,7 +523,7 @@ static std::map> cmdmap = { {"list", DmListCmdHandler}, {"help", HelpCmdHandler}, {"getpath", GetPathCmdHandler}, - {"getuuid", GetUUIDCmdHandler}, + {"getuuid", GetUuidCmdHandler}, {"info", InfoCmdHandler}, {"table", TableCmdHandler}, {"status", StatusCmdHandler},