qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Tomoki Sekiyama <tomoki.sekiyama@hds.com>
To: qemu-devel@nongnu.org
Cc: mitsuhiro.tanino@hds.com, mdroth@linux.vnet.ibm.com
Subject: [Qemu-devel] [PATCH v3 2/2] qga: Add guest-get-fs-info command
Date: Thu, 22 May 2014 09:56:59 -0400	[thread overview]
Message-ID: <20140522135659.6110.54728.stgit@hds.com> (raw)
In-Reply-To: <20140522135641.6110.28511.stgit@hds.com>

Add command to get mounted filesystems information in the guest.
The returned value contains a list of mountpoint paths and
corresponding disks info such as disk bus type, drive address,
and the disk controllers' PCI addresses, so that management layer
such as libvirt can resolve the disk backends.

For example, when `lsblk' result is:

    NAME           MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
    sdb              8:16   0    1G  0 disk
    `-sdb1           8:17   0 1024M  0 part
      `-vg0-lv0    253:1    0  1.4G  0 lvm  /mnt/test
    sdc              8:32   0    1G  0 disk
    `-sdc1           8:33   0  512M  0 part
      `-vg0-lv0    253:1    0  1.4G  0 lvm  /mnt/test
    vda            252:0    0   25G  0 disk
    `-vda1         252:1    0   25G  0 part /

where sdb is a SCSI disk with PCI controller 0000:00:0a.0 and ID=1,
      sdc is an IDE disk with PCI controller 0000:00:01.1, and
      vda is a virtio-blk disk with PCI device 0000:00:06.0,

guest-get-fs-info command will return the following result:

    {"return":
     [{"name":"dm-1",
       "mountpoint":"/mnt/test",
       "disk":[
        {"bus-type":"scsi","bus":0,"unit":1,"target":0,
         "pci-controller":{"bus":0,"slot":10,"domain":0,"function":0}},
        {"bus-type":"ide","bus":0,"unit":0,"target":0,
         "pci-controller":{"bus":0,"slot":1,"domain":0,"function":1}}],
       "type":"xfs"},
      {"name":"vda1", "mountpoint":"/",
       "disk":[
        {"bus-type":"virtio","bus":0,"unit":0,"target":0,
         "pci-controller":{"bus":0,"slot":6,"domain":0,"function":0}}],
       "type":"ext4"}]}

In Linux guest, the disk information is resolved from sysfs. So far,
it only supports virtio-blk, virtio-scsi, IDE, SATA, SCSI disks on x86
hosts, and "disk" parameter may be empty for unsupported disk types.

Signed-off-by: Tomoki Sekiyama <tomoki.sekiyama@hds.com>
---
 qga/commands-posix.c |  420 ++++++++++++++++++++++++++++++++++++++++++++++++++
 qga/qapi-schema.json |   78 +++++++++
 2 files changed, 497 insertions(+), 1 deletion(-)

diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 771f00c..c7a2c09 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -18,6 +18,7 @@
 #include <unistd.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <dirent.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/stat.h>
@@ -575,6 +576,7 @@ static void guest_file_init(void)
 typedef struct FsMount {
     char *dirname;
     char *devtype;
+    unsigned int devmajor, devminor;
     QTAILQ_ENTRY(FsMount) next;
 } FsMount;
 
@@ -596,15 +598,40 @@ static void free_fs_mount_list(FsMountList *mounts)
      }
 }
 
+static int dev_major_minor(const char *devpath,
+                           unsigned int *devmajor, unsigned int *devminor)
+{
+    struct stat st;
+
+    *devmajor = 0;
+    *devminor = 0;
+
+    if (stat(devpath, &st) < 0) {
+        slog("failed to stat device file '%s': %s", devpath, strerror(errno));
+        return -1;
+    }
+    if (S_ISDIR(st.st_mode)) {
+        /* It is bind mount */
+        return -2;
+    }
+    if (S_ISBLK(st.st_mode)) {
+        *devmajor = major(st.st_rdev);
+        *devminor = minor(st.st_rdev);
+        return 0;
+    }
+    return -1;
+}
+
 /*
  * Walk the mount table and build a list of local file systems
  */
