[libvirt] [PATCH 6/8] Docs: Add description and validation for blkdeviotune
by Lei Li
Signed-off-by: Lei Li <lilei(a)linux.vnet.ibm.com>
Signed-off-by: Zhi Yong Wu <wuzhy(a)linux.vnet.ibm.com>
---
docs/formatdomain.html.in | 31 +++++++++++++++++++++++++++++++
docs/schemas/domaincommon.rng | 24 ++++++++++++++++++++++++
2 files changed, 55 insertions(+), 0 deletions(-)
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
index cbad196..733062d 100644
--- a/docs/formatdomain.html.in
+++ b/docs/formatdomain.html.in
@@ -893,6 +893,14 @@
<driver name="tap" type="aio" cache="default"/>
<source file='/var/lib/xen/images/fv0'/ startupPolicy='optional'>
<target dev='hda' bus='ide'/>
+ <iotune>
+ <total_bytes_sec>n</total_bytes_sec>
+ <read_bytes_sec>n</read_bytes_sec>
+ <write_bytes_sec>n</write_bytes_sec>
+ <total_iops_sec>n</total_iops_sec>
+ <read_bytes_sec>n</read_bytes_sec>
+ <write_bytes_sec>n</write_bytes_sec>
+ </iotune>
<boot order='2'/>
<encryption type='...'>
...
@@ -1010,6 +1018,29 @@
<span class="since">Since 0.0.3; <code>bus</code> attribute since 0.4.3;
"usb" attribute value since after 0.4.4; "sata" attribute value since
0.9.7</span></dd>
+ <dt><code>iotune</code></dt>
+ <dd>The optional <code>iotune</code> element provides the ability
+ to set or get block I/O throttling for the device. Block I/O
+ throtting be implemented by qemu, is specified per-disk and can
+ vary across multiple disks.</dd>
+ <dt><code>total_bytes_sec</code></dt>
+ <dd>The optinal <code>total_bytes_sec</code> element is the total throughput
+ limit in bytes per second.</dd>
+ <dt><code>read_bytes_sec</code></dt>
+ <dd>The optinal <code>read_bytes_sec</code> element is the read throughput
+ limit in bytes per second.</dd>
+ <dt><code>write_bytes_sec</code</dt>
+ <dd>The optinal <code>write_bytes_sec</code> element is the write throughput
+ limit in bytes per second.</dd>
+ <dt><code>total_iops_sec</code></dt>
+ <dd>The optional <code>total_iops_sec</code> element is the total I/O operations
+ per second.</dd>
+ <dt><code>read_iops_sec</code></dt>
+ <dd>The optional <code>read_iops_sec</code> element is the read I/O operations
+ per second.</dd>
+ <dt><code>write_iops_sec</code></dt>
+ <dd>The optional <code>write_iops_sec</code> element is the write I/O operations
+ per second.</dd>
<dt><code>driver</code></dt>
<dd>
The optional driver element allows specifying further details
diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng
index b6f858e..c6873a0 100644
--- a/docs/schemas/domaincommon.rng
+++ b/docs/schemas/domaincommon.rng
@@ -698,6 +698,30 @@
<optional>
<ref name="snapshot"/>
</optional>
+ <optional>
+ <element name="iotune">
+ <choice>
+ <element name="total_bytes_sec">
+ <ref name="unsignedLongLong">
+ </element>
+ <element name="read_bytes_sec">
+ <ref name="unsignedLongLong">
+ </element>
+ <element name="write_bytes_sec">
+ <ref name="unsignedLongLong">
+ </element>
+ <element name="total_iops_sec">
+ <ref name="unsignedLongLong">
+ </element>
+ <element name="read_iops_sec">
+ <ref name="unsignedLongLong">
+ </element>
+ <element name="write_iopw_sec">
+ <ref name="unsignedLongLong">
+ </emement>
+ </choice>
+ </element>
+ </optional>
<choice>
<group>
<attribute name="type">
--
1.7.1
13 years, 4 months
[libvirt] [PATCH] nwfilter: Fix return value of virNWFilterHashTablePut
by Michal Privoznik
In libvirt it is common to return value <0 on error not vice versa.
---
src/conf/nwfilter_params.c | 16 ++++++++--------
src/nwfilter/nwfilter_gentech_driver.c | 4 ++--
src/nwfilter/nwfilter_learnipaddr.c | 2 +-
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/conf/nwfilter_params.c b/src/conf/nwfilter_params.c
index 12c6524..ba41972 100644
--- a/src/conf/nwfilter_params.c
+++ b/src/conf/nwfilter_params.c
@@ -490,7 +490,7 @@ hashDataFree(void *payload, const void *name ATTRIBUTE_UNUSED)
* @val: The value associated with the key
* @freeName: Whether the name must be freed on table destruction
*
- * Returns 0 on success, 1 on failure.
+ * Returns 0 on success, -1 on failure.
*
* Put an entry into the hashmap replacing and freeing an existing entry
* if one existed.
@@ -505,11 +505,11 @@ virNWFilterHashTablePut(virNWFilterHashTablePtr table,
if (copyName) {
name = strdup(name);
if (!name)
- return 1;
+ return -1;
if (VIR_REALLOC_N(table->names, table->nNames + 1) < 0) {
VIR_FREE(name);
- return 1;
+ return -1;
}
table->names[table->nNames++] = (char *)name;
}
@@ -519,11 +519,11 @@ virNWFilterHashTablePut(virNWFilterHashTablePtr table,
VIR_FREE(name);
table->nNames--;
}
- return 1;
+ return -1;
}
} else {
if (virHashUpdateEntry(table->hashTable, name, val) != 0) {
- return 1;
+ return -1;
}
}
return 0;
@@ -614,7 +614,7 @@ addToTable(void *payload, const void *name, void *data)
return;
}
- if (virNWFilterHashTablePut(atts->target, (const char *)name, val, 1) != 0) {
+ if (virNWFilterHashTablePut(atts->target, (const char *)name, val, 1) < 0) {
virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
_("Could not put variable '%s' into hashmap"),
(const char *)name);
@@ -640,7 +640,7 @@ virNWFilterHashTablePutAll(virNWFilterHashTablePtr src,
return 0;
err_exit:
- return 1;
+ return -1;
}
@@ -700,7 +700,7 @@ virNWFilterParseParamAttributes(xmlNodePtr cur)
value = virNWFilterParseVarValue(val);
if (!value)
goto skip_entry;
- if (virNWFilterHashTablePut(table, nam, value, 1))
+ if (virNWFilterHashTablePut(table, nam, value, 1) < 0)
goto err_exit;
}
value = NULL;
diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c
index 84a959b..946ed14 100644
--- a/src/nwfilter/nwfilter_gentech_driver.c
+++ b/src/nwfilter/nwfilter_gentech_driver.c
@@ -343,10 +343,10 @@ virNWFilterCreateVarsFrom(virNWFilterHashTablePtr vars1,
return NULL;
}
- if (virNWFilterHashTablePutAll(vars1, res))
+ if (virNWFilterHashTablePutAll(vars1, res) < 0)
goto err_exit;
- if (virNWFilterHashTablePutAll(vars2, res))
+ if (virNWFilterHashTablePutAll(vars2, res) < 0)
goto err_exit;
return res;
diff --git a/src/nwfilter/nwfilter_learnipaddr.c b/src/nwfilter/nwfilter_learnipaddr.c
index 2e373a6..425e8a2 100644
--- a/src/nwfilter/nwfilter_learnipaddr.c
+++ b/src/nwfilter/nwfilter_learnipaddr.c
@@ -800,7 +800,7 @@ virNWFilterLearnIPAddress(virNWFilterTechDriverPtr techdriver,
goto err_free_req;
}
- if (virNWFilterHashTablePutAll(filterparams, ht))
+ if (virNWFilterHashTablePutAll(filterparams, ht) < 0)
goto err_free_ht;
req->filtername = strdup(filtername);
--
1.7.3.4
13 years, 4 months
[libvirt] [PATCH 8/8] Add tests for blkdeviotune
by Lei Li
Signed-off-by: Lei Li <lilei(a)linux.vnet.ibm.com>
Signed-off-by: Zhi Yong Wu <wuzhy(a)linux.vnet.ibm.com>
---
.../qemuxml2argv-blkdeviotune.args | 4 ++
.../qemuxml2argvdata/qemuxml2argv-blkdeviotune.xml | 36 ++++++++++++++++++++
tests/qemuxml2argvtest.c | 1 +
tests/qemuxml2xmltest.c | 1 +
4 files changed, 42 insertions(+), 0 deletions(-)
create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-blkdeviotune.args
create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-blkdeviotune.xml
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-blkdeviotune.args b/tests/qemuxml2argvdata/qemuxml2argv-blkdeviotune.args
new file mode 100644
index 0000000..6f34698
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-blkdeviotune.args
@@ -0,0 +1,4 @@
+LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test /usr/bin/qemu -S -M \
+pc -m 214 -smp 1 -name QEMUGuest1 -nographic -monitor unix:/tmp/test-monitor, \
+server,nowait -no-acpi -boot c -hda /dev/HostVG/QEMUGuest1 -net none -serial \
+none -parallel none -usb
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-blkdeviotune.xml b/tests/qemuxml2argvdata/qemuxml2argv-blkdeviotune.xml
new file mode 100644
index 0000000..1fceef8
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-blkdeviotune.xml
@@ -0,0 +1,36 @@
+<domain type='qemu'>
+ <name>QEMUGuest1</name>
+ <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+ <memory>219100</memory>
+ <currentMemory>219100</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' snapshot='internal'>
+ <driver name='qemu' type='qcow2' cache='none'/>
+ <source dev='/dev/HostVG/QEMUGuest1'/>
+ <target dev='hda' bus='ide'/>
+ <iotune>
+ <total_bytes_sec>5000</total_bytes_sec>
+ <total_iops_sec>6000</total_iops_sec>
+ <address type='drive' controller='0' bus='0' unit='0'/>
+ </disk>
+ <disk type='block' device='cdrom' snapshot='no'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest2'/>
+ <target dev='hdc' bus='ide'/>
+ <readonly/>
+ <address type='drive' controller='0' bus='1' unit='0'/>
+ </disk>
+ <controller type='ide' index='0'/>
+ <memballoon model='virtio'/>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index d9a6e8d..1ebb950 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -585,6 +585,7 @@ mymain(void)
DO_TEST("blkiotune", false, QEMU_CAPS_NAME);
DO_TEST("cputune", false, QEMU_CAPS_NAME);
DO_TEST("numatune-memory", false, NONE);
+ DO_TEST("blkdeviotune", false, QEMU_CAPS_NAME);
DO_TEST("multifunction-pci-device", false,
QEMU_CAPS_DRIVE, QEMU_CAPS_DEVICE, QEMU_CAPS_NODEFCONFIG,
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
index 3f37520..2e6b5c7 100644
--- a/tests/qemuxml2xmltest.c
+++ b/tests/qemuxml2xmltest.c
@@ -191,6 +191,7 @@ mymain(void)
DO_TEST("event_idx");
DO_TEST("usb-redir");
+ DO_TEST("blkdeviotune");
/* These tests generate different XML */
DO_TEST_DIFFERENT("balloon-device-auto");
--
1.7.1
13 years, 4 months
[libvirt] [PATCH 7/8] Support virDomain{Set, Get}BlockIoTune in the python API
by Lei Li
Python support for both setting and getting block I/O throttle.
Signed-off-by: Lei Li <lilei(a)linux.vnet.ibm.com>
Signed-off-by: Zhi Yong Wu <wuzhy(a)linux.vnet.ibm.com>
---
python/generator.py | 2 +
python/libvirt-override-api.xml | 16 ++++
python/libvirt-override.c | 178 +++++++++++++++++++++++++++++++++++++++
3 files changed, 196 insertions(+), 0 deletions(-)
diff --git a/python/generator.py b/python/generator.py
index 71afdb7..88c52b9 100755
--- a/python/generator.py
+++ b/python/generator.py
@@ -414,6 +414,8 @@ skip_impl = (
'virDomainGetBlockJobInfo',
'virDomainMigrateGetMaxSpeed',
'virDomainBlockStatsFlags',
+ 'virDomainSetBlockIoTune',
+ 'virDomainGetBlockIoTune',
)
qemu_skip_impl = (
diff --git a/python/libvirt-override-api.xml b/python/libvirt-override-api.xml
index ef02f34..a05da3c 100644
--- a/python/libvirt-override-api.xml
+++ b/python/libvirt-override-api.xml
@@ -375,5 +375,21 @@
<arg name='flags' type='unsigned int' info='flags, currently unused, pass 0.'/>
<return type='unsigned long' info='current max migration speed, or None in case of error'/>
</function>
+ <function name='virDomainSetBlockIoTune' file='python'>
+ <info>Change the I/O throttle for a block device</info>
+ <arg name='dom' type='virDomainPtr' info='pointer to the domain'/>
+ <arg name='disk' type='const char *' info='disk name'/>
+ <arg name='params' type='virTypedParameterPtr' info='Pointer to blkio throttle params object'/>
+ <arg name='flags' type='unsigned int' info='an OR'ed set of virDomainModificationImpact'/>
+ <return type='int' info='0 in case of success, -1 in case of failure'/>
+ </function>
+ <function name='virDomainGetBlockIoTune' file='python'>
+ <info>Get the I/O throttle a block device</info>
+ <arg name='dom' type='virDomainPtr' info='pointer to the domain'/>
+ <arg name='disk' type='const char *' info='disk name'/>
+ <arg name='params' type='virTypedParameterPtr' info='Pointer to blkio throttle params object'/>
+ <arg name='flags' type='unsigned int' info='an OR'ed set of virDomainModificationImpact'/>
+ <return type='int' info='0 in case of success, -1 in case of failure'/>
+ </function>
</symbols>
</api>
diff --git a/python/libvirt-override.c b/python/libvirt-override.c
index 1759bae..be76d87 100644
--- a/python/libvirt-override.c
+++ b/python/libvirt-override.c
@@ -3195,6 +3195,182 @@ LIBVIRT_END_ALLOW_THREADS;
return ret;
}
+static PyObject *
+libvirt_virDomainSetBlockIoTune(PyObject *self ATTRIBUTE_UNUSED,
+ PyObject *args)
+{
+ virDomainPtr domain;
+ PyObject *pyobj_domain, *pyinfo;
+ const char *disk;
+ unsigned int flags;
+ virTypedParameterPtr params;
+ int nparams = 0, i;
+ int c_ret;
+
+ if (!PyArg_ParseTuple(args, (char *)"Ozi:virDomainSetBlockIoTune",
+ &pyobj_domain, &disk, &pyinfo, &flags))
+ return(NULL);
+ domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain);
+
+ LIBVIRT_BEGIN_ALLOW_THREADS;
+ c_ret = virDomainGetBlockIoTune(domain, disk, NULL, &nparams, flags);
+ LIBVIRT_END_ALLOW_THREADS;
+
+ if (c_ret < 0)
+ return VIR_PY_INT_FAIL;
+
+ if ((params = malloc(sizeof(*params)*nparams)) == NULL)
+ return VIR_PY_INT_FAIL;
+
+ LIBVIRT_BEGIN_ALLOW_THREADS;
+ c_ret = virDomainGetBlockIoTune(domain, disk, params, &nparams, flags);
+ LIBVIRT_END_ALLOW_THREADS;
+
+ if (c_ret < 0) {
+ free(params);
+ return VIR_PY_INT_FAIL;
+ }
+
+ /* convert to a Python tuple of long objects */
+ for (i = 0; i < nparams; i++) {
+ PyObject *key, *val;
+ key = libvirt_constcharPtrWrap(params[i].field);
+ val = PyDict_GetItem(pyinfo, key);
+ Py_DECREF(key);
+
+ if (val == NULL)
+ continue;
+
+ switch (params[i].type) {
+ case VIR_TYPED_PARAM_INT:
+ params[i].value.i = (int)PyInt_AS_LONG(val);
+ break;
+
+ case VIR_TYPED_PARAM_UINT:
+ params[i].value.ui = (unsigned int)PyInt_AS_LONG(val);
+ break;
+
+ case VIR_TYPED_PARAM_LLONG:
+ params[i].value.l = (long long)PyLong_AsLongLong(val);
+ break;
+
+ case VIR_TYPED_PARAM_ULLONG:
+ params[i].value.ul = (unsigned long long)PyLong_AsLongLong(val);
+ break;
+
+ case VIR_TYPED_PARAM_DOUBLE:
+ params[i].value.d = (double)PyFloat_AsDouble(val);
+ break;
+
+ case VIR_TYPED_PARAM_BOOLEAN:
+ {
+ PyObject *hacktrue = PyBool_FromLong(1);
+ params[i].value.b = hacktrue == val ? 1: 0;
+ Py_DECREF(hacktrue);
+ }
+ break;
+
+ default:
+ free(params);
+ return VIR_PY_INT_FAIL;
+ }
+ }
+
+ LIBVIRT_BEGIN_ALLOW_THREADS;
+ c_ret = virDomainSetMemoryParameters(domain, params, nparams, flags);
+ LIBVIRT_END_ALLOW_THREADS;
+
+ if (c_ret < 0) {
+ free(params);
+ return VIR_PY_INT_FAIL;
+ }
+
+ free(params);
+ return VIR_PY_INT_SUCCESS;
+}
+
+static PyObject *
+libvirt_virDomainGetBlockIoTune(PyObject *self ATTRIBUTE_UNUSED,
+ PyObject *args)
+{
+ virDomainPtr domain;
+ PyObject *pyobj_domain, *pyreply;
+ const char *disk;
+ int nparams = 0, i;
+ unsigned int flags;
+ virTypedParameterPtr params;
+ int c_ret;
+
+ if (!PyArg_ParseTuple(args, (char *)"Oi:virDomainGetBlockIoTune",
+ &pyobj_domain, &disk, &flags))
+ return(NULL);
+ domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain);
+
+ LIBVIRT_BEGIN_ALLOW_THREADS;
+ c_ret = virDomainGetBlockIoTune(domain, disk, NULL, &nparams, flags);
+ LIBVIRT_END_ALLOW_THREADS;
+
+ if (c_ret < 0)
+ return VIR_PY_NONE;
+
+ if ((params = malloc(sizeof(*params)*nparams)) == NULL)
+ return VIR_PY_NONE;
+
+ LIBVIRT_BEGIN_ALLOW_THREADS;
+ c_ret = virDomainGetBlockIoTune(domain, disk, params, &nparams, flags);
+ LIBVIRT_END_ALLOW_THREADS;
+
+ if (c_ret < 0) {
+ free(params);
+ return VIR_PY_NONE;
+ }
+
+ /* convert to a Python tuple of long objects */
+ if ((pyreply = PyDict_New()) == NULL) {
+ free(params);
+ return VIR_PY_NONE;
+ }
+ for (i = 0 ; i < nparams ; i++) {
+ PyObject *key, *val;
+
+ switch (params[i].type) {
+ case VIR_TYPED_PARAM_INT:
+ val = PyInt_FromLong((long)params[i].value.i);
+ break;
+
+ case VIR_TYPED_PARAM_UINT:
+ val = PyInt_FromLong((long)params[i].value.ui);
+ break;
+
+ case VIR_TYPED_PARAM_LLONG:
+ val = PyLong_FromLongLong((long long)params[i].value.l);
+ break;
+
+ case VIR_TYPED_PARAM_ULLONG:
+ val = PyLong_FromLongLong((unsigned long long)params[i].value.ul);
+ break;
+
+ case VIR_TYPED_PARAM_DOUBLE:
+ val = PyFloat_FromDouble((double)params[i].value.d);
+ break;
+
+ case VIR_TYPED_PARAM_BOOLEAN:
+ val = PyBool_FromLong((long)params[i].value.b);
+ break;
+
+ default:
+ free(params);
+ Py_DECREF(pyreply);
+ return VIR_PY_NONE;
+ }
+
+ key = libvirt_constcharPtrWrap(params[i].field);
+ PyDict_SetItem(pyreply, key, val);
+ }
+ free(params);
+ return(pyreply);
+}
+
/*******************************************
* Helper functions to avoid importing modules
* for every callback
@@ -4837,6 +5013,8 @@ static PyMethodDef libvirtMethods[] = {
{(char *) "virDomainSnapshotListNames", libvirt_virDomainSnapshotListNames, METH_VARARGS, NULL},
{(char *) "virDomainRevertToSnapshot", libvirt_virDomainRevertToSnapshot, METH_VARARGS, NULL},
{(char *) "virDomainGetBlockJobInfo", libvirt_virDomainGetBlockJobInfo, METH_VARARGS, NULL},
+ {(char *) "virDomainSetBlockIoTune", libvirt_virDomainSetBlockIoTune, METH_VARARGS, NULL},
+ {(char *) "virDomainGetBlockIoTune", libvirt_virDomainGetBlockIoTune, METH_VARARGS, NULL},
{(char *) "virDomainSendKey", libvirt_virDomainSendKey, METH_VARARGS, NULL},
{(char *) "virDomainMigrateGetMaxSpeed", libvirt_virDomainMigrateGetMaxSpeed, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
--
1.7.1
13 years, 4 months
[libvirt] [PATCH 5/8] Enable the blkdeviotune command in virsh
by Lei Li
Support virsh command blkdeviotune. Can set or query a block disk
I/O throttle setting.
Signed-off-by: Lei Li <lilei(a)linux.vnet.ibm.com>
Signed-off-by: Zhi Yong Wu <wuzhy(a)linux.vnet.ibm.com>
---
tools/virsh.c | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
tools/virsh.pod | 23 +++++
2 files changed, 263 insertions(+), 0 deletions(-)
diff --git a/tools/virsh.c b/tools/virsh.c
index 83dc3c7..9eec68e 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -6074,6 +6074,245 @@ cmdNetworkAutostart(vshControl *ctl, const vshCmd *cmd)
}
/*
+ * "blkdeviotune" command
+ */
+static const vshCmdInfo info_blkdeviotune[] = {
+ {"help", N_("Set or query a block disk I/O throttle setting.")},
+ {"desc", N_("Set or query a block disk I/O throttle setting.\n" \
+ " To query the block disk I/O throttle setting use the following" \
+ " command: \n\n" \
+ " virsh # blkdeviotune <domain> <device>")},
+ {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_blkdeviotune[] = {
+ {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+ {"device", VSH_OT_DATA, VSH_OFLAG_REQ, N_("block device")},
+ {"total_bytes_sec", VSH_OT_INT, VSH_OFLAG_NONE, N_("total throughput limit in bytes per second")},
+ {"read_bytes_sec", VSH_OT_INT, VSH_OFLAG_NONE, N_("read throughput limit in bytes per second")},
+ {"write_bytes_sec", VSH_OT_INT, VSH_OFLAG_NONE, N_("write throughput limit in bytes per second")},
+ {"total_iops_sec", VSH_OT_INT, VSH_OFLAG_NONE, N_("total I/O operations limit per second")},
+ {"read_iops_sec", VSH_OT_INT, VSH_OFLAG_NONE, N_("read I/O operations limit per second")},
+ {"write_iops_sec", VSH_OT_INT, VSH_OFLAG_NONE, N_("write I/O operations limit per second")},
+ {"config", VSH_OT_BOOL, 0, N_("affect next boot")},
+ {"live", VSH_OT_BOOL, 0, N_("affect running domain")},
+ {"current", VSH_OT_BOOL, 0, N_("affect current domain")},
+ {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdBlkdeviotune(vshControl *ctl, const vshCmd *cmd)
+{
+ virDomainPtr dom = NULL;
+ const char *name, *disk;
+ unsigned long long total_bytes_sec = 0, read_bytes_sec = 0, write_bytes_sec = 0;
+ unsigned long long total_iops_sec = 0, read_iops_sec = 0, write_iops_sec = 0;
+ int nparams = 0;
+ virTypedParameterPtr params = NULL, temp = NULL;
+ unsigned int flags = 0, i = 0;
+ int rv = 0;
+ int current = vshCommandOptBool(cmd, "current");
+ int config = vshCommandOptBool(cmd, "config");
+ int live = vshCommandOptBool(cmd, "live");
+
+ if (current) {
+ if (live || config) {
+ vshError(ctl, "%s", _("--current must be specified exclusively"));
+ return false;
+ }
+ flags = VIR_DOMAIN_AFFECT_CURRENT;
+ } else {
+ if (config)
+ flags |= VIR_DOMAIN_AFFECT_CONFIG;
+ if (live)
+ flags |= VIR_DOMAIN_AFFECT_LIVE;
+ }
+
+ if (!vshConnectionUsability(ctl, ctl->conn))
+ goto out;
+
+ if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
+ goto out;
+
+ if (vshCommandOptString(cmd, "device", &disk) < 0)
+ goto out;
+
+ if ((rv = vshCommandOptULongLong(cmd, "total_bytes_sec", &total_bytes_sec)) < 0) {
+ vshError(ctl, "%s",
+ _("Unable to parse integer parameter"));
+ goto out;
+ } else if (rv > 0) {
+ nparams++;
+ }
+
+ if ((rv = vshCommandOptULongLong(cmd, "read_bytes_sec", &read_bytes_sec)) < 0) {
+ vshError(ctl, "%s",
+ _("Unable to parse integer parameter"));
+ goto out;
+ } else if (rv > 0) {
+ nparams++;
+ }
+
+ if ((rv = vshCommandOptULongLong(cmd, "write_bytes_sec", &write_bytes_sec)) < 0) {
+ vshError(ctl, "%s",
+ _("Unable to parse integer parameter"));
+ goto out;
+ } else if (rv > 0) {
+ nparams++;
+ }
+
+ if ((rv = vshCommandOptULongLong(cmd, "total_iops_sec", &total_iops_sec)) < 0) {
+ vshError(ctl, "%s",
+ _("Unable to parse integer parameter"));
+ goto out;
+ } else if (rv > 0) {
+ nparams++;
+ }
+
+ if ((rv = vshCommandOptULongLong(cmd, "read_iops_sec", &read_iops_sec)) < 0) {
+ vshError(ctl, "%s",
+ _("Unable to parse integer parameter"));
+ goto out;
+ } else if (rv > 0) {
+ nparams++;
+ }
+
+ if ((rv = vshCommandOptULongLong(cmd, "write_iops_sec", &write_iops_sec)) < 0) {
+ vshError(ctl, "%s",
+ _("Unable to parse integer parameter"));
+ goto out;
+ } else if (rv > 0) {
+ nparams++;
+ }
+
+ if (nparams == 0) {
+
+ if ((virDomainGetBlockIoTune(dom, disk, NULL, &nparams, flags)) != 0) {
+ vshError(ctl, "%s",
+ _("Unable to get number of block I/O throttle parameters"));
+ goto out;
+ }
+
+ if (nparams == 0) {
+ virDomainFree(dom);
+ return true;
+ }
+
+ params = vshCalloc(ctl, nparams, sizeof(*params));
+
+ if ((virDomainGetBlockIoTune(dom, disk, params, &nparams, flags)) != 0) {
+ vshError(ctl, "%s",
+ _("Unable to get block I/O throttle parameters"));
+ goto out;
+ }
+
+ for (i = 0; i < nparams; i++) {
+ switch(params[i].type) {
+ case VIR_TYPED_PARAM_INT:
+ vshPrint(ctl, "%-15s: %d\n", params[i].field,
+ params[i].value.i);
+ break;
+ case VIR_TYPED_PARAM_UINT:
+ vshPrint(ctl, "%-15s: %u\n", params[i].field,
+ params[i].value.ui);
+ break;
+ case VIR_TYPED_PARAM_LLONG:
+ vshPrint(ctl, "%-15s: %lld\n", params[i].field,
+ params[i].value.l);
+ break;
+ case VIR_TYPED_PARAM_ULLONG:
+ vshPrint(ctl, "%-15s: %llu\n", params[i].field,
+ params[i].value.ul);
+ break;
+ case VIR_TYPED_PARAM_DOUBLE:
+ vshPrint(ctl, "%-15s: %f\n", params[i].field,
+ params[i].value.d);
+ break;
+ case VIR_TYPED_PARAM_BOOLEAN:
+ vshPrint(ctl, "%-15s: %d\n", params[i].field,
+ params[i].value.b);
+ break;
+ default:
+ vshPrint(ctl, "unimplemented block I/O throttle parameter type\n");
+ }
+ }
+
+ virDomainFree(dom);
+ return true;
+ } else {
+ /* Set the block I/O throttle, match by opt since parameters can be 0 */
+ params = vshCalloc(ctl, nparams, sizeof(*params));
+ i = 0;
+
+ if ((i < nparams) && (vshCommandOptBool(cmd, "total_bytes_sec"))) {
+ temp = ¶ms[i];
+ temp->type = VIR_TYPED_PARAM_ULLONG;
+ strncpy(temp->field, VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_BYTES_SEC,
+ sizeof(temp->field));
+ temp->value.ul = total_bytes_sec;
+ i++;
+ }
+
+ if ((i < nparams) && (vshCommandOptBool(cmd, "read_bytes_sec"))) {
+ temp = ¶ms[i];
+ temp->type = VIR_TYPED_PARAM_ULLONG;
+ strncpy(temp->field, VIR_DOMAIN_BLOCK_IOTUNE_READ_BYTES_SEC,
+ sizeof(temp->field));
+ temp->value.ul = read_bytes_sec;
+ i++;
+ }
+
+ if ((i < nparams) && (vshCommandOptBool(cmd, "write_bytes_sec"))) {
+ temp = ¶ms[i];
+ temp->type = VIR_TYPED_PARAM_ULLONG;
+ strncpy(temp->field, VIR_DOMAIN_BLOCK_IOTUNE_WRITE_BYTES_SEC,
+ sizeof(temp->field));
+ temp->value.ul = write_bytes_sec;
+ i++;
+ }
+
+ if ((i < nparams) && (vshCommandOptBool(cmd, "total_iops_sec"))) {
+ temp = ¶ms[i];
+ temp->type = VIR_TYPED_PARAM_ULLONG;
+ strncpy(temp->field, VIR_DOMAIN_BLOCK_IOTUNE_TOTAL_IOPS_SEC,
+ sizeof(temp->field));
+ temp->value.ul = total_iops_sec;
+ i++;
+ }
+
+ if ((i < nparams) && (vshCommandOptBool(cmd, "read_iops_sec"))) {
+ temp = ¶ms[i];
+ temp->type = VIR_TYPED_PARAM_ULLONG;
+ strncpy(temp->field, VIR_DOMAIN_BLOCK_IOTUNE_READ_IOPS_SEC,
+ sizeof(temp->field));
+ temp->value.ul = read_iops_sec;
+ i++;
+ }
+
+ if ((i < nparams) && (vshCommandOptBool(cmd, "write_iops_sec"))) {
+ temp = ¶ms[i];
+ temp->type = VIR_TYPED_PARAM_ULLONG;
+ strncpy(temp->field, VIR_DOMAIN_BLOCK_IOTUNE_WRITE_IOPS_SEC,
+ sizeof(temp->field));
+ temp->value.ul = write_iops_sec;
+ }
+
+ if ((virDomainSetBlockIoTune(dom, disk, params, nparams, flags)) != 0) {
+ vshError(ctl, "%s",
+ _("Unable to change block I/O throttle"));
+ goto out;
+ } else {
+ virDomainFree(dom);
+ return true;
+ }
+ }
+
+out:
+ virDomainFree(dom);
+ return false;
+}
+
+/*
* "net-create" command
*/
static const vshCmdInfo info_network_create[] = {
@@ -14023,6 +14262,7 @@ static const vshCmdDef domManagementCmds[] = {
{"blkiotune", cmdBlkiotune, opts_blkiotune, info_blkiotune, 0},
{"blockpull", cmdBlockPull, opts_block_pull, info_block_pull, 0},
{"blockjob", cmdBlockJob, opts_block_job, info_block_job, 0},
+ {"blkdeviotune", cmdBlkdeviotune, opts_blkdeviotune, info_blkdeviotune, 0},
#ifndef WIN32
{"console", cmdConsole, opts_console, info_console, 0},
#endif
diff --git a/tools/virsh.pod b/tools/virsh.pod
index 775d302..85927d5 100644
--- a/tools/virsh.pod
+++ b/tools/virsh.pod
@@ -572,6 +572,29 @@ operation can be checked with B<blockjob>.
I<path> specifies fully-qualified path of the disk.
I<bandwidth> specifies copying bandwidth limit in Mbps.
+=item B<blkdeviotune> I<domain> I<device> [[I<--total_bytes_sec> B<total_bytes_sec>]
+| [[I<--read_bytes_sec> B<read_bytes_sec>] [I<--write_bytes_sec> B<write_bytes_sec>]]
+[[I<--total_iops_sec> B<total_iops_sec>] | [[I<--read_iops_sec> B<read_iops_sec>]
+[I<--write_iops_sec> B<write_iops_sec>]] [[I<--config>] [I<--live>] | [I<--current>]]
+
+Set or query the block disk io limits settting.
+I<path> specifies block disk name.
+I<--total_bytes_sec> specifies total throughput limit in bytes per second.
+I<--read_bytes_sec> specifies read throughput limit in bytes per second.
+I<--write_bytes_sec> specifies write throughput limit in bytes per second.
+I<--total_iops_sec> specifies total I/O operations limit per second.
+I<--read_iops_sec> specifies read I/O operations limit per second.
+I<--write_iops_sec> specifies write I/O operations limit per second.
+
+If I<--live> is specified, affect a running guest.
+If I<--config> is specified, affect the next boot of a persistent guest.
+If I<--current> is specified, affect the current guest state.
+Both I<--live> and I<--current> flags may be given, but I<--current> is
+exclusive. If no flag is specified, behavior is different depending
+on hypervisor.
+
+If no limit is specified, it will query current I/O limits setting.
+
=item B<blockjob> I<domain> I<path> [I<--abort>] [I<--info>] [I<bandwidth>]
Manage active block operations.
--
1.7.1
13 years, 4 months
[libvirt] [PATCH] qemu: Avoid dereference of NULL pointer
by Peter Krempa
If something fails while initializing qemu job object in
qemuDomainObjPrivateAlloc(), memory to the private pointer is freed, but
after that, the pointer is still dereferenced, which may result in a
segfault.
* qemuDomainObjPrivateAlloc() - Don't dereference NULL pointer.
---
I added the label and jump with future expansions in mind, as I've
found this bug while modifying said function.
src/qemu/qemu_domain.c | 6 +++++-
1 files changed, 5 insertions(+), 1 deletions(-)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 3e755d7..d33d1d9 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -214,11 +214,15 @@ static void *qemuDomainObjPrivateAlloc(void)
return NULL;
if (qemuDomainObjInitJob(priv) < 0)
- VIR_FREE(priv);
+ goto error;
priv->migMaxBandwidth = QEMU_DOMAIN_DEFAULT_MIG_BANDWIDTH_MAX;
return priv;
+
+error:
+ VIR_FREE(priv);
+ return NULL;
}
static void qemuDomainObjPrivateFree(void *data)
--
1.7.3.4
13 years, 4 months
[libvirt] libvirt-gconfig patches
by Christophe Fergeau
Here is the 3rd version of my libvirt-gconfig patches, I did the renaming
to put all the new classes it introduces in the GVirConfigDomain namespace.
GVirConfigDomainSnapshot looks a bit out of place now though...
Apart from this, I fixed the other issues that were reported, and
rearranged a bit the patches introducing GVirConfigDomainInterfaceNetwork.
I've also pushed the first 5 patches that were in v2 since they've already
been ack'ed
Christophe
13 years, 4 months
[libvirt] [PATCH] qemu: fix a const-correctness issue
by Eric Blake
Generally, functions which return malloc'd strings should be typed
as 'char *', not 'const char *', to make it obvious that the caller
is responsible to free things. free(const char *) fails to compile,
and although we have a cast embedded in VIR_FREE to work around poor
code that frees const char *, it's better to not rely on that hack.
* src/qemu/qemu_driver.c (qemuDiskPathToAlias): Change return type.
(qemuDomainBlockJobImpl): Update callers.
---
Pushing under the trivial rule.
src/qemu/qemu_driver.c | 7 ++++---
1 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 98ce695..94fbe94 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -10565,8 +10565,9 @@ cleanup:
return ret;
}
-static const char *
-qemuDiskPathToAlias(virDomainObjPtr vm, const char *path) {
+static char *
+qemuDiskPathToAlias(virDomainObjPtr vm, const char *path)
+{
int i;
char *ret = NULL;
virDomainDiskDefPtr disk;
@@ -10605,7 +10606,7 @@ qemuDomainBlockJobImpl(virDomainPtr dom, const char *path,
virDomainObjPtr vm = NULL;
qemuDomainObjPrivatePtr priv;
char uuidstr[VIR_UUID_STRING_BUFLEN];
- const char *device = NULL;
+ char *device = NULL;
int ret = -1;
qemuDriverLock(driver);
--
1.7.7.3
13 years, 4 months
[libvirt] [PATCH 4/8] Support block I/O throtte in XML
by Lei Li
Enable block I/O throttle for per-disk in XML.
Signed-off-by: Lei Li <lilei(a)linux.vnet.ibm.com>
Signed-off-by: Zhi Yong Wu <wuzhy(a)linux.vnet.ibm.com>
---
src/conf/domain_conf.c | 101 +++++++++++++++++++++++++++++++++++++++++++++-
src/conf/domain_conf.h | 12 ++++++
src/qemu/qemu_command.c | 33 +++++++++++++++
src/util/xml.h | 2 +
4 files changed, 145 insertions(+), 3 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 58f4d0f..a157b80 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -2318,7 +2318,8 @@ static virDomainDiskDefPtr
virDomainDiskDefParseXML(virCapsPtr caps,
xmlNodePtr node,
virBitmapPtr bootMap,
- unsigned int flags)
+ unsigned int flags,
+ xmlXPathContextPtr ctxt)
{
virDomainDiskDefPtr def;
xmlNodePtr cur, child;
@@ -2517,6 +2518,62 @@ virDomainDiskDefParseXML(virCapsPtr caps,
}
child = child->next;
}
+ } else if (xmlStrEqual(cur->name, BAD_CAST "iotune")) {
+ if (virXPathULongLong("string(./devices/disk/iotune/total_bytes_sec)",
+ ctxt, &def->blkdeviotune.total_bytes_sec) < 0) {
+ def->blkdeviotune.total_bytes_sec = 0;
+ } else {
+ def->blkdeviotune.mark = 1;
+ }
+
+ if (virXPathULongLong("string(./devices/disk/iotune/read_bytes_sec)",
+ ctxt, &def->blkdeviotune.read_bytes_sec) < 0) {
+ def->blkdeviotune.read_bytes_sec = 0;
+ } else {
+ def->blkdeviotune.mark = 1;
+ }
+
+ if (virXPathULongLong("string(./devices/disk/iotune/write_bytes_sec)",
+ ctxt, &def->blkdeviotune.write_bytes_sec) < 0) {
+ def->blkdeviotune.write_bytes_sec = 0;
+ } else {
+ def->blkdeviotune.mark = 1;
+ }
+
+ if (virXPathULongLong("string(./devices/disk/iotune/total_iops_sec)",
+ ctxt, &def->blkdeviotune.total_iops_sec) < 0) {
+ def->blkdeviotune.total_iops_sec = 0;
+ } else {
+ def->blkdeviotune.mark = 1;
+ }
+
+ if (virXPathULongLong("string(./devices/disk/iotune/read_iops_sec)",
+ ctxt, &def->blkdeviotune.read_iops_sec) < 0) {
+ def->blkdeviotune.read_iops_sec = 0;
+ } else {
+ def->blkdeviotune.mark = 1;
+ }
+
+ if (virXPathULongLong("string(./devices/disk/iotune/write_iops_sec)",
+ ctxt, &def->blkdeviotune.write_iops_sec) < 0) {
+ def->blkdeviotune.write_iops_sec = 0;
+ } else {
+ def->blkdeviotune.mark = 1;
+ }
+
+ if ((def->blkdeviotune.total_bytes_sec && def->blkdeviotune.read_bytes_sec)
+ || (def->blkdeviotune.total_bytes_sec && def->blkdeviotune.write_bytes_sec)) {
+ virDomainReportError(VIR_ERR_XML_ERROR,
+ _("total and read/write bytes_sec cannot be set at the same time"));
+ goto error;
+ }
+
+ if ((def->blkdeviotune.total_iops_sec && def->blkdeviotune.read_iops_sec)
+ || (def->blkdeviotune.total_iops_sec && def->blkdeviotune.write_iops_sec)) {
+ virDomainReportError(VIR_ERR_XML_ERROR,
+ _("total and read/write iops_sec cannot be set at the same time"));
+ goto error;
+ }
} else if (xmlStrEqual(cur->name, BAD_CAST "readonly")) {
def->readonly = 1;
} else if (xmlStrEqual(cur->name, BAD_CAST "shareable")) {
@@ -6003,7 +6060,7 @@ virDomainDeviceDefPtr virDomainDeviceDefParse(virCapsPtr caps,
if (xmlStrEqual(node->name, BAD_CAST "disk")) {
dev->type = VIR_DOMAIN_DEVICE_DISK;
if (!(dev->data.disk = virDomainDiskDefParseXML(caps, node,
- NULL, flags)))
+ NULL, flags, NULL)))
goto error;
} else if (xmlStrEqual(node->name, BAD_CAST "lease")) {
dev->type = VIR_DOMAIN_DEVICE_LEASE;
@@ -7076,7 +7133,8 @@ static virDomainDefPtr virDomainDefParseXML(virCapsPtr caps,
virDomainDiskDefPtr disk = virDomainDiskDefParseXML(caps,
nodes[i],
bootMap,
- flags);
+ flags,
+ ctxt);
if (!disk)
goto error;
@@ -9511,6 +9569,43 @@ virDomainDiskDefFormat(virBufferPtr buf,
virBufferAsprintf(buf, " <target dev='%s' bus='%s'/>\n",
def->dst, bus);
+ /*disk I/O throttling*/
+ if (def->blkdeviotune.mark) {
+ virBufferAddLit(buf, " <iotune>\n");
+ if (def->blkdeviotune.total_bytes_sec) {
+ virBufferAsprintf(buf, " <total_bytes_sec>%llu</total_bytes_sec>\n",
+ def->blkdeviotune.total_bytes_sec);
+ }
+
+ if (def->blkdeviotune.read_bytes_sec) {
+ virBufferAsprintf(buf, " <read_bytes_sec>%llu</read_bytes_sec>\n",
+ def->blkdeviotune.read_bytes_sec);
+
+ }
+
+ if (def->blkdeviotune.write_bytes_sec) {
+ virBufferAsprintf(buf, " <write_bytes_sec>%llu</write_bytes_sec>\n",
+ def->blkdeviotune.write_bytes_sec);
+ }
+
+ if (def->blkdeviotune.total_iops_sec) {
+ virBufferAsprintf(buf, " <total_iops_sec>%llu</total_iops_sec>\n",
+ def->blkdeviotune.total_iops_sec);
+ }
+
+ if (def->blkdeviotune.read_iops_sec) {
+ virBufferAsprintf(buf, " <read_iops_sec>%llu</read_iops_sec>",
+ def->blkdeviotune.read_iops_sec);
+ }
+
+ if (def->blkdeviotune.write_iops_sec) {
+ virBufferAsprintf(buf, " <write_iops_sec>%llu</write_iops_sec>",
+ def->blkdeviotune.write_iops_sec);
+ }
+
+ virBufferAddLit(buf, " </iotune>\n");
+ }
+
if (def->bootIndex)
virBufferAsprintf(buf, " <boot order='%d'/>\n", def->bootIndex);
if (def->readonly)
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index a3cb834..d95e239 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -333,6 +333,18 @@ struct _virDomainDiskDef {
} auth;
char *driverName;
char *driverType;
+
+ /*disk I/O throttling*/
+ struct {
+ unsigned long long total_bytes_sec;
+ unsigned long long read_bytes_sec;
+ unsigned long long write_bytes_sec;
+ unsigned long long total_iops_sec;
+ unsigned long long read_iops_sec;
+ unsigned long long write_iops_sec;
+ unsigned int mark;
+ } blkdeviotune;
+
char *serial;
int cachemode;
int error_policy; /* enum virDomainDiskErrorPolicy */
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 2fbf691..91c6508 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -1690,6 +1690,39 @@ qemuBuildDriveStr(virConnectPtr conn ATTRIBUTE_UNUSED,
}
}
+ /*block I/O throttling*/
+ if (disk->blkdeviotune.mark) {
+ if (disk->blkdeviotune.total_bytes_sec) {
+ virBufferAsprintf(&opt, ",bps=%llu",
+ disk->blkdeviotune.total_bytes_sec);
+ }
+
+ if (disk->blkdeviotune.read_bytes_sec) {
+ virBufferAsprintf(&opt, ",bps_rd=%llu",
+ disk->blkdeviotune.read_bytes_sec);
+ }
+
+ if (disk->blkdeviotune.write_bytes_sec) {
+ virBufferAsprintf(&opt, ",bps_wr=%llu",
+ disk->blkdeviotune.write_bytes_sec);
+ }
+
+ if (disk->blkdeviotune.total_iops_sec) {
+ virBufferAsprintf(&opt, ",iops=%llu",
+ disk->blkdeviotune.total_iops_sec);
+ }
+
+ if (disk->blkdeviotune.read_iops_sec) {
+ virBufferAsprintf(&opt, ",iops_rd=%llu",
+ disk->blkdeviotune.read_iops_sec);
+ }
+
+ if (disk->blkdeviotune.write_iops_sec) {
+ virBufferAsprintf(&opt, ",iops_wr=%llu",
+ disk->blkdeviotune.write_iops_sec);
+ }
+ }
+
if (virBufferError(&opt)) {
virReportOOMError();
goto error;
diff --git a/src/util/xml.h b/src/util/xml.h
index c492063..5742f19 100644
--- a/src/util/xml.h
+++ b/src/util/xml.h
@@ -50,6 +50,8 @@ xmlNodePtr virXPathNode(const char *xpath,
int virXPathNodeSet(const char *xpath,
xmlXPathContextPtr ctxt,
xmlNodePtr **list);
+int virXMLStringToULongLong (const char *stringval,
+ unsigned long long *value);
char * virXMLPropString(xmlNodePtr node,
const char *name);
--
1.7.1
13 years, 4 months