fs_mgr: replace DM_TABLE_STATUS use with libdm.
This change introduces a new GetTableStatus method on DeviceMapper,
which returns a vector of information about each target in a device's
table. Some target types (such as verity) can also return additional
information as a string.
Support for this call has also been added to the "dmctl" tool via a
"table" command. Examples:
$ dmctl create blah zero 0 8000 linear 8000 1000 /dev/block/sdd1 0
$ dmctl table blah
Targets in the device-mapper table for blah:
0-8000: zero
8000-9000: linear
For verity:
sailfish:/ # dmctl table system
Targets in the device-mapper table for system:
0-4128792: android-verity, V
Bug: 110035986
Test: libdm_test gtest; AVB1 device still boots
Change-Id: Iaf13450d3b32e2264c7c399a8af8d6bade260592
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 9856126..6ffc26d 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -50,6 +50,7 @@
#include <ext4_utils/ext4_sb.h>
#include <ext4_utils/ext4_utils.h>
#include <ext4_utils/wipe.h>
+#include <libdm/dm.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <linux/magic.h>
@@ -76,6 +77,8 @@
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+using DeviceMapper = android::dm::DeviceMapper;
+
// record fs stat
enum FsStatFlags {
FS_STAT_IS_EXT4 = 0x0001,
@@ -1369,12 +1372,6 @@
return false;
}
- android::base::unique_fd fd(TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC)));
- if (fd == -1) {
- PERROR << "Error opening device mapper";
- return false;
- }
-
std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
fs_mgr_free_fstab);
if (!fstab) {
@@ -1382,8 +1379,8 @@
return false;
}
- alignas(dm_ioctl) char buffer[DM_BUF_SIZE];
- struct dm_ioctl* io = (struct dm_ioctl*)buffer;
+ DeviceMapper& dm = DeviceMapper::Instance();
+
bool system_root = android::base::GetProperty("ro.build.system_root_image", "") == "true";
for (int i = 0; i < fstab->num_entries; i++) {
@@ -1399,20 +1396,20 @@
mount_point = basename(fstab->recs[i].mount_point);
}
- fs_mgr_dm_ioctl_init(io, DM_BUF_SIZE, mount_point);
+ const char* status = nullptr;
- const char* status;
- if (ioctl(fd, DM_TABLE_STATUS, io)) {
+ std::vector<DeviceMapper::TargetInfo> table;
+ if (!dm.GetTableStatus(mount_point, &table) || table.empty() || table[0].data.empty()) {
if (fstab->recs[i].fs_mgr_flags & MF_VERIFYATBOOT) {
status = "V";
} else {
PERROR << "Failed to query DM_TABLE_STATUS for " << mount_point.c_str();
continue;
}
+ } else {
+ status = table[0].data.c_str();
}
- status = &buffer[io->data_start + sizeof(struct dm_target_spec)];
-
// To be consistent in vboot 1.0 and vboot 2.0 (AVB), change the mount_point
// back to 'system' for the callback. So it has property [partition.system.verified]
// instead of [partition.vroot.verified].
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index b96f4c1..ad3b6f4 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -272,6 +272,46 @@
return true;
}
+bool DeviceMapper::GetTableStatus(const std::string& name, std::vector<TargetInfo>* table) {
+ char buffer[4096];
+ struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(buffer);
+
+ InitIo(io, name);
+ io->data_size = sizeof(buffer);
+ io->data_start = sizeof(*io);
+ if (ioctl(fd_, DM_TABLE_STATUS, io) < 0) {
+ PLOG(ERROR) << "DM_TABLE_STATUS failed for " << name;
+ return false;
+ }
+ if (io->flags & DM_BUFFER_FULL_FLAG) {
+ PLOG(ERROR) << "DM_TABLE_STATUS result for " << name << " was too large";
+ return false;
+ }
+
+ uint32_t cursor = io->data_start;
+ uint32_t data_end = std::min(io->data_size, uint32_t(sizeof(buffer)));
+ for (uint32_t i = 0; i < io->target_count; i++) {
+ if (cursor + sizeof(struct dm_target_spec) > data_end) {
+ break;
+ }
+ // After each dm_target_spec is a status string. spec->next is an
+ // offset from |io->data_start|, and we clamp it to the size of our
+ // buffer.
+ struct dm_target_spec* spec = reinterpret_cast<struct dm_target_spec*>(buffer + cursor);
+ uint32_t data_offset = cursor + sizeof(dm_target_spec);
+ uint32_t next_cursor = std::min(io->data_start + spec->next, data_end);
+
+ std::string data;
+ if (next_cursor > data_offset) {
+ // Note: we use c_str() to eliminate any extra trailing 0s.
+ data = std::string(buffer + data_offset, next_cursor - data_offset).c_str();
+ }
+ table->emplace_back(*spec, data);
+ cursor = next_cursor;
+ }
+ return true;
+}
+
// private methods of DeviceMapper
void DeviceMapper::InitIo(struct dm_ioctl* io, const std::string& name) const {
CHECK(io != nullptr) << "nullptr passed to dm_ioctl initialization";
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index 85f8e4a..cc61917 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -160,6 +160,20 @@
ASSERT_EQ(strncmp(sector, message2, sizeof(message2)), 0);
}
+ // Test GetTableStatus.
+ DeviceMapper& dm = DeviceMapper::Instance();
+ vector<DeviceMapper::TargetInfo> targets;
+ ASSERT_TRUE(dm.GetTableStatus(dev.name(), &targets));
+ ASSERT_EQ(targets.size(), 2);
+ EXPECT_EQ(strcmp(targets[0].spec.target_type, "linear"), 0);
+ EXPECT_TRUE(targets[0].data.empty());
+ EXPECT_EQ(targets[0].spec.sector_start, 0);
+ EXPECT_EQ(targets[0].spec.length, 1);
+ EXPECT_EQ(strcmp(targets[1].spec.target_type, "linear"), 0);
+ EXPECT_TRUE(targets[1].data.empty());
+ EXPECT_EQ(targets[1].spec.sector_start, 1);
+ EXPECT_EQ(targets[1].spec.length, 1);
+
// Normally the TestDevice destructor would delete this, but at least one
// test should ensure that device deletion works.
ASSERT_TRUE(dev.Destroy());
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 60bceed..e2bc729 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -26,6 +26,7 @@
#include <memory>
#include <string>
+#include <utility>
#include <vector>
#include <android-base/logging.h>
@@ -117,6 +118,18 @@
}
}
+ // Query the status of a table, given a device name. The output vector will
+ // contain one TargetInfo for each target in the table. If the device does
+ // not exist, or there were too many targets, the call will fail and return
+ // false.
+ struct TargetInfo {
+ struct dm_target_spec spec;
+ std::string data;
+ TargetInfo(const struct dm_target_spec& spec, const std::string& data)
+ : spec(spec), data(data) {}
+ };
+ bool GetTableStatus(const std::string& name, std::vector<TargetInfo>* table);
+
private:
// Maximum possible device mapper targets registered in the kernel.
// This is only used to read the list of targets from kernel so we allocate
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 9d48b8c..5e11c84 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -50,6 +50,7 @@
std::cerr << " delete <dm-name>" << std::endl;
std::cerr << " list <devices | targets>" << std::endl;
std::cerr << " getpath <dm-name>" << std::endl;
+ std::cerr << " table <dm-name>" << std::endl;
std::cerr << " help" << std::endl;
std::cerr << std::endl;
std::cerr << "Target syntax:" << std::endl;
@@ -258,6 +259,31 @@
return 0;
}
+static int TableCmdHandler(int argc, char** argv) {
+ if (argc != 1) {
+ std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+ return -EINVAL;
+ }
+
+ DeviceMapper& dm = DeviceMapper::Instance();
+ std::vector<DeviceMapper::TargetInfo> table;
+ if (!dm.GetTableStatus(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) {
+ std::cout << target.spec.sector_start << "-"
+ << (target.spec.sector_start + target.spec.length) << ": "
+ << target.spec.target_type;
+ if (!target.data.empty()) {
+ std::cout << ", " << target.data;
+ }
+ std::cout << std::endl;
+ }
+ return 0;
+}
+
static std::map<std::string, std::function<int(int, char**)>> cmdmap = {
// clang-format off
{"create", DmCreateCmdHandler},
@@ -265,6 +291,7 @@
{"list", DmListCmdHandler},
{"help", HelpCmdHandler},
{"getpath", GetPathCmdHandler},
+ {"table", TableCmdHandler},
// clang-format on
};