Merge changes I1dc28606,I4d77c435

* changes:
  libsnapshot:VABC: Allow batch merge
  libsnaphot: Refactor cow_snapuserd test
This commit is contained in:
Akilesh Kailash 2020-12-02 22:36:38 +00:00 committed by Gerrit Code Review
commit dd58ffd1a6
9 changed files with 495 additions and 450 deletions

View file

@ -563,6 +563,7 @@ cc_test {
"libsnapshot_snapuserd",
"libcutils_sockets",
"libz",
"libfs_mgr",
"libdm",
],
header_libs: [

View file

@ -189,29 +189,138 @@ bool CowReader::ParseOps(std::optional<uint64_t> 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;

View file

@ -12,10 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <fcntl.h>
#include <linux/fs.h>
#include <linux/memfd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <chrono>
#include <iostream>
@ -24,7 +28,9 @@
#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <fs_mgr/file_wait.h>
#include <gtest/gtest.h>
#include <libdm/dm.h>
#include <libdm/loop_control.h>
#include <libsnapshot/cow_writer.h>
#include <libsnapshot/snapuserd_client.h>
@ -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<TemporaryFile>();
ASSERT_GE(cow_system_->fd, 0) << strerror(errno);
static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
cow_product_ = std::make_unique<TemporaryFile>();
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<TemporaryFile>();
ASSERT_GE(cow_system_1_->fd, 0) << strerror(errno);
TempDevice(const TempDevice&) = delete;
TempDevice& operator=(const TempDevice&) = delete;
cow_product_1_ = std::make_unique<TemporaryFile>();
ASSERT_GE(cow_product_1_->fd, 0) << strerror(errno);
std::string path = android::base::GetExecutableDirectory();
system_a_ = std::make_unique<TemporaryFile>(path);
ASSERT_GE(system_a_->fd, 0) << strerror(errno);
product_a_ = std::make_unique<TemporaryFile>(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<TemporaryFile> system_a_;
std::unique_ptr<TemporaryFile> product_a_;
std::unique_ptr<LoopDevice> system_a_loop_;
std::unique_ptr<LoopDevice> product_a_loop_;
std::unique_ptr<TemporaryFile> cow_system_;
std::unique_ptr<TemporaryFile> cow_product_;
std::unique_ptr<TemporaryFile> cow_system_1_;
std::unique_ptr<TemporaryFile> 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<uint8_t[]> random_buffer_1_;
std::unique_ptr<uint8_t[]> random_buffer_2_;
std::unique_ptr<uint8_t[]> zero_buffer_;
std::unique_ptr<uint8_t[]> system_buffer_;
std::unique_ptr<uint8_t[]> product_buffer_;
void Init();
void InitCowDevices();
void InitDaemon();
void CreateCowDevice(std::unique_ptr<TemporaryFile>& cow);
void CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow);
void CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow);
void DeleteDmUser(std::unique_ptr<TemporaryFile>& 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<uint8_t[]>& buffer);
std::unique_ptr<SnapuserdClient> 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<LoopDevice> base_loop_;
unique_ptr<TempDevice> dmuser_dev_;
unique_ptr<TempDevice> snapshot_dev_;
std::string system_device_ctrl_name_;
std::string system_device_name_;
unique_fd base_fd_;
std::unique_ptr<TemporaryFile> cow_system_;
std::unique_ptr<SnapuserdClient> client_;
std::unique_ptr<uint8_t[]> orig_buffer_;
std::unique_ptr<uint8_t[]> 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<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(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<uint8_t[]>(size_);
random_buffer_2_ = std::make_unique<uint8_t[]>(size_);
system_buffer_ = std::make_unique<uint8_t[]>(size_);
product_buffer_ = std::make_unique<uint8_t[]>(size_);
zero_buffer_ = std::make_unique<uint8_t[]>(size_);
std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(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<LoopDevice>(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<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(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<TemporaryFile>(path);
rnd_fd.reset(open("/dev/random", O_RDONLY));
ASSERT_TRUE(rnd_fd > 0);
std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
std::unique_ptr<uint8_t[]> random_buffer_2_ = std::make_unique<uint8_t[]>(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<LoopDevice>(std::string(system_a_->path), 10s);
ASSERT_TRUE(system_a_loop_->valid());
product_a_loop_ = std::make_unique<LoopDevice>(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<TemporaryFile>& 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<TemporaryFile>& 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<uint8_t[]>(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<TemporaryFile>& 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<TemporaryFile>& cow, std::string snapshot_device) {
std::string cmd;
void CowSnapuserdTest::CreateDmUserDevice() {
DmTable dmuser_table;
ASSERT_TRUE(dmuser_table.AddTarget(
std::make_unique<DmTargetUser>(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<TempDevice>(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<TemporaryFile>& 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<DmTargetSnapshot>(
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<TempDevice>("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<uint8_t[]>& buffer) {
loff_t offset = 0;
// std::unique_ptr<uint8_t[]> buffer = std::move(buf);
std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(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<DmTargetSnapshot>(
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<DeviceMapper::TargetInfo> 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<uint8_t[]>(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

View file

@ -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) {

View file

@ -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<uint64_t> label);

View file

@ -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;

View file

@ -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; }

View file

@ -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, &copy_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<CowWriter>(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<uint8_t[]> 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

View file

@ -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> 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";