Add dm-snapshot targets to libdm and dmctl.
This adds DmTargetSnapshotOrigin and DmTargetSnapshot. The latter target can handle both "snapshot" and "snapshot-merge" targets. The syntax for dmctl is as follows: dmctl create <name> snapshot <start> <num_sectors> <base_device> \ <cow_device> <P|N> <chunk_size> dmctl create <name> snapshot-merge <start> <num_sectors> <base_device> \ <cow_device> <chunk_size> dmctl create <name> snapshot-origin <start> <num_sectors> <device> Bug: N/A Test: libdm_test gtests Change-Id: I8eef987cb92121e81bedd37b9a66fad04c7a23a3
This commit is contained in:
parent
be9c2c0310
commit
29e6bf282f
6 changed files with 494 additions and 23 deletions
|
@ -210,6 +210,20 @@ bool DeviceMapper::GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DeviceMapper::GetTargetByName(const std::string& name, DmTargetTypeInfo* info) {
|
||||
std::vector<DmTargetTypeInfo> targets;
|
||||
if (!GetAvailableTargets(&targets)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& target : targets) {
|
||||
if (target.name() == name) {
|
||||
if (info) *info = target;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeviceMapper::GetAvailableDevices(std::vector<DmBlockDevice>* devices) {
|
||||
devices->clear();
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/macros.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/strings.h>
|
||||
|
||||
#include <libdm/dm.h>
|
||||
|
@ -115,5 +116,82 @@ std::string DmTargetAndroidVerity::GetParameterString() const {
|
|||
return keyid_ + " " + block_device_;
|
||||
}
|
||||
|
||||
std::string DmTargetSnapshot::name() const {
|
||||
if (mode_ == SnapshotStorageMode::Merge) {
|
||||
return "snapshot-merge";
|
||||
}
|
||||
return "snapshot";
|
||||
}
|
||||
|
||||
std::string DmTargetSnapshot::GetParameterString() const {
|
||||
std::string mode;
|
||||
switch (mode_) {
|
||||
case SnapshotStorageMode::Persistent:
|
||||
case SnapshotStorageMode::Merge:
|
||||
// Note: "O" lets us query for overflow in the status message. This
|
||||
// is only supported on kernels 4.4+. On earlier kernels, an overflow
|
||||
// will be reported as "Invalid" in the status string.
|
||||
mode = "P";
|
||||
if (ReportsOverflow(name())) {
|
||||
mode += "O";
|
||||
}
|
||||
break;
|
||||
case SnapshotStorageMode::Transient:
|
||||
mode = "N";
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR) << "DmTargetSnapshot unknown mode";
|
||||
break;
|
||||
}
|
||||
return base_device_ + " " + cow_device_ + " " + mode + " " + std::to_string(chunk_size_);
|
||||
}
|
||||
|
||||
bool DmTargetSnapshot::ReportsOverflow(const std::string& target_type) {
|
||||
DeviceMapper& dm = DeviceMapper::Instance();
|
||||
DmTargetTypeInfo info;
|
||||
if (!dm.GetTargetByName(target_type, &info)) {
|
||||
return false;
|
||||
}
|
||||
if (target_type == "snapshot") {
|
||||
return info.IsAtLeast(1, 15, 0);
|
||||
}
|
||||
if (target_type == "snapshot-merge") {
|
||||
return info.IsAtLeast(1, 4, 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DmTargetSnapshot::ParseStatusText(const std::string& text, Status* status) {
|
||||
auto sections = android::base::Split(text, " ");
|
||||
if (sections.size() == 1) {
|
||||
// This is probably an error code, "Invalid" is possible as is "Overflow"
|
||||
// on 4.4+.
|
||||
status->error = text;
|
||||
return true;
|
||||
}
|
||||
if (sections.size() != 2) {
|
||||
LOG(ERROR) << "snapshot status should have two components";
|
||||
return false;
|
||||
}
|
||||
auto sector_info = android::base::Split(sections[0], "/");
|
||||
if (sector_info.size() != 2) {
|
||||
LOG(ERROR) << "snapshot sector info should have two components";
|
||||
return false;
|
||||
}
|
||||
if (!android::base::ParseUint(sections[1], &status->metadata_sectors)) {
|
||||
LOG(ERROR) << "could not parse metadata sectors";
|
||||
return false;
|
||||
}
|
||||
if (!android::base::ParseUint(sector_info[0], &status->sectors_allocated)) {
|
||||
LOG(ERROR) << "could not parse sectors allocated";
|
||||
return false;
|
||||
}
|
||||
if (!android::base::ParseUint(sector_info[1], &status->total_sectors)) {
|
||||
LOG(ERROR) << "could not parse total sectors";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dm
|
||||
} // namespace android
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
|
||||
|
@ -35,21 +36,15 @@
|
|||
#include "test_util.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono_literals;
|
||||
using namespace android::dm;
|
||||
using unique_fd = android::base::unique_fd;
|
||||
|
||||
TEST(libdm, HasMinimumTargets) {
|
||||
DmTargetTypeInfo info;
|
||||
|
||||
DeviceMapper& dm = DeviceMapper::Instance();
|
||||
vector<DmTargetTypeInfo> targets;
|
||||
ASSERT_TRUE(dm.GetAvailableTargets(&targets));
|
||||
|
||||
map<string, DmTargetTypeInfo> by_name;
|
||||
for (const auto& target : targets) {
|
||||
by_name[target.name()] = target;
|
||||
}
|
||||
|
||||
auto iter = by_name.find("linear");
|
||||
EXPECT_NE(iter, by_name.end());
|
||||
ASSERT_TRUE(dm.GetTargetByName("linear", &info));
|
||||
}
|
||||
|
||||
// Helper to ensure that device mapper devices are released.
|
||||
|
@ -201,3 +196,245 @@ TEST(libdm, DmVerityArgsAvb2) {
|
|||
"2 fec_blocks 126955 fec_start 126955 restart_on_corruption ignore_zero_blocks";
|
||||
EXPECT_EQ(target.GetParameterString(), expected);
|
||||
}
|
||||
|
||||
TEST(libdm, DmSnapshotArgs) {
|
||||
DmTargetSnapshot target1(0, 512, "base", "cow", SnapshotStorageMode::Persistent, 8);
|
||||
if (DmTargetSnapshot::ReportsOverflow("snapshot")) {
|
||||
EXPECT_EQ(target1.GetParameterString(), "base cow PO 8");
|
||||
} else {
|
||||
EXPECT_EQ(target1.GetParameterString(), "base cow P 8");
|
||||
}
|
||||
EXPECT_EQ(target1.name(), "snapshot");
|
||||
|
||||
DmTargetSnapshot target2(0, 512, "base", "cow", SnapshotStorageMode::Transient, 8);
|
||||
EXPECT_EQ(target2.GetParameterString(), "base cow N 8");
|
||||
EXPECT_EQ(target2.name(), "snapshot");
|
||||
|
||||
DmTargetSnapshot target3(0, 512, "base", "cow", SnapshotStorageMode::Merge, 8);
|
||||
if (DmTargetSnapshot::ReportsOverflow("snapshot-merge")) {
|
||||
EXPECT_EQ(target3.GetParameterString(), "base cow PO 8");
|
||||
} else {
|
||||
EXPECT_EQ(target3.GetParameterString(), "base cow P 8");
|
||||
}
|
||||
EXPECT_EQ(target3.name(), "snapshot-merge");
|
||||
}
|
||||
|
||||
TEST(libdm, DmSnapshotOriginArgs) {
|
||||
DmTargetSnapshotOrigin target(0, 512, "base");
|
||||
EXPECT_EQ(target.GetParameterString(), "base");
|
||||
EXPECT_EQ(target.name(), "snapshot-origin");
|
||||
}
|
||||
|
||||
class SnapshotTestHarness final {
|
||||
public:
|
||||
bool Setup();
|
||||
bool Merge();
|
||||
|
||||
std::string origin_dev() const { return origin_dev_->path(); }
|
||||
std::string snapshot_dev() const { return snapshot_dev_->path(); }
|
||||
|
||||
int base_fd() const { return base_fd_; }
|
||||
|
||||
static const uint64_t kBaseDeviceSize = 1024 * 1024;
|
||||
static const uint64_t kCowDeviceSize = 1024 * 64;
|
||||
static const uint64_t kSectorSize = 512;
|
||||
|
||||
private:
|
||||
void SetupImpl();
|
||||
void MergeImpl();
|
||||
|
||||
unique_fd base_fd_;
|
||||
unique_fd cow_fd_;
|
||||
unique_ptr<LoopDevice> base_loop_;
|
||||
unique_ptr<LoopDevice> cow_loop_;
|
||||
unique_ptr<TempDevice> origin_dev_;
|
||||
unique_ptr<TempDevice> snapshot_dev_;
|
||||
bool setup_ok_ = false;
|
||||
bool merge_ok_ = false;
|
||||
};
|
||||
|
||||
bool SnapshotTestHarness::Setup() {
|
||||
SetupImpl();
|
||||
return setup_ok_;
|
||||
}
|
||||
|
||||
void SnapshotTestHarness::SetupImpl() {
|
||||
base_fd_ = CreateTempFile("base_device", kBaseDeviceSize);
|
||||
ASSERT_GE(base_fd_, 0);
|
||||
cow_fd_ = CreateTempFile("cow_device", kCowDeviceSize);
|
||||
ASSERT_GE(cow_fd_, 0);
|
||||
|
||||
base_loop_ = std::make_unique<LoopDevice>(base_fd_);
|
||||
ASSERT_TRUE(base_loop_->valid());
|
||||
cow_loop_ = std::make_unique<LoopDevice>(cow_fd_);
|
||||
ASSERT_TRUE(cow_loop_->valid());
|
||||
|
||||
DmTable origin_table;
|
||||
ASSERT_TRUE(origin_table.AddTarget(make_unique<DmTargetSnapshotOrigin>(
|
||||
0, kBaseDeviceSize / kSectorSize, base_loop_->device())));
|
||||
ASSERT_TRUE(origin_table.valid());
|
||||
|
||||
origin_dev_ = std::make_unique<TempDevice>("libdm-test-dm-snapshot-origin", origin_table);
|
||||
ASSERT_TRUE(origin_dev_->valid());
|
||||
ASSERT_FALSE(origin_dev_->path().empty());
|
||||
ASSERT_TRUE(origin_dev_->WaitForUdev());
|
||||
|
||||
// chunk size = 4K blocks.
|
||||
DmTable snap_table;
|
||||
ASSERT_TRUE(snap_table.AddTarget(make_unique<DmTargetSnapshot>(
|
||||
0, kBaseDeviceSize / kSectorSize, base_loop_->device(), cow_loop_->device(),
|
||||
SnapshotStorageMode::Persistent, 8)));
|
||||
ASSERT_TRUE(snap_table.valid());
|
||||
|
||||
snapshot_dev_ = std::make_unique<TempDevice>("libdm-test-dm-snapshot", snap_table);
|
||||
ASSERT_TRUE(snapshot_dev_->valid());
|
||||
ASSERT_FALSE(snapshot_dev_->path().empty());
|
||||
ASSERT_TRUE(snapshot_dev_->WaitForUdev());
|
||||
|
||||
setup_ok_ = true;
|
||||
}
|
||||
|
||||
bool SnapshotTestHarness::Merge() {
|
||||
MergeImpl();
|
||||
return merge_ok_;
|
||||
}
|
||||
|
||||
void SnapshotTestHarness::MergeImpl() {
|
||||
DmTable merge_table;
|
||||
ASSERT_TRUE(merge_table.AddTarget(
|
||||
make_unique<DmTargetSnapshot>(0, kBaseDeviceSize / kSectorSize, base_loop_->device(),
|
||||
cow_loop_->device(), SnapshotStorageMode::Merge, 8)));
|
||||
ASSERT_TRUE(merge_table.valid());
|
||||
|
||||
DeviceMapper& dm = DeviceMapper::Instance();
|
||||
ASSERT_TRUE(dm.LoadTableAndActivate("libdm-test-dm-snapshot", merge_table));
|
||||
|
||||
while (true) {
|
||||
vector<DeviceMapper::TargetInfo> status;
|
||||
ASSERT_TRUE(dm.GetTableStatus("libdm-test-dm-snapshot", &status));
|
||||
ASSERT_EQ(status.size(), 1);
|
||||
ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")),
|
||||
0);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool CheckSnapshotAvailability() {
|
||||
DmTargetTypeInfo info;
|
||||
|
||||
DeviceMapper& dm = DeviceMapper::Instance();
|
||||
if (!dm.GetTargetByName("snapshot", &info)) {
|
||||
cout << "snapshot module not enabled; skipping test" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!dm.GetTargetByName("snapshot-merge", &info)) {
|
||||
cout << "snapshot-merge module not enabled; skipping test" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!dm.GetTargetByName("snapshot-origin", &info)) {
|
||||
cout << "snapshot-origin module not enabled; skipping test" << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(libdm, DmSnapshot) {
|
||||
if (!CheckSnapshotAvailability()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnapshotTestHarness harness;
|
||||
ASSERT_TRUE(harness.Setup());
|
||||
|
||||
// Open the dm devices.
|
||||
unique_fd origin_fd(open(harness.origin_dev().c_str(), O_RDONLY | O_CLOEXEC));
|
||||
ASSERT_GE(origin_fd, 0);
|
||||
unique_fd snapshot_fd(open(harness.snapshot_dev().c_str(), O_RDWR | O_CLOEXEC | O_SYNC));
|
||||
ASSERT_GE(snapshot_fd, 0);
|
||||
|
||||
// Write to the first block of the snapshot device.
|
||||
std::string data("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
||||
ASSERT_TRUE(android::base::WriteFully(snapshot_fd, data.data(), data.size()));
|
||||
ASSERT_EQ(lseek(snapshot_fd, 0, SEEK_SET), 0);
|
||||
|
||||
// We should get the same data back from the snapshot device.
|
||||
std::string read(data.size(), '\0');
|
||||
ASSERT_TRUE(android::base::ReadFully(snapshot_fd, read.data(), read.size()));
|
||||
ASSERT_EQ(read, data);
|
||||
|
||||
// We should see the original data from the origin device.
|
||||
std::string zeroes(data.size(), '\0');
|
||||
ASSERT_TRUE(android::base::ReadFully(origin_fd, read.data(), read.size()));
|
||||
ASSERT_EQ(lseek(snapshot_fd, 0, SEEK_SET), 0);
|
||||
ASSERT_EQ(read, zeroes);
|
||||
|
||||
// We should also see the original data from the base device.
|
||||
ASSERT_TRUE(android::base::ReadFully(harness.base_fd(), read.data(), read.size()));
|
||||
ASSERT_EQ(lseek(harness.base_fd(), 0, SEEK_SET), 0);
|
||||
ASSERT_EQ(read, zeroes);
|
||||
|
||||
// Now, perform the merge and wait.
|
||||
ASSERT_TRUE(harness.Merge());
|
||||
|
||||
// Reading from the base device should give us the modified data.
|
||||
ASSERT_TRUE(android::base::ReadFully(harness.base_fd(), read.data(), read.size()));
|
||||
ASSERT_EQ(lseek(harness.base_fd(), 0, SEEK_SET), 0);
|
||||
ASSERT_EQ(read, data);
|
||||
}
|
||||
|
||||
TEST(libdm, DmSnapshotOverflow) {
|
||||
if (!CheckSnapshotAvailability()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnapshotTestHarness harness;
|
||||
ASSERT_TRUE(harness.Setup());
|
||||
|
||||
// Open the dm devices.
|
||||
unique_fd snapshot_fd(open(harness.snapshot_dev().c_str(), O_RDWR | O_CLOEXEC));
|
||||
ASSERT_GE(snapshot_fd, 0);
|
||||
|
||||
// Fill the copy-on-write device until it overflows.
|
||||
uint64_t bytes_remaining = SnapshotTestHarness::kCowDeviceSize;
|
||||
uint8_t byte = 1;
|
||||
while (bytes_remaining) {
|
||||
std::string data(4096, char(byte));
|
||||
if (!android::base::WriteFully(snapshot_fd, data.data(), data.size())) {
|
||||
ASSERT_EQ(errno, EIO);
|
||||
break;
|
||||
}
|
||||
bytes_remaining -= data.size();
|
||||
}
|
||||
|
||||
// If writes succeed (because they are buffered), then we should expect an
|
||||
// fsync to fail with EIO.
|
||||
if (!bytes_remaining) {
|
||||
ASSERT_EQ(fsync(snapshot_fd), -1);
|
||||
ASSERT_EQ(errno, EIO);
|
||||
}
|
||||
|
||||
DeviceMapper& dm = DeviceMapper::Instance();
|
||||
|
||||
vector<DeviceMapper::TargetInfo> target_status;
|
||||
ASSERT_TRUE(dm.GetTableStatus("libdm-test-dm-snapshot", &target_status));
|
||||
ASSERT_EQ(target_status.size(), 1);
|
||||
ASSERT_EQ(strncmp(target_status[0].spec.target_type, "snapshot", strlen("snapshot")), 0);
|
||||
|
||||
DmTargetSnapshot::Status status;
|
||||
ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(target_status[0].data, &status));
|
||||
if (DmTargetSnapshot::ReportsOverflow("snapshot")) {
|
||||
ASSERT_EQ(status.error, "Overflow");
|
||||
} else {
|
||||
ASSERT_EQ(status.error, "Invalid");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,10 @@ class DeviceMapper final {
|
|||
// successfully read and stored in 'targets'. Returns 'false' otherwise.
|
||||
bool GetAvailableTargets(std::vector<DmTargetTypeInfo>* targets);
|
||||
|
||||
// Finds a target by name and returns its information if found. |info| may
|
||||
// be null to check for the existence of a target.
|
||||
bool GetTargetByName(const std::string& name, DmTargetTypeInfo* info);
|
||||
|
||||
// Return 'true' if it can successfully read the list of device mapper block devices
|
||||
// currently created. 'devices' will be empty if the kernel interactions
|
||||
// were successful and there are no block devices at the moment. Returns
|
||||
|
|
|
@ -40,6 +40,18 @@ class DmTargetTypeInfo {
|
|||
return std::to_string(major_) + "." + std::to_string(minor_) + "." + std::to_string(patch_);
|
||||
}
|
||||
|
||||
uint32_t major_version() const { return major_; }
|
||||
uint32_t minor_version() const { return minor_; }
|
||||
uint32_t patch_level() const { return patch_; }
|
||||
|
||||
bool IsAtLeast(uint32_t major, uint32_t minor, uint32_t patch) const {
|
||||
if (major_ > major) return true;
|
||||
if (major_ < major) return false;
|
||||
if (minor_ > minor) return true;
|
||||
if (minor_ < minor) return false;
|
||||
return patch_ >= patch;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
uint32_t major_;
|
||||
|
@ -170,6 +182,65 @@ class DmTargetBow final : public DmTarget {
|
|||
std::string target_string_;
|
||||
};
|
||||
|
||||
enum class SnapshotStorageMode {
|
||||
// The snapshot will be persisted to the COW device.
|
||||
Persistent,
|
||||
// The snapshot will be lost on reboot.
|
||||
Transient,
|
||||
// The snapshot will be merged from the COW device into the base device,
|
||||
// in the background.
|
||||
Merge
|
||||
};
|
||||
|
||||
// Writes to a snapshot device will be written to the given COW device. Reads
|
||||
// will read from the COW device or base device. The chunk size is specified
|
||||
// in sectors.
|
||||
class DmTargetSnapshot final : public DmTarget {
|
||||
public:
|
||||
DmTargetSnapshot(uint64_t start, uint64_t length, const std::string& base_device,
|
||||
const std::string& cow_device, SnapshotStorageMode mode, uint64_t chunk_size)
|
||||
: DmTarget(start, length),
|
||||
base_device_(base_device),
|
||||
cow_device_(cow_device),
|
||||
mode_(mode),
|
||||
chunk_size_(chunk_size) {}
|
||||
|
||||
std::string name() const override;
|
||||
std::string GetParameterString() const override;
|
||||
bool Valid() const override { return true; }
|
||||
|
||||
struct Status {
|
||||
uint64_t sectors_allocated;
|
||||
uint64_t total_sectors;
|
||||
uint64_t metadata_sectors;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
static bool ParseStatusText(const std::string& text, Status* status);
|
||||
static bool ReportsOverflow(const std::string& target_type);
|
||||
|
||||
private:
|
||||
std::string base_device_;
|
||||
std::string cow_device_;
|
||||
SnapshotStorageMode mode_;
|
||||
uint64_t chunk_size_;
|
||||
};
|
||||
|
||||
// snapshot-origin will read/write directly to the backing device, updating any
|
||||
// snapshot devices with a matching origin.
|
||||
class DmTargetSnapshotOrigin final : public DmTarget {
|
||||
public:
|
||||
DmTargetSnapshotOrigin(uint64_t start, uint64_t length, const std::string& device)
|
||||
: DmTarget(start, length), device_(device) {}
|
||||
|
||||
std::string name() const override { return "snapshot-origin"; }
|
||||
std::string GetParameterString() const override { return device_; }
|
||||
bool Valid() const override { return true; }
|
||||
|
||||
private:
|
||||
std::string device_;
|
||||
};
|
||||
|
||||
} // namespace dm
|
||||
} // namespace android
|
||||
|
||||
|
|
|
@ -38,15 +38,7 @@
|
|||
#include <vector>
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
using DeviceMapper = ::android::dm::DeviceMapper;
|
||||
using DmTable = ::android::dm::DmTable;
|
||||
using DmTarget = ::android::dm::DmTarget;
|
||||
using DmTargetLinear = ::android::dm::DmTargetLinear;
|
||||
using DmTargetZero = ::android::dm::DmTargetZero;
|
||||
using DmTargetAndroidVerity = ::android::dm::DmTargetAndroidVerity;
|
||||
using DmTargetBow = ::android::dm::DmTargetBow;
|
||||
using DmTargetTypeInfo = ::android::dm::DmTargetTypeInfo;
|
||||
using namespace android::dm;
|
||||
using DmBlockDevice = ::android::dm::DeviceMapper::DmBlockDevice;
|
||||
|
||||
static int Usage(void) {
|
||||
|
@ -57,6 +49,7 @@ static int Usage(void) {
|
|||
std::cerr << " delete <dm-name>" << std::endl;
|
||||
std::cerr << " list <devices | targets> [-v]" << std::endl;
|
||||
std::cerr << " getpath <dm-name>" << std::endl;
|
||||
std::cerr << " status <dm-name>" << std::endl;
|
||||
std::cerr << " table <dm-name>" << std::endl;
|
||||
std::cerr << " help" << std::endl;
|
||||
std::cerr << std::endl;
|
||||
|
@ -122,6 +115,62 @@ class TargetParser final {
|
|||
}
|
||||
std::string block_device = NextArg();
|
||||
return std::make_unique<DmTargetBow>(start_sector, num_sectors, block_device);
|
||||
} else if (target_type == "snapshot-origin") {
|
||||
if (!HasArgs(1)) {
|
||||
std::cerr << "Expected \"snapshot-origin\" <block_device>" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
std::string block_device = NextArg();
|
||||
return std::make_unique<DmTargetSnapshotOrigin>(start_sector, num_sectors,
|
||||
block_device);
|
||||
} else if (target_type == "snapshot") {
|
||||
if (!HasArgs(4)) {
|
||||
std::cerr
|
||||
<< "Expected \"snapshot\" <block_device> <block_device> <mode> <chunk_size>"
|
||||
<< std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
std::string base_device = NextArg();
|
||||
std::string cow_device = NextArg();
|
||||
std::string mode_str = NextArg();
|
||||
std::string chunk_size_str = NextArg();
|
||||
|
||||
SnapshotStorageMode mode;
|
||||
if (mode_str == "P") {
|
||||
mode = SnapshotStorageMode::Persistent;
|
||||
} else if (mode_str == "N") {
|
||||
mode = SnapshotStorageMode::Transient;
|
||||
} else {
|
||||
std::cerr << "Unrecognized mode: " << mode_str << "\n";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t chunk_size;
|
||||
if (!android::base::ParseUint(chunk_size_str, &chunk_size)) {
|
||||
std::cerr << "Chunk size must be an unsigned integer.\n";
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<DmTargetSnapshot>(start_sector, num_sectors, base_device,
|
||||
cow_device, mode, chunk_size);
|
||||
} else if (target_type == "snapshot-merge") {
|
||||
if (!HasArgs(3)) {
|
||||
std::cerr
|
||||
<< "Expected \"snapshot-merge\" <block_device> <block_device> <chunk_size>"
|
||||
<< std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
std::string base_device = NextArg();
|
||||
std::string cow_device = NextArg();
|
||||
std::string chunk_size_str = NextArg();
|
||||
SnapshotStorageMode mode = SnapshotStorageMode::Merge;
|
||||
|
||||
uint32_t chunk_size;
|
||||
if (!android::base::ParseUint(chunk_size_str, &chunk_size)) {
|
||||
std::cerr << "Chunk size must be an unsigned integer.\n";
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<DmTargetSnapshot>(start_sector, num_sectors, base_device,
|
||||
cow_device, mode, chunk_size);
|
||||
} else {
|
||||
std::cerr << "Unrecognized target type: " << target_type << std::endl;
|
||||
return nullptr;
|
||||
|
@ -308,7 +357,7 @@ static int GetPathCmdHandler(int argc, char** argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int TableCmdHandler(int argc, char** argv) {
|
||||
static int DumpTable(const std::string& mode, int argc, char** argv) {
|
||||
if (argc != 1) {
|
||||
std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
|
||||
return -EINVAL;
|
||||
|
@ -316,9 +365,18 @@ static int TableCmdHandler(int argc, char** argv) {
|
|||
|
||||
DeviceMapper& dm = DeviceMapper::Instance();
|
||||
std::vector<DeviceMapper::TargetInfo> table;
|
||||
if (!dm.GetTableInfo(argv[0], &table)) {
|
||||
std::cerr << "Could not query table status of device \"" << argv[0] << "\"." << std::endl;
|
||||
return -EINVAL;
|
||||
if (mode == "status") {
|
||||
if (!dm.GetTableStatus(argv[0], &table)) {
|
||||
std::cerr << "Could not query table status of device \"" << argv[0] << "\"."
|
||||
<< std::endl;
|
||||
return -EINVAL;
|
||||
}
|
||||
} else if (mode == "table") {
|
||||
if (!dm.GetTableInfo(argv[0], &table)) {
|
||||
std::cerr << "Could not query table status of device \"" << argv[0] << "\"."
|
||||
<< std::endl;
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
std::cout << "Targets in the device-mapper table for " << argv[0] << ":" << std::endl;
|
||||
for (const auto& target : table) {
|
||||
|
@ -333,6 +391,14 @@ static int TableCmdHandler(int argc, char** argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int TableCmdHandler(int argc, char** argv) {
|
||||
return DumpTable("table", argc, argv);
|
||||
}
|
||||
|
||||
static int StatusCmdHandler(int argc, char** argv) {
|
||||
return DumpTable("status", argc, argv);
|
||||
}
|
||||
|
||||
static std::map<std::string, std::function<int(int, char**)>> cmdmap = {
|
||||
// clang-format off
|
||||
{"create", DmCreateCmdHandler},
|
||||
|
@ -341,6 +407,7 @@ static std::map<std::string, std::function<int(int, char**)>> cmdmap = {
|
|||
{"help", HelpCmdHandler},
|
||||
{"getpath", GetPathCmdHandler},
|
||||
{"table", TableCmdHandler},
|
||||
{"status", StatusCmdHandler},
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue