blob: d358649aa08c761b19260036ca265487e61d97f4 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless requied by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#define LOG_TAG "BowTest"
#include <fstream>
#include <string>
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include <libdm/dm.h>
#include <utils/Log.h>
namespace android {
using base::unique_fd;
using namespace dm;
bool blockCheckpointsSupported() {
static bool supported = false;
static bool evaluated = false;
if (evaluated) return supported;
pid_t pid = fork();
EXPECT_NE(pid, -1);
if (pid == 0) {
static const char* args[] = {"/system/bin/vdc", "checkpoint",
"supportsBlockCheckpoint"};
EXPECT_NE(execv(args[0], const_cast<char* const*>(args)), -1);
}
int status;
EXPECT_NE(waitpid(pid, &status, 0), -1);
supported = status == 1;
evaluated = true;
return supported;
}
template <void (*Prepare)(std::string)>
class LoopbackTestFixture : public ::testing::Test {
protected:
void SetUp() {
Prepare(loop_file_);
// Get free loop device name
unique_fd cfd(open("/dev/loop-control", O_RDWR));
ASSERT_NE(cfd.get(), -1);
int i = ioctl(cfd, 0x4C82); // LOOP_CTL_GET_FREE
ASSERT_GE(i, 0);
loop_device_ = std::string("/dev/block/loop") + std::to_string(i);
// Associate loop device with file
unique_fd lfd(open(loop_device_.c_str(), O_RDWR));
ASSERT_NE(lfd.get(), -1);
unique_fd ffd(open(loop_file_.c_str(), O_RDWR));
ASSERT_NE(ffd.get(), -1);
ASSERT_EQ(ioctl(lfd.get(), LOOP_SET_FD, ffd.get()), 0);
}
void TearDown() {
unique_fd lfd(open(loop_device_.c_str(), O_RDWR));
EXPECT_NE(lfd.get(), -1);
EXPECT_EQ(ioctl(lfd, LOOP_CLR_FD, 0), 0);
EXPECT_EQ(remove(loop_file_.c_str()), 0);
}
const static std::string loop_file_;
public:
const static size_t sector_size_ = 512;
const static size_t loop_size_ = 4096 * sector_size_;
std::string loop_device_;
};
template <void (*Prepare)(std::string)>
const std::string LoopbackTestFixture<Prepare>::loop_file_ =
"/data/local/tmp/bow_loop";
void PrepareBowDefault(std::string) {}
template <void (*PrepareLoop)(std::string),
void (*PrepareBow)(std::string) = PrepareBowDefault>
class BowTestFixture : public LoopbackTestFixture<PrepareLoop> {
std::string GetTableStatus() {
std::vector<DeviceMapper::TargetInfo> targets;
DeviceMapper& dm = DeviceMapper::Instance();
EXPECT_TRUE(dm.GetTableInfo("bow1", &targets));
EXPECT_EQ(targets.size(), 1);
return targets[0].data;
}
bool torn_down_;
protected:
void SetUp() {
if (!blockCheckpointsSupported()) return;
LoopbackTestFixture<PrepareLoop>::SetUp();
PrepareBow(loop_device_);
torn_down_ = false;
DmTable table;
table.AddTarget(std::make_unique<DmTargetBow>(0, loop_size_ / 512,
loop_device_.c_str()));
DeviceMapper& dm = DeviceMapper::Instance();
ASSERT_TRUE(dm.CreateDevice("bow1", table));
ASSERT_TRUE(dm.GetDmDevicePathByName("bow1", &bow_device_));
}
void TearDown() {
if (!blockCheckpointsSupported()) return;
BowTearDown();
LoopbackTestFixture<PrepareLoop>::TearDown();
}
bool TornDown() const { return torn_down_; }
public:
using LoopbackTestFixture<PrepareLoop>::loop_size_;
using LoopbackTestFixture<PrepareLoop>::sector_size_;
using LoopbackTestFixture<PrepareLoop>::loop_device_;
void BowTearDown() {
if (torn_down_) return;
torn_down_ = true;
EXPECT_TRUE(DeviceMapper::Instance().DeleteDevice("bow1"));
}
void SetState(int i) {
std::string state_file = "/sys" + bow_device_.substr(4) + "/bow/state";
std::ofstream(state_file) << i;
int j;
std::ifstream(state_file) >> j;
EXPECT_EQ(i, j);
}
enum SectorTypes {
INVALID,
SECTOR0,
SECTOR0_CURRENT,
UNCHANGED,
BACKUP,
FREE,
CHANGED,
TOP
};
struct TableEntry {
SectorTypes type;
uint64_t offset;
bool operator==(const TableEntry& te) const {
return type == te.type && offset == te.offset;
}
};
std::vector<TableEntry> GetTable() {
std::string status = GetTableStatus();
std::istringstream i(status);
std::vector<TableEntry> table;
while (true) {
TableEntry te = {};
std::string s;
i >> s >> te.offset;
if (!i) {
EXPECT_EQ(s, "");
break;
}
if (s == "Sector0:")
te.type = SECTOR0;
else if (s == "Sector0_current:")
te.type = SECTOR0_CURRENT;
else if (s == "Unchanged:")
te.type = UNCHANGED;
else if (s == "Backup:")
te.type = BACKUP;
else if (s == "Free:")
te.type = FREE;
else if (s == "Changed:")
te.type = CHANGED;
else if (s == "Top:")
te.type = TOP;
else
ADD_FAILURE();
te.offset /= sector_size_ / 512;
table.push_back(te);
}
return table;
}
std::string bow_device_;
};
void PrepareFile(std::string loop_file) {
auto const& sector_size_ = LoopbackTestFixture<PrepareFile>::sector_size_;
auto const& loop_size_ = LoopbackTestFixture<PrepareFile>::loop_size_;
unique_fd fd(
open(loop_file.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR));
ASSERT_NE(fd.get(), -1);
for (int i = 0; i < loop_size_ / sector_size_; ++i) {
char buffer[sector_size_] = {};
snprintf(buffer, sizeof(buffer), "Sector %d", i);
write(fd.get(), buffer, sizeof(buffer));
}
}
class FileBowTestFixture : public BowTestFixture<&PrepareFile> {
void SetUp() {
if (!blockCheckpointsSupported()) return;
BowTestFixture<&PrepareFile>::SetUp();
fd_ = unique_fd(open(bow_device_.c_str(), O_RDWR));
ASSERT_NE(fd_.get(), -1);
}
void TearDown() {
fd_ = unique_fd();
BowTestFixture<&PrepareFile>::TearDown();
}
unique_fd fd_;
public:
void Discard(uint64_t offset, uint64_t length) {
uint64_t range[2] = {offset * sector_size_, length * sector_size_};
EXPECT_EQ(ioctl(fd_.get(), BLKDISCARD, range), 0);
}
int Write(SectorTypes type) {
for (auto i : GetTable())
if (i.type == type) {
EXPECT_NE(lseek(fd_, i.offset * sector_size_, SEEK_SET), -1);
EXPECT_EQ(write(fd_, "Changed", 8), 8);
return i.offset;
}
EXPECT_TRUE(false);
return -1;
}
void FindChanged(std::vector<TableEntry> const& free, int expected_changed) {
unique_fd fd;
if (TornDown())
fd = unique_fd(open(loop_device_.c_str(), O_RDONLY));
else
fd = unique_fd(open(bow_device_.c_str(), O_RDONLY));
EXPECT_NE(fd.get(), -1);
if (fd.get() == -1) return;
int changed = -1;
for (int i = 0; i < loop_size_ / sector_size_; ++i) {
if (i != expected_changed) {
auto type = SECTOR0;
for (auto j : free)
if (j.offset > i)
break;
else
type = j.type;
if (type == FREE) continue;
}
char buffer[sector_size_];
EXPECT_NE(lseek(fd.get(), i * sector_size_, SEEK_SET), -1);
EXPECT_EQ(read(fd.get(), buffer, sizeof(buffer)), sizeof(buffer));
if (strcmp(buffer, "Changed") == 0) {
EXPECT_EQ(changed, -1);
changed = i;
} else {
std::string expected = "Sector " + std::to_string(i);
EXPECT_STREQ(buffer, expected.c_str());
}
}
EXPECT_EQ(changed, expected_changed);
}
void DumpSector0() {
char buffer[sector_size_];
lseek(fd_.get(), 0, SEEK_SET);
read(fd_.get(), buffer, sizeof(buffer));
for (int i = 0; i < sector_size_ * 2; ++i) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< (int)buffer[i];
if (i % 32 == 31)
std::cout << std::endl;
else
std::cout << " ";
}
}
void WriteSubmit(SectorTypes type) {
if (!blockCheckpointsSupported()) return;
Discard(1, 1);
Discard(3, 2);
auto free = GetTable();
SetState(1);
auto changed = Write(type);
SetState(2);
FindChanged(free, changed);
}
void WriteRestore(SectorTypes type) {
if (!blockCheckpointsSupported()) return;
Discard(1, 1);
Discard(3, 2);
auto free = GetTable();
SetState(1);
Write(type);
BowTearDown();
system(std::string("vdc checkpoint restoreCheckpoint " + loop_device_)
.c_str());
FindChanged(free, -1);
}
};
TEST_F(FileBowTestFixture, discardVisible) {
if (!blockCheckpointsSupported()) return;
Discard(8, 1);
Discard(16, 1);
Discard(12, 1);
Discard(4, 1);
std::vector<TableEntry> table = {
{UNCHANGED, 0}, {FREE, 4},
{UNCHANGED, 5}, {FREE, 8},
{UNCHANGED, 9}, {FREE, 12},
{UNCHANGED, 13}, {FREE, 16},
{UNCHANGED, 17}, {TOP, loop_size_ / sector_size_},
};
EXPECT_EQ(GetTable(), table);
}
TEST_F(FileBowTestFixture, writeSector0Submit) {
SCOPED_TRACE("write submit SECTOR0");
WriteSubmit(SECTOR0);
}
TEST_F(FileBowTestFixture, writeSector0Revert) {
SCOPED_TRACE("write restore SECTOR0");
WriteRestore(SECTOR0);
}
TEST_F(FileBowTestFixture, writeSector0_CurrentSubmit) {
SCOPED_TRACE("write submit SECTOR0_CURRENT");
WriteSubmit(SECTOR0_CURRENT);
}
TEST_F(FileBowTestFixture, writeSector0_CurrentRevert) {
SCOPED_TRACE("write restore SECTOR0_CURRENT");
WriteRestore(SECTOR0_CURRENT);
}
TEST_F(FileBowTestFixture, writeUnchangedSubmit) {
SCOPED_TRACE("write submit UNCHANGED");
WriteSubmit(UNCHANGED);
}
TEST_F(FileBowTestFixture, writeUnchangedRevert) {
SCOPED_TRACE("write restore UNCHANGED");
WriteRestore(UNCHANGED);
}
TEST_F(FileBowTestFixture, writeBackupSubmit) {
SCOPED_TRACE("write submit BACKUP");
WriteSubmit(BACKUP);
}
TEST_F(FileBowTestFixture, writeBackupRevert) {
SCOPED_TRACE("write restore BACKUP");
WriteRestore(BACKUP);
}
TEST_F(FileBowTestFixture, writeFreeSubmit) {
SCOPED_TRACE("write submit FREE");
WriteSubmit(FREE);
}
TEST_F(FileBowTestFixture, writeFreeRevert) {
SCOPED_TRACE("write restore FREE");
WriteRestore(FREE);
}
/* There are no changed sectors at start, so these can't work as is
TEST_F(BowTestFixture, writeChangedSubmit) {
SCOPED_TRACE("write submit CHANGED");
WriteSubmit(CHANGED);
}
TEST_F(BowTestFixture, writeChangedRevert) {
SCOPED_TRACE("write restore CHANGED");
WriteRestore(CHANGED);
}
*/
void PrepareFileSystem(std::string loop_file) {
EXPECT_EQ(
system((std::string("dd if=/dev/zero bs=512 count=4096 of=") + loop_file)
.c_str()),
0);
}
#define MOUNT_POINT "/data/local/tmp/mount"
void SetupFileSystem(std::string loop_device) {
EXPECT_EQ(system((std::string("mke2fs ") + loop_device).c_str()), 0);
EXPECT_EQ(system("mkdir " MOUNT_POINT), 0);
EXPECT_EQ(
system((std::string("mount ") + loop_device + " " MOUNT_POINT).c_str()),
0);
EXPECT_EQ(system("echo Original > " MOUNT_POINT "/file"), 0);
EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
EXPECT_EQ(system("rmdir " MOUNT_POINT), 0);
}
void Trim() {
unique_fd fd(open(MOUNT_POINT, O_RDONLY));
EXPECT_NE(fd.get(), -1);
struct fstrim_range range = {};
range.len = ULLONG_MAX;
EXPECT_EQ(ioctl(fd, FITRIM, &range), 0);
}
typedef BowTestFixture<&PrepareFileSystem, &SetupFileSystem>
FileSystemBowTestFixture;
TEST_F(FileSystemBowTestFixture, filesystemSubmit) {
if (!blockCheckpointsSupported()) return;
EXPECT_EQ(system("mkdir " MOUNT_POINT), 0);
EXPECT_EQ(
system((std::string("mount ") + bow_device_ + " " MOUNT_POINT).c_str()),
0);
Trim();
SetState(1);
EXPECT_EQ(system("echo Changed > " MOUNT_POINT "/file"), 0);
SetState(2);
EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
BowTearDown();
EXPECT_EQ(
system((std::string("mount ") + loop_device_ + " " MOUNT_POINT).c_str()),
0);
std::string contents;
std::ifstream(MOUNT_POINT "/file") >> contents;
EXPECT_EQ(contents, std::string("Changed"));
EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
EXPECT_EQ(system("rmdir " MOUNT_POINT), 0);
}
TEST_F(FileSystemBowTestFixture, filesystemRevert) {
if (!blockCheckpointsSupported()) return;
EXPECT_EQ(system("mkdir " MOUNT_POINT), 0);
EXPECT_EQ(
system((std::string("mount ") + bow_device_ + " " MOUNT_POINT).c_str()),
0);
Trim();
SetState(1);
EXPECT_EQ(system("echo Changed > " MOUNT_POINT "/file"), 0);
EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
BowTearDown();
system((std::string("vdc checkpoint restoreCheckpoint ") + loop_device_)
.c_str());
EXPECT_EQ(
system((std::string("mount ") + loop_device_ + " " MOUNT_POINT).c_str()),
0);
std::string contents;
std::ifstream(MOUNT_POINT "/file") >> contents;
EXPECT_EQ(contents, std::string("Original"));
EXPECT_EQ(system("umount -D " MOUNT_POINT), 0);
EXPECT_EQ(system("rmdir " MOUNT_POINT), 0);
}
} // namespace android