8fced6
From 086957b970a8f4165249589e2bc0cc08d1800db3 Mon Sep 17 00:00:00 2001
8fced6
From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= <marcandre.lureau@redhat.com>
8fced6
Date: Wed, 16 Dec 2020 16:06:12 -0500
8fced6
Subject: [PATCH 11/14] qga: add implementation of guest-get-disks for Linux
8fced6
MIME-Version: 1.0
8fced6
Content-Type: text/plain; charset=UTF-8
8fced6
Content-Transfer-Encoding: 8bit
8fced6
8fced6
RH-Author: Marc-André Lureau <marcandre.lureau@redhat.com>
8fced6
Message-id: <20201216160615.324213-8-marcandre.lureau@redhat.com>
8fced6
Patchwork-id: 100478
8fced6
O-Subject: [RHEL-8.4.0 qemu-kvm PATCH v2 07/10] qga: add implementation of guest-get-disks for Linux
8fced6
Bugzilla: 1859494
8fced6
RH-Acked-by: Danilo de Paula <ddepaula@redhat.com>
8fced6
RH-Acked-by: Sergio Lopez Pascual <slp@redhat.com>
8fced6
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
8fced6
8fced6
From: Tomáš Golembiovský <tgolembi@redhat.com>
8fced6
8fced6
The command lists all disks (real and virtual) as well as disk
8fced6
partitions. For each disk the list of dependent disks is also listed and
8fced6
/dev path is used as a handle so it can be matched with "name" field of
8fced6
other returned disk entries. For disk partitions the "dependents" list
8fced6
is populated with the the parent device for easier tracking of
8fced6
hierarchy.
8fced6
8fced6
Example output:
8fced6
{
8fced6
  "return": [
8fced6
    ...
8fced6
    {
8fced6
      "name": "/dev/dm-0",
8fced6
      "partition": false,
8fced6
      "dependents": [
8fced6
        "/dev/sda2"
8fced6
      ],
8fced6
      "alias": "luks-7062202e-5b9b-433e-81e8-6628c40da9f7"
8fced6
    },
8fced6
    {
8fced6
      "name": "/dev/sda2",
8fced6
      "partition": true,
8fced6
      "dependents": [
8fced6
        "/dev/sda"
8fced6
      ]
8fced6
    },
8fced6
    {
8fced6
      "name": "/dev/sda",
8fced6
      "partition": false,
8fced6
      "address": {
8fced6
        "serial": "SAMSUNG_MZ7LN512HCHP-000L1_S1ZKNXAG822493",
8fced6
        "bus-type": "sata",
8fced6
        ...
8fced6
        "dev": "/dev/sda",
8fced6
        "target": 0
8fced6
      },
8fced6
      "dependents": []
8fced6
    },
8fced6
    ...
8fced6
  ]
8fced6
}
8fced6
8fced6
Signed-off-by: Tomáš Golembiovský <tgolembi@redhat.com>
8fced6
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
8fced6
*add missing stub for !defined(CONFIG_FSFREEZE)
8fced6
*remove unused deps_dir variable
8fced6
Signed-off-by: Michael Roth <michael.roth@amd.com>
8fced6
(cherry picked from commit fed3956429d560a06fc2d2fcf1a01efb58659f87)
8fced6
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
8fced6
---
8fced6
 qga/commands-posix.c | 303 +++++++++++++++++++++++++++++++++++++++++--
8fced6
 1 file changed, 292 insertions(+), 11 deletions(-)
8fced6
8fced6
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
8fced6
index 5095104afc0..96f5ddafd3a 100644
8fced6
--- a/qga/commands-posix.c
8fced6
+++ b/qga/commands-posix.c
8fced6
@@ -1152,13 +1152,27 @@ static void build_guest_fsinfo_for_virtual_device(char const *syspath,
8fced6
     closedir(dir);
8fced6
 }
8fced6
 
