libdm: Implement zero and linear targets.

This change implements DmTargetZero and DmTargetLinear, and integrates
them into dmctl. It also implements DmTarget and DmTable serialization.

Example dmctl invocation:

  dmctl create my-device -ro \
        linear 0 800 /dev/block/by-name/system 0 \
        zero   800 1200                          \
        linear 1200 1500 /dev/block/by-name/system 1200

Bug: 110035986
Test: libdm_test gtest
Change-Id: I7f945c1d9e23cfb78239c23a1aad88e8aef4972b
This commit is contained in:
David Anderson 2018-06-20 16:37:36 -07:00
parent c7def6849a
commit bac58aeecf
6 changed files with 194 additions and 20 deletions

View File

@ -114,6 +114,9 @@ bool DeviceMapper::LoadTableAndActivate(const std::string& name, const DmTable&
io->data_size = ioctl_buffer.size();
io->data_start = sizeof(struct dm_ioctl);
io->target_count = static_cast<uint32_t>(table.num_targets());
if (table.readonly()) {
io->flags |= DM_READONLY_FLAG;
}
if (ioctl(fd_, DM_TABLE_LOAD, io)) {
PLOG(ERROR) << "DM_TABLE_LOAD failed";
return false;

View File

@ -22,7 +22,8 @@
namespace android {
namespace dm {
bool DmTable::AddTarget(std::unique_ptr<DmTarget>&& /* target */) {
bool DmTable::AddTarget(std::unique_ptr<DmTarget>&& target) {
targets_.push_back(std::move(target));
return true;
}
@ -31,6 +32,14 @@ bool DmTable::RemoveTarget(std::unique_ptr<DmTarget>&& /* target */) {
}
bool DmTable::valid() const {
if (targets_.empty()) {
LOG(ERROR) << "Device-mapper table must have at least one target.";
return "";
}
if (targets_[0]->start() != 0) {
LOG(ERROR) << "Device-mapper table must start at logical sector 0.";
return "";
}
return true;
}
@ -38,14 +47,22 @@ uint64_t DmTable::num_sectors() const {
return valid() ? num_sectors_ : 0;
}
// Returns a string represnetation of the table that is ready to be passed
// down to the kernel for loading
// Returns a string representation of the table that is ready to be passed
// down to the kernel for loading.
//
// Implementation must verify there are no gaps in the table, table starts
// with sector == 0, and iterate over each target to get its table
// serialized.
std::string DmTable::Serialize() const {
return "";
if (!valid()) {
return "";
}
std::string table;
for (const auto& target : targets_) {
table += target->Serialize();
}
return table;
}
} // namespace dm

View File

@ -19,6 +19,41 @@
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <libdm/dm.h>
namespace android {
namespace dm {} // namespace dm
namespace dm {
std::string DmTarget::Serialize() const {
// Create a string containing a dm_target_spec, parameter data, and an
// explicit null terminator.
std::string data(sizeof(dm_target_spec), '\0');
data += GetParameterString();
data.push_back('\0');
// The kernel expects each target to be 8-byte aligned.
size_t padding = DM_ALIGN(data.size()) - data.size();
for (size_t i = 0; i < padding; i++) {
data.push_back('\0');
}
// Finally fill in the dm_target_spec.
struct dm_target_spec* spec = reinterpret_cast<struct dm_target_spec*>(&data[0]);
spec->sector_start = start();
spec->length = size();
strlcpy(spec->target_type, name().c_str(), sizeof(spec->target_type));
spec->next = (uint32_t)data.size();
return data;
}
std::string DmTargetZero::GetParameterString() const {
// The zero target type has no additional parameters.
return "";
}
std::string DmTargetLinear::GetParameterString() const {
return block_device_ + " " + std::to_string(physical_sector_);
}
} // namespace dm
} // namespace android

View File

@ -30,7 +30,7 @@ namespace dm {
class DmTable {
public:
DmTable() : num_sectors_(0){};
DmTable() : num_sectors_(0), readonly_(false) {}
// Adds a target to the device mapper table for a range specified in the target object.
// The function will return 'true' if the target was successfully added and doesn't overlap with
@ -59,6 +59,9 @@ class DmTable {
// as part of the DM_TABLE_LOAD ioctl.
std::string Serialize() const;
void set_readonly(bool readonly) { readonly_ = readonly; }
bool readonly() const { return readonly_; }
~DmTable() = default;
private:
@ -70,6 +73,9 @@ class DmTable {
// Total size in terms of # of sectors, as calculated by looking at the last and the first
// target in 'target_'.
uint64_t num_sectors_;
// True if the device should be read-only; false otherwise.
bool readonly_;
};
} // namespace dm

View File

@ -55,7 +55,7 @@ class DmTarget {
virtual ~DmTarget() = default;
// Returns name of the target.
virtual const std::string& name() const = 0;
virtual std::string name() const = 0;
// Return the first logical sector represented by this target.
uint64_t start() const { return start_; }
@ -67,7 +67,12 @@ class DmTarget {
// Function that converts this object to a string of arguments that can
// be passed to the kernel for adding this target in a table. Each target (e.g. verity, linear)
// must implement this, for it to be used on a device.
virtual std::string Serialize() const = 0;
std::string Serialize() const;
protected:
// Get the parameter string that is passed to the end of the dm_target_spec
// for this target type.
virtual std::string GetParameterString() const = 0;
private:
// logical sector number start and total length (in terms of 512-byte sectors) represented
@ -75,6 +80,28 @@ class DmTarget {
uint64_t start_, length_;
};
class DmTargetZero final : public DmTarget {
public:
DmTargetZero(uint64_t start, uint64_t length) : DmTarget(start, length) {}
std::string name() const override { return "zero"; }
std::string GetParameterString() const override;
};
class DmTargetLinear final : public DmTarget {
public:
DmTargetLinear(uint64_t start, uint64_t length, const std::string& block_device,
uint64_t physical_sector)
: DmTarget(start, length), block_device_(block_device), physical_sector_(physical_sector) {}
std::string name() const override { return "linear"; }
std::string GetParameterString() const override;
private:
std::string block_device_;
uint64_t physical_sector_;
};
} // namespace dm
} // namespace android

View File

@ -22,6 +22,7 @@
#include <sys/types.h>
#include <unistd.h>
#include <android-base/parseint.h>
#include <android-base/unique_fd.h>
#include <libdm/dm.h>
@ -30,46 +31,131 @@
#include <ios>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
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 DmTargetTypeInfo = ::android::dm::DmTargetTypeInfo;
using DmBlockDevice = ::android::dm::DeviceMapper::DmBlockDevice;
static int Usage(void) {
std::cerr << "usage: dmctl <command> [command options]" << std::endl;
std::cerr << "commands:" << std::endl;
std::cerr << " create <dm-name> [<dm-target> [-lo <filename>] <dm-target-args>]" << std::endl;
std::cerr << " create <dm-name> [-ro] <targets...>" << std::endl;
std::cerr << " delete <dm-name>" << std::endl;
std::cerr << " list <devices | targets>" << std::endl;
std::cerr << " help" << std::endl;
std::cerr << std::endl;
std::cerr << "Target syntax:" << std::endl;
std::cerr << " <target_type> <start_sector> <num_sectors> [target_data]" << std::endl;
return -EINVAL;
}
class TargetParser final {
public:
TargetParser(int argc, char** argv) : arg_index_(0), argc_(argc), argv_(argv) {}
bool More() const { return arg_index_ < argc_; }
std::unique_ptr<DmTarget> Next() {
if (!HasArgs(3)) {
std::cerr << "Expected <target_type> <start_sector> <num_sectors>" << std::endl;
return nullptr;
}
std::string target_type = NextArg();
uint64_t start_sector, num_sectors;
if (!android::base::ParseUint(NextArg(), &start_sector)) {
std::cerr << "Expected start sector, got: " << PreviousArg() << std::endl;
return nullptr;
}
if (!android::base::ParseUint(NextArg(), &num_sectors) || !num_sectors) {
std::cerr << "Expected non-zero sector count, got: " << PreviousArg() << std::endl;
return nullptr;
}
if (target_type == "zero") {
return std::make_unique<DmTargetZero>(start_sector, num_sectors);
} else if (target_type == "linear") {
if (!HasArgs(2)) {
std::cerr << "Expected \"linear\" <block_device> <sector>" << std::endl;
return nullptr;
}
std::string block_device = NextArg();
uint64_t physical_sector;
if (!android::base::ParseUint(NextArg(), &physical_sector)) {
std::cerr << "Expected sector, got: \"" << PreviousArg() << "\"" << std::endl;
return nullptr;
}
return std::make_unique<DmTargetLinear>(start_sector, num_sectors, block_device,
physical_sector);
} else {
std::cerr << "Unrecognized target type: " << target_type << std::endl;
return nullptr;
}
}
private:
bool HasArgs(int count) { return arg_index_ + count <= argc_; }
const char* NextArg() {
CHECK(arg_index_ < argc_);
return argv_[arg_index_++];
}
const char* PreviousArg() {
CHECK(arg_index_ >= 0);
return argv_[arg_index_ - 1];
}
private:
int arg_index_;
int argc_;
char** argv_;
};
static int DmCreateCmdHandler(int argc, char** argv) {
if (argc < 1) {
std::cerr << "Usage: dmctl create <name> [table-args]" << std::endl;
std::cerr << "Usage: dmctl create <dm-name> [-ro] <targets...>" << std::endl;
return -EINVAL;
}
std::string name = argv[0];
// Parse extended options first.
DmTable table;
int arg_index = 1;
while (arg_index < argc && argv[arg_index][0] == '-') {
if (strcmp(argv[arg_index], "-ro") == 0) {
table.set_readonly(true);
} else {
std::cerr << "Unrecognized option: " << argv[arg_index] << std::endl;
return -EINVAL;
}
arg_index++;
}
// Parse everything else as target information.
TargetParser parser(argc - arg_index, argv + arg_index);
while (parser.More()) {
std::unique_ptr<DmTarget> target = parser.Next();
if (!target || !table.AddTarget(std::move(target))) {
return -EINVAL;
}
}
if (table.num_targets() == 0) {
std::cerr << "Must define at least one target." << std::endl;
return -EINVAL;
}
DmTable table;
std::string name = argv[0];
DeviceMapper& dm = DeviceMapper::Instance();
if (!dm.CreateDevice(name, table)) {
std::cerr << "Failed to create device-mapper device with name: " << name << std::endl;
return -EIO;
}
// if we also have target specified
if (argc > 1) {
// fall through for now. This will eventually create a DmTarget() based on the target name
// passing it the table that is specified at the command line
}
return 0;
}