fs_mgr: libdm: Add support to list existing device mapper devices

Test: dmctl create system; dmctl list devices; dmctl delete system;
      dmctl list devices
Bug: 110035986

Change-Id: I4ae5d40041458421068976fa2a99c662c542a9a1
Signed-off-by: Sandeep Patil <sspatil@google.com>
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index c2f732b..57c1270 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -182,6 +182,69 @@
     return true;
 }
 
+bool DeviceMapper::GetAvailableDevices(std::vector<DmBlockDevice>* devices) {
+    devices->clear();
+
+    // calculate the space needed to read a maximum of 256 targets, each with
+    // name with maximum length of 16 bytes
+    uint32_t payload_size = sizeof(struct dm_name_list);
+    // 128-bytes for the name
+    payload_size += DM_NAME_LEN;
+    // dm wants every device spec to be aligned at 8-byte boundary
+    payload_size = DM_ALIGN(payload_size);
+    payload_size *= kMaxPossibleDmDevices;
+    uint32_t data_size = sizeof(struct dm_ioctl) + payload_size;
+    auto buffer = std::unique_ptr<void, void (*)(void*)>(calloc(1, data_size), free);
+    if (buffer == nullptr) {
+        LOG(ERROR) << "failed to allocate memory";
+        return false;
+    }
+
+    // Sets appropriate data size and data_start to make sure we tell kernel
+    // about the total size of the buffer we are passing and where to start
+    // writing the list of targets.
+    struct dm_ioctl* io = reinterpret_cast<struct dm_ioctl*>(buffer.get());
+    InitIo(io);
+    io->data_size = data_size;
+    io->data_start = sizeof(*io);
+
+    if (ioctl(fd_, DM_LIST_DEVICES, io)) {
+        PLOG(ERROR) << "Failed to get DM_LIST_DEVICES from kernel";
+        return false;
+    }
+
+    // If the provided buffer wasn't enough to list all devices any data
+    // beyond sizeof(*io) must not be read.
+    if (io->flags & DM_BUFFER_FULL_FLAG) {
+        LOG(INFO) << data_size << " is not enough memory to list all dm devices";
+        return false;
+    }
+
+    // if there are no devices created yet, return success with empty vector
+    if (io->data_size == sizeof(*io)) {
+        return true;
+    }
+
+    // Parse each device and add a new DmBlockDevice to the vector
+    // created from the kernel data.
+    uint32_t next = sizeof(*io);
+    data_size = io->data_size - next;
+    struct dm_name_list* dm_dev =
+            reinterpret_cast<struct dm_name_list*>(static_cast<char*>(buffer.get()) + next);
+
+    while (next && data_size) {
+        devices->emplace_back((dm_dev));
+        if (dm_dev->next == 0) {
+            break;
+        }
+        next += dm_dev->next;
+        data_size -= dm_dev->next;
+        dm_dev = reinterpret_cast<struct dm_name_list*>(static_cast<char*>(buffer.get()) + next);
+    }
+
+    return true;
+}
+
 // Accepts a device mapper device name (like system_a, vendor_b etc) and
 // returns the path to it's device node (or symlink to the device node)
 std::string DeviceMapper::GetDmDevicePathByName(const std::string& /* name */) {
diff --git a/fs_mgr/libdm/include/dm.h b/fs_mgr/libdm/include/dm.h
index d839393..52a9a11 100644
--- a/fs_mgr/libdm/include/dm.h
+++ b/fs_mgr/libdm/include/dm.h
@@ -20,6 +20,8 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/dm-ioctl.h>
+#include <linux/kdev_t.h>
+#include <sys/sysmacros.h>
 #include <unistd.h>
 
 #include <memory>
@@ -43,6 +45,28 @@
 
 class DeviceMapper final {
   public:
+    class DmBlockDevice final {
+      public:
+        // only allow creating this with dm_name_list
+        DmBlockDevice() = delete;
+
+        explicit DmBlockDevice(struct dm_name_list* d) : name_(d->name), dev_(d->dev){};
+
+        // Returs device mapper name associated with the block device
+        const std::string& name() const { return name_; }
+
+        // Return major number for the block device
+        uint32_t Major() const { return major(dev_); }
+
+        // Return minor number for the block device
+        uint32_t Minor() const { return minor(dev_); }
+        ~DmBlockDevice() = default;
+
+      private:
+        std::string name_;
+        uint64_t dev_;
+    };
+
     // Creates a device mapper device with given name.
     // Return 'true' on success and 'false' on failure to
     // create OR if a device mapper device with the same name already
@@ -74,6 +98,12 @@
     // successfully read and stored in 'targets'. Returns 'false' otherwise.
     bool GetAvailableTargets(std::vector<DmTarget>* targets);
 
+    // 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
+    // 'false' in case of any failure along the way.
+    bool GetAvailableDevices(std::vector<DmBlockDevice>* devices);
+
     // Returns the path to the device mapper device node in '/dev' corresponding to
     // 'name'.
     std::string GetDmDevicePathByName(const std::string& name);
@@ -93,6 +123,12 @@
     // a finite amount of memory. This limit is in no way enforced by the kernel.
     static constexpr uint32_t kMaxPossibleDmTargets = 256;
 
+    // Maximum possible device mapper created block devices. Note that this is restricted by
+    // the minor numbers (that used to be 8 bits) that can be range from 0 to 2^20-1 in newer
+    // kernels. In Android systems however, we never expect these to grow beyond the artificial
+    // limit we are imposing here of 256.
+    static constexpr uint32_t kMaxPossibleDmDevices = 256;
+
     void InitIo(struct dm_ioctl* io, const std::string& name = std::string()) const;
 
     DeviceMapper() : fd_(-1) {
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index c123830..f5bdc35 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -35,13 +35,14 @@
 
 using DeviceMapper = ::android::dm::DeviceMapper;
 using DmTarget = ::android::dm::DmTarget;
+using DmBlockDevice = ::android::dm::DeviceMapper::DmBlockDevice;
 
 static int Usage(void) {
     std::cerr << "usage: dmctl <command> [command options]";
     std::cerr << "commands:";
     std::cerr << "  create <dm-name> [dm-target> [-lo <filename>] <dm-target-args>]";
     std::cerr, "  delete <dm-name>";
-    std::cerr, "  list";
+    std::cerr, "  list <devices | targets>";
     std::cerr, "  help";
     return -EINVAL;
 }
@@ -84,16 +85,14 @@
     return 0;
 }
 
-static int DmListCmdHandler(int /* argc */, char** /* argv */) {
-    std::cout << "Available Device Mapper Targets:" << std::endl;
-
-    DeviceMapper& dm = DeviceMapper::Instance();
+static int DmListTargets(DeviceMapper& dm) {
     std::vector<DmTarget> targets;
     if (!dm.GetAvailableTargets(&targets)) {
         std::cerr << "Failed to read available device mapper targets" << std::endl;
         return -errno;
     }
 
+    std::cout << "Available Device Mapper Targets:" << std::endl;
     if (targets.empty()) {
         std::cout << "  <empty>" << std::endl;
         return 0;
@@ -107,6 +106,46 @@
     return 0;
 }
 
+static int DmListDevices(DeviceMapper& dm) {
+    std::vector<DmBlockDevice> devices;
+    if (!dm.GetAvailableDevices(&devices)) {
+        std::cerr << "Failed to read available device mapper devices" << std::endl;
+        return -errno;
+    }
+    std::cout << "Available Device Mapper Devices:" << std::endl;
+    if (devices.empty()) {
+        std::cout << "  <empty>" << std::endl;
+        return 0;
+    }
+
+    for (const auto& dev : devices) {
+        std::cout << std::left << std::setw(20) << dev.name() << " : " << dev.Major() << ":"
+                  << dev.Minor() << std::endl;
+    }
+
+    return 0;
+}
+
+static const std::map<std::string, std::function<int(DeviceMapper&)>> listmap = {
+        {"targets", DmListTargets},
+        {"devices", DmListDevices},
+};
+
+static int DmListCmdHandler(int argc, char** argv) {
+    if (argc < 1) {
+        std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl;
+        return -EINVAL;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    for (const auto& l : listmap) {
+        if (l.first == argv[0]) return l.second(dm);
+    }
+
+    std::cerr << "Invalid argument to \'dmctl list\': " << argv[0] << std::endl;
+    return -EINVAL;
+}
+
 static int HelpCmdHandler(int /* argc */, char** /* argv */) {
     Usage();
     return 0;