8fced6
+static bool is_disk_virtual(const char *devpath, Error **errp)
8fced6
+{
8fced6
+    g_autofree char *syspath = realpath(devpath, NULL);
8fced6
+
8fced6
+    if (!syspath) {
8fced6
+        error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
8fced6
+        return false;
8fced6
+    }
8fced6
+    return strstr(syspath, "/devices/virtual/block/") != NULL;
8fced6
+}
8fced6
+
8fced6
 /* Dispatch to functions for virtual/real device */
8fced6
 static void build_guest_fsinfo_for_device(char const *devpath,
8fced6
                                           GuestFilesystemInfo *fs,
8fced6
                                           Error **errp)
8fced6
 {
8fced6
-    char *syspath = realpath(devpath, NULL);
8fced6
+    ERRP_GUARD();
8fced6
+    g_autofree char *syspath = NULL;
8fced6
+    bool is_virtual = false;
8fced6
 
8fced6
+    syspath = realpath(devpath, NULL);
8fced6
     if (!syspath) {
8fced6
         error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
8fced6
         return;
8fced6
@@ -1169,16 +1183,281 @@ static void build_guest_fsinfo_for_device(char const *devpath,
8fced6
     }
8fced6
 
8fced6
     g_debug("  parse sysfs path '%s'", syspath);
8fced6
-
8fced6
-    if (strstr(syspath, "/devices/virtual/block/")) {
8fced6
+    is_virtual = is_disk_virtual(syspath, errp);
8fced6
+    if (*errp != NULL) {
8fced6
+        return;
8fced6
+    }
8fced6
+    if (is_virtual) {
8fced6
         build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
8fced6
     } else {
8fced6
         build_guest_fsinfo_for_real_device(syspath, fs, errp);
8fced6
     }
8fced6
+}
8fced6
+
8fced6
+#ifdef CONFIG_LIBUDEV
8fced6
+
8fced6
+/*
8fced6
+ * Wrapper around build_guest_fsinfo_for_device() for getting just
8fced6
+ * the disk address.
8fced6
+ */
8fced6
+static GuestDiskAddress *get_disk_address(const char *syspath, Error **errp)
8fced6
+{
8fced6
+    g_autoptr(GuestFilesystemInfo) fs = NULL;
8fced6
 
8fced6
-    free(syspath);
8fced6
+    fs = g_new0(GuestFilesystemInfo, 1);
8fced6
+    build_guest_fsinfo_for_device(syspath, fs, errp);
8fced6
+    if (fs->disk != NULL) {
8fced6
+        return g_steal_pointer(&fs->disk->value);
8fced6
+    }
8fced6
+    return NULL;
8fced6
 }
8fced6
 
8fced6
+static char *get_alias_for_syspath(const char *syspath)
8fced6
+{
8fced6
+    struct udev *udev = NULL;
8fced6
+    struct udev_device *udevice = NULL;
8fced6
+    char *ret = NULL;
8fced6
+
8fced6
+    udev = udev_new();
8fced6
+    if (udev == NULL) {
8fced6
+        g_debug("failed to query udev");
8fced6
+        goto out;
8fced6
+    }
8fced6
+    udevice = udev_device_new_from_syspath(udev, syspath);
8fced6
+    if (udevice == NULL) {
8fced6
+        g_debug("failed to query udev for path: %s", syspath);
8fced6
+        goto out;
8fced6
+    } else {
8fced6
+        const char *alias = udev_device_get_property_value(
8fced6
+            udevice, "DM_NAME");
8fced6
+        /*
8fced6
+         * NULL means there was an error and empty string means there is no
8fced6
+         * alias. In case of no alias we return NULL instead of empty string.
8fced6
+         */
8fced6
+        if (alias == NULL) {
8fced6
+            g_debug("failed to query udev for device alias for: %s",
8fced6
+                syspath);
8fced6
+        } else if (*alias != 0) {
8fced6
+            ret = g_strdup(alias);
8fced6
+        }
8fced6
+    }
8fced6
+
8fced6
+out:
8fced6
+    udev_unref(udev);
8fced6
+    udev_device_unref(udevice);
8fced6
+    return ret;
8fced6
+}
8fced6
+
8fced6
+static char *get_device_for_syspath(const char *syspath)
8fced6
+{
8fced6
+    struct udev *udev = NULL;
8fced6
+    struct udev_device *udevice = NULL;
8fced6
+    char *ret = NULL;
8fced6
+
8fced6
+    udev = udev_new();
8fced6
+    if (udev == NULL) {
8fced6
+        g_debug("failed to query udev");
8fced6
+        goto out;
8fced6
+    }
8fced6
+    udevice = udev_device_new_from_syspath(udev, syspath);
8fced6
+    if (udevice == NULL) {
8fced6
+        g_debug("failed to query udev for path: %s", syspath);
8fced6
+        goto out;
8fced6
+    } else {
8fced6
+        ret = g_strdup(udev_device_get_devnode(udevice));
8fced6
+    }
8fced6
+
8fced6
+out:
8fced6
+    udev_unref(udev);
8fced6
+    udev_device_unref(udevice);
8fced6
+    return ret;
8fced6
+}
8fced6
+
8fced6
+static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk)
8fced6
+{
8fced6
+    g_autofree char *deps_dir = NULL;
8fced6
+    const gchar *dep;
8fced6
+    GDir *dp_deps = NULL;
8fced6
+
8fced6
+    /* List dependent disks */
8fced6
+    deps_dir = g_strdup_printf("%s/slaves", disk_dir);
8fced6
+    g_debug("  listing entries in: %s", deps_dir);
8fced6
+    dp_deps = g_dir_open(deps_dir, 0, NULL);
8fced6
+    if (dp_deps == NULL) {
8fced6
+        g_debug("failed to list entries in %s", deps_dir);
8fced6
+        return;
8fced6
+    }
8fced6
+    while ((dep = g_dir_read_name(dp_deps)) != NULL) {
8fced6
+        g_autofree char *dep_dir = NULL;
8fced6
+        strList *dep_item = NULL;
8fced6
+        char *dev_name;
8fced6
+
8fced6
+        /* Add dependent disks */
8fced6
+        dep_dir = g_strdup_printf("%s/%s", deps_dir, dep);
8fced6
+        dev_name = get_device_for_syspath(dep_dir);
8fced6
+        if (dev_name != NULL) {
8fced6
+            g_debug("  adding dependent device: %s", dev_name);
8fced6
+            dep_item = g_new0(strList, 1);
8fced6
+            dep_item->value = dev_name;
8fced6
+            dep_item->next = disk->dependents;
8fced6
+            disk->dependents = dep_item;
8fced6
+        }
8fced6
+    }
8fced6
+    g_dir_close(dp_deps);
8fced6
+}
8fced6
+
8fced6
+/*
8fced6
+ * Detect partitions subdirectory, name is "<disk_name><number>" or
8fced6
+ * "<disk_name>p<number>"
8fced6
+ *
8fced6
+ * @disk_name -- last component of /sys path (e.g. sda)
8fced6
+ * @disk_dir -- sys path of the disk (e.g. /sys/block/sda)
8fced6
+ * @disk_dev -- device node of the disk (e.g. /dev/sda)
8fced6
+ */
8fced6
+static GuestDiskInfoList *get_disk_partitions(
8fced6
+    GuestDiskInfoList *list,
8fced6
+    const char *disk_name, const char *disk_dir,
8fced6
+    const char *disk_dev)
8fced6
+{
8fced6
+    GuestDiskInfoList *item, *ret = list;
8fced6
+    struct dirent *de_disk;
8fced6
+    DIR *dp_disk = NULL;
8fced6
+    size_t len = strlen(disk_name);
8fced6
+
8fced6
+    dp_disk = opendir(disk_dir);
8fced6
+    while ((de_disk = readdir(dp_disk)) != NULL) {
8fced6
+        g_autofree char *partition_dir = NULL;
8fced6
+        char *dev_name;
8fced6
+        GuestDiskInfo *partition;
8fced6
+
8fced6
+        if (!(de_disk->d_type & DT_DIR)) {
8fced6
+            continue;
8fced6
+        }
8fced6
+
8fced6
+        if (!(strncmp(disk_name, de_disk->d_name, len) == 0 &&
8fced6
+            ((*(de_disk->d_name + len) == 'p' &&
8fced6
+            isdigit(*(de_disk->d_name + len + 1))) ||
8fced6
+                isdigit(*(de_disk->d_name + len))))) {
8fced6
+            continue;
8fced6
+        }
8fced6
+
8fced6
+        partition_dir = g_strdup_printf("%s/%s",
8fced6
+            disk_dir, de_disk->d_name);
8fced6
+        dev_name = get_device_for_syspath(partition_dir);
8fced6
+        if (dev_name == NULL) {
8fced6
+            g_debug("Failed to get device name for syspath: %s",
8fced6
+                disk_dir);
8fced6
+            continue;
8fced6
+        }
8fced6
+        partition = g_new0(GuestDiskInfo, 1);
8fced6
+        partition->name = dev_name;
8fced6
+        partition->partition = true;
8fced6
+        /* Add parent disk as dependent for easier tracking of hierarchy */
8fced6
+        partition->dependents = g_new0(strList, 1);
8fced6
+        partition->dependents->value = g_strdup(disk_dev);
8fced6
+
8fced6
+        item = g_new0(GuestDiskInfoList, 1);
8fced6
+        item->value = partition;
8fced6
+        item->next = ret;
8fced6
+        ret = item;
8fced6
+
8fced6
+    }
8fced6
+    closedir(dp_disk);
8fced6
+
8fced6
+    return ret;
8fced6
+}
8fced6
+
8fced6
+GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
8fced6
+{
8fced6
+    GuestDiskInfoList *item, *ret = NULL;
8fced6
+    GuestDiskInfo *disk;
8fced6
+    DIR *dp = NULL;
8fced6
+    struct dirent *de = NULL;
8fced6
+
8fced6
+    g_debug("listing /sys/block directory");
8fced6
+    dp = opendir("/sys/block");
8fced6
+    if (dp == NULL) {
8fced6
+        error_setg_errno(errp, errno, "Can't open directory \"/sys/block\"");
8fced6
+        return NULL;
8fced6
+    }
8fced6
+    while ((de = readdir(dp)) != NULL) {
8fced6
+        g_autofree char *disk_dir = NULL, *line = NULL,
8fced6
+            *size_path = NULL;
8fced6
+        char *dev_name;
8fced6
+        Error *local_err = NULL;
8fced6
+        if (de->d_type != DT_LNK) {
8fced6
+            g_debug("  skipping entry: %s", de->d_name);
8fced6
+            continue;
8fced6
+        }
8fced6
+
8fced6
+        /* Check size and skip zero-sized disks */
8fced6
+        g_debug("  checking disk size");
8fced6
+        size_path = g_strdup_printf("/sys/block/%s/size", de->d_name);
8fced6
+        if (!g_file_get_contents(size_path, &line, NULL, NULL)) {
8fced6
+            g_debug("  failed to read disk size");
8fced6
+            continue;
8fced6
+        }
8fced6
+        if (g_strcmp0(line, "0\n") == 0) {
8fced6
+            g_debug("  skipping zero-sized disk");
8fced6
+            continue;
8fced6
+        }
8fced6
+
8fced6
+        g_debug("  adding %s", de->d_name);
8fced6
+        disk_dir = g_strdup_printf("/sys/block/%s", de->d_name);
8fced6
+        dev_name = get_device_for_syspath(disk_dir);
8fced6
+        if (dev_name == NULL) {
8fced6
+            g_debug("Failed to get device name for syspath: %s",
8fced6
+                disk_dir);
8fced6
+            continue;
8fced6
+        }
8fced6
+        disk = g_new0(GuestDiskInfo, 1);
8fced6
+        disk->name = dev_name;
8fced6
+        disk->partition = false;
8fced6
+        disk->alias = get_alias_for_syspath(disk_dir);
8fced6
+        disk->has_alias = (disk->alias != NULL);
8fced6
+        item = g_new0(GuestDiskInfoList, 1);
8fced6
+        item->value = disk;
8fced6
+        item->next = ret;
8fced6
+        ret = item;
8fced6
+
8fced6
+        /* Get address for non-virtual devices */
8fced6
+        bool is_virtual = is_disk_virtual(disk_dir, &local_err);
8fced6
+        if (local_err != NULL) {
8fced6
+            g_debug("  failed to check disk path, ignoring error: %s",
8fced6
+                error_get_pretty(local_err));
8fced6
+            error_free(local_err);
8fced6
+            local_err = NULL;
8fced6
+            /* Don't try to get the address */
8fced6
+            is_virtual = true;
8fced6
+        }
8fced6
+        if (!is_virtual) {
8fced6
+            disk->address = get_disk_address(disk_dir, &local_err);
8fced6
+            if (local_err != NULL) {
8fced6
+                g_debug("  failed to get device info, ignoring error: %s",
8fced6
+                    error_get_pretty(local_err));
8fced6
+                error_free(local_err);
8fced6
+                local_err = NULL;
8fced6
+            } else if (disk->address != NULL) {
8fced6
+                disk->has_address = true;
8fced6
+            }
8fced6
+        }
8fced6
+
8fced6
+        get_disk_deps(disk_dir, disk);
8fced6
+        ret = get_disk_partitions(ret, de->d_name, disk_dir, dev_name);
8fced6
+    }
8fced6
+    return ret;
8fced6
+}
8fced6
+
8fced6
+#else
8fced6
+
8fced6
+GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
8fced6
+{
8fced6
+    error_setg(errp, QERR_UNSUPPORTED);
8fced6
+    return NULL;
8fced6
+}
8fced6
+
8fced6
+#endif
8fced6
+
8fced6
 /* Return a list of the disk device(s)' info which @mount lies on */
8fced6
 static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
8fced6
                                                Error **errp)
