Fixed since v1:
- avoid setup/restore security contexts
- some hotplug code improvements, although the lack of dynamic chardev
limits the utility of this code for now
Fixed since v2:
- create a new "redirdev" element instead of extending "hostdev"
---
docs/formatdomain.html.in | 39 ++++++
docs/schemas/domain.rng | 44 +++++--
src/conf/domain_audit.c | 65 ++++++++++
src/conf/domain_audit.h | 5 +
src/conf/domain_conf.c | 127 +++++++++++++++++++-
src/conf/domain_conf.h | 24 ++++
src/libvirt_private.syms | 3 +
src/qemu/qemu_command.c | 96 +++++++++++++++
src/qemu/qemu_command.h | 4 +-
src/qemu/qemu_driver.c | 7 +
src/qemu/qemu_hotplug.c | 43 +++++++
src/qemu/qemu_hotplug.h | 3 +
tests/qemuxml2argvdata/qemuxml2argv-usb-redir.args | 8 ++
tests/qemuxml2argvdata/qemuxml2argv-usb-redir.xml | 38 ++++++
tests/qemuxml2argvtest.c | 4 +
tests/qemuxml2xmltest.c | 2 +
16 files changed, 494 insertions(+), 18 deletions(-)
create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-usb-redir.args
create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-usb-redir.xml
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
index e35b76b..e1fd6ed 100644
--- a/docs/formatdomain.html.in
+++ b/docs/formatdomain.html.in
@@ -1385,6 +1385,45 @@
not used by qemu.</dd>
</dl>
+ <h4><a name="elementsRedir">Redirected
devices</a></h4>
+
+ <p>
+ USB device redirection through a character device is
+ supported <span class="since">since after 0.9.5 (KVM
+ only)</span>:
+ </p>
+
+<pre>
+ ...
+ <devices>
+ <redirdev bus='usb' type='tcp'>
+ <source mode='connect' host='localhost'
service='4000'/>
+ </redirdev>
+ </devices>
+ ...</pre>
+
+ <dl>
+ <dt><code>redirdev</code></dt>
+ <dd>The <code>redirdev</code> element is the main container for
+ describing redirected devices. <code>bus</code> must be
"usb"
+ for a USB device.
+
+ An additional attribute <code>type</code> is required,
+ matching one of the
+ supported <a href="#elementsConsole">serial device</a>
types,
+ to describe the host side of the
+ tunnel; <code>type='tcp'</code>
+ or <code>type='spicevmc'</code> (which uses the usbredir
+ channel of a <a href="#elementsGraphics">SPICE graphics
+ device</a>) are typical. Further sub-elements, such
+ as <code><source></code>, may be required according to
+ the given type, although a <code><target></code>
+ sub-element is not required (since the consumer of the
+ character device is the hypervisor itself, rather than a
+ device visible in the guest).</dd>
+
+ </dl>
+
<h4><a name="elementsSmartcard">Smartcard
devices</a></h4>
<p>
diff --git a/docs/schemas/domain.rng b/docs/schemas/domain.rng
index b496a32..b89108c 100644
--- a/docs/schemas/domain.rng
+++ b/docs/schemas/domain.rng
@@ -1723,21 +1723,25 @@
</element>
</define>
+ <define name="qemucdevSrcTypeChoice">
+ <choice>
+ <value>dev</value>
+ <value>file</value>
+ <value>pipe</value>
+ <value>unix</value>
+ <value>tcp</value>
+ <value>udp</value>
+ <value>null</value>
+ <value>stdio</value>
+ <value>vc</value>
+ <value>pty</value>
+ <value>spicevmc</value>
+ </choice>
+ </define>
+
<define name="qemucdevSrcType">
<attribute name="type">
- <choice>
- <value>dev</value>
- <value>file</value>
- <value>pipe</value>
- <value>unix</value>
- <value>tcp</value>
- <value>udp</value>
- <value>null</value>
- <value>stdio</value>
- <value>vc</value>
- <value>pty</value>
- <value>spicevmc</value>
- </choice>
+ <ref name="qemucdevSrcTypeChoice"/>
</attribute>
</define>
<define name="qemucdevSrcDef">
@@ -1974,6 +1978,19 @@
</optional>
</element>
</define>
+ <define name="redirdev">
+ <element name="redirdev">
+ <attribute name="bus">
+ <choice>
+ <value>usb</value>
+ </choice>
+ </attribute>
+ <attribute name="type">
+ <ref name="qemucdevSrcTypeChoice"/>
+ </attribute>
+ <ref name="qemucdevSrcDef"/>
+ </element>
+ </define>
<define name="hostdev">
<element name="hostdev">
<optional>
@@ -2138,6 +2155,7 @@
<ref name="channel"/>
<ref name="smartcard"/>
<ref name="hub"/>
+ <ref name="redirdev"/>
</choice>
</zeroOrMore>
<optional>
diff --git a/src/conf/domain_audit.c b/src/conf/domain_audit.c
index 9d89c94..ef0f47a 100644
--- a/src/conf/domain_audit.c
+++ b/src/conf/domain_audit.c
@@ -309,6 +309,66 @@ cleanup:
/**
+ * virDomainAuditRedirdev:
+ * @vm: domain making a change in pass-through host device
+ * @redirdev: device being attached or removed
+ * @reason: one of "start", "attach", or "detach"
+ * @success: true if the device passthrough operation succeeded
+ *
+ * Log an audit message about an attempted device passthrough change.
+ */
+void
+virDomainAuditRedirdev(virDomainObjPtr vm, virDomainRedirdevDefPtr redirdev,
+ const char *reason, bool success)
+{
+ char uuidstr[VIR_UUID_STRING_BUFLEN];
+ char *vmname;
+ char *address;
+ char *device;
+ const char *virt;
+
+ virUUIDFormat(vm->def->uuid, uuidstr);
+ if (!(vmname = virAuditEncode("vm", vm->def->name))) {
+ VIR_WARN("OOM while encoding audit message");
+ return;
+ }
+
+ if (!(virt = virDomainVirtTypeToString(vm->def->virtType))) {
+ VIR_WARN("Unexpected virt type %d while encoding audit message",
vm->def->virtType);
+ virt = "?";
+ }
+
+ switch (redirdev->bus) {
+ case VIR_DOMAIN_REDIRDEV_BUS_USB:
+ if (virAsprintf(&address, "USB redirdev") < 0) {
+ VIR_WARN("OOM while encoding audit message");
+ goto cleanup;
+ }
+ default:
+ VIR_WARN("Unexpected redirdev bus while encoding audit message: %d",
+ redirdev->bus);
+ goto cleanup;
+ }
+
+ if (!(device = virAuditEncode("device", VIR_AUDIT_STR(address)))) {
+ VIR_WARN("OOM while encoding audit message");
+ goto cleanup;
+ }
+
+ VIR_AUDIT(VIR_AUDIT_RECORD_RESOURCE, success,
+ "virt=%s resrc=dev reason=%s %s uuid=%s bus=%s %s",
+ virt, reason, vmname, uuidstr,
+ virDomainRedirdevBusTypeToString(redirdev->bus),
+ device);
+
+cleanup:
+ VIR_FREE(vmname);
+ VIR_FREE(device);
+ VIR_FREE(address);
+}
+
+
+/**
* virDomainAuditCgroup:
* @vm: domain making the cgroups ACL change
* @cgroup: cgroup that manages the devices
@@ -538,6 +598,11 @@ virDomainAuditStart(virDomainObjPtr vm, const char *reason, bool
success)
virDomainAuditHostdev(vm, hostdev, "start", true);
}
+ for (i = 0 ; i < vm->def->nredirdevs ; i++) {
+ virDomainRedirdevDefPtr redirdev = vm->def->redirdevs[i];
+ virDomainAuditRedirdev(vm, redirdev, "start", true);
+ }
+
virDomainAuditMemory(vm, 0, vm->def->mem.cur_balloon, "start",
true);
virDomainAuditVcpu(vm, 0, vm->def->vcpus, "start", true);
diff --git a/src/conf/domain_audit.h b/src/conf/domain_audit.h
index 0e88fd3..9ea9d6d 100644
--- a/src/conf/domain_audit.h
+++ b/src/conf/domain_audit.h
@@ -101,5 +101,10 @@ void virDomainAuditVcpu(virDomainObjPtr vm,
void virDomainAuditSecurityLabel(virDomainObjPtr vm,
bool success)
ATTRIBUTE_NONNULL(1);
+void virDomainAuditRedirdev(virDomainObjPtr vm,
+ virDomainRedirdevDefPtr def,
+ const char *reason,
+ bool success)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
#endif /* __VIR_DOMAIN_AUDIT_H__ */
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 4fe92d7..08d957a 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -127,7 +127,8 @@ VIR_ENUM_IMPL(virDomainDevice, VIR_DOMAIN_DEVICE_LAST,
"watchdog",
"controller",
"graphics",
- "hub")
+ "hub",
+ "redirdev")
VIR_ENUM_IMPL(virDomainDeviceAddress, VIR_DOMAIN_DEVICE_ADDRESS_TYPE_LAST,
"none",
@@ -440,6 +441,9 @@ VIR_ENUM_IMPL(virDomainState, VIR_DOMAIN_CRASHED+1,
VIR_ENUM_IMPL(virDomainHub, VIR_DOMAIN_HUB_TYPE_LAST,
"usb")
+VIR_ENUM_IMPL(virDomainRedirdevBus, VIR_DOMAIN_REDIRDEV_BUS_LAST,
+ "usb")
+
#define VIR_DOMAIN_NOSTATE_LAST (VIR_DOMAIN_NOSTATE_UNKNOWN + 1)
VIR_ENUM_IMPL(virDomainNostateReason, VIR_DOMAIN_NOSTATE_LAST,
"unknown")
@@ -1012,6 +1016,17 @@ void virDomainHubDefFree(virDomainHubDefPtr def)
VIR_FREE(def);
}
+void virDomainRedirdevDefFree(virDomainRedirdevDefPtr def)
+{
+ if (!def)
+ return;
+
+ virDomainChrSourceDefClear(&def->source.chr);
+ virDomainDeviceInfoClear(&def->info);
+
+ VIR_FREE(def);
+}
+
void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
{
if (!def)
@@ -1051,6 +1066,9 @@ void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
case VIR_DOMAIN_DEVICE_HUB:
virDomainHubDefFree(def->data.hub);
break;
+ case VIR_DOMAIN_DEVICE_REDIRDEV:
+ virDomainRedirdevDefFree(def->data.redirdev);
+ break;
}
VIR_FREE(def);
@@ -5343,7 +5361,6 @@ virDomainHostdevDefParseXML(const xmlNodePtr node,
virBitmapPtr bootMap,
unsigned int flags)
{
-
xmlNodePtr cur;
virDomainHostdevDefPtr def;
char *mode, *type = NULL, *managed = NULL;
@@ -5390,8 +5407,8 @@ virDomainHostdevDefParseXML(const xmlNodePtr node,
if (xmlStrEqual(cur->name, BAD_CAST "source")) {
if (def->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) {
- if (virDomainHostdevSubsysUsbDefParseXML(cur, def) < 0)
- goto error;
+ if (virDomainHostdevSubsysUsbDefParseXML(cur, def) < 0)
+ goto error;
}
if (def->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) {
@@ -5444,6 +5461,68 @@ error:
}
+static virDomainRedirdevDefPtr
+virDomainRedirdevDefParseXML(const xmlNodePtr node,
+ unsigned int flags)
+{
+ xmlNodePtr cur;
+ virDomainRedirdevDefPtr def;
+ char *bus, *type = NULL;
+
+ if (VIR_ALLOC(def) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ bus = virXMLPropString(node, "bus");
+ if (bus) {
+ if ((def->bus = virDomainRedirdevBusTypeFromString(bus)) < 0) {
+ virDomainReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unknown redirdev bus '%s'"), bus);
+ goto error;
+ }
+ } else {
+ def->bus = VIR_DOMAIN_REDIRDEV_BUS_USB;
+ }
+
+ type = virXMLPropString(node, "type");
+ if (type) {
+ if ((def->source.chr.type = virDomainChrTypeFromString(type)) < 0) {
+ virDomainReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unknown redirdev character device type
'%s'"), type);
+ goto error;
+ }
+ } else {
+ virDomainReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", _("missing type in redirdev"));
+ goto error;
+ }
+
+ cur = node->children;
+ while (cur != NULL) {
+ if (cur->type == XML_ELEMENT_NODE) {
+ if (xmlStrEqual(cur->name, BAD_CAST "source")) {
+ int remaining;
+
+ remaining = virDomainChrSourceDefParseXML(&def->source.chr, cur,
flags);
+ if (remaining != 0)
+ goto error;
+ }
+ }
+ cur = cur->next;
+ }
+
+cleanup:
+ VIR_FREE(bus);
+ VIR_FREE(type);
+ return def;
+
+error:
+ virDomainRedirdevDefFree(def);
+ def = NULL;
+ goto cleanup;
+}
+
static int virDomainLifecycleParseXML(xmlXPathContextPtr ctxt,
const char *xpath,
int *val,
@@ -5654,6 +5733,10 @@ virDomainDeviceDefPtr virDomainDeviceDefParse(virCapsPtr caps,
dev->type = VIR_DOMAIN_DEVICE_HUB;
if (!(dev->data.hub = virDomainHubDefParseXML(node, flags)))
goto error;
+ } else if (xmlStrEqual(node->name, BAD_CAST "redirdev")) {
+ dev->type = VIR_DOMAIN_DEVICE_REDIRDEV;
+ if (!(dev->data.redirdev = virDomainRedirdevDefParseXML(node, flags)))
+ goto error;
} else {
virDomainReportError(VIR_ERR_XML_ERROR,
"%s", _("unknown device type"));
@@ -7073,6 +7156,22 @@ static virDomainDefPtr virDomainDefParseXML(virCapsPtr caps,
}
VIR_FREE(nodes);
+ /* analysis of the redirected devices */
+ if ((n = virXPathNodeSet("./devices/redirdev", ctxt, &nodes)) < 0)
{
+ goto error;
+ }
+ if (n && VIR_ALLOC_N(def->redirdevs, n) < 0)
+ goto no_memory;
+ for (i = 0 ; i < n ; i++) {
+ virDomainRedirdevDefPtr redirdev = virDomainRedirdevDefParseXML(nodes[i],
+ flags);
+ if (!redirdev)
+ goto error;
+
+ def->redirdevs[def->nredirdevs++] = redirdev;
+ }
+ VIR_FREE(nodes);
+
/* analysis of security label */
if (virSecurityLabelDefParseXML(def, ctxt, flags) == -1)
goto error;
@@ -10162,6 +10261,22 @@ virDomainHostdevDefFormat(virBufferPtr buf,
return 0;
}
+static int
+virDomainRedirdevDefFormat(virBufferPtr buf,
+ virDomainRedirdevDefPtr def,
+ unsigned int flags)
+{
+ const char *bus;
+
+ bus = virDomainRedirdevBusTypeToString(def->bus);
+
+ virBufferAsprintf(buf, " <redirdev bus='%s'", bus);
+ if (virDomainChrSourceDefFormat(buf, &def->source.chr, false, flags) < 0)
+ return -1;
+ virBufferAddLit(buf, " </redirdev>\n");
+
+ return 0;
+}
static int
virDomainHubDefFormat(virBufferPtr buf,
@@ -10596,6 +10711,10 @@ virDomainDefFormatInternal(virDomainDefPtr def,
if (virDomainHostdevDefFormat(&buf, def->hostdevs[n], flags) < 0)
goto cleanup;
+ for (n = 0 ; n < def->nredirdevs ; n++)
+ if (virDomainRedirdevDefFormat(&buf, def->redirdevs[n], flags) < 0)
+ goto cleanup;
+
for (n = 0 ; n < def->nhubs ; n++)
if (virDomainHubDefFormat(&buf, def->hubs[n], flags) < 0)
goto cleanup;
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 48bfd0f..c79610c 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -938,6 +938,23 @@ struct _virDomainHostdevDef {
virDomainDeviceInfo info; /* Guest address */
};
+enum virDomainRedirdevBus {
+ VIR_DOMAIN_REDIRDEV_BUS_USB,
+
+ VIR_DOMAIN_REDIRDEV_BUS_LAST
+};
+
+typedef struct _virDomainRedirdevDef virDomainRedirdevDef;
+typedef virDomainRedirdevDef *virDomainRedirdevDefPtr;
+struct _virDomainRedirdevDef {
+ int bus; /* enum virDomainRedirdevBus */
+
+ union {
+ virDomainChrSourceDef chr;
+ } source;
+
+ virDomainDeviceInfo info; /* Guest address */
+};
enum {
VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO,
@@ -978,6 +995,7 @@ enum virDomainDeviceType {
VIR_DOMAIN_DEVICE_CONTROLLER,
VIR_DOMAIN_DEVICE_GRAPHICS,
VIR_DOMAIN_DEVICE_HUB,
+ VIR_DOMAIN_DEVICE_REDIRDEV,
VIR_DOMAIN_DEVICE_LAST,
};
@@ -999,6 +1017,7 @@ struct _virDomainDeviceDef {
virDomainWatchdogDefPtr watchdog;
virDomainGraphicsDefPtr graphics;
virDomainHubDefPtr hub;
+ virDomainRedirdevDefPtr redirdev;
} data;
};
@@ -1311,6 +1330,9 @@ struct _virDomainDef {
int nhostdevs;
virDomainHostdevDefPtr *hostdevs;
+ int nredirdevs;
+ virDomainRedirdevDefPtr *redirdevs;
+
int nsmartcards;
virDomainSmartcardDefPtr *smartcards;
@@ -1476,6 +1498,7 @@ void virDomainWatchdogDefFree(virDomainWatchdogDefPtr def);
void virDomainVideoDefFree(virDomainVideoDefPtr def);
void virDomainHostdevDefFree(virDomainHostdevDefPtr def);
void virDomainHubDefFree(virDomainHubDefPtr def);
+void virDomainRedirdevDefFree(virDomainRedirdevDefPtr def);
void virDomainDeviceDefFree(virDomainDeviceDefPtr def);
int virDomainDeviceAddressIsValid(virDomainDeviceInfoPtr info,
int type);
@@ -1757,6 +1780,7 @@ VIR_ENUM_DECL(virDomainVideo)
VIR_ENUM_DECL(virDomainHostdevMode)
VIR_ENUM_DECL(virDomainHostdevSubsys)
VIR_ENUM_DECL(virDomainHub)
+VIR_ENUM_DECL(virDomainRedirdevBus)
VIR_ENUM_DECL(virDomainInput)
VIR_ENUM_DECL(virDomainInputBus)
VIR_ENUM_DECL(virDomainGraphics)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 6642ba9..101f99e 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -219,6 +219,7 @@ virDomainAuditHostdev;
virDomainAuditMemory;
virDomainAuditNet;
virDomainAuditNetDevice;
+virDomainAuditRedirdev;
virDomainAuditSecurityLabel;
virDomainAuditStart;
virDomainAuditStop;
@@ -375,6 +376,8 @@ virDomainObjSetState;
virDomainObjTaint;
virDomainObjUnlock;
virDomainObjUnref;
+virDomainRedirdevBusTypeFromString;
+virDomainRedirdevBusTypeToString;
virDomainRemoveInactive;
virDomainSaveConfig;
virDomainSaveStatus;
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index eaab981..1ea9f20 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -605,6 +605,33 @@ qemuAssignDeviceHostdevAlias(virDomainDefPtr def,
virDomainHostdevDefPtr hostdev
int
+qemuAssignDeviceRedirdevAlias(virDomainDefPtr def, virDomainRedirdevDefPtr redirdev, int
idx)
+{
+ if (idx == -1) {
+ int i;
+ idx = 0;
+ for (i = 0 ; i < def->nredirdevs ; i++) {
+ int thisidx;
+ if ((thisidx = qemuDomainDeviceAliasIndex(&def->redirdevs[i]->info,
"redir")) < 0) {
+ qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Unable to determine device index for redirected
device"));
+ return -1;
+ }
+ if (thisidx >= idx)
+ idx = thisidx + 1;
+ }
+ }
+
+ if (virAsprintf(&redirdev->info.alias, "redir%d", idx) < 0) {
+ virReportOOMError();
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int
qemuAssignDeviceControllerAlias(virDomainControllerDefPtr controller)
{
const char *prefix = virDomainControllerTypeToString(controller->type);
@@ -651,6 +678,10 @@ qemuAssignDeviceAliases(virDomainDefPtr def, virBitmapPtr qemuCaps)
if (qemuAssignDeviceHostdevAlias(def, def->hostdevs[i], i) < 0)
return -1;
}
+ for (i = 0; i < def->nredirdevs ; i++) {
+ if (qemuAssignDeviceRedirdevAlias(def, def->redirdevs[i], i) < 0)
+ return -1;
+ }
for (i = 0; i < def->nvideos ; i++) {
if (virAsprintf(&def->videos[i]->info.alias, "video%d", i)
< 0)
goto no_memory;
@@ -2330,6 +2361,45 @@ qemuBuildPCIHostdevPCIDevStr(virDomainHostdevDefPtr dev)
char *
+qemuBuildRedirdevDevStr(virDomainRedirdevDefPtr dev,
+ virBitmapPtr qemuCaps)
+{
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+
+ if (dev->bus != VIR_DOMAIN_REDIRDEV_BUS_USB) {
+ qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("Redirection bus %s is not supported by QEMU"),
+ virDomainRedirdevBusTypeToString(dev->bus));
+ goto error;
+ }
+
+ if (!qemuCapsGet(qemuCaps, QEMU_CAPS_USB_REDIR)) {
+ qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("USB redirection is not supported "
+ "by this version of QEMU"));
+ goto error;
+ }
+
+ virBufferAsprintf(&buf, "usb-redir,chardev=char%s,id=%s",
+ dev->info.alias,
+ dev->info.alias);
+
+ if (qemuBuildDeviceAddressStr(&buf, &dev->info, qemuCaps) < 0)
+ goto error;
+
+ if (virBufferError(&buf)) {
+ virReportOOMError();
+ goto error;
+ }
+
+ return virBufferContentAndReset(&buf);
+
+error:
+ virBufferFreeAndReset(&buf);
+ return NULL;
+}
+
+char *
qemuBuildUSBHostdevDevStr(virDomainHostdevDefPtr dev,
virBitmapPtr qemuCaps)
{
@@ -4820,6 +4890,32 @@ qemuBuildCommandLine(virConnectPtr conn,
virCommandAddArgList(cmd, "-watchdog-action", action, NULL);
}
+ /* Add redirected devices */
+ for (i = 0 ; i < def->nredirdevs ; i++) {
+ virDomainRedirdevDefPtr redirdev = def->redirdevs[i];
+ char *devstr;
+
+ virCommandAddArg(cmd, "-chardev");
+ if (!(devstr = qemuBuildChrChardevStr(&redirdev->source.chr,
+ redirdev->info.alias,
+ qemuCaps))) {
+ goto error;
+ }
+
+ virCommandAddArg(cmd, devstr);
+ VIR_FREE(devstr);
+
+ if (!qemuCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
+ goto error;
+
+ virCommandAddArg(cmd, "-device");
+ if (!(devstr = qemuBuildRedirdevDevStr(redirdev, qemuCaps)))
+ goto error;
+ virCommandAddArg(cmd, devstr);
+ VIR_FREE(devstr);
+ }
+
+
/* Add host passthrough hardware */
for (i = 0 ; i < def->nhostdevs ; i++) {
virDomainHostdevDefPtr hostdev = def->hostdevs[i];
diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h
index 22bc15d..00e58a2 100644
--- a/src/qemu/qemu_command.h
+++ b/src/qemu/qemu_command.h
@@ -120,6 +120,7 @@ char * qemuBuildUSBHostdevDevStr(virDomainHostdevDefPtr dev,
virBitmapPtr qemuCaps);
char * qemuBuildHubDevStr(virDomainHubDefPtr dev, virBitmapPtr qemuCaps);
+char * qemuBuildRedirdevDevStr(virDomainRedirdevDefPtr dev, virBitmapPtr qemuCaps);
int qemuNetworkIfaceConnect(virDomainDefPtr def,
@@ -189,8 +190,9 @@ int qemuAssignDevicePCISlots(virDomainDefPtr def,
qemuDomainPCIAddressSetPtr ad
int qemuDomainNetVLAN(virDomainNetDefPtr def);
int qemuAssignDeviceNetAlias(virDomainDefPtr def, virDomainNetDefPtr net, int idx);
int qemuAssignDeviceDiskAlias(virDomainDiskDefPtr def, virBitmapPtr qemuCaps);
-int qemuAssignDeviceHostdevAlias(virDomainDefPtr def, virDomainHostdevDefPtr net, int
idx);
+int qemuAssignDeviceHostdevAlias(virDomainDefPtr def, virDomainHostdevDefPtr hostdev, int
idx);
int qemuAssignDeviceControllerAlias(virDomainControllerDefPtr controller);
+int qemuAssignDeviceRedirdevAlias(virDomainDefPtr def, virDomainRedirdevDefPtr redirdev,
int idx);
int
qemuParseKeywords(const char *str,
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 421a98e..cbc31e6 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -4908,6 +4908,13 @@ qemuDomainAttachDeviceLive(virDomainObjPtr vm,
dev->data.hostdev = NULL;
break;
+ case VIR_DOMAIN_DEVICE_REDIRDEV:
+ ret = qemuDomainAttachRedirdevDevice(driver, vm,
+ dev->data.redirdev);
+ if (!ret)
+ dev->data.redirdev = NULL;
+ break;
+
default:
qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("device type '%s' cannot be attached"),
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 60cd241..6ae834c 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -911,6 +911,49 @@ error:
}
+int qemuDomainAttachRedirdevDevice(struct qemud_driver *driver,
+ virDomainObjPtr vm,
+ virDomainRedirdevDefPtr redirdev)
+{
+ int ret;
+ qemuDomainObjPrivatePtr priv = vm->privateData;
+ char *devstr = NULL;
+
+ if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE)) {
+ if (qemuAssignDeviceRedirdevAlias(vm->def, redirdev, -1) < 0)
+ goto error;
+ if (!(devstr = qemuBuildRedirdevDevStr(redirdev, priv->qemuCaps)))
+ goto error;
+ }
+
+ if (VIR_REALLOC_N(vm->def->redirdevs, vm->def->nredirdevs+1) < 0) {
+ virReportOOMError();
+ goto error;
+ }
+
+ qemuDomainObjEnterMonitorWithDriver(driver, vm);
+ if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE))
+ ret = qemuMonitorAddDevice(priv->mon, devstr);
+ else
+ goto error;
+
+ qemuDomainObjExitMonitorWithDriver(driver, vm);
+ virDomainAuditRedirdev(vm, redirdev, "attach", ret == 0);
+ if (ret < 0)
+ goto error;
+
+ vm->def->redirdevs[vm->def->nredirdevs++] = redirdev;
+
+ VIR_FREE(devstr);
+
+ return 0;
+
+error:
+ VIR_FREE(devstr);
+ return -1;
+
+}
+
int qemuDomainAttachHostUsbDevice(struct qemud_driver *driver,
virDomainObjPtr vm,
virDomainHostdevDefPtr hostdev)
diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h
index 009f1f6..ea1cca0 100644
--- a/src/qemu/qemu_hotplug.h
+++ b/src/qemu/qemu_hotplug.h
@@ -53,6 +53,9 @@ int qemuDomainAttachHostPciDevice(struct qemud_driver *driver,
int qemuDomainAttachHostUsbDevice(struct qemud_driver *driver,
virDomainObjPtr vm,
virDomainHostdevDefPtr hostdev);
+int qemuDomainAttachRedirdevDevice(struct qemud_driver *driver,
+ virDomainObjPtr vm,
+ virDomainRedirdevDefPtr hostdev);
int qemuDomainAttachHostDevice(struct qemud_driver *driver,
virDomainObjPtr vm,
virDomainHostdevDefPtr hostdev);
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-usb-redir.args
b/tests/qemuxml2argvdata/qemuxml2argv-usb-redir.args
new file mode 100644
index 0000000..445aa5f
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-usb-redir.args
@@ -0,0 +1,8 @@
+LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test /usr/bin/qemu -S -M pc -m 214
-smp 1 -nographic -nodefconfig -nodefaults -chardev
socket,id=charmonitor,path=/tmp/test-monitor,server,nowait -mon
chardev=charmonitor,id=monitor,mode=readline -no-acpi -boot c \
+-device ich9-usb-ehci1,id=usb,bus=pci.0,multifunction=on,addr=0x4.0x7 \
+-device
ich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x4.0x0 \
+-device
ich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,multifunction=on,addr=0x4.0x1 \
+-device
ich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,multifunction=on,addr=0x4.0x2 \
+-chardev socket,id=charredir0,host=localhost,port=4000 \
+-device usb-redir,chardev=charredir0,id=redir0 \
+-device virtio-balloon-pci,id=balloon0,bus=pci.0,multifunction=on,addr=0x3.0x0
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-usb-redir.xml
b/tests/qemuxml2argvdata/qemuxml2argv-usb-redir.xml
new file mode 100644
index 0000000..d644216
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-usb-redir.xml
@@ -0,0 +1,38 @@
+<domain type='qemu'>
+ <name>QEMUGuest1</name>
+ <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+ <memory>219136</memory>
+ <currentMemory>219200</currentMemory>
+ <vcpu>1</vcpu>
+ <os>
+ <type arch='i686' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu</emulator>
+ <controller type='usb' index='0' model='ich9-ehci1'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x04' function='0x7'/>
+ </controller>
+ <controller type='usb' index='0' model='ich9-uhci1'>
+ <master startport='0'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x04' function='0x0'/>
+ </controller>
+ <controller type='usb' index='0' model='ich9-uhci2'>
+ <master startport='2'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x04' function='0x1'/>
+ </controller>
+ <controller type='usb' index='0' model='ich9-uhci3'>
+ <master startport='4'/>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x04' function='0x2'/>
+ </controller>
+ <redirdev bus='usb' type='tcp'>
+ <source mode='connect' host='localhost'
service='4000'/>
+ <protocol type='raw'/>
+ </redirdev>
+ <memballoon model='virtio'/>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index 9a8ecca..35e6d27 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -508,6 +508,10 @@ mymain(void)
QEMU_CAPS_CHARDEV, QEMU_CAPS_DEVICE, QEMU_CAPS_NODEFCONFIG,
QEMU_CAPS_PCI_MULTIFUNCTION, QEMU_CAPS_PIIX3_USB_UHCI,
QEMU_CAPS_USB_HUB, QEMU_CAPS_ICH9_USB_EHCI1);
+ DO_TEST("usb-redir", false,
+ QEMU_CAPS_CHARDEV, QEMU_CAPS_DEVICE, QEMU_CAPS_NODEFCONFIG,
+ QEMU_CAPS_PCI_MULTIFUNCTION, QEMU_CAPS_USB_HUB,
+ QEMU_CAPS_ICH9_USB_EHCI1, QEMU_CAPS_USB_REDIR);
DO_TEST("smbios", false, QEMU_CAPS_SMBIOS_TYPE);
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
index 4e109d5..af635d9 100644
--- a/tests/qemuxml2xmltest.c
+++ b/tests/qemuxml2xmltest.c
@@ -189,6 +189,8 @@ mymain(void)
DO_TEST("lease");
DO_TEST("event_idx");
+ DO_TEST("usb-redir");
+
/* These tests generate different XML */
DO_TEST_DIFFERENT("balloon-device-auto");
DO_TEST_DIFFERENT("channel-virtio-auto");
--
1.7.6