-static void build_fs_mount_list(FsMountList *mounts, Error **errp)
+static void build_fs_mount_list_from_mtab(FsMountList *mounts, Error **errp)
 {
     struct mntent *ment;
     FsMount *mount;
     char const *mtab = "/proc/self/mounts";
     FILE *fp;
+    unsigned int devmajor, devminor;
 
     fp = setmntent(mtab, "r");
     if (!fp) {
@@ -624,20 +651,368 @@ static void build_fs_mount_list(FsMountList *mounts, Error **errp)
             (strcmp(ment->mnt_type, "cifs") == 0)) {
             continue;
         }
+        if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) == -2) {
+            /* Skip bind mounts */
+            continue;
+        }
 
         mount = g_malloc0(sizeof(FsMount));
         mount->dirname = g_strdup(ment->mnt_dir);
         mount->devtype = g_strdup(ment->mnt_type);
+        mount->devmajor = devmajor;
+        mount->devminor = devminor;
 
         QTAILQ_INSERT_TAIL(mounts, mount, next);
     }
 
     endmntent(fp);
 }
+
+static void decode_mntname(char *name, int len)
+{
+    int i, j = 0;
+    for (i = 0; i <= len; i++) {
+        if (name[i] != '\\') {
+            name[j++] = name[i];
+        } else if (name[i+1] == '\\') {
+            name[j++] = '\\';
+            i++;
+        } else if (name[i+1] == '0' && name[i+2] == '4' && name[i+3] == '0') {
+            name[j++] = ' ';
+            i += 3;
+        } else if (name[i+1] == '0' && name[i+2] == '1' && name[i+3] == '1') {
+            name[j++] = '\t';
+            i += 3;
+        } else if (name[i+1] == '0' && name[i+2] == '1' && name[i+3] == '2') {
+            name[j++] = '\n';
+            i += 3;
+        } else if (name[i+1] == '1' && name[i+2] == '3' && name[i+3] == '4') {
+            name[j++] = '\\';
+            i += 3;
+        } else {
+            name[j++] = name[i];
+        }
+    }
+}
+
+static void build_fs_mount_list(FsMountList *mounts, Error **errp)
+{
+    FsMount *mount;
+    char const *mountinfo = "/proc/self/mountinfo";
+    FILE *fp;
+    char *line = NULL;
+    size_t n;
+    char check;
+    unsigned int devmajor, devminor;
+    int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
+
+    fp = fopen(mountinfo, "r");
+    if (!fp) {
+        build_fs_mount_list_from_mtab(mounts, errp);
+        return;
+    }
+
+    while (getline(&line, &n, fp) != -1) {
+        ret = sscanf(line,
+                     "%*u %*u %u:%u %*s %n%*s%n %*s %*s %*s %n%*s%n %n%*s%n%c",
+                     &devmajor, &devminor, &dir_s, &dir_e, &type_s, &type_e,
+                     &dev_s, &dev_e, &check);
+        if (ret < 3) {
+            continue;
+        }
+        line[dir_e] = 0;
+        line[type_e] = 0;
+        line[dev_e] = 0;
+        decode_mntname(line+dir_s, dir_e-dir_s);
+        decode_mntname(line+dev_s, dev_e-dev_s);
+        if (devmajor == 0) {
+            /* btrfs reports major number = 0 */
+            if (strcmp("btrfs", line+type_s) != 0 ||
+                dev_major_minor(line+dev_s, &devmajor, &devminor) < 0) {
+                continue;
+            }
+        }
+
+        mount = g_malloc0(sizeof(FsMount));
+        mount->dirname = g_strdup(line+dir_s);
+        mount->devtype = g_strdup(line+type_s);
+        mount->devmajor = devmajor;
+        mount->devminor = devminor;
+
+        QTAILQ_INSERT_TAIL(mounts, mount, next);
+    }
+    free(line);
+
+    fclose(fp);
+}
 #endif
 
 #if defined(CONFIG_FSFREEZE)
 
+static char *get_pci_driver(char const *syspath, int pathlen, Error **errp)
+{
+    char *path;
+    char *dpath;
+    char *driver = NULL;
+    char buf[PATH_MAX];
+    ssize_t len;
+
+    path = g_strndup(syspath, pathlen);
+    dpath = g_strdup_printf("%s/driver", path);
+    len = readlink(dpath, buf, sizeof(buf)-1);
+    if (len != -1) {
+        buf[len] = 0;
+        driver = g_strdup(basename(buf));
+    }
+    g_free(dpath);
+    g_free(path);
+    return driver;
+}
+
+static int compare_uint(const void *_a, const void *_b)
+{
+    unsigned int a = *(unsigned int *)_a;
+    unsigned int b = *(unsigned int *)_b;
+
+    return a < b ? -1 : a > b ? 1 : 0;
+}
+
+/* Walk the specified sysfs path and build a sorted list of ata port numbers */
+static int build_ata_ports(char const *syspath, char const *ata,
+                           unsigned int *ports, int ports_max, Error **errp)
+{
+    char *path;
+    DIR *dir;
+    struct dirent *entry;
+    int i = 0;
+
+    path = g_strndup(syspath, ata - syspath);
+    dir = opendir(path);
+    if (!dir) {
+        error_setg_errno(errp, errno, "opendir(\"%s\")", path);
+        g_free(path);
+        return -1;
+    }
+
+    while (i < ports_max) {
+        entry = readdir(dir);
+        if (!entry) {
+            break;
+        }
+        if (sscanf(entry->d_name, "ata%d", ports+i) == 1) {
+            ++i;
+        }
+    }
+
+    qsort(ports, i, sizeof(ports[0]), compare_uint);
+
+    g_free(path);
+    closedir(dir);
+    return i;
+}
+
+static bool __build_mounted_fs_info(char const *syspath,
+                                    GuestFilesystemInfo *fs, Error **errp)
+{
+    unsigned int pci[4], ata, tgt[3], ports[8];
+    int i, nports = 0, pcilen;
+    GuestDiskAddress *disk;
+    GuestPCIAddress *pciaddr;
+    GuestDiskAddressList *list = NULL;
+    bool has_ata = false, has_tgt = false;
+    char *p, *driver = NULL;
+    bool ret = false;
+
+    p = strstr(syspath, "/devices/pci");
+    if (!p || sscanf(p+12, "%*x:%*x/%x:%x:%x.%x%n",
+                     pci, pci+1, pci+2, pci+3, &pcilen) < 4) {
+        slog("only pci device is supported: sysfs path \"%s\"", syspath);
+        return false;
+    }
+
+    driver = get_pci_driver(syspath, (p+12+pcilen)-syspath, errp);
+    if (!driver) {
+        goto cleanup;
+    }
+
+    p = strstr(syspath, "/target");
+    if (p && sscanf(p+7, "%*u:%*u:%*u/%*u:%u:%u:%u", tgt, tgt+1, tgt+2) == 3) {
+        has_tgt = true;
+    }
+
+    p = strstr(syspath, "/ata");
+    if (p && sscanf(p+4, "%u", &ata) == 1) {
+        has_ata = true;
+        nports = build_ata_ports(syspath, p, ports,
+                                 sizeof(ports)/sizeof(ports[0]), errp);
+    }
+
+    pciaddr = g_malloc0(sizeof(*pciaddr));
+    pciaddr->domain = pci[0];
+    pciaddr->bus = pci[1];
+    pciaddr->slot = pci[2];
+    pciaddr->function = pci[3];
+
+    disk = g_malloc0(sizeof(*disk));
+    disk->pci_controller = pciaddr;
+
+    list = g_malloc0(sizeof(*list));
+    list->value = disk;
+
+    if (strcmp(driver, "ata_piix") == 0) {
+        /* an ata port per ide bus, target*:0:<unit>:0 */
+        if (!has_ata || !has_tgt) {
+            goto cleanup;
+        }
+        for (i = 0; i < nports; i++) {
+            if (ata == ports[i]) {
+                disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
+                disk->bus = i;
+                disk->unit = tgt[1];
+                goto ok;
+            }
+        }
+        goto cleanup;
+    } else if (strcmp(driver, "sym53c8xx") == 0) {
+        /* scsi(LSI Logic): target*:0:<unit>:0 */
+        if (!has_tgt) {
+            goto cleanup;
+        }
+        disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
+        disk->unit = tgt[1];
+        goto ok;
+    } else if (strcmp(driver, "virtio-pci") == 0) {
+        if (has_tgt) {
+            /* virtio-scsi: target*:0:0:<unit> */
+            disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
+            disk->unit = tgt[2];
+        } else {
+            /* virtio-blk: 1 disk per 1 device */
+            disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
+        }
+        goto ok;
+    } else if (strcmp(driver, "ahci") == 0) {
+        /* ahci: an ata port per unit */
+        if (!has_ata || !has_tgt) {
+            goto cleanup;
+        }
+        for (i = 0; i < nports; i++) {
+            if (ata == ports[i]) {
+                disk->unit = i;
+                disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
+                goto ok;
+            }
+        }
+    } else {
+        g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath);
+        goto cleanup;
+    }
+
+ok:
+    ret = true;
+    list->next = fs->disk;
+    fs->disk = list;
+
+cleanup:
+    g_free(driver);
+    if (!ret && list) {
+        qapi_free_GuestDiskAddressList(list);
+    }
+    return ret;
+}
+
+static bool _build_mounted_fs_info(char const *dirpath,
+                                   GuestFilesystemInfo *fs, Error **errp);
+
+/* Return true if some of slave devices of virtual volume specified by @syspath
+ * are listed in @disks */
+static bool __build_mounted_fs_info_virtual(char const *syspath,
+                                            GuestFilesystemInfo *fs,
+                                            Error **errp)
+{
+    bool ret = false;
+    DIR *dir;
+    char *dirpath;
+    struct dirent entry, *result;
+
+    dirpath = g_strdup_printf("%s/slaves", syspath);
+    dir = opendir(dirpath);
+    if (!dir) {
+        error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
+        g_free(dirpath);
+        return false;
+    }
+    g_free(dirpath);
+
+    for (;;) {
+        if (readdir_r(dir, &entry, &result) != 0) {
+            error_setg_errno(errp, errno, "readdir_r(\"%s\")", dirpath);
+            break;
+        }
+        if (!result) {
+            break;
+        }
+
+        if (entry.d_type == DT_LNK) {
+            g_debug(" slave device '%s'", entry.d_name);
+            dirpath = g_strdup_printf("%s/slaves/%s", syspath, entry.d_name);
+            ret = _build_mounted_fs_info(dirpath, fs, errp) || ret;
+            g_free(dirpath);
+
+            if (error_is_set(errp)) {
+                break;
+            }
+        }
+    }
+
+    closedir(dir);
+    return ret;
+}
+
+static bool _build_mounted_fs_info(char const *dirpath,
+                                   GuestFilesystemInfo *fs, Error **errp)
+{
+    char *syspath = realpath(dirpath, NULL);
+    bool ret;
+
+    if (!syspath) {
+        error_setg_errno(errp, errno, "realpath(\"%s\")", dirpath);
+        return false;
+    }
+
+    if (!fs->name) {
+        fs->name = g_strdup(basename(syspath));
+    }
+
+    g_debug("  parse sysfs path '%s'", syspath);
+
+    if (strstr(syspath, "/devices/virtual/block/")) {
+        ret = __build_mounted_fs_info_virtual(syspath, fs, errp);
+    } else {
+        ret = __build_mounted_fs_info(syspath, fs, errp);
+    }
+
+    free(syspath);
+    return ret;
+}
+
+/* Return true if @mount is on the disk device(s) listed in @disks. */
+static GuestFilesystemInfo *build_mounted_fs_info(struct FsMount *mount,
+                                                  Error **errp)
+{
+    GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
+    char *dirpath = g_strdup_printf("/sys/dev/block/%u:%u",
+                                    mount->devmajor, mount->devminor);
+
+    fs->mountpoint = g_strdup(mount->dirname);
+    fs->type = g_strdup(mount->devtype);
+    _build_mounted_fs_info(dirpath, fs, errp);
+
+    g_free(dirpath);
+    return fs;
+}
+
+
 typedef enum {
     FSFREEZE_HOOK_THAW = 0,
     FSFREEZE_HOOK_FREEZE,
@@ -1436,6 +1811,44 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
     return processed;
 }
 
+GuestFilesystemInfoList *qmp_guest_get_fs_info(Error **errp)
+{
+    FsMountList mounts;
+    struct FsMount *mount;
+    GuestFilesystemInfoList *new, *ret = NULL;
+    GuestFilesystemInfo *fs;
+    Error *local_err = NULL;
+
+    QTAILQ_INIT(&mounts);
+    build_fs_mount_list(&mounts, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return NULL;
+    }
+
+    QTAILQ_FOREACH(mount, &mounts, next) {
+        g_debug("Building mounted fs info for '%s'", mount->dirname);
+
+        fs = build_mounted_fs_info(mount, &local_err);
+        if (!fs) {
+            continue;
+        }
+        new = g_malloc0(sizeof(*ret));
+        new->value = fs;
+        new->next = ret;
+        ret = new;
+        if (local_err) {
+            error_propagate(errp, local_err);
+            qapi_free_GuestFilesystemInfoList(ret);
+            ret = NULL;
+            break;
+        }
+    }
+
+    free_fs_mount_list(&mounts);
+    return ret;
+}
+
 #else /* defined(__linux__) */
 
 void qmp_guest_suspend_disk(Error **errp)
@@ -1471,6 +1884,11 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
     return -1;
 }
 
+GuestFilesystemInfoList *qmp_guest_get_mounted_filesystems(Error **errp)
+{
+    error_set(errp, QERR_UNSUPPORTED);
+    return NULL;
+}
 #endif
 
 #if !defined(CONFIG_FSFREEZE)
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 31c0dc8..ef6c4a6 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -646,3 +646,81 @@
 { 'command': 'guest-set-vcpus',
   'data':    {'vcpus': ['GuestLogicalProcessor'] },
   'returns': 'int' }
