diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index 34049d41c..e32fde062 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -563,6 +563,7 @@ cc_test { "libsnapshot_snapuserd", "libcutils_sockets", "libz", + "libfs_mgr", "libdm", ], header_libs: [ diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp index 6b7ada57e..09070325a 100644 --- a/fs_mgr/libsnapshot/cow_reader.cpp +++ b/fs_mgr/libsnapshot/cow_reader.cpp @@ -189,29 +189,138 @@ bool CowReader::ParseOps(std::optional label) { LOG(INFO) << "No COW Footer, recovered data"; } - if (header_.num_merge_ops > 0) { - uint64_t merge_ops = header_.num_merge_ops; - uint64_t metadata_ops = 0; - uint64_t current_op_num = 0; - - CHECK(ops_buffer->size() >= merge_ops); - while (merge_ops) { - auto& current_op = ops_buffer->data()[current_op_num]; - if (current_op.type == kCowLabelOp || current_op.type == kCowFooterOp) { - metadata_ops += 1; - } else { - merge_ops -= 1; - } - current_op_num += 1; - } - ops_buffer->erase(ops_buffer.get()->begin(), - ops_buffer.get()->begin() + header_.num_merge_ops + metadata_ops); - } - ops_ = ops_buffer; return true; } +void CowReader::InitializeMerge() { + uint64_t num_copy_ops = 0; + + // Remove all the metadata operations + ops_->erase(std::remove_if(ops_.get()->begin(), ops_.get()->end(), + [](CowOperation& op) { + return (op.type == kCowFooterOp || op.type == kCowLabelOp); + }), + ops_.get()->end()); + + // We will re-arrange the vector in such a way that + // kernel can batch merge. Ex: + // + // Existing COW format; All the copy operations + // are at the beginning. + // ======================================= + // Copy-op-1 - cow_op->new_block = 1 + // Copy-op-2 - cow_op->new_block = 2 + // Copy-op-3 - cow_op->new_block = 3 + // Replace-op-4 - cow_op->new_block = 6 + // Replace-op-5 - cow_op->new_block = 4 + // Replace-op-6 - cow_op->new_block = 8 + // Replace-op-7 - cow_op->new_block = 9 + // Zero-op-8 - cow_op->new_block = 7 + // Zero-op-9 - cow_op->new_block = 5 + // ======================================= + // + // First find the operation which isn't a copy-op + // and then sort all the operations in descending order + // with the key being cow_op->new_block (source block) + // + // The data-structure will look like: + // + // ======================================= + // Copy-op-1 - cow_op->new_block = 1 + // Copy-op-2 - cow_op->new_block = 2 + // Copy-op-3 - cow_op->new_block = 3 + // Replace-op-7 - cow_op->new_block = 9 + // Replace-op-6 - cow_op->new_block = 8 + // Zero-op-8 - cow_op->new_block = 7 + // Replace-op-4 - cow_op->new_block = 6 + // Zero-op-9 - cow_op->new_block = 5 + // Replace-op-5 - cow_op->new_block = 4 + // ======================================= + // + // Daemon will read the above data-structure in reverse-order + // when reading metadata. Thus, kernel will get the metadata + // in the following order: + // + // ======================================== + // Replace-op-5 - cow_op->new_block = 4 + // Zero-op-9 - cow_op->new_block = 5 + // Replace-op-4 - cow_op->new_block = 6 + // Zero-op-8 - cow_op->new_block = 7 + // Replace-op-6 - cow_op->new_block = 8 + // Replace-op-7 - cow_op->new_block = 9 + // Copy-op-3 - cow_op->new_block = 3 + // Copy-op-2 - cow_op->new_block = 2 + // Copy-op-1 - cow_op->new_block = 1 + // =========================================== + // + // When merging begins, kernel will start from the last + // metadata which was read: In the above format, Copy-op-1 + // will be the first merge operation. + // + // Now, batching of the merge operations happens only when + // 1: origin block numbers in the base device are contiguous + // (cow_op->new_block) and, + // 2: cow block numbers which are assigned by daemon in ReadMetadata() + // are contiguous. These are monotonically increasing numbers. + // + // When both (1) and (2) are true, kernel will batch merge the operations. + // However, we do not want copy operations to be batch merged as + // a crash or system reboot during an overlapping copy can drive the device + // to a corrupted state. Hence, merging of copy operations should always be + // done as a individual 4k block. In the above case, since the + // cow_op->new_block numbers are contiguous, we will ensure that the + // cow block numbers assigned in ReadMetadata() for these respective copy + // operations are not contiguous forcing kernel to issue merge for each + // copy operations without batch merging. + // + // For all the other operations viz. Replace and Zero op, the cow block + // numbers assigned by daemon will be contiguous allowing kernel to batch + // merge. + // + // The final format after assiging COW block numbers by the daemon will + // look something like: + // + // ========================================================= + // Replace-op-5 - cow_op->new_block = 4 cow-block-num = 2 + // Zero-op-9 - cow_op->new_block = 5 cow-block-num = 3 + // Replace-op-4 - cow_op->new_block = 6 cow-block-num = 4 + // Zero-op-8 - cow_op->new_block = 7 cow-block-num = 5 + // Replace-op-6 - cow_op->new_block = 8 cow-block-num = 6 + // Replace-op-7 - cow_op->new_block = 9 cow-block-num = 7 + // Copy-op-3 - cow_op->new_block = 3 cow-block-num = 9 + // Copy-op-2 - cow_op->new_block = 2 cow-block-num = 11 + // Copy-op-1 - cow_op->new_block = 1 cow-block-num = 13 + // ========================================================== + // + // Merge sequence will look like: + // + // Merge-1 - Copy-op-1 + // Merge-2 - Copy-op-2 + // Merge-3 - Copy-op-3 + // Merge-4 - Batch-merge {Replace-op-7, Replace-op-6, Zero-op-8, + // Replace-op-4, Zero-op-9, Replace-op-5 } + //============================================================== + + for (uint64_t i = 0; i < ops_->size(); i++) { + auto& current_op = ops_->data()[i]; + if (current_op.type != kCowCopyOp) { + break; + } + num_copy_ops += 1; + } + + std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(), + [](CowOperation& op1, CowOperation& op2) -> bool { + return op1.new_block > op2.new_block; + }); + + if (header_.num_merge_ops > 0) { + CHECK(ops_->size() >= header_.num_merge_ops); + ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops); + } +} + bool CowReader::GetHeader(CowHeader* header) { *header = header_; return true; diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp index 5483fd06f..16d931378 100644 --- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp +++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp @@ -12,10 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include +#include #include #include +#include #include +#include #include #include @@ -24,7 +28,9 @@ #include #include +#include #include +#include #include #include #include @@ -37,111 +43,203 @@ using namespace android::storage_literals; using android::base::unique_fd; using LoopDevice = android::dm::LoopDevice; using namespace std::chrono_literals; +using namespace android::dm; +using namespace std; -class SnapuserdTest : public ::testing::Test { - protected: - void SetUp() override { - // TODO: Daemon started through first stage - // init does not have permission to read files - // from /data/nativetest. - system("setenforce 0"); - cow_system_ = std::make_unique(); - ASSERT_GE(cow_system_->fd, 0) << strerror(errno); +static constexpr char kSnapuserdSocketTest[] = "snapuserdTest"; - cow_product_ = std::make_unique(); - ASSERT_GE(cow_product_->fd, 0) << strerror(errno); +class TempDevice { + public: + TempDevice(const std::string& name, const DmTable& table) + : dm_(DeviceMapper::Instance()), name_(name), valid_(false) { + valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5)); + } + TempDevice(TempDevice&& other) noexcept + : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) { + other.valid_ = false; + } + ~TempDevice() { + if (valid_) { + dm_.DeleteDevice(name_); + } + } + bool Destroy() { + if (!valid_) { + return false; + } + valid_ = false; + return dm_.DeleteDevice(name_); + } + const std::string& path() const { return path_; } + const std::string& name() const { return name_; } + bool valid() const { return valid_; } - cow_system_1_ = std::make_unique(); - ASSERT_GE(cow_system_1_->fd, 0) << strerror(errno); + TempDevice(const TempDevice&) = delete; + TempDevice& operator=(const TempDevice&) = delete; - cow_product_1_ = std::make_unique(); - ASSERT_GE(cow_product_1_->fd, 0) << strerror(errno); - - std::string path = android::base::GetExecutableDirectory(); - - system_a_ = std::make_unique(path); - ASSERT_GE(system_a_->fd, 0) << strerror(errno); - - product_a_ = std::make_unique(path); - ASSERT_GE(product_a_->fd, 0) << strerror(errno); - - size_ = 100_MiB; + TempDevice& operator=(TempDevice&& other) noexcept { + name_ = other.name_; + valid_ = other.valid_; + other.valid_ = false; + return *this; } - void TearDown() override { - system("setenforce 1"); - cow_system_ = nullptr; - cow_product_ = nullptr; - - cow_system_1_ = nullptr; - cow_product_1_ = nullptr; - } - - std::unique_ptr system_a_; - std::unique_ptr product_a_; - - std::unique_ptr system_a_loop_; - std::unique_ptr product_a_loop_; - - std::unique_ptr cow_system_; - std::unique_ptr cow_product_; - - std::unique_ptr cow_system_1_; - std::unique_ptr cow_product_1_; - - 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::string system_device_ctrl_name_; - std::string product_device_ctrl_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 InitCowDevices(); - void InitDaemon(); - void CreateCowDevice(std::unique_ptr& cow); - void CreateSystemDmUser(std::unique_ptr& cow); - void CreateProductDmUser(std::unique_ptr& cow); - void DeleteDmUser(std::unique_ptr& cow, std::string snapshot_device); - void StartSnapuserdDaemon(); - void CreateSnapshotDevices(); - void SwitchSnapshotDevices(); - - std::string GetSystemControlPath() { - return std::string("/dev/dm-user/") + system_device_ctrl_name_; - } - std::string GetProductControlPath() { - return std::string("/dev/dm-user/") + product_device_ctrl_name_; - } - - void TestIO(unique_fd& snapshot_fd, std::unique_ptr& buffer); - std::unique_ptr client_; + private: + DeviceMapper& dm_; + std::string name_; + std::string path_; + bool valid_; }; -void SnapuserdTest::Init() { +class CowSnapuserdTest final { + public: + bool Setup(); + bool Merge(); + void ValidateMerge(); + void ReadSnapshotDeviceAndValidate(); + void Shutdown(); + + std::string snapshot_dev() const { return snapshot_dev_->path(); } + + static const uint64_t kSectorSize = 512; + + private: + void SetupImpl(); + void MergeImpl(); + void CreateCowDevice(); + void CreateBaseDevice(); + void InitCowDevice(); + void SetDeviceControlName(); + void InitDaemon(); + void CreateDmUserDevice(); + void StartSnapuserdDaemon(); + void CreateSnapshotDevice(); + unique_fd CreateTempFile(const std::string& name, size_t size); + + unique_ptr base_loop_; + unique_ptr dmuser_dev_; + unique_ptr snapshot_dev_; + + std::string system_device_ctrl_name_; + std::string system_device_name_; + + unique_fd base_fd_; + std::unique_ptr cow_system_; + std::unique_ptr client_; + std::unique_ptr orig_buffer_; + std::unique_ptr merged_buffer_; + bool setup_ok_ = false; + bool merge_ok_ = false; + size_t size_ = 1_MiB; + int cow_num_sectors_; + int total_base_size_; +}; + +unique_fd CowSnapuserdTest::CreateTempFile(const std::string& name, size_t size) { + unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING)); + if (fd < 0) { + return {}; + } + if (size) { + if (ftruncate(fd, size) < 0) { + perror("ftruncate"); + return {}; + } + if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) { + perror("fcntl"); + return {}; + } + } + return fd; +} + +void CowSnapuserdTest::Shutdown() { + ASSERT_TRUE(client_->StopSnapuserd()); + ASSERT_TRUE(snapshot_dev_->Destroy()); + ASSERT_TRUE(dmuser_dev_->Destroy()); +} + +bool CowSnapuserdTest::Setup() { + SetupImpl(); + return setup_ok_; +} + +void CowSnapuserdTest::StartSnapuserdDaemon() { + pid_t pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + std::string arg0 = "/system/bin/snapuserd"; + std::string arg1 = kSnapuserdSocketTest; + char* const argv[] = {arg0.data(), arg1.data(), nullptr}; + ASSERT_GE(execv(arg0.c_str(), argv), 0); + } else { + client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s); + ASSERT_NE(client_, nullptr); + } +} + +void CowSnapuserdTest::CreateBaseDevice() { unique_fd rnd_fd; - loff_t offset = 0; - std::unique_ptr random_buffer = std::make_unique(1_MiB); + + total_base_size_ = (size_ * 4); + base_fd_ = CreateTempFile("base_device", total_base_size_); + ASSERT_GE(base_fd_, 0); rnd_fd.reset(open("/dev/random", O_RDONLY)); ASSERT_TRUE(rnd_fd > 0); - 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_); + std::unique_ptr random_buffer = std::make_unique(1_MiB); + + for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) { + ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true); + ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true); + } + + ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0); + + base_loop_ = std::make_unique(base_fd_, 10s); + ASSERT_TRUE(base_loop_->valid()); +} + +void CowSnapuserdTest::ReadSnapshotDeviceAndValidate() { + unique_fd snapshot_fd(open(snapshot_dev_->path().c_str(), O_RDONLY)); + ASSERT_TRUE(snapshot_fd > 0); + + std::unique_ptr snapuserd_buffer = std::make_unique(size_); + + // COPY + loff_t offset = 0; + ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0); + + // REPLACE + offset += size_; + ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0); + + // ZERO + offset += size_; + ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0); + + // REPLACE + offset += size_; + ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); + ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0); +} + +void CowSnapuserdTest::CreateCowDevice() { + unique_fd rnd_fd; + loff_t offset = 0; + + std::string path = android::base::GetExecutableDirectory(); + cow_system_ = std::make_unique(path); + + rnd_fd.reset(open("/dev/random", O_RDONLY)); + ASSERT_TRUE(rnd_fd > 0); + + std::unique_ptr random_buffer_1_ = std::make_unique(size_); + std::unique_ptr random_buffer_2_ = std::make_unique(size_); // Fill random data for (size_t j = 0; j < (size_ / 1_MiB); j++) { @@ -154,77 +252,32 @@ void SnapuserdTest::Init() { offset += 1_MiB; } - for (size_t j = 0; j < (800_MiB / 1_MiB); j++) { - ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true); - ASSERT_EQ(android::base::WriteFully(system_a_->fd, random_buffer.get(), 1_MiB), true); - } - - for (size_t j = 0; j < (800_MiB / 1_MiB); j++) { - ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true); - ASSERT_EQ(android::base::WriteFully(product_a_->fd, random_buffer.get(), 1_MiB), true); - } - - // Create loopback devices - system_a_loop_ = std::make_unique(std::string(system_a_->path), 10s); - ASSERT_TRUE(system_a_loop_->valid()); - - product_a_loop_ = std::make_unique(std::string(product_a_->path), 10s); - ASSERT_TRUE(product_a_loop_->valid()); - - sys_fd_.reset(open(system_a_loop_->device().c_str(), O_RDONLY)); - ASSERT_TRUE(sys_fd_ > 0); - - product_fd_.reset(open(product_a_loop_->device().c_str(), 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); - - // Read from product 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 - // - // 0-100 MB of replace operation with random data - // 100-200 MB of copy operation - // 200-300 MB of zero operation - // 300-400 MB of replace operation with random data - CowOptions options; options.compression = "gz"; CowWriter writer(options); - 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.Initialize(cow_system_->fd)); 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 blk_src_copy = num_blocks; + size_t blk_end_copy = blk_src_copy + num_blocks; size_t source_blk = 0; - // Copy blocks - source_blk starts from 0 as snapuserd - // 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 or product_a - for (size_t i = blk_start_copy; i < blk_end_copy; i++) { - ASSERT_TRUE(writer.AddCopy(i, source_blk)); + while (source_blk < num_blocks) { + ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy)); source_blk += 1; + blk_src_copy += 1; } - size_t blk_zero_copy_start = blk_end_copy; + ASSERT_EQ(blk_src_copy, blk_end_copy); + + ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_)); + + size_t blk_zero_copy_start = source_blk + num_blocks; size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks; - // 100 MB filled with zeroes ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks)); - // 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_)); @@ -232,233 +285,133 @@ void SnapuserdTest::CreateCowDevice(std::unique_ptr& cow) { // Flush operations ASSERT_TRUE(writer.Finalize()); - ASSERT_EQ(lseek(cow->fd, 0, SEEK_SET), 0); + // Construct the buffer required for validation + orig_buffer_ = std::make_unique(total_base_size_); + std::string zero_buffer(size_, 0); + + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true); + memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_); + memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_); + memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_2_.get(), size_); } -void SnapuserdTest::CreateSystemDmUser(std::unique_ptr& cow) { - std::string cmd; +void CowSnapuserdTest::InitCowDevice() { + cow_num_sectors_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path, + base_loop_->device()); + ASSERT_NE(cow_num_sectors_, 0); +} + +void CowSnapuserdTest::SetDeviceControlName() { system_device_name_.clear(); system_device_ctrl_name_.clear(); - std::string str(cow->path); + 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); - // Create a control device system_device_ctrl_name_ = system_device_name_ + "-ctrl"; - cmd = "dmctl create " + system_device_name_ + " user 0 " + std::to_string(system_blksize_); - cmd += " " + system_device_ctrl_name_; - - system(cmd.c_str()); } -void SnapuserdTest::DeleteDmUser(std::unique_ptr& cow, std::string snapshot_device) { - std::string cmd; +void CowSnapuserdTest::CreateDmUserDevice() { + DmTable dmuser_table; + ASSERT_TRUE(dmuser_table.AddTarget( + std::make_unique(0, cow_num_sectors_, system_device_ctrl_name_))); + ASSERT_TRUE(dmuser_table.valid()); - cmd = "dmctl delete " + snapshot_device; - system(cmd.c_str()); + dmuser_dev_ = std::make_unique(system_device_name_, dmuser_table); + ASSERT_TRUE(dmuser_dev_->valid()); + ASSERT_FALSE(dmuser_dev_->path().empty()); - cmd.clear(); - - std::string str(cow->path); - std::size_t found = str.find_last_of("/\\"); - ASSERT_NE(found, std::string::npos); - std::string device_name = str.substr(found + 1); - - cmd = "dmctl delete " + device_name; - - system(cmd.c_str()); + auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_; + ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s)); } -void SnapuserdTest::CreateProductDmUser(std::unique_ptr& cow) { - std::string cmd; - product_device_name_.clear(); - product_device_ctrl_name_.clear(); - - std::string str(cow->path); - std::size_t found = str.find_last_of("/\\"); - ASSERT_NE(found, std::string::npos); - product_device_name_ = str.substr(found + 1); - product_device_ctrl_name_ = product_device_name_ + "-ctrl"; - cmd = "dmctl create " + product_device_name_ + " user 0 " + std::to_string(product_blksize_); - cmd += " " + product_device_ctrl_name_; - - system(cmd.c_str()); -} - -void SnapuserdTest::InitCowDevices() { - system_blksize_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path, - system_a_loop_->device()); - ASSERT_NE(system_blksize_, 0); - - product_blksize_ = client_->InitDmUserCow(product_device_ctrl_name_, cow_product_->path, - product_a_loop_->device()); - ASSERT_NE(product_blksize_, 0); -} - -void SnapuserdTest::InitDaemon() { +void CowSnapuserdTest::InitDaemon() { bool ok = client_->AttachDmUser(system_device_ctrl_name_); ASSERT_TRUE(ok); - - ok = client_->AttachDmUser(product_device_ctrl_name_); - ASSERT_TRUE(ok); } -void SnapuserdTest::StartSnapuserdDaemon() { - ASSERT_TRUE(EnsureSnapuserdStarted()); +void CowSnapuserdTest::CreateSnapshotDevice() { + DmTable snap_table; + ASSERT_TRUE(snap_table.AddTarget(std::make_unique( + 0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(), + SnapshotStorageMode::Persistent, 8))); + ASSERT_TRUE(snap_table.valid()); - client_ = SnapuserdClient::Connect(kSnapuserdSocket, 5s); - ASSERT_NE(client_, nullptr); + snap_table.set_readonly(true); + + snapshot_dev_ = std::make_unique("cowsnapuserd-test-dm-snapshot", snap_table); + ASSERT_TRUE(snapshot_dev_->valid()); + ASSERT_FALSE(snapshot_dev_->path().empty()); } -void SnapuserdTest::CreateSnapshotDevices() { - std::string cmd; +void CowSnapuserdTest::SetupImpl() { + CreateBaseDevice(); + CreateCowDevice(); - cmd = "dmctl create system-snapshot -ro snapshot 0 " + std::to_string(system_blksize_); - cmd += " " + system_a_loop_->device(); - cmd += " /dev/block/mapper/" + system_device_name_; - cmd += " P 8"; - - system(cmd.c_str()); - - cmd.clear(); - - cmd = "dmctl create product-snapshot -ro snapshot 0 " + std::to_string(product_blksize_); - cmd += " " + product_a_loop_->device(); - cmd += " /dev/block/mapper/" + product_device_name_; - cmd += " P 8"; - - system(cmd.c_str()); -} - -void SnapuserdTest::SwitchSnapshotDevices() { - std::string cmd; - - cmd = "dmctl create system-snapshot-1 -ro snapshot 0 " + std::to_string(system_blksize_); - cmd += " " + system_a_loop_->device(); - cmd += " /dev/block/mapper/" + system_device_name_; - cmd += " P 8"; - - system(cmd.c_str()); - - cmd.clear(); - - cmd = "dmctl create product-snapshot-1 -ro snapshot 0 " + std::to_string(product_blksize_); - cmd += " " + product_a_loop_->device(); - cmd += " /dev/block/mapper/" + product_device_name_; - cmd += " P 8"; - - system(cmd.c_str()); -} - -void SnapuserdTest::TestIO(unique_fd& snapshot_fd, std::unique_ptr& buffer) { - loff_t offset = 0; - // std::unique_ptr buffer = std::move(buf); - - std::unique_ptr snapuserd_buffer = std::make_unique(size_); - - //================Start IO operation on dm-snapshot device================= - // This will test the following paths: - // - // 1: IO path for all three operations and interleaving of operations. - // 2: Merging of blocks in kernel during metadata read - // 3: Bulk IO issued by kernel duing merge operation - - // Read from snapshot device of size 100MB from offset 0. This tests the - // 1st replace operation. - // - // IO path: - // - // 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); - - // Update the offset - offset += size_; - - // 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_); - - // Read from snapshot device of size 100MB from offset 100MB. This tests the - // copy operation. - // - // IO path: - // - // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_from_(system_a/product_a) partition - // (copy op) -> return - ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true); - - // Update the offset - offset += size_; - - // 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. - // - // IO path: - // - // 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); - - // Compare data with zero filled buffer - ASSERT_EQ(memcmp(snapuserd_buffer.get(), zero_buffer_.get(), size_), 0); - - // Update the offset - offset += size_; - - // Read from snapshot device of size 100MB from offset 300MB. This tests the - // final replace operation. - // - // IO path: - // - // 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); - - // 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_); + SetDeviceControlName(); StartSnapuserdDaemon(); - InitCowDevices(); - - CreateSystemDmUser(cow_system_); - CreateProductDmUser(cow_product_); + InitCowDevice(); + CreateDmUserDevice(); InitDaemon(); - CreateSnapshotDevices(); + CreateSnapshotDevice(); + setup_ok_ = true; +} - snapshot_fd.reset(open("/dev/block/mapper/system-snapshot", O_RDONLY)); - ASSERT_TRUE(snapshot_fd > 0); - TestIO(snapshot_fd, system_buffer_); +bool CowSnapuserdTest::Merge() { + MergeImpl(); + return merge_ok_; +} - snapshot_fd.reset(open("/dev/block/mapper/product-snapshot", O_RDONLY)); - ASSERT_TRUE(snapshot_fd > 0); - TestIO(snapshot_fd, product_buffer_); +void CowSnapuserdTest::MergeImpl() { + DmTable merge_table; + ASSERT_TRUE(merge_table.AddTarget(std::make_unique( + 0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(), + SnapshotStorageMode::Merge, 8))); + ASSERT_TRUE(merge_table.valid()); + ASSERT_EQ(total_base_size_ / kSectorSize, merge_table.num_sectors()); - snapshot_fd.reset(-1); + DeviceMapper& dm = DeviceMapper::Instance(); + ASSERT_TRUE(dm.LoadTableAndActivate("cowsnapuserd-test-dm-snapshot", merge_table)); - DeleteDmUser(cow_system_, "system-snapshot"); - DeleteDmUser(cow_product_, "product-snapshot"); + while (true) { + vector status; + ASSERT_TRUE(dm.GetTableStatus("cowsnapuserd-test-dm-snapshot", &status)); + ASSERT_EQ(status.size(), 1); + ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")), + 0); - ASSERT_TRUE(client_->StopSnapuserd()); + 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; +} + +void CowSnapuserdTest::ValidateMerge() { + merged_buffer_ = std::make_unique(total_base_size_); + ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0), + true); + ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0); +} + +TEST(Snapuserd_Test, Snapshot) { + CowSnapuserdTest harness; + ASSERT_TRUE(harness.Setup()); + harness.ReadSnapshotDeviceAndValidate(); + ASSERT_TRUE(harness.Merge()); + harness.ValidateMerge(); + harness.Shutdown(); } } // namespace snapshot diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp index 957ba35fa..f7cc6ff6b 100644 --- a/fs_mgr/libsnapshot/cow_writer.cpp +++ b/fs_mgr/libsnapshot/cow_writer.cpp @@ -421,7 +421,7 @@ bool CowWriter::Sync() { return true; } -bool CowWriter::CommitMerge(int merged_ops) { +bool CowWriter::CommitMerge(int merged_ops, bool sync) { CHECK(merge_in_progress_); header_.num_merge_ops += merged_ops; @@ -436,7 +436,11 @@ bool CowWriter::CommitMerge(int merged_ops) { return false; } - return Sync(); + // Sync only for merging of copy operations. + if (sync) { + return Sync(); + } + return true; } bool CowWriter::Truncate(off_t length) { diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h index be692253d..62b54f901 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h @@ -140,6 +140,8 @@ class CowReader : public ICowReader { void UpdateMergeProgress(uint64_t merge_ops) { header_.num_merge_ops += merge_ops; } + void InitializeMerge(); + private: bool ParseOps(std::optional label); diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h index e9320b0b3..36cd238e0 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h @@ -98,7 +98,7 @@ class CowWriter : public ICowWriter { bool InitializeAppend(android::base::borrowed_fd fd, uint64_t label); void InitializeMerge(android::base::borrowed_fd fd, CowHeader* header); - bool CommitMerge(int merged_ops); + bool CommitMerge(int merged_ops, bool sync); bool Finalize() override; diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h index 24b44fae7..eec6d4562 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h @@ -81,7 +81,7 @@ class Snapuserd final { bool ReadDiskExceptions(chunk_t chunk, size_t size); bool ReadData(chunk_t chunk, size_t size); bool IsChunkIdMetadata(chunk_t chunk); - chunk_t GetNextAllocatableChunkId(chunk_t chunk); + chunk_t GetNextAllocatableChunkId(chunk_t chunk_id); bool ProcessReplaceOp(const CowOperation* cow_op); bool ProcessCopyOp(const CowOperation* cow_op); @@ -90,8 +90,7 @@ class Snapuserd final { loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer, int* unmerged_exceptions); int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset, - int unmerged_exceptions); - bool AdvanceMergedOps(int merged_ops_cur_iter); + int unmerged_exceptions, bool* copy_op); bool ProcessMergeComplete(chunk_t chunk, void* buffer); sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; } chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; } diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp index 49e6c3d2b..d1d252166 100644 --- a/fs_mgr/libsnapshot/snapuserd.cpp +++ b/fs_mgr/libsnapshot/snapuserd.cpp @@ -31,7 +31,7 @@ using android::base::unique_fd; #define SNAP_LOG(level) LOG(level) << misc_name_ << ": " #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": " -static constexpr size_t PAYLOAD_SIZE = (1UL << 16); +static constexpr size_t PAYLOAD_SIZE = (1UL << 20); static_assert(PAYLOAD_SIZE >= BLOCK_SIZE); @@ -156,11 +156,11 @@ bool Snapuserd::ReadData(chunk_t chunk, size_t size) { size_t read_size = size; bool ret = true; chunk_t chunk_key = chunk; - uint32_t stride; - lldiv_t divresult; - // Size should always be aligned - CHECK((read_size & (BLOCK_SIZE - 1)) == 0); + if (!((read_size & (BLOCK_SIZE - 1)) == 0)) { + SNAP_LOG(ERROR) << "ReadData - unaligned read_size: " << read_size; + return false; + } while (read_size > 0) { const CowOperation* cow_op = chunk_map_[chunk_key]; @@ -204,24 +204,8 @@ bool Snapuserd::ReadData(chunk_t chunk, size_t size) { // are contiguous chunk_key += 1; - if (cow_op->type == kCowCopyOp) CHECK(read_size == 0); - - // This is similar to the way when chunk IDs were assigned - // in ReadMetadata(). - // - // Skip if the chunk id represents a metadata chunk. - stride = exceptions_per_area_ + 1; - divresult = lldiv(chunk_key, stride); - if (divresult.rem == NUM_SNAPSHOT_HDR_CHUNKS) { - // Crossing exception boundary. Kernel will never - // issue IO which is spanning between a data chunk - // and a metadata chunk. This should be perfectly aligned. - // - // Since the input read_size is 4k aligned, we will - // always end up reading all 256 data chunks in one area. - // Thus, every multiple of 4K IO represents 256 data chunks + if (cow_op->type == kCowCopyOp) { CHECK(read_size == 0); - break; } } @@ -330,7 +314,7 @@ loff_t Snapuserd::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer } int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset, - int unmerged_exceptions) { + int unmerged_exceptions, bool* copy_op) { int merged_ops_cur_iter = 0; // Find the operations which are merged in this cycle. @@ -346,6 +330,12 @@ int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, if (cow_de->new_chunk != 0) { merged_ops_cur_iter += 1; offset += sizeof(struct disk_exception); + const CowOperation* cow_op = chunk_map_[cow_de->new_chunk]; + CHECK(cow_op != nullptr); + CHECK(cow_op->new_block == cow_de->old_chunk); + if (cow_op->type == kCowCopyOp) { + *copy_op = true; + } // zero out to indicate that operation is merged. cow_de->old_chunk = 0; cow_de->new_chunk = 0; @@ -367,44 +357,12 @@ int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, } } + if (*copy_op) { + CHECK(merged_ops_cur_iter == 1); + } return merged_ops_cur_iter; } -bool Snapuserd::AdvanceMergedOps(int merged_ops_cur_iter) { - // Advance the merge operation pointer in the - // vector. - // - // cowop_iter_ is already initialized in ReadMetadata(). Just resume the - // merge process - while (!cowop_iter_->Done() && merged_ops_cur_iter) { - const CowOperation* cow_op = &cowop_iter_->Get(); - CHECK(cow_op != nullptr); - - if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) { - cowop_iter_->Next(); - continue; - } - - if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp || - cow_op->type == kCowCopyOp)) { - SNAP_LOG(ERROR) << "Unknown operation-type found during merge: " << cow_op->type; - return false; - } - - merged_ops_cur_iter -= 1; - SNAP_LOG(DEBUG) << "Merge op found of type " << cow_op->type - << "Pending-merge-ops: " << merged_ops_cur_iter; - cowop_iter_->Next(); - } - - if (cowop_iter_->Done()) { - CHECK(merged_ops_cur_iter == 0); - SNAP_LOG(DEBUG) << "All cow operations merged successfully in this cycle"; - } - - return true; -} - bool Snapuserd::ProcessMergeComplete(chunk_t chunk, void* buffer) { uint32_t stride = exceptions_per_area_ + 1; CowHeader header; @@ -423,21 +381,47 @@ bool Snapuserd::ProcessMergeComplete(chunk_t chunk, void* buffer) { int unmerged_exceptions = 0; loff_t offset = GetMergeStartOffset(buffer, vec_[divresult.quot].get(), &unmerged_exceptions); - int merged_ops_cur_iter = - GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset, unmerged_exceptions); + bool copy_op = false; + // Check if the merged operation is a copy operation. If so, then we need + // to explicitly sync the metadata before initiating the next merge. + // For ex: Consider a following sequence of copy operations in the COW file: + // + // Op-1: Copy 2 -> 3 + // Op-2: Copy 1 -> 2 + // Op-3: Copy 5 -> 10 + // + // Op-1 and Op-2 are overlapping copy operations. The merge sequence will + // look like: + // + // Merge op-1: Copy 2 -> 3 + // Merge op-2: Copy 1 -> 2 + // Merge op-3: Copy 5 -> 10 + // + // Now, let's say we have a crash _after_ Merge op-2; Block 2 contents would + // have been over-written by Block-1 after merge op-2. During next reboot, + // kernel will request the metadata for all the un-merged blocks. If we had + // not sync the metadata after Merge-op 1 and Merge op-2, snapuser daemon + // will think that these merge operations are still pending and hence will + // inform the kernel that Op-1 and Op-2 are un-merged blocks. When kernel + // resumes back the merging process, it will attempt to redo the Merge op-1 + // once again. However, block 2 contents are wrong as it has the contents + // of block 1 from previous merge cycle. Although, merge will silently succeed, + // this will lead to silent data corruption. + // + int merged_ops_cur_iter = GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset, + unmerged_exceptions, ©_op); // There should be at least one operation merged in this cycle CHECK(merged_ops_cur_iter > 0); - if (!AdvanceMergedOps(merged_ops_cur_iter)) return false; header.num_merge_ops += merged_ops_cur_iter; reader_->UpdateMergeProgress(merged_ops_cur_iter); - if (!writer_->CommitMerge(merged_ops_cur_iter)) { + if (!writer_->CommitMerge(merged_ops_cur_iter, copy_op)) { SNAP_LOG(ERROR) << "CommitMerge failed..."; return false; } - SNAP_LOG(DEBUG) << "Merge success"; + SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk; return true; } @@ -532,6 +516,7 @@ bool Snapuserd::ReadMetadata() { CHECK(header.block_size == BLOCK_SIZE); SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops; + reader_->InitializeMerge(); writer_ = std::make_unique(options); writer_->InitializeMerge(cow_fd_.get(), &header); @@ -543,7 +528,8 @@ bool Snapuserd::ReadMetadata() { // Start from chunk number 2. Chunk 0 represents header and chunk 1 // represents first metadata page. - chunk_t next_free = NUM_SNAPSHOT_HDR_CHUNKS + 1; + chunk_t data_chunk_id = NUM_SNAPSHOT_HDR_CHUNKS + 1; + size_t num_ops = 0; loff_t offset = 0; std::unique_ptr de_ptr = @@ -553,7 +539,6 @@ bool Snapuserd::ReadMetadata() { // is 0. When Area is not filled completely with all 256 exceptions, // this memset will ensure that metadata read is completed. memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception))); - size_t num_ops = 0; while (!cowop_riter_->Done()) { const CowOperation* cow_op = &cowop_riter_->Get(); @@ -565,31 +550,23 @@ bool Snapuserd::ReadMetadata() { continue; } - if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp || - cow_op->type == kCowCopyOp)) { - SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type; - return false; - } - metadata_found = true; if ((cow_op->type == kCowCopyOp || prev_copy_op)) { - next_free = GetNextAllocatableChunkId(next_free); + data_chunk_id = GetNextAllocatableChunkId(data_chunk_id); } prev_copy_op = (cow_op->type == kCowCopyOp); // Construct the disk-exception de->old_chunk = cow_op->new_block; - de->new_chunk = next_free; + de->new_chunk = data_chunk_id; SNAP_LOG(DEBUG) << "Old-chunk: " << de->old_chunk << "New-chunk: " << de->new_chunk; // Store operation pointer. - chunk_map_[next_free] = cow_op; + chunk_map_[data_chunk_id] = cow_op; num_ops += 1; - offset += sizeof(struct disk_exception); - cowop_riter_->Next(); if (num_ops == exceptions_per_area_) { @@ -610,7 +587,7 @@ bool Snapuserd::ReadMetadata() { } } - next_free = GetNextAllocatableChunkId(next_free); + data_chunk_id = GetNextAllocatableChunkId(data_chunk_id); } // Partially filled area or there is no metadata @@ -622,14 +599,11 @@ bool Snapuserd::ReadMetadata() { << "Areas : " << vec_.size(); } - SNAP_LOG(DEBUG) << "ReadMetadata() completed. chunk_id: " << next_free - << "Num Sector: " << ChunkToSector(next_free); - - // Initialize the iterator for merging - cowop_iter_ = reader_->GetOpIter(); + SNAP_LOG(DEBUG) << "ReadMetadata() completed. Final_chunk_id: " << data_chunk_id + << "Num Sector: " << ChunkToSector(data_chunk_id); // Total number of sectors required for creating dm-user device - num_sectors_ = ChunkToSector(next_free); + num_sectors_ = ChunkToSector(data_chunk_id); metadata_read_done_ = true; return true; } @@ -759,6 +733,8 @@ bool Snapuserd::Run() { << "Sector: " << header->sector; } } else { + SNAP_LOG(DEBUG) << "ReadData: chunk: " << chunk << " len: " << header->len + << " read_size: " << read_size << " offset: " << offset; chunk_t num_chunks_read = (offset >> BLOCK_SHIFT); if (!ReadData(chunk + num_chunks_read, read_size)) { SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp index 7282bff77..16d02e424 100644 --- a/fs_mgr/libsnapshot/snapuserd_client.cpp +++ b/fs_mgr/libsnapshot/snapuserd_client.cpp @@ -57,7 +57,7 @@ bool EnsureSnapuserdStarted() { SnapuserdClient::SnapuserdClient(android::base::unique_fd&& sockfd) : sockfd_(std::move(sockfd)) {} static inline bool IsRetryErrno() { - return errno == ECONNREFUSED || errno == EINTR; + return errno == ECONNREFUSED || errno == EINTR || errno == ENOENT; } std::unique_ptr SnapuserdClient::Connect(const std::string& socket_name, @@ -112,6 +112,7 @@ bool SnapuserdClient::ValidateConnection() { } bool SnapuserdClient::Sendmsg(const std::string& msg) { + LOG(DEBUG) << "Sendmsg: msg " << msg << " sockfd: " << sockfd_; ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), 0)); if (numBytesSent < 0) { PLOG(ERROR) << "Send failed";