Add support for file descriptor sets by converting some of the command line parameters to use /dev/fdset/%d if -add-fd is found to be supported by QEMU. For those devices libvirt now open()s the device to obtain the file descriptor and 'transfers' the fd using virCommand. For the following fragments of domain XML <disk type='file' device='disk'> <driver name='qemu' type='raw'/> <source file='/var/lib/libvirt/images/fc14-x86_64.img'/> <target dev='hda' bus='ide'/> <address type='drive' controller='0' bus='0' target='0' unit='0'/> </disk> <serial type='dev'> <source path='/dev/ttyS0'/> <target port='0'/> </serial> <serial type='pipe'> <source path='/tmp/testpipe'/> <target port='1'/> </serial> now create the following parts of command line: -add-fd set=0,fd=23,opaque=/var/lib/libvirt/images/fc14-x86_64.img -drive file=/dev/fdset/0,if=none,id=drive-ide0-0-0,format=raw -add-fd set=1,fd=30,opaque=/dev/ttyS0 -chardev tty,id=charserial0,path=/dev/fdset/1 -add-fd set=2,fd=32,opaque=/tmp/testpipe -chardev pipe,id=charserial1,path=/dev/fdset/2 --- src/qemu/qemu_command.c | 224 +++++++++++++++++++++++++++++++++++++---------- src/qemu/qemu_command.h | 13 ++ src/qemu/qemu_driver.c | 6 + src/qemu/qemu_hotplug.c | 9 + src/qemu/qemu_process.c | 3 tests/qemuxml2argvtest.c | 8 + tests/qemuxmlnstest.c | 7 + 7 files changed, 215 insertions(+), 55 deletions(-) Index: libvirt/src/qemu/qemu_command.c =================================================================== --- libvirt.orig/src/qemu/qemu_command.c +++ libvirt/src/qemu/qemu_command.c @@ -46,6 +46,7 @@ #include "base64.h" #include "device_conf.h" #include "virstoragefile.h" +#include "virintset.h" #include <sys/stat.h> #include <fcntl.h> @@ -133,6 +134,65 @@ VIR_ENUM_IMPL(qemuDomainFSDriver, VIR_DO /** + * qemuVirCommandGetFDSet: + * @cmd: the command to modify + * @fd: fd to reassign to the child + * + * Get the parameters for the the QEMU -add-fd command line option + * for the given file descriptor. The file descriptor must previously + * have been 'transferred' in a virCommandTransfer() call. + * This function for example returns "set=10,fd=20". + */ +static char * +qemuVirCommandGetFDSet(virIntSetPtr fdset, int fd, + const char *opaque) +{ + char *result = NULL; + int idx = virIntSetIndexOf(fdset, fd); + + if (idx >= 0) { + if (virAsprintf(&result, "set=%d,fd=%d%s%s", idx, fd, + (opaque) ? ",opaque=" : "", + (opaque) ? opaque : "") < 0) { + virReportOOMError(); + } + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("file descriptor %d not inn fdset"), fd); + } + + return result; +} + +/** + * qemuVirCommandGetDevSet: + * @cmd: the command to modify + * @fd: fd to reassign to the child + * + * Get the parameters for the the QEMU path= parameter where a file + * descriptor is accessed via a file descriptor set, for example + * /dev/fdset/10. The file descriptor must previously have been + * 'transferred' in a virCommandTransfer() call. + */ +static char * +qemuVirCommandGetDevSet(virIntSetPtr fdset, int fd) +{ + char *result = NULL; + int idx = virIntSetIndexOf(fdset, fd); + + if (idx >= 0) { + if (virAsprintf(&result, "/dev/fdset/%d", idx) < 0) { + virReportOOMError(); + } + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("file descriptor %d not in fdset"), fd); + } + return result; +} + + +/** * qemuPhysIfaceConnect: * @def: the definition of the VM (needed by 802.1Qbh and audit) * @driver: pointer to the driver instance @@ -2223,11 +2283,65 @@ no_memory: goto cleanup; } +static char * +qemuCreatePathForFilePath(const char *fmt, const char *path, + virCommandPtr cmd, virIntSetPtr fdset, + qemuCapsPtr caps) +{ + char *res = NULL; + char *devset = NULL; + char *fdsetstr = NULL; + + /* + * cmd == NULL hints at hotplugging; use old way of doing things + */ + if (!cmd || !qemuCapsGet(caps, QEMU_CAPS_ADD_FD)) { + if (virAsprintf(&res, fmt, path) < 0) { + virReportOOMError(); + return NULL; + } + } else { + int fd = open(path, O_RDWR); + if (fd < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not open %s"), path); + return NULL; + } + virCommandTransferFD(cmd, fd); + if (virIntSetAdd(fdset, fd) == -ENOMEM) { + virReportOOMError(); + return NULL; + } + devset = qemuVirCommandGetDevSet(fdset, fd); + if (!devset) + return NULL; + if (virAsprintf(&res, fmt, devset) < 0) + goto error; + + fdsetstr = qemuVirCommandGetFDSet(fdset, fd, path); + if (!fdsetstr) + goto error; + virCommandAddArgList(cmd, "-add-fd", fdsetstr, NULL); + } + + VIR_FREE(devset); + VIR_FREE(fdsetstr); + + return res; + +error: + VIR_FREE(devset); + VIR_FREE(fdset); + return NULL; +} + char * qemuBuildDriveStr(virConnectPtr conn ATTRIBUTE_UNUSED, virDomainDiskDefPtr disk, bool bootable, - qemuCapsPtr caps) + qemuCapsPtr caps, + virCommandPtr cmd, + virIntSetPtr fdset) { virBuffer opt = VIR_BUFFER_INITIALIZER; const char *bus = virDomainDiskQEMUBusTypeToString(disk->bus); @@ -2235,6 +2349,7 @@ qemuBuildDriveStr(virConnectPtr conn ATT virDomainDiskGeometryTransTypeToString(disk->geometry.trans); int idx = virDiskNameToIndex(disk->dst); int busid = -1, unitid = -1; + char *pathfdset = NULL; if (idx < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, @@ -2389,7 +2504,12 @@ qemuBuildDriveStr(virConnectPtr conn ATT "block type disk")); goto error; } - virBufferEscape(&opt, ',', ",", "file=%s,", disk->src); + pathfdset = qemuCreatePathForFilePath("file=%s", disk->src, + cmd, fdset, caps); + if (!pathfdset) + goto error; + virBufferEscape(&opt, ',', ",", "%s,", pathfdset); + VIR_FREE(pathfdset); } } if (qemuCapsGet(caps, QEMU_CAPS_DEVICE)) @@ -2886,11 +3006,14 @@ error: char *qemuBuildFSStr(virDomainFSDefPtr fs, - qemuCapsPtr caps ATTRIBUTE_UNUSED) + qemuCapsPtr caps ATTRIBUTE_UNUSED, + virCommandPtr cmd, + virIntSetPtr fdset) { virBuffer opt = VIR_BUFFER_INITIALIZER; const char *driver = qemuDomainFSDriverTypeToString(fs->fsdriver); const char *wrpolicy = virDomainFSWrpolicyTypeToString(fs->wrpolicy); + char *pathfdset = NULL; if (fs->type != VIR_DOMAIN_FS_TYPE_MOUNT) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", @@ -2935,7 +3058,10 @@ char *qemuBuildFSStr(virDomainFSDefPtr f } virBufferAsprintf(&opt, ",id=%s%s", QEMU_FSDEV_HOST_PREFIX, fs->info.alias); - virBufferAsprintf(&opt, ",path=%s", fs->src); + pathfdset = qemuCreatePathForFilePath("path=%s", fs->src, cmd, fdset, caps); + if (!pathfdset) + goto error; + virBufferAsprintf(&opt, ",%s", pathfdset); if (fs->readonly) { if (qemuCapsGet(caps, QEMU_CAPS_FSDEV_READONLY)) { @@ -2952,10 +3078,12 @@ char *qemuBuildFSStr(virDomainFSDefPtr f virReportOOMError(); goto error; } + VIR_FREE(pathfdset); return virBufferContentAndReset(&opt); error: + VIR_FREE(pathfdset); virBufferFreeAndReset(&opt); return NULL; } @@ -3884,15 +4012,16 @@ qemuBuildUSBHostdevUsbDevStr(virDomainHo } - /* This function outputs a -chardev command line option which describes only the * host side of the character device */ static char * qemuBuildChrChardevStr(virDomainChrSourceDefPtr dev, const char *alias, - qemuCapsPtr caps) + qemuCapsPtr caps, virCommandPtr cmd, + virIntSetPtr fdset) { virBuffer buf = VIR_BUFFER_INITIALIZER; bool telnet; + char *pathfdset = NULL; switch (dev->type) { case VIR_DOMAIN_CHR_TYPE_NULL: @@ -3908,19 +4037,31 @@ qemuBuildChrChardevStr(virDomainChrSourc break; case VIR_DOMAIN_CHR_TYPE_DEV: - virBufferAsprintf(&buf, "%s,id=char%s,path=%s", + pathfdset = qemuCreatePathForFilePath("path=%s", dev->data.file.path, + cmd, fdset, caps); + if (!pathfdset) + goto error; + virBufferAsprintf(&buf, "%s,id=char%s,%s", STRPREFIX(alias, "parallel") ? "parport" : "tty", - alias, dev->data.file.path); + alias, pathfdset); break; case VIR_DOMAIN_CHR_TYPE_FILE: - virBufferAsprintf(&buf, "file,id=char%s,path=%s", alias, - dev->data.file.path); + pathfdset = qemuCreatePathForFilePath("path=%s", dev->data.file.path, + cmd, fdset, caps); + if (!pathfdset) + goto error; + virBufferAsprintf(&buf, "file,id=char%s,%s", alias, + pathfdset); break; case VIR_DOMAIN_CHR_TYPE_PIPE: - virBufferAsprintf(&buf, "pipe,id=char%s,path=%s", alias, - dev->data.file.path); + pathfdset = qemuCreatePathForFilePath("path=%s", dev->data.file.path, + cmd, fdset, caps); + if (!pathfdset) + goto error; + virBufferAsprintf(&buf, "pipe,id=char%s,%s", alias, + pathfdset); break; case VIR_DOMAIN_CHR_TYPE_STDIO: @@ -3989,10 +4130,13 @@ qemuBuildChrChardevStr(virDomainChrSourc goto error; } + VIR_FREE(pathfdset); + return virBufferContentAndReset(&buf); error: virBufferFreeAndReset(&buf); + VIR_FREE(pathfdset); return NULL; } @@ -5056,7 +5200,8 @@ qemuBuildCommandLine(virConnectPtr conn, const char *migrateFrom, int migrateFd, virDomainSnapshotObjPtr snapshot, - enum virNetDevVPortProfileOp vmop) + enum virNetDevVPortProfileOp vmop, + virIntSetPtr fdset) { int i, j; int disableKQEMU = 0; @@ -5350,11 +5495,10 @@ qemuBuildCommandLine(virConnectPtr conn, /* Use -chardev if it's available */ if (qemuCapsGet(caps, QEMU_CAPS_CHARDEV)) { - virCommandAddArg(cmd, "-chardev"); if (!(chrdev = qemuBuildChrChardevStr(monitor_chr, "monitor", - caps))) + caps, cmd, fdset))) goto error; - virCommandAddArg(cmd, chrdev); + virCommandAddArgList(cmd, "-chardev", chrdev, NULL); VIR_FREE(chrdev); virCommandAddArg(cmd, "-mon"); @@ -5797,8 +5941,6 @@ qemuBuildCommandLine(virConnectPtr conn, break; } - virCommandAddArg(cmd, "-drive"); - /* Unfortunately it is not possible to use -device for floppies, or Xen paravirt devices. Fortunately, those don't need @@ -5814,12 +5956,12 @@ qemuBuildCommandLine(virConnectPtr conn, } optstr = qemuBuildDriveStr(conn, disk, emitBootindex ? false : !!bootindex, - caps); + caps, cmd, fdset); if (deviceFlagMasked) qemuCapsSet(caps, QEMU_CAPS_DEVICE); if (!optstr) goto error; - virCommandAddArg(cmd, optstr); + virCommandAddArgList(cmd, "-drive", optstr, NULL); VIR_FREE(optstr); if (!emitBootindex) @@ -5995,7 +6137,7 @@ qemuBuildCommandLine(virConnectPtr conn, virDomainFSDefPtr fs = def->fss[i]; virCommandAddArg(cmd, "-fsdev"); - if (!(optstr = qemuBuildFSStr(fs, caps))) + if (!(optstr = qemuBuildFSStr(fs, caps, cmd, fdset))) goto error; virCommandAddArg(cmd, optstr); VIR_FREE(optstr); @@ -6275,14 +6417,13 @@ qemuBuildCommandLine(virConnectPtr conn, goto error; } - virCommandAddArg(cmd, "-chardev"); if (!(devstr = qemuBuildChrChardevStr(&smartcard->data.passthru, smartcard->info.alias, - caps))) { + caps, cmd, fdset))) { virBufferFreeAndReset(&opt); goto error; } - virCommandAddArg(cmd, devstr); + virCommandAddArgList(cmd, "-chardev", devstr, NULL); VIR_FREE(devstr); virBufferAsprintf(&opt, "ccid-card-passthru,chardev=char%s", @@ -6313,12 +6454,11 @@ qemuBuildCommandLine(virConnectPtr conn, /* Use -chardev with -device if they are available */ if (qemuCapsGet(caps, QEMU_CAPS_CHARDEV) && qemuCapsGet(caps, QEMU_CAPS_DEVICE)) { - virCommandAddArg(cmd, "-chardev"); if (!(devstr = qemuBuildChrChardevStr(&serial->source, serial->info.alias, - caps))) + caps, cmd, fdset))) goto error; - virCommandAddArg(cmd, devstr); + virCommandAddArgList(cmd, "-chardev", devstr, NULL); VIR_FREE(devstr); virCommandAddArg(cmd, "-device"); @@ -6350,12 +6490,11 @@ qemuBuildCommandLine(virConnectPtr conn, /* Use -chardev with -device if they are available */ if (qemuCapsGet(caps, QEMU_CAPS_CHARDEV) && qemuCapsGet(caps, QEMU_CAPS_DEVICE)) { - virCommandAddArg(cmd, "-chardev"); if (!(devstr = qemuBuildChrChardevStr(¶llel->source, parallel->info.alias, - caps))) + caps, cmd, fdset))) goto error; - virCommandAddArg(cmd, devstr); + virCommandAddArgList(cmd, "-chardev", devstr, NULL); VIR_FREE(devstr); virCommandAddArg(cmd, "-device"); @@ -6387,12 +6526,11 @@ qemuBuildCommandLine(virConnectPtr conn, goto error; } - virCommandAddArg(cmd, "-chardev"); if (!(devstr = qemuBuildChrChardevStr(&channel->source, channel->info.alias, - caps))) + caps, cmd, fdset))) goto error; - virCommandAddArg(cmd, devstr); + virCommandAddArgList(cmd, "-chardev", devstr, NULL); VIR_FREE(devstr); addr = virSocketAddrFormat(channel->target.addr); @@ -6422,12 +6560,11 @@ qemuBuildCommandLine(virConnectPtr conn, * the newer -chardev interface. */ ; } else { - virCommandAddArg(cmd, "-chardev"); if (!(devstr = qemuBuildChrChardevStr(&channel->source, channel->info.alias, - caps))) + caps, cmd, fdset))) goto error; - virCommandAddArg(cmd, devstr); + virCommandAddArgList(cmd, "-chardev", devstr, NULL); VIR_FREE(devstr); } @@ -6460,12 +6597,11 @@ qemuBuildCommandLine(virConnectPtr conn, goto error; } - virCommandAddArg(cmd, "-chardev"); if (!(devstr = qemuBuildChrChardevStr(&console->source, console->info.alias, - caps))) + caps, cmd, fdset))) goto error; - virCommandAddArg(cmd, devstr); + virCommandAddArgList(cmd, "-chardev", devstr, NULL); VIR_FREE(devstr); virCommandAddArg(cmd, "-device"); @@ -6482,12 +6618,11 @@ qemuBuildCommandLine(virConnectPtr conn, goto error; } - virCommandAddArg(cmd, "-chardev"); if (!(devstr = qemuBuildChrChardevStr(&console->source, console->info.alias, - caps))) + caps, cmd, fdset))) goto error; - virCommandAddArg(cmd, devstr); + virCommandAddArgList(cmd, "-chardev", devstr, NULL); VIR_FREE(devstr); virCommandAddArg(cmd, "-device"); @@ -6824,14 +6959,13 @@ qemuBuildCommandLine(virConnectPtr conn, virDomainRedirdevDefPtr redirdev = def->redirdevs[i]; char *devstr; - virCommandAddArg(cmd, "-chardev"); if (!(devstr = qemuBuildChrChardevStr(&redirdev->source.chr, redirdev->info.alias, - caps))) { + caps, cmd, fdset))) { goto error; } - virCommandAddArg(cmd, devstr); + virCommandAddArgList(cmd, "-chardev", devstr, NULL); VIR_FREE(devstr); if (!qemuCapsGet(caps, QEMU_CAPS_DEVICE)) Index: libvirt/src/qemu/qemu_command.h =================================================================== --- libvirt.orig/src/qemu/qemu_command.h +++ libvirt/src/qemu/qemu_command.h @@ -30,6 +30,7 @@ # include "qemu_conf.h" # include "qemu_domain.h" # include "qemu_capabilities.h" +# include "virintset.h" /* Config type for XML import/export conversions */ # define QEMU_CONFIG_FORMAT_ARGV "qemu-argv" @@ -58,7 +59,8 @@ virCommandPtr qemuBuildCommandLine(virCo const char *migrateFrom, int migrateFd, virDomainSnapshotObjPtr current_snapshot, - enum virNetDevVPortProfileOp vmop) + enum virNetDevVPortProfileOp vmop, + virIntSetPtr fdset) ATTRIBUTE_NONNULL(1); /* Generate string for arch-specific '-device' parameter */ @@ -95,9 +97,14 @@ char *qemuDeviceDriveHostAlias(virDomain char *qemuBuildDriveStr(virConnectPtr conn, virDomainDiskDefPtr disk, bool bootable, - qemuCapsPtr caps); + qemuCapsPtr caps, + virCommandPtr cmd, + virIntSetPtr fdset); + char *qemuBuildFSStr(virDomainFSDefPtr fs, - qemuCapsPtr caps); + qemuCapsPtr caps, + virCommandPtr cmd, + virIntSetPtr fdset); /* Current, best practice */ char * qemuBuildDriveDevStr(virDomainDefPtr def, Index: libvirt/src/qemu/qemu_driver.c =================================================================== --- libvirt.orig/src/qemu/qemu_driver.c +++ libvirt/src/qemu/qemu_driver.c @@ -5336,7 +5336,9 @@ static char *qemuDomainXMLToNative(virCo virCommandPtr cmd = NULL; char *ret = NULL; int i; + virIntSet fdset; + virIntSetInit(&fdset); virCheckFlags(0, NULL); qemuDriverLock(driver); @@ -5437,7 +5439,8 @@ static char *qemuDomainXMLToNative(virCo if (!(cmd = qemuBuildCommandLine(conn, driver, def, &monConfig, monitor_json, caps, - NULL, -1, NULL, VIR_NETDEV_VPORT_PROFILE_OP_NO_OP))) + NULL, -1, NULL, VIR_NETDEV_VPORT_PROFILE_OP_NO_OP, + &fdset))) goto cleanup; ret = virCommandToString(cmd); @@ -5448,6 +5451,7 @@ cleanup: virObjectUnref(caps); virCommandFree(cmd); virDomainDefFree(def); + virIntSetClear(&fdset); return ret; } Index: libvirt/src/qemu/qemu_hotplug.c =================================================================== --- libvirt.orig/src/qemu/qemu_hotplug.c +++ libvirt/src/qemu/qemu_hotplug.c @@ -262,7 +262,8 @@ int qemuDomainAttachPciDiskDevice(virCon if (qemuAssignDeviceDiskAlias(vm->def, disk, priv->caps) < 0) goto error; - if (!(drivestr = qemuBuildDriveStr(conn, disk, false, priv->caps))) + if (!(drivestr = qemuBuildDriveStr(conn, disk, false, priv->caps, + NULL, &priv->fdset))) goto error; if (!(devstr = qemuBuildDriveDevStr(NULL, disk, 0, priv->caps))) @@ -503,7 +504,8 @@ int qemuDomainAttachSCSIDisk(virConnectP goto error; } - if (!(drivestr = qemuBuildDriveStr(conn, disk, false, priv->caps))) + if (!(drivestr = qemuBuildDriveStr(conn, disk, false, priv->caps, NULL, + &priv->fdset))) goto error; for (i = 0 ; i <= disk->info.addr.drive.controller ; i++) { @@ -622,7 +624,8 @@ int qemuDomainAttachUsbMassstorageDevice if (qemuCapsGet(priv->caps, QEMU_CAPS_DEVICE)) { if (qemuAssignDeviceDiskAlias(vm->def, disk, priv->caps) < 0) goto error; - if (!(drivestr = qemuBuildDriveStr(conn, disk, false, priv->caps))) + if (!(drivestr = qemuBuildDriveStr(conn, disk, false, priv->caps, + NULL, &priv->fdset))) goto error; if (!(devstr = qemuBuildDriveDevStr(NULL, disk, 0, priv->caps))) goto error; Index: libvirt/src/qemu/qemu_process.c =================================================================== --- libvirt.orig/src/qemu/qemu_process.c +++ libvirt/src/qemu/qemu_process.c @@ -3779,7 +3779,8 @@ int qemuProcessStart(virConnectPtr conn, VIR_DEBUG("Building emulator command line"); if (!(cmd = qemuBuildCommandLine(conn, driver, vm->def, priv->monConfig, priv->monJSON != 0, priv->caps, - migrateFrom, stdin_fd, snapshot, vmop))) + migrateFrom, stdin_fd, snapshot, vmop, + &priv->fdset))) goto cleanup; /* now that we know it is about to start call the hook if present */ Index: libvirt/tests/qemuxml2argvtest.c =================================================================== --- libvirt.orig/tests/qemuxml2argvtest.c +++ libvirt/tests/qemuxml2argvtest.c @@ -18,6 +18,7 @@ # include "qemu/qemu_domain.h" # include "datatypes.h" # include "cpu/cpu_map.h" +# include "virintset.h" # include "testutilsqemu.h" @@ -92,6 +93,9 @@ static int testCompareXMLToArgvFiles(con virConnectPtr conn; char *log = NULL; virCommandPtr cmd = NULL; + virIntSet fdset; + + virIntSetInit(&fdset); if (!(conn = virGetConnect())) goto out; @@ -151,7 +155,8 @@ static int testCompareXMLToArgvFiles(con if (!(cmd = qemuBuildCommandLine(conn, &driver, vmdef, &monitor_chr, (flags & FLAG_JSON), extraFlags, migrateFrom, migrateFd, NULL, - VIR_NETDEV_VPORT_PROFILE_OP_NO_OP))) { + VIR_NETDEV_VPORT_PROFILE_OP_NO_OP, + &fdset))) { if (flags & FLAG_EXPECT_FAILURE) { ret = 0; virResetLastError(); @@ -198,6 +203,7 @@ out: virCommandFree(cmd); virDomainDefFree(vmdef); virObjectUnref(conn); + virIntSetClear(&fdset); return ret; } Index: libvirt/tests/qemuxmlnstest.c =================================================================== --- libvirt.orig/tests/qemuxmlnstest.c +++ libvirt/tests/qemuxmlnstest.c @@ -41,6 +41,9 @@ static int testCompareXMLToArgvFiles(con char *log = NULL; char *emulator = NULL; virCommandPtr cmd = NULL; + virIntSet fdset; + + virIntSetInit(&fdset); if (!(conn = virGetConnect())) goto fail; @@ -110,7 +113,8 @@ static int testCompareXMLToArgvFiles(con if (!(cmd = qemuBuildCommandLine(conn, &driver, vmdef, &monitor_chr, json, extraFlags, migrateFrom, migrateFd, NULL, - VIR_NETDEV_VPORT_PROFILE_OP_NO_OP))) + VIR_NETDEV_VPORT_PROFILE_OP_NO_OP, + &fdset))) goto fail; if (!!virGetLastError() != expectError) { @@ -151,6 +155,7 @@ static int testCompareXMLToArgvFiles(con virCommandFree(cmd); virDomainDefFree(vmdef); virObjectUnref(conn); + virIntSetClear(&fdset); return ret; } -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list