mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-07 20:27:23 +00:00
Extend parser and add support for USB devices in QEmu/KVM
* src/domain_conf.c src/domain_conf.h src/qemu_conf.c src/qemu_driver.c: Patch from Guido Günther allowing to pass usb devices to qemu/kvm * docs/libvirt.rng: add the new functionality to the grammar * tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-address.args tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-address.xml tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-product.args tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-product.xml tests/qemuxml2argvtest.c tests/qemuxml2xmltest.c: adding examples to the regression tests * libvirt.spec.in: fix the licence tag Daniel
This commit is contained in:
parent
c11e64efbd
commit
d1710d35ea
14
ChangeLog
14
ChangeLog
@ -1,3 +1,17 @@
|
|||||||
|
Fri Aug 8 16:15:55 CEST 2008 Daniel Veillard <veillard@redhat.com>
|
||||||
|
|
||||||
|
* src/domain_conf.c src/domain_conf.h src/qemu_conf.c
|
||||||
|
src/qemu_driver.c: Patch from Guido Günther allowing to pass
|
||||||
|
usb devices to qemu/kvm
|
||||||
|
* docs/libvirt.rng: add the new functionality to the grammar
|
||||||
|
* tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-address.args
|
||||||
|
tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-address.xml
|
||||||
|
tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-product.args
|
||||||
|
tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-product.xml
|
||||||
|
tests/qemuxml2argvtest.c tests/qemuxml2xmltest.c: adding examples
|
||||||
|
to the regression tests
|
||||||
|
* libvirt.spec.in: fix the licence tag
|
||||||
|
|
||||||
Fri Aug 8 19:18:43 JST 2008 Atsushi SAKAI <sakaia@jp.fujitsu.com>
|
Fri Aug 8 19:18:43 JST 2008 Atsushi SAKAI <sakaia@jp.fujitsu.com>
|
||||||
|
|
||||||
* docs/formatdomain.html docs/formatdomain.html.in
|
* docs/formatdomain.html docs/formatdomain.html.in
|
||||||
|
@ -848,6 +848,57 @@
|
|||||||
</element>
|
</element>
|
||||||
</define>
|
</define>
|
||||||
|
|
||||||
|
<define name='hostdev'>
|
||||||
|
<element name='hostdev'>
|
||||||
|
<optional>
|
||||||
|
<attribute name='mode'>
|
||||||
|
<choice>
|
||||||
|
<value>subsystem</value>
|
||||||
|
<value>capabilities</value>
|
||||||
|
</choice>
|
||||||
|
</attribute>
|
||||||
|
<attribute name='type'>
|
||||||
|
<choice>
|
||||||
|
<value>usb</value>
|
||||||
|
<value>pci</value>
|
||||||
|
</choice>
|
||||||
|
</attribute>
|
||||||
|
</optional>
|
||||||
|
<group>
|
||||||
|
<element name='source'>
|
||||||
|
<choice>
|
||||||
|
<ref name="usbproduct"/>
|
||||||
|
<ref name="usbaddress"/>
|
||||||
|
</choice>
|
||||||
|
</element>
|
||||||
|
</group>
|
||||||
|
</element>
|
||||||
|
</define>
|
||||||
|
|
||||||
|
<define name="usbproduct">
|
||||||
|
<element name="vendor">
|
||||||
|
<attribute name="id">
|
||||||
|
<ref name="usbId"/>
|
||||||
|
</attribute>
|
||||||
|
</element>
|
||||||
|
<element name="product">
|
||||||
|
<attribute name="id">
|
||||||
|
<ref name="usbId"/>
|
||||||
|
</attribute>
|
||||||
|
</element>
|
||||||
|
</define>
|
||||||
|
|
||||||
|
<define name="usbaddress">
|
||||||
|
<element name="address">
|
||||||
|
<attribute name="bus">
|
||||||
|
<ref name="usbAddr"/>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="device">
|
||||||
|
<ref name="usbAddr"/>
|
||||||
|
</attribute>
|
||||||
|
</element>
|
||||||
|
</define>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Devices attached to a domain.
|
Devices attached to a domain.
|
||||||
-->
|
-->
|
||||||
@ -868,6 +919,7 @@
|
|||||||
<ref name='parallel'/>
|
<ref name='parallel'/>
|
||||||
<ref name='serial'/>
|
<ref name='serial'/>
|
||||||
<ref name='input'/>
|
<ref name='input'/>
|
||||||
|
<ref name='hostdev'/>
|
||||||
</choice>
|
</choice>
|
||||||
</zeroOrMore>
|
</zeroOrMore>
|
||||||
</interleave>
|
</interleave>
|
||||||
@ -986,4 +1038,14 @@
|
|||||||
<param name="pattern">([0-2]?[0-9]?[0-9]\.){3}[0-2]?[0-9]?[0-9]</param>
|
<param name="pattern">([0-2]?[0-9]?[0-9]\.){3}[0-2]?[0-9]?[0-9]</param>
|
||||||
</data>
|
</data>
|
||||||
</define>
|
</define>
|
||||||
|
<define name='usbId'>
|
||||||
|
<data type='string'>
|
||||||
|
<param name="pattern">(0x)?[0-9a-fA-F]{1,4}</param>
|
||||||
|
</data>
|
||||||
|
</define>
|
||||||
|
<define name='usbAddr'>
|
||||||
|
<data type='string'>
|
||||||
|
<param name="pattern">(0x)?[0-9a-fA-F]{1,3}</param>
|
||||||
|
</data>
|
||||||
|
</define>
|
||||||
</grammar>
|
</grammar>
|
||||||
|
@ -29,7 +29,7 @@ Summary: Library providing a simple API virtualization
|
|||||||
Name: libvirt
|
Name: libvirt
|
||||||
Version: @VERSION@
|
Version: @VERSION@
|
||||||
Release: 1%{?dist}%{?extra_release}
|
Release: 1%{?dist}%{?extra_release}
|
||||||
License: LGPL
|
License: LGPLv2+
|
||||||
Group: Development/Libraries
|
Group: Development/Libraries
|
||||||
Source: libvirt-%{version}.tar.gz
|
Source: libvirt-%{version}.tar.gz
|
||||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
|
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
|
||||||
|
@ -131,6 +131,13 @@ VIR_ENUM_IMPL(virDomainGraphics, VIR_DOMAIN_GRAPHICS_TYPE_LAST,
|
|||||||
"sdl",
|
"sdl",
|
||||||
"vnc")
|
"vnc")
|
||||||
|
|
||||||
|
VIR_ENUM_IMPL(virDomainHostdevMode, VIR_DOMAIN_HOSTDEV_MODE_LAST,
|
||||||
|
"subsystem",
|
||||||
|
"capabilities")
|
||||||
|
|
||||||
|
VIR_ENUM_IMPL(virDomainHostdevSubsys, VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST,
|
||||||
|
"usb",
|
||||||
|
"pci")
|
||||||
|
|
||||||
static void virDomainReportError(virConnectPtr conn,
|
static void virDomainReportError(virConnectPtr conn,
|
||||||
int code, const char *fmt, ...)
|
int code, const char *fmt, ...)
|
||||||
@ -332,6 +339,16 @@ void virDomainSoundDefFree(virDomainSoundDefPtr def)
|
|||||||
VIR_FREE(def);
|
VIR_FREE(def);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void virDomainHostdevDefFree(virDomainHostdevDefPtr def)
|
||||||
|
{
|
||||||
|
if (!def)
|
||||||
|
return;
|
||||||
|
|
||||||
|
VIR_FREE(def->target);
|
||||||
|
virDomainHostdevDefFree(def->next);
|
||||||
|
VIR_FREE(def);
|
||||||
|
}
|
||||||
|
|
||||||
void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
|
void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
|
||||||
{
|
{
|
||||||
if (!def)
|
if (!def)
|
||||||
@ -350,6 +367,9 @@ void virDomainDeviceDefFree(virDomainDeviceDefPtr def)
|
|||||||
case VIR_DOMAIN_DEVICE_SOUND:
|
case VIR_DOMAIN_DEVICE_SOUND:
|
||||||
virDomainSoundDefFree(def->data.sound);
|
virDomainSoundDefFree(def->data.sound);
|
||||||
break;
|
break;
|
||||||
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
||||||
|
virDomainHostdevDefFree(def->data.hostdev);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
VIR_FREE(def);
|
VIR_FREE(def);
|
||||||
@ -369,7 +389,7 @@ void virDomainDefFree(virDomainDefPtr def)
|
|||||||
virDomainChrDefFree(def->parallels);
|
virDomainChrDefFree(def->parallels);
|
||||||
virDomainChrDefFree(def->console);
|
virDomainChrDefFree(def->console);
|
||||||
virDomainSoundDefFree(def->sounds);
|
virDomainSoundDefFree(def->sounds);
|
||||||
|
virDomainHostdevDefFree(def->hostdevs);
|
||||||
|
|
||||||
VIR_FREE(def->os.type);
|
VIR_FREE(def->os.type);
|
||||||
VIR_FREE(def->os.arch);
|
VIR_FREE(def->os.arch);
|
||||||
@ -1400,6 +1420,180 @@ error:
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
virDomainHostdevSubsysUsbDefParseXML(virConnectPtr conn,
|
||||||
|
const xmlNodePtr node,
|
||||||
|
virDomainHostdevDefPtr def) {
|
||||||
|
|
||||||
|
int ret = -1;
|
||||||
|
xmlNodePtr cur;
|
||||||
|
|
||||||
|
cur = node->children;
|
||||||
|
while (cur != NULL) {
|
||||||
|
if (cur->type == XML_ELEMENT_NODE) {
|
||||||
|
if (xmlStrEqual(cur->name, BAD_CAST "vendor")) {
|
||||||
|
char *vendor = virXMLPropString(cur, "id");
|
||||||
|
|
||||||
|
if (vendor) {
|
||||||
|
if (virStrToLong_ui(vendor, NULL, 0,
|
||||||
|
&def->source.subsys.usb.vendor) < 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("cannot parse vendor id %s"), vendor);
|
||||||
|
VIR_FREE(vendor);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
VIR_FREE(vendor);
|
||||||
|
} else {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
"%s", _("usb vendor needs id"));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else if (xmlStrEqual(cur->name, BAD_CAST "product")) {
|
||||||
|
char* product = virXMLPropString(cur, "id");
|
||||||
|
|
||||||
|
if (product) {
|
||||||
|
if (virStrToLong_ui(product, NULL, 0,
|
||||||
|
&def->source.subsys.usb.product) < 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("cannot parse product %s"), product);
|
||||||
|
VIR_FREE(product);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
VIR_FREE(product);
|
||||||
|
} else {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
"%s", _("usb product needs id"));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else if (xmlStrEqual(cur->name, BAD_CAST "address")) {
|
||||||
|
char *bus, *device;
|
||||||
|
|
||||||
|
bus = virXMLPropString(cur, "bus");
|
||||||
|
if (bus) {
|
||||||
|
if (virStrToLong_ui(bus, NULL, 0,
|
||||||
|
&def->source.subsys.usb.bus) < 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("cannot parse bus %s"), bus);
|
||||||
|
VIR_FREE(bus);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
VIR_FREE(bus);
|
||||||
|
} else {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
"%s", _("usb address needs bus id"));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
device = virXMLPropString(cur, "device");
|
||||||
|
if (device) {
|
||||||
|
if (virStrToLong_ui(device, NULL, 0,
|
||||||
|
&def->source.subsys.usb.device) < 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("cannot parse device %s"),
|
||||||
|
device);
|
||||||
|
VIR_FREE(device);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
VIR_FREE(device);
|
||||||
|
} else {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
"%s", _("usb address needs device id"));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("unknown usb source type '%s'"), cur->name);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (def->source.subsys.usb.vendor == 0 &&
|
||||||
|
def->source.subsys.usb.product != 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("missing vendor"));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (def->source.subsys.usb.vendor != 0 &&
|
||||||
|
def->source.subsys.usb.product == 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("missing product"));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
out:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static virDomainHostdevDefPtr
|
||||||
|
virDomainHostdevDefParseXML(virConnectPtr conn,
|
||||||
|
const xmlNodePtr node) {
|
||||||
|
|
||||||
|
xmlNodePtr cur;
|
||||||
|
virDomainHostdevDefPtr def;
|
||||||
|
char *mode, *type = NULL;
|
||||||
|
|
||||||
|
if (VIR_ALLOC(def) < 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_NO_MEMORY, NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
def->target = NULL;
|
||||||
|
|
||||||
|
mode = virXMLPropString(node, "mode");
|
||||||
|
if (mode) {
|
||||||
|
if ((def->mode=virDomainHostdevModeTypeFromString(mode)) < 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("unknown hostdev mode '%s'"), mode);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
type = virXMLPropString(node, "type");
|
||||||
|
if (type) {
|
||||||
|
if ((def->source.subsys.type = virDomainHostdevSubsysTypeFromString(type)) < 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("unknown host device type '%s'"), type);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
"%s", _("missing type in hostdev"));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
cur = node->children;
|
||||||
|
while (cur != NULL) {
|
||||||
|
if (cur->type == XML_ELEMENT_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(conn, cur, def) < 0)
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("uknown node %s"), cur->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
VIR_FREE(type);
|
||||||
|
VIR_FREE(mode);
|
||||||
|
return def;
|
||||||
|
|
||||||
|
error:
|
||||||
|
virDomainHostdevDefFree(def);
|
||||||
|
def = NULL;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int virDomainLifecycleParseXML(virConnectPtr conn,
|
static int virDomainLifecycleParseXML(virConnectPtr conn,
|
||||||
xmlXPathContextPtr ctxt,
|
xmlXPathContextPtr ctxt,
|
||||||
@ -1471,6 +1665,10 @@ virDomainDeviceDefPtr virDomainDeviceDefParse(virConnectPtr conn,
|
|||||||
dev->type = VIR_DOMAIN_DEVICE_SOUND;
|
dev->type = VIR_DOMAIN_DEVICE_SOUND;
|
||||||
if (!(dev->data.sound = virDomainSoundDefParseXML(conn, node)))
|
if (!(dev->data.sound = virDomainSoundDefParseXML(conn, node)))
|
||||||
goto error;
|
goto error;
|
||||||
|
} else if (xmlStrEqual(node->name, BAD_CAST "hostdev")) {
|
||||||
|
dev->type = VIR_DOMAIN_DEVICE_HOSTDEV;
|
||||||
|
if (!(dev->data.hostdev = virDomainHostdevDefParseXML(conn, node)))
|
||||||
|
goto error;
|
||||||
} else {
|
} else {
|
||||||
virDomainReportError(conn, VIR_ERR_XML_ERROR,
|
virDomainReportError(conn, VIR_ERR_XML_ERROR,
|
||||||
"%s", _("unknown device type"));
|
"%s", _("unknown device type"));
|
||||||
@ -1965,6 +2163,22 @@ static virDomainDefPtr virDomainDefParseXML(virConnectPtr conn,
|
|||||||
}
|
}
|
||||||
VIR_FREE(nodes);
|
VIR_FREE(nodes);
|
||||||
|
|
||||||
|
/* analysis of the host devices */
|
||||||
|
if ((n = virXPathNodeSet(conn, "./devices/hostdev", ctxt, &nodes)) < 0) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
"%s", _("cannot extract host devices"));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
for (i = 0 ; i < n ; i++) {
|
||||||
|
virDomainHostdevDefPtr hostdev = virDomainHostdevDefParseXML(conn, nodes[i]);
|
||||||
|
if (!hostdev)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
hostdev->next = def->hostdevs;
|
||||||
|
def->hostdevs = hostdev;
|
||||||
|
}
|
||||||
|
VIR_FREE(nodes);
|
||||||
|
|
||||||
return def;
|
return def;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
@ -2706,6 +2920,50 @@ virDomainGraphicsDefFormat(virConnectPtr conn,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
virDomainHostdevDefFormat(virConnectPtr conn,
|
||||||
|
virBufferPtr buf,
|
||||||
|
virDomainHostdevDefPtr def)
|
||||||
|
{
|
||||||
|
const char *mode = virDomainHostdevModeTypeToString(def->mode);
|
||||||
|
const char *type;
|
||||||
|
|
||||||
|
if (!mode || def->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("unexpected hostdev mode %d"), def->mode);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
type = virDomainHostdevSubsysTypeToString(def->source.subsys.type);
|
||||||
|
if (!type || def->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) {
|
||||||
|
virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
|
_("unexpected hostdev type %d"),
|
||||||
|
def->source.subsys.type);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virBufferVSprintf(buf, " <hostdev mode='%s' type='%s'>\n", mode, type);
|
||||||
|
virBufferAddLit(buf, " <source>\n");
|
||||||
|
|
||||||
|
if (def->source.subsys.usb.vendor) {
|
||||||
|
virBufferVSprintf(buf, " <vendor id='0x%.4x'/>\n",
|
||||||
|
def->source.subsys.usb.vendor);
|
||||||
|
virBufferVSprintf(buf, " <product id='0x%.4x'/>\n",
|
||||||
|
def->source.subsys.usb.product);
|
||||||
|
} else {
|
||||||
|
virBufferVSprintf(buf, " <address bus='%d' device='%d'/>\n",
|
||||||
|
def->source.subsys.usb.bus,
|
||||||
|
def->source.subsys.usb.device);
|
||||||
|
}
|
||||||
|
|
||||||
|
virBufferAddLit(buf, " </source>\n");
|
||||||
|
virBufferAddLit(buf, " </hostdev>\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
char *virDomainDefFormat(virConnectPtr conn,
|
char *virDomainDefFormat(virConnectPtr conn,
|
||||||
virDomainDefPtr def,
|
virDomainDefPtr def,
|
||||||
int flags)
|
int flags)
|
||||||
@ -2719,6 +2977,7 @@ char *virDomainDefFormat(virConnectPtr conn,
|
|||||||
virDomainSoundDefPtr sound;
|
virDomainSoundDefPtr sound;
|
||||||
virDomainInputDefPtr input;
|
virDomainInputDefPtr input;
|
||||||
virDomainChrDefPtr chr;
|
virDomainChrDefPtr chr;
|
||||||
|
virDomainHostdevDefPtr hostdev;
|
||||||
const char *type = NULL, *tmp;
|
const char *type = NULL, *tmp;
|
||||||
int n, allones = 1;
|
int n, allones = 1;
|
||||||
|
|
||||||
@ -2931,6 +3190,13 @@ char *virDomainDefFormat(virConnectPtr conn,
|
|||||||
sound = sound->next;
|
sound = sound->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostdev = def->hostdevs;
|
||||||
|
while (hostdev) {
|
||||||
|
if (virDomainHostdevDefFormat(conn, &buf, hostdev) < 0)
|
||||||
|
goto cleanup;
|
||||||
|
hostdev = hostdev->next;
|
||||||
|
}
|
||||||
|
|
||||||
virBufferAddLit(&buf, " </devices>\n");
|
virBufferAddLit(&buf, " </devices>\n");
|
||||||
virBufferAddLit(&buf, "</domain>\n");
|
virBufferAddLit(&buf, "</domain>\n");
|
||||||
|
|
||||||
|
@ -279,7 +279,52 @@ struct _virDomainGraphicsDef {
|
|||||||
} data;
|
} data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum virDomainHostdevMode {
|
||||||
|
VIR_DOMAIN_HOSTDEV_MODE_SUBSYS,
|
||||||
|
VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES,
|
||||||
|
|
||||||
|
VIR_DOMAIN_HOSTDEV_MODE_LAST,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum virDomainHostdevSubsysType {
|
||||||
|
VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB,
|
||||||
|
VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI,
|
||||||
|
|
||||||
|
VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct _virDomainHostdevDef virDomainHostdevDef;
|
||||||
|
typedef virDomainHostdevDef *virDomainHostdevDefPtr;
|
||||||
|
struct _virDomainHostdevDef {
|
||||||
|
int mode; /* enum virDomainHostdevMode */
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
int type; /* enum virDomainHostdevBusType */
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
unsigned bus;
|
||||||
|
unsigned device;
|
||||||
|
|
||||||
|
unsigned vendor;
|
||||||
|
unsigned product;
|
||||||
|
} usb;
|
||||||
|
struct {
|
||||||
|
unsigned domain;
|
||||||
|
unsigned bus;
|
||||||
|
unsigned slot;
|
||||||
|
unsigned function;
|
||||||
|
} pci;
|
||||||
|
};
|
||||||
|
} subsys;
|
||||||
|
struct {
|
||||||
|
/* TBD: struct capabilities see:
|
||||||
|
* https://www.redhat.com/archives/libvir-list/2008-July/msg00429.html
|
||||||
|
*/
|
||||||
|
} caps;
|
||||||
|
} source;
|
||||||
|
char* target;
|
||||||
|
virDomainHostdevDefPtr next;
|
||||||
|
};
|
||||||
|
|
||||||
/* Flags for the 'type' field in next struct */
|
/* Flags for the 'type' field in next struct */
|
||||||
enum virDomainDeviceType {
|
enum virDomainDeviceType {
|
||||||
@ -288,6 +333,7 @@ enum virDomainDeviceType {
|
|||||||
VIR_DOMAIN_DEVICE_NET,
|
VIR_DOMAIN_DEVICE_NET,
|
||||||
VIR_DOMAIN_DEVICE_INPUT,
|
VIR_DOMAIN_DEVICE_INPUT,
|
||||||
VIR_DOMAIN_DEVICE_SOUND,
|
VIR_DOMAIN_DEVICE_SOUND,
|
||||||
|
VIR_DOMAIN_DEVICE_HOSTDEV,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct _virDomainDeviceDef virDomainDeviceDef;
|
typedef struct _virDomainDeviceDef virDomainDeviceDef;
|
||||||
@ -300,6 +346,7 @@ struct _virDomainDeviceDef {
|
|||||||
virDomainNetDefPtr net;
|
virDomainNetDefPtr net;
|
||||||
virDomainInputDefPtr input;
|
virDomainInputDefPtr input;
|
||||||
virDomainSoundDefPtr sound;
|
virDomainSoundDefPtr sound;
|
||||||
|
virDomainHostdevDefPtr hostdev;
|
||||||
} data;
|
} data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -386,6 +433,7 @@ struct _virDomainDef {
|
|||||||
virDomainNetDefPtr nets;
|
virDomainNetDefPtr nets;
|
||||||
virDomainInputDefPtr inputs;
|
virDomainInputDefPtr inputs;
|
||||||
virDomainSoundDefPtr sounds;
|
virDomainSoundDefPtr sounds;
|
||||||
|
virDomainHostdevDefPtr hostdevs;
|
||||||
virDomainChrDefPtr serials;
|
virDomainChrDefPtr serials;
|
||||||
virDomainChrDefPtr parallels;
|
virDomainChrDefPtr parallels;
|
||||||
virDomainChrDefPtr console;
|
virDomainChrDefPtr console;
|
||||||
@ -441,6 +489,7 @@ void virDomainFSDefFree(virDomainFSDefPtr def);
|
|||||||
void virDomainNetDefFree(virDomainNetDefPtr def);
|
void virDomainNetDefFree(virDomainNetDefPtr def);
|
||||||
void virDomainChrDefFree(virDomainChrDefPtr def);
|
void virDomainChrDefFree(virDomainChrDefPtr def);
|
||||||
void virDomainSoundDefFree(virDomainSoundDefPtr def);
|
void virDomainSoundDefFree(virDomainSoundDefPtr def);
|
||||||
|
void virDomainHostdevDefFree(virDomainHostdevDefPtr def);
|
||||||
void virDomainDeviceDefFree(virDomainDeviceDefPtr def);
|
void virDomainDeviceDefFree(virDomainDeviceDefPtr def);
|
||||||
void virDomainDefFree(virDomainDefPtr vm);
|
void virDomainDefFree(virDomainDefPtr vm);
|
||||||
void virDomainObjFree(virDomainObjPtr vm);
|
void virDomainObjFree(virDomainObjPtr vm);
|
||||||
@ -515,6 +564,8 @@ VIR_ENUM_DECL(virDomainFS)
|
|||||||
VIR_ENUM_DECL(virDomainNet)
|
VIR_ENUM_DECL(virDomainNet)
|
||||||
VIR_ENUM_DECL(virDomainChr)
|
VIR_ENUM_DECL(virDomainChr)
|
||||||
VIR_ENUM_DECL(virDomainSoundModel)
|
VIR_ENUM_DECL(virDomainSoundModel)
|
||||||
|
VIR_ENUM_DECL(virDomainHostdevMode)
|
||||||
|
VIR_ENUM_DECL(virDomainHostdevSubsys)
|
||||||
VIR_ENUM_DECL(virDomainInput)
|
VIR_ENUM_DECL(virDomainInput)
|
||||||
VIR_ENUM_DECL(virDomainInputBus)
|
VIR_ENUM_DECL(virDomainInputBus)
|
||||||
VIR_ENUM_DECL(virDomainGraphics)
|
VIR_ENUM_DECL(virDomainGraphics)
|
||||||
|
@ -723,6 +723,7 @@ int qemudBuildCommandLine(virConnectPtr conn,
|
|||||||
virDomainNetDefPtr net = vm->def->nets;
|
virDomainNetDefPtr net = vm->def->nets;
|
||||||
virDomainInputDefPtr input = vm->def->inputs;
|
virDomainInputDefPtr input = vm->def->inputs;
|
||||||
virDomainSoundDefPtr sound = vm->def->sounds;
|
virDomainSoundDefPtr sound = vm->def->sounds;
|
||||||
|
virDomainHostdevDefPtr hostdev = vm->def->hostdevs;
|
||||||
virDomainChrDefPtr serial = vm->def->serials;
|
virDomainChrDefPtr serial = vm->def->serials;
|
||||||
virDomainChrDefPtr parallel = vm->def->parallels;
|
virDomainChrDefPtr parallel = vm->def->parallels;
|
||||||
struct utsname ut;
|
struct utsname ut;
|
||||||
@ -1152,6 +1153,34 @@ int qemudBuildCommandLine(virConnectPtr conn,
|
|||||||
ADD_ARG(modstr);
|
ADD_ARG(modstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add host passthrough hardware */
|
||||||
|
while (hostdev) {
|
||||||
|
int ret;
|
||||||
|
char* usbdev;
|
||||||
|
|
||||||
|
if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
|
||||||
|
hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) {
|
||||||
|
if(hostdev->source.subsys.usb.vendor) {
|
||||||
|
ret = asprintf(&usbdev, "host:%.4x:%.4x",
|
||||||
|
hostdev->source.subsys.usb.vendor,
|
||||||
|
hostdev->source.subsys.usb.product);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ret = asprintf(&usbdev, "host:%.3d.%.3d",
|
||||||
|
hostdev->source.subsys.usb.bus,
|
||||||
|
hostdev->source.subsys.usb.device);
|
||||||
|
}
|
||||||
|
if (ret < 0) {
|
||||||
|
usbdev = NULL;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
ADD_ARG_LIT("-usbdevice");
|
||||||
|
ADD_ARG_LIT(usbdev);
|
||||||
|
VIR_FREE(usbdev);
|
||||||
|
}
|
||||||
|
hostdev = hostdev->next;
|
||||||
|
}
|
||||||
|
|
||||||
if (migrateFrom) {
|
if (migrateFrom) {
|
||||||
ADD_ARG_LIT("-incoming");
|
ADD_ARG_LIT("-incoming");
|
||||||
ADD_ARG_LIT(migrateFrom);
|
ADD_ARG_LIT(migrateFrom);
|
||||||
|
@ -2951,12 +2951,95 @@ static int qemudDomainChangeCDROM(virDomainPtr dom,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int qemudDomainAttachCdromDevice(virDomainPtr dom,
|
||||||
|
virDomainDeviceDefPtr dev)
|
||||||
|
{
|
||||||
|
struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
|
||||||
|
virDomainObjPtr vm = virDomainFindByUUID(driver->domains, dom->uuid);
|
||||||
|
virDomainDiskDefPtr disk;
|
||||||
|
|
||||||
|
if (!vm) {
|
||||||
|
qemudReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN,
|
||||||
|
"%s", _("no domain with matching uuid"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
disk = vm->def->disks;
|
||||||
|
while (disk) {
|
||||||
|
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM &&
|
||||||
|
STREQ(disk->dst, dev->data.disk->dst))
|
||||||
|
break;
|
||||||
|
disk = disk->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!disk) {
|
||||||
|
qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT,
|
||||||
|
"%s", _("CDROM not attached, cannot change media"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qemudDomainChangeCDROM(dom, vm, disk, dev->data.disk) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qemudDomainAttachHostDevice(virDomainPtr dom, virDomainDeviceDefPtr dev)
|
||||||
|
{
|
||||||
|
struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
|
||||||
|
virDomainObjPtr vm = virDomainFindByUUID(driver->domains, dom->uuid);
|
||||||
|
int ret;
|
||||||
|
char *cmd, *reply;
|
||||||
|
|
||||||
|
if (!vm) {
|
||||||
|
qemudReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN,
|
||||||
|
"%s", _("no domain with matching uuid"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->data.hostdev->source.subsys.usb.vendor) {
|
||||||
|
ret = asprintf(&cmd, "usb_add host:%.4x:%.4x",
|
||||||
|
dev->data.hostdev->source.subsys.usb.vendor,
|
||||||
|
dev->data.hostdev->source.subsys.usb.product);
|
||||||
|
} else {
|
||||||
|
ret = asprintf(&cmd, "usb_add host:%.3d.%.3d",
|
||||||
|
dev->data.hostdev->source.subsys.usb.bus,
|
||||||
|
dev->data.hostdev->source.subsys.usb.device);
|
||||||
|
}
|
||||||
|
if (ret == -1) {
|
||||||
|
qemudReportError(dom->conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qemudMonitorCommand(driver, vm, cmd, &reply) < 0) {
|
||||||
|
qemudReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
|
||||||
|
"%s", _("cannot attach usb device"));
|
||||||
|
VIR_FREE(cmd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG ("attach_usb reply: %s", reply);
|
||||||
|
/* If the command failed qemu prints:
|
||||||
|
* Could not add ... */
|
||||||
|
if (strstr(reply, "Could not add ")) {
|
||||||
|
qemudReportError (dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED,
|
||||||
|
"%s",
|
||||||
|
_("adding usb device failed"));
|
||||||
|
VIR_FREE(reply);
|
||||||
|
VIR_FREE(cmd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
VIR_FREE(reply);
|
||||||
|
VIR_FREE(cmd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int qemudDomainAttachDevice(virDomainPtr dom,
|
static int qemudDomainAttachDevice(virDomainPtr dom,
|
||||||
const char *xml) {
|
const char *xml) {
|
||||||
struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
|
struct qemud_driver *driver = (struct qemud_driver *)dom->conn->privateData;
|
||||||
virDomainObjPtr vm = virDomainFindByUUID(driver->domains, dom->uuid);
|
virDomainObjPtr vm = virDomainFindByUUID(driver->domains, dom->uuid);
|
||||||
virDomainDeviceDefPtr dev;
|
virDomainDeviceDefPtr dev;
|
||||||
virDomainDiskDefPtr disk;
|
int ret = 0;
|
||||||
|
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
qemudReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN,
|
qemudReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN,
|
||||||
@ -2975,36 +3058,21 @@ static int qemudDomainAttachDevice(virDomainPtr dom,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev->type != VIR_DOMAIN_DEVICE_DISK ||
|
if (dev->type == VIR_DOMAIN_DEVICE_DISK &&
|
||||||
dev->data.disk->device != VIR_DOMAIN_DISK_DEVICE_CDROM) {
|
dev->data.disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) {
|
||||||
|
ret = qemudDomainAttachCdromDevice(dom, dev);
|
||||||
|
} else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV &&
|
||||||
|
dev->data.hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
|
||||||
|
dev->data.hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) {
|
||||||
|
ret = qemudDomainAttachHostDevice(dom, dev);
|
||||||
|
} else {
|
||||||
qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT,
|
qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT,
|
||||||
"%s", _("only CDROM disk devices can be attached"));
|
"%s", _("this devicetype cannnot be attached"));
|
||||||
VIR_FREE(dev);
|
ret = -1;
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
disk = vm->def->disks;
|
|
||||||
while (disk) {
|
|
||||||
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM &&
|
|
||||||
STREQ(disk->dst, dev->data.disk->dst))
|
|
||||||
break;
|
|
||||||
disk = disk->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!disk) {
|
|
||||||
qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT,
|
|
||||||
"%s", _("CDROM not attached, cannot change media"));
|
|
||||||
VIR_FREE(dev);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qemudDomainChangeCDROM(dom, vm, disk, dev->data.disk) < 0) {
|
|
||||||
VIR_FREE(dev);
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VIR_FREE(dev);
|
VIR_FREE(dev);
|
||||||
return 0;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qemudDomainGetAutostart(virDomainPtr dom,
|
static int qemudDomainGetAutostart(virDomainPtr dom,
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
/usr/bin/qemu -S -M pc -m 214 -smp 1 -nographic -monitor pty -no-acpi -boot c -hda /dev/HostVG/QEMUGuest1 -net none -serial none -parallel none -usb -usbdevice host:014.006
|
27
tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-address.xml
Normal file
27
tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-address.xml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<domain type='qemu'>
|
||||||
|
<name>QEMUGuest1</name>
|
||||||
|
<uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
|
||||||
|
<memory>219200</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>
|
||||||
|
<disk type='block' device='disk'>
|
||||||
|
<source dev='/dev/HostVG/QEMUGuest1'/>
|
||||||
|
<target dev='hda' bus='ide'/>
|
||||||
|
</disk>
|
||||||
|
<hostdev mode='subsystem' type='usb'>
|
||||||
|
<source>
|
||||||
|
<address bus='14' device='6'/>
|
||||||
|
</source>
|
||||||
|
</hostdev>
|
||||||
|
</devices>
|
||||||
|
</domain>
|
@ -0,0 +1 @@
|
|||||||
|
/usr/bin/qemu -S -M pc -m 214 -smp 1 -nographic -monitor pty -no-acpi -boot c -hda /dev/HostVG/QEMUGuest1 -net none -serial none -parallel none -usb -usbdevice host:0204:6025
|
28
tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-product.xml
Normal file
28
tests/qemuxml2argvdata/qemuxml2argv-hostdev-usb-product.xml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<domain type='qemu'>
|
||||||
|
<name>QEMUGuest1</name>
|
||||||
|
<uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
|
||||||
|
<memory>219200</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>
|
||||||
|
<disk type='block' device='disk'>
|
||||||
|
<source dev='/dev/HostVG/QEMUGuest1'/>
|
||||||
|
<target dev='hda' bus='ide'/>
|
||||||
|
</disk>
|
||||||
|
<hostdev mode='subsystem' type='usb'>
|
||||||
|
<source>
|
||||||
|
<vendor id='0x0204'/>
|
||||||
|
<product id='0x6025'/>
|
||||||
|
</source>
|
||||||
|
</hostdev>
|
||||||
|
</devices>
|
||||||
|
</domain>
|
@ -179,6 +179,9 @@ mymain(int argc, char **argv)
|
|||||||
DO_TEST("console-compat", 0);
|
DO_TEST("console-compat", 0);
|
||||||
DO_TEST("sound", 0);
|
DO_TEST("sound", 0);
|
||||||
|
|
||||||
|
DO_TEST("hostdev-usb-product", 0);
|
||||||
|
DO_TEST("hostdev-usb-address", 0);
|
||||||
|
|
||||||
virCapabilitiesFree(driver.caps);
|
virCapabilitiesFree(driver.caps);
|
||||||
|
|
||||||
return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
return(ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||||
|
@ -118,6 +118,9 @@ mymain(int argc, char **argv)
|
|||||||
DO_TEST("parallel-tcp");
|
DO_TEST("parallel-tcp");
|
||||||
DO_TEST("console-compat");
|
DO_TEST("console-compat");
|
||||||
|
|
||||||
|
DO_TEST("hostdev-usb-product");
|
||||||
|
DO_TEST("hostdev-usb-address");
|
||||||
|
|
||||||
virCapabilitiesFree(driver.caps);
|
virCapabilitiesFree(driver.caps);
|
||||||
|
|
||||||
return (ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
return (ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user