8fced6
@@ -2770,6 +3049,13 @@ int64_t qmp_guest_fsfreeze_thaw(Error **errp)
8fced6
 
8fced6
     return 0;
8fced6
 }
8fced6
+
8fced6
+GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
8fced6
+{
8fced6
+    error_setg(errp, QERR_UNSUPPORTED);
8fced6
+    return NULL;
8fced6
+}
8fced6
+
8fced6
 #endif /* CONFIG_FSFREEZE */
8fced6
 
8fced6
 #if !defined(CONFIG_FSTRIM)
8fced6
@@ -2806,7 +3092,8 @@ GList *ga_command_blacklist_init(GList *blacklist)
8fced6
         const char *list[] = {
8fced6
             "guest-get-fsinfo", "guest-fsfreeze-status",
8fced6
             "guest-fsfreeze-freeze", "guest-fsfreeze-freeze-list",
8fced6
-            "guest-fsfreeze-thaw", "guest-get-fsinfo", NULL};
8fced6
+            "guest-fsfreeze-thaw", "guest-get-fsinfo",
8fced6
+            "guest-get-disks", NULL};
8fced6
         char **p = (char **)list;
8fced6
 
8fced6
         while (*p) {
8fced6
@@ -3039,9 +3326,3 @@ GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
8fced6
 
8fced6
     return info;
8fced6
 }
8fced6
-
8fced6
-GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
8fced6
-{
8fced6
-    error_setg(errp, QERR_UNSUPPORTED);
8fced6
-    return NULL;
8fced6
-}
8fced6
-- 
8fced6
2.27.0
8fced6