[libvirt] [v3 13/14] Add "redirdev" redirection device

Marc-André Lureau marcandre.lureau at gmail.com
Fri Sep 2 01:18:23 UTC 2011


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




More information about the libvir-list mailing list