Merge changes I1dc28606,I4d77c435
* changes: libsnapshot:VABC: Allow batch merge libsnaphot: Refactor cow_snapuserd test
This commit is contained in:
commit
dd58ffd1a6
9 changed files with 495 additions and 450 deletions
|
@ -563,6 +563,7 @@ cc_test {
|
|||
"libsnapshot_snapuserd",
|
||||
"libcutils_sockets",
|
||||
"libz",
|
||||
"libfs_mgr",
|
||||
"libdm",
|
||||
],
|
||||
header_libs: [
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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<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
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue