Skip to contentSkip to conversation

qga: Add 'mountpoints' argument to guest-fsfreeze-freeze command

PATCH Superseded Tomoki Sekiyama 3 participants 10 messages May 22, 2014 – Jun 4, 2014

v3 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v3 cover qga: Add 'mountpoints' argument to guest-fsfreeze-freeze command

Hi,

This is v3 patch for qemu-ga to add argument to specify which filesystems to be frozen by guest-fsfreeze-freeze command.

Changes since v2:
- new PATCH 2/2 to add a command 'guest-get-fs-info' get mounted filesysmtes information
- rebased to latest master
(v2: http://lists.gnu.org/archive/html/qemu-devel/2014-04/msg04433.html)

---
Tomoki Sekiyama (2):
qga: Add 'mountpoints' argument to guest-fsfreeze-freeze command
qga: Add guest-get-fs-info command

 qga/commands-posix.c |  437 ++++++++++++++++++++++++++++++++++++++++++++++++++
 qga/commands-win32.c |    3 
 qga/qapi-schema.json |   82 +++++++++
 3 files changed, 519 insertions(+), 3 deletions(-)
Signature · 2 lines
Regards,
Tomoki Sekiyama
#1 lore raw

PATCH v3 1/2 qga: Add 'mountpoints' argument to guest-fsfreeze-freeze command

When an array of mount point paths is specified as 'mountpoints' argument of guest-fsfreeze-freeze, qemu-ga with this patch will only freeze the file systems mounted on specified paths in Linux.
This would be useful when the host wants to create partial disk snapshots.

---
 qga/commands-posix.c |   17 ++++++++++++++++-
 qga/commands-win32.c |    3 ++-
 qga/qapi-schema.json |    4 ++++
 3 files changed, 22 insertions(+), 2 deletions(-)
qga/commands-posix.c +16 -1
index 34ddba0..771f00c 100644
@@ -714,9 +714,11 @@ GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
  * Walk list of mounted file systems in the guest, and freeze the ones which
  * are real local file systems.
  */
-int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+int64_t qmp_guest_fsfreeze_freeze(bool has_mountpoints, strList *mountpoints,
+                                  Error **errp)
 {
     int ret = 0, i = 0;
+    strList *list;
     FsMountList mounts;
     struct FsMount *mount;
     Error *local_err = NULL;
@@ -741,6 +743,19 @@ int64_t qmp_guest_fsfreeze_freeze(Error **errp)
     ga_set_frozen(ga_state);
 
     QTAILQ_FOREACH_REVERSE(mount, &mounts, FsMountList, next) {
+        /* To issue fsfreeze in the reverse order of mounts, check if the
+         * mount is listed in the list here */
+        if (has_mountpoints) {
+            for (list = mountpoints; list; list = list->next) {
+                if (strcmp(list->value, mount->dirname) == 0) {
+                    break;
+                }
+            }
+            if (!list) {
+                continue;
+            }
+        }
+
         fd = qemu_open(mount->dirname, O_RDONLY);
         if (fd == -1) {
             error_setg_errno(errp, errno, "failed to open %s", mount->dirname);
qga/commands-win32.c +2 -1
index d793dd0..0c6296d 100644
@@ -171,7 +171,8 @@ GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
  * Freeze local file systems using Volume Shadow-copy Service.
  * The frozen state is limited for up to 10 seconds by VSS.
  */
-int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+int64_t qmp_guest_fsfreeze_freeze(bool has_mountpoints, strList *mountpoints,
+                                  Error **errp)
 {
     int i;
     Error *local_err = NULL;
qga/qapi-schema.json +4 -0
index a8cdcb3..31c0dc8 100644
@@ -378,12 +378,16 @@
 #
 # Sync and freeze all freezable, local guest filesystems
 #
+# @mountpoints: #optional an array of mountpoints of filesystems to be frozen.
+#               If omitted, every mounted filesystem is frozen. (Since: 2.1)
+#
 # Returns: Number of file systems currently frozen. On error, all filesystems
 # will be thawed.
 #
 # Since: 0.15.0
 ##
 { 'command': 'guest-fsfreeze-freeze',
+  'data':    { '*mountpoints': ['str'] },
   'returns': 'int' }
 
 ##
#2 lore raw

Quoting Tomoki Sekiyama (2014-05-22 08:56:53)

Quoted 4 lines

When an array of mount point paths is specified as 'mountpoints' argument of guest-fsfreeze-freeze, qemu-ga with this patch will only freeze the file systems mounted on specified paths in Linux.
This would be useful when the host wants to create partial disk snapshots.

Since this isn't really applicable for win32, and it's hard to discover optional params via guest-info without some extensive changes to how we handle capabilities negotiation, I think it makes more sense to introduce a new command for this, something like guest-fsfreeze-freeze-filesystems, which we can easily discover and properly mark as unsupported on win32.

Other than that looks good.

Quoted 80 lines
---
 qga/commands-posix.c |   17 ++++++++++++++++-
 qga/commands-win32.c |    3 ++-
 qga/qapi-schema.json |    4 ++++
 3 files changed, 22 insertions(+), 2 deletions(-)
qga/commands-posix.c +16 -1
index 34ddba0..771f00c 100644
@@ -714,9 +714,11 @@ GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
  * Walk list of mounted file systems in the guest, and freeze the ones which
  * are real local file systems.
  */
-int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+int64_t qmp_guest_fsfreeze_freeze(bool has_mountpoints, strList *mountpoints,
+                                  Error **errp)
 {
     int ret = 0, i = 0;
+    strList *list;
     FsMountList mounts;
     struct FsMount *mount;
     Error *local_err = NULL;
@@ -741,6 +743,19 @@ int64_t qmp_guest_fsfreeze_freeze(Error **errp)
     ga_set_frozen(ga_state);
 
     QTAILQ_FOREACH_REVERSE(mount, &mounts, FsMountList, next) {
+        /* To issue fsfreeze in the reverse order of mounts, check if the
+         * mount is listed in the list here */
+        if (has_mountpoints) {
+            for (list = mountpoints; list; list = list->next) {
+                if (strcmp(list->value, mount->dirname) == 0) {
+                    break;
+                }
+            }
+            if (!list) {
+                continue;
+            }
+        }
+
         fd = qemu_open(mount->dirname, O_RDONLY);
         if (fd == -1) {
             error_setg_errno(errp, errno, "failed to open %s", mount->dirname);
qga/commands-win32.c +2 -1
index d793dd0..0c6296d 100644
@@ -171,7 +171,8 @@ GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
  * Freeze local file systems using Volume Shadow-copy Service.
  * The frozen state is limited for up to 10 seconds by VSS.
  */
-int64_t qmp_guest_fsfreeze_freeze(Error **errp)
+int64_t qmp_guest_fsfreeze_freeze(bool has_mountpoints, strList *mountpoints,
+                                  Error **errp)
 {
     int i;
     Error *local_err = NULL;
qga/qapi-schema.json +4 -0
index a8cdcb3..31c0dc8 100644
@@ -378,12 +378,16 @@
 #
 # Sync and freeze all freezable, local guest filesystems
 #
+# @mountpoints: #optional an array of mountpoints of filesystems to be frozen.
+#               If omitted, every mounted filesystem is frozen. (Since: 2.1)
+#
 # Returns: Number of file systems currently frozen. On error, all filesystems
 # will be thawed.
 #
 # Since: 0.15.0
 ##
 { 'command': 'guest-fsfreeze-freeze',
+  'data':    { '*mountpoints': ['str'] },
   'returns': 'int' }
 
 ##
#3 lore raw
Quoted 11 lines · Michael Roth

Quoting Tomoki Sekiyama (2014-05-22 08:56:53)

Quoted 4 lines

When an array of mount point paths is specified as 'mountpoints' argument of guest-fsfreeze-freeze, qemu-ga with this patch will only freeze the file systems mounted on specified paths in Linux.
This would be useful when the host wants to create partial disk snapshots.

Since this isn't really applicable for win32, and it's hard to discover optional params via guest-info without some extensive changes to how we handle capabilities negotiation, I think it makes more sense to introduce a new command for this, something like guest-fsfreeze-freeze-filesystems, which we can easily discover and properly mark as unsupported on win32.

Bikeshedding on the proposed name: given that 'fs' is an abbreviation of 'filesystem', "fsfreeze-freeze-filesystems" sounds rather redundant. I would suggest guest-fsfreeze-list as a shorter name that conveys the intent, without quite as much repetition.

Quoted 3 lines

Other than that looks good.

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
#4 lore raw

Quoting Eric Blake (2014-06-03 16:38:35)

Quoted 13 lines · Michael Roth
Quoted 11 lines

Quoting Tomoki Sekiyama (2014-05-22 08:56:53)

Quoted 4 lines

When an array of mount point paths is specified as 'mountpoints' argument of guest-fsfreeze-freeze, qemu-ga with this patch will only freeze the file systems mounted on specified paths in Linux.
This would be useful when the host wants to create partial disk snapshots.

Since this isn't really applicable for win32, and it's hard to discover optional params via guest-info without some extensive changes to how we handle capabilities negotiation, I think it makes more sense to introduce a new command for this, something like guest-fsfreeze-freeze-filesystems, which we can easily discover and properly mark as unsupported on win32.

Quoted 4 lines

Bikeshedding on the proposed name: given that 'fs' is an abbreviation of 'filesystem', "fsfreeze-freeze-filesystems" sounds rather redundant. I would suggest guest-fsfreeze-list as a shorter name that conveys the intent, without quite as much repetition.

Somewhat agree, though I think we should retain the guest-<command_group>-<verb> structure and at least go with guest-fsfreeze-freeze-list.

I do think that is easy to confuse with 'get me a list of frozen mounts', but probably nothing a little documentation shouldn't clarify. I'll throw guest-fsfreeze-freeze-mountpoints out there, but don't have a strong preference either way.

Quoted 8 lines
Quoted 4 lines

Other than that looks good.

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
#5 lore raw
Quoted 7 lines · Michael Roth
Quoted 4 lines

Bikeshedding on the proposed name: given that 'fs' is an abbreviation of 'filesystem', "fsfreeze-freeze-filesystems" sounds rather redundant. I would suggest guest-fsfreeze-list as a shorter name that conveys the intent, without quite as much repetition.

Somewhat agree, though I think we should retain the guest-<command_group>-<verb> structure and at least go with guest-fsfreeze-freeze-list.

guest- prefix is uncontroversial
command_group is 'fsfreeze'.
Currently in that group are the verbs 'status', 'freeze', and 'thaw'; and my proposal is to add 'list' (not 'freeze-list') as the new verb (just as we don't have 'freeze-status' as a verb).

Oh, and adding this command to freeze by list may require a change to the existing guest-fsfreeze-status command to report a third enum value of 'partial' (neither fully 'thawed' nor fully 'frozen').

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
#6 lore raw
Quoted 7 lines · Eric Blake
Quoted 5 lines · Michael Roth
Quoted 4 lines

Bikeshedding on the proposed name: given that 'fs' is an abbreviation of 'filesystem', "fsfreeze-freeze-filesystems" sounds rather redundant. I would suggest guest-fsfreeze-list as a shorter name that conveys the intent, without quite as much repetition.

Quoted 8 lines
Quoted 2 lines

Somewhat agree, though I think we should retain the guest-<command_group>-<verb> structure and at least go with guest-fsfreeze-freeze-list.

guest- prefix is uncontroversial
command_group is 'fsfreeze'.
Currently in that group are the verbs 'status', 'freeze', and 'thaw'; and my proposal is to add 'list' (not 'freeze-list') as the new verb (just as we don't have 'freeze-status' as a verb).

Uggh, now that I reread the thread...

'freeze-list' is indeed a better verb than 'list' - we aren't listing the mountpoints (that is patch 2/2 with guest-fs-get-info), but freezing a list of mountpoints (the existing 'freeze' action is taken, so we are doing a new action based on freeze but with a longer name).

Okay, I can live with 'guest-fsfreeze-freeze-list'.

Do we need a guest-fsfreeze-thaw-list counterpart, or is it sufficient to always thaw all systems without worrying about listing them?

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
#7 lore raw
Quoted 20 lines · Eric Blake
Quoted 13 lines · Eric Blake
Quoted 10 lines · Michael Roth
Quoted 6 lines

Bikeshedding on the proposed name: given that 'fs' is an abbreviation of
'filesystem', "fsfreeze-freeze-filesystems" sounds rather redundant. I
would suggest guest-fsfreeze-list as a shorter name that conveys the intent, without quite as much repetition.

Somewhat agree, though I think we should retain the guest-<command_group>-<verb>
structure and at least go with guest-fsfreeze-freeze-list.

Quoted 5 lines

guest- prefix is uncontroversial
command_group is 'fsfreeze'.
Currently in that group are the verbs 'status', 'freeze', and 'thaw'; and my proposal is to add 'list' (not 'freeze-list') as the new verb (just as we don't have 'freeze-status' as a verb).

Quoted 8 lines

Uggh, now that I reread the thread...

'freeze-list' is indeed a better verb than 'list' - we aren't listing the mountpoints (that is patch 2/2 with guest-fs-get-info), but freezing a list of mountpoints (the existing 'freeze' action is taken, so we are doing a new action based on freeze but with a longer name).

Okay, I can live with 'guest-fsfreeze-freeze-list'.

I'm okay with 'guest-fsfreeze-freeze-list' too.

Quoted 2 lines

Do we need a guest-fsfreeze-thaw-list counterpart, or is it sufficient to always thaw all systems without worrying about listing them?

I think current guest-fsfreeze-thaw is sufficient; don't want to risk leaving some filesystems unfrozen that may cause deadlocks..

Thanks,
Tomoki Sekiyama

#8 lore raw
In reply to Eric Blake

Quoting Eric Blake (2014-06-03 17:06:22)

Quoted 16 lines · Michael Roth
Quoted 8 lines
Quoted 5 lines

Bikeshedding on the proposed name: given that 'fs' is an abbreviation of 'filesystem', "fsfreeze-freeze-filesystems" sounds rather redundant. I would suggest guest-fsfreeze-list as a shorter name that conveys the intent, without quite as much repetition.

Somewhat agree, though I think we should retain the guest-<command_group>-<verb> structure and at least go with guest-fsfreeze-freeze-list.

guest- prefix is uncontroversial
command_group is 'fsfreeze'.
Currently in that group are the verbs 'status', 'freeze', and 'thaw'; and my proposal is to add 'list' (not 'freeze-list') as the new verb (just as we don't have 'freeze-status' as a verb).

Quoted 3 lines

Oh, and adding this command to freeze by list may require a change to the existing guest-fsfreeze-status command to report a third enum value of 'partial' (neither fully 'thawed' nor fully 'frozen').

Since multiple disk snapshots can be done concurrently, and it's possible management might initiate snapshotting for another disk while a prior snapshot is still being done, being able to freeze a separate subset of filesystems while others are still in a frozen state (and, unfreeze a subset of filesystem while leaving others are left frozen) does seem desirable.

OTOH (and I know this may be relying too much on implementation details), technically you only need to flush/freeze the filesystems long enough for QEMU to pivot over to another overlay image (correct me if I'm wrong on how libvirt actually handles this), at which point you should have an immutable, data-consistent backing chain that can be backed up in the background. So, at least for the common use-case, the extra book-keeping doesn't seem very beneficial.

In any case, since the current implementation doesn't allow for freezing additional filesystems when others are still frozen, I think that sort of handling could be done as a follow-up. And until that sort of behavior is supported I don't think a 'partial' state really has a useful meaning for users.

Quoted 4 lines
Signature · 3 lines
-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
#9 lore raw

PATCH v3 2/2 qga: Add guest-get-fs-info command

In reply to Tomoki Sekiyama

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.

---
 qga/commands-posix.c |  420 ++++++++++++++++++++++++++++++++++++++++++++++++++
 qga/qapi-schema.json |   78 +++++++++
 2 files changed, 497 insertions(+), 1 deletion(-)
qga/commands-posix.c +419 -1
index 771f00c..c7a2c09 100644
@@ -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)
qga/qapi-schema.json +78 -0

81 lines not on this page · raw message