+
+##
+# @GuestDiskBusType
+#
+# An enumeration of bus type of disks
+#
+# @ide: IDE disks
+# @fdc: floppy disks
+# @scsi: SCSI disks
+# @virtio: virtio disks
+# @xen: Xen disks
+# @usb: USB disks
+# @uml: UML disks
+# @sata: SATA disks
+# @sd: SD cards
+#
+# Since: 2.1
+##
+{ 'enum': 'GuestDiskBusType',
+  'data': [ 'ide', 'fdc', 'scsi', 'virtio', 'xen', 'usb', 'uml', 'sata',
+            'sd' ] }
+
+##
+# @GuestPCIAddress:
+#
+# @domain: domain id
+# @bus: bus id
+# @slot: slot id
+# @function: function id
+#
+# Since: 2.1
+##
+{ 'type': 'GuestPCIAddress',
+  'data': {'domain': 'int', 'bus': 'int',
+           'slot': 'int', 'function': 'int'} }
+
+##
+# @GuestDiskAddress:
+#
+# @pci-controller: controller's PCI address
+# @type: bus type
+# @bus: bus id
+# @target: target id
+# @unit: unit id
+#
+# Since: 2.1
+##
+{ 'type': 'GuestDiskAddress',
+  'data': {'pci-controller': 'GuestPCIAddress',
+           'bus-type': 'GuestDiskBusType',
+           'bus': 'int', 'target': 'int', 'unit': 'int'} }
+
+##
+# @GuestFilesystemInfo
+#
+# @name: disk name
+# @mountpoint: mount point path
+# @type: file system type string
+# @disk: an array of disk hardware information that the volume lies on,
+#        which may be empty if the disk type is not supported
+#
+# Since: 2.1
+##
+{ 'type': 'GuestFilesystemInfo',
+  'data': {'name': 'str', 'mountpoint': 'str', 'type': 'str',
+           'disk': ['GuestDiskAddress']} }
+
+##
+# @guest-get-fs-info:
+#
+# Returns: The list of filesystems information mounted in the guest.
+#          The returned mountpoints may be specified to @guest-fsfreeze-freeze.
+#          Network filesystems (such as CIFS and NFS) are not listed.
+#
+# Since: 2.1
+##
+{ 'command': 'guest-get-fs-info',
+  'returns': ['GuestFilesystemInfo'] }

      parent reply	other threads:[~2014-05-22 13:57 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-05-22 13:56 [Qemu-devel] [PATCH v3 0/2] qga: Add 'mountpoints' argument to guest-fsfreeze-freeze command Tomoki Sekiyama
2014-05-22 13:56 ` [Qemu-devel] [PATCH v3 1/2] " Tomoki Sekiyama
2014-06-03 21:21   ` Michael Roth
2014-06-03 21:38     ` Eric Blake
2014-06-03 21:58       ` Michael Roth
2014-06-03 22:06         ` Eric Blake
2014-06-03 22:10           ` Eric Blake
2014-06-03 23:02             ` Tomoki Sekiyama
2014-06-04  0:39           ` Michael Roth
2014-05-22 13:56 ` Tomoki Sekiyama [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20140522135659.6110.54728.stgit@hds.com \
    --to=tomoki.sekiyama@hds.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=mitsuhiro.tanino@hds.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).