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-fsinfo 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.sekiy...@hds.com> --- qga/commands-posix.c | 422 ++++++++++++++++++++++++++++++++++++++++++++++++++ qga/commands-win32.c | 6 + qga/qapi-schema.json | 79 +++++++++ 3 files changed, 506 insertions(+), 1 deletion(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 212988f..b1756b3 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,407 @@ 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; +} + +/* Store disk device info specified by @sysfs into @fs */ +static void __build_guest_fsinfo(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; + + 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) { + g_debug("only pci device is supported: sysfs path \"%s\"", syspath); + return; + } + + 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); + if (nports < 0) { + goto cleanup; + } + } + + 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) { + g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); + 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]; + break; + } + } + if (i >= nports) { + g_debug("no ata port for '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + } else if (strcmp(driver, "sym53c8xx") == 0) { + /* scsi(LSI Logic): target*:0:<unit>:0 */ + if (!has_tgt) { + g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI; + disk->unit = tgt[1]; + } 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; + } + } else if (strcmp(driver, "ahci") == 0) { + /* ahci: an ata port per unit */ + if (!has_ata || !has_tgt) { + g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + for (i = 0; i < nports; i++) { + if (ata == ports[i]) { + disk->unit = i; + disk->bus_type = GUEST_DISK_BUS_TYPE_SATA; + break; + } + } + if (i >= nports) { + g_debug("no ata port for '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + } else { + g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath); + goto cleanup; + } + + list->next = fs->disk; + fs->disk = list; + g_free(driver); + return; + +cleanup: + if (list) { + qapi_free_GuestDiskAddressList(list); + } + g_free(driver); +} + +static void _build_guest_fsinfo(char const *dirpath, + GuestFilesystemInfo *fs, Error **errp); + +/* Store a list of slave devices of virtual volume specified by @syspath into + * @fs */ +static void __build_guest_fsinfo_virtual(char const *syspath, + GuestFilesystemInfo *fs, + Error **errp) +{ + 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; + } + 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); + _build_guest_fsinfo(dirpath, fs, errp); + g_free(dirpath); + + if (*errp) { + break; + } + } + } + + closedir(dir); +} + +static void _build_guest_fsinfo(char const *dirpath, + GuestFilesystemInfo *fs, Error **errp) +{ + char *syspath = realpath(dirpath, NULL); + + if (!syspath) { + error_setg_errno(errp, errno, "realpath(\"%s\")", dirpath); + return; + } + + if (!fs->name) { + fs->name = g_strdup(basename(syspath)); + } + + g_debug(" parse sysfs path '%s'", syspath); + + if (strstr(syspath, "/devices/virtual/block/")) { + __build_guest_fsinfo_virtual(syspath, fs, errp); + } else { + __build_guest_fsinfo(syspath, fs, errp); + } + + free(syspath); +} + +/* Return a list of the disk device(s)' info which @mount lies on */ +static GuestFilesystemInfo *build_guest_fsinfo(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_guest_fsinfo(dirpath, fs, errp); + + g_free(dirpath); + return fs; +} + +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) +{ + FsMountList mounts; + struct FsMount *mount; + GuestFilesystemInfoList *new, *ret = NULL; + 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 guest fsinfo for '%s'", mount->dirname); + + new = g_malloc0(sizeof(*ret)); + new->value = build_guest_fsinfo(mount, &local_err); + 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; +} + + typedef enum { FSFREEZE_HOOK_THAW = 0, FSFREEZE_HOOK_FREEZE, @@ -1481,6 +1895,12 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) #if !defined(CONFIG_FSFREEZE) +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return NULL; +} + GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp) { error_set(errp, QERR_UNSUPPORTED); diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 6184d05..8c9ec34 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -150,6 +150,12 @@ void qmp_guest_file_flush(int64_t handle, Error **errp) error_set(errp, QERR_UNSUPPORTED); } +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return NULL; +} + /* * Return status of freeze/thaw */ diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index caa4612..8604a68 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -659,3 +659,82 @@ { '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-fsinfo: +# +# Returns: The list of filesystems information mounted in the guest. +# The returned mountpoints may be specified to +# @guest-fsfreeze-freeze-list. +# Network filesystems (such as CIFS and NFS) are not listed. +# +# Since: 2.1 +## +{ 'command': 'guest-get-fsinfo', + 'returns': ['GuestFilesystemInfo'] }