[libvirt] [PATCH v3 3/5] hyperv: add hypervInvokeMethod
Matthias Bolte
matthias.bolte at googlemail.com
Mon May 1 22:26:44 UTC 2017
2017-04-24 20:19 GMT+02:00 Sri Ramanujam <sramanujam at datto.com>:
> This commit adds support for invoking methods on remote objects
> via hypervInvokeMethod.
> ---
> src/hyperv/hyperv_wmi.c | 569 ++++++++++++++++++++++++++++++++++++++++++++++++
> src/hyperv/hyperv_wmi.h | 3 +
> src/hyperv/openwsman.h | 4 +
> 3 files changed, 576 insertions(+)
>
> diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c
> index 1960c4c..deea907 100644
> --- a/src/hyperv/hyperv_wmi.c
> +++ b/src/hyperv/hyperv_wmi.c
> @@ -24,6 +24,7 @@
> */
>
> #include <config.h>
> +#include <wsman-soap.h>
>
> #include "internal.h"
> #include "virerror.h"
> @@ -34,11 +35,14 @@
> #include "hyperv_private.h"
> #include "hyperv_wmi.h"
> #include "virstring.h"
> +#include "openwsman.h"
> +#include "virlog.h"
>
> #define WS_SERIALIZER_FREE_MEM_WORKS 0
>
> #define VIR_FROM_THIS VIR_FROM_HYPERV
>
> +VIR_LOG_INIT("hyperv.hyperv_wmi");
>
> static int
> hypervGetWmiClassInfo(hypervPrivate *priv, hypervWmiClassInfoListPtr list,
> @@ -406,6 +410,571 @@ hypervAddEmbeddedParam(hypervInvokeParamsListPtr params, hypervPrivate *priv,
> }
>
>
> +/*
> + * Serializing parameters to XML and invoking methods
> + */
> +
> +static int
> +hypervGetCimTypeInfo(hypervCimTypePtr typemap, const char *name,
> + hypervCimTypePtr *property)
> +{
> + size_t i = 0;
> + while (typemap[i].name[0] != '\0') {
> + if (STREQ(typemap[i].name, name)) {
> + *property = &typemap[i];
> + return 0;
> + }
> + i++;
> + }
> +
> + return -1;
> +}
> +
> +
> +static int
> +hypervCreateInvokeXmlDoc(hypervInvokeParamsListPtr params, WsXmlDocH *docRoot)
> +{
> + int result = -1;
> + char *method = NULL;
> + WsXmlNodeH xmlNodeMethod = NULL;
> +
> + if (virAsprintf(&method, "%s_INPUT", params->method) < 0)
> + goto error;
> +
> + *docRoot = ws_xml_create_doc(NULL, method);
> + if (*docRoot == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not instantiate XML document"));
> + goto error;
> + }
> +
> + xmlNodeMethod = xml_parser_get_root(*docRoot);
> + if (xmlNodeMethod == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not get root node of XML document"));
> + goto error;
> + }
> +
> + /* add resource URI as namespace */
> + ws_xml_set_ns(xmlNodeMethod, params->resourceUri, "p");
> +
> + result = 0;
> + goto cleanup;
> +
> + error:
> + if (*docRoot != NULL) {
> + ws_xml_destroy_doc(*docRoot);
> + *docRoot = NULL;
> + }
> + cleanup:
> + VIR_FREE(method);
> + return result;
The error and cleanup label could be merged:
result = 0;
cleanup:
if (result < 0 && *docRoot != NULL) {
ws_xml_destroy_doc(*docRoot);
*docRoot = NULL;
}
VIR_FREE(method);
return result;
}
> +}
> +
> +static int
> +hypervSerializeSimpleParam(hypervParamPtr p, const char *resourceUri,
> + WsXmlNodeH *methodNode)
> +{
> + int result = -1;
> + WsXmlNodeH xmlNodeParam = NULL;
> +
> + xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri,
> + p->simple.name, p->simple.value);
> + if (xmlNodeParam == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not create simple param"));
> + goto cleanup;
There is no actual cleanup, just return -1 here.
> + }
> +
> + result = 0;
> +
> + cleanup:
> + return result;
And just return 0 here.
> +}
> +
> +static int
> +hypervSerializeEprParam(hypervParamPtr p, hypervPrivate *priv,
> + const char *resourceUri, WsXmlDocH doc, WsXmlNodeH *methodNode)
> +{
> + int result = -1;
> + WsXmlNodeH xmlNodeParam = NULL,
> + xmlNodeTemp = NULL,
> + xmlNodeAddr = NULL,
> + xmlNodeRef = NULL;
> + xmlNodePtr xmlNodeAddrPtr = NULL,
> + xmlNodeRefPtr = NULL;
> + WsXmlDocH xmlDocResponse = NULL;
> + xmlDocPtr docPtr = (xmlDocPtr) doc->parserDoc;
> + WsXmlNsH ns = NULL;
> + client_opt_t *options = NULL;
> + filter_t *filter = NULL;
> + char *enumContext = NULL;
> + char *query_string = NULL;
> +
> + /* init and set up options */
> + options = wsmc_options_init();
> + if (!options) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not init options"));
> + goto cleanup;
> + }
> + wsmc_set_action_option(options, FLAG_ENUMERATION_ENUM_EPR);
> +
> + /* Get query and create filter based on it */
> + query_string = virBufferContentAndReset(p->epr.query);
> + filter = filter_create_simple(WSM_WQL_FILTER_DIALECT, query_string);
> + if (!filter) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create WQL filter"));
> + goto cleanup;
> + }
> +
> + /* enumerate based on the filter from this query */
> + xmlDocResponse = wsmc_action_enumerate(priv->client, p->epr.info->rootUri,
> + options, filter);
> + if (hypervVerifyResponse(priv->client, xmlDocResponse, "enumeration") < 0)
> + goto cleanup;
> +
> + /* Get context */
> + enumContext = wsmc_get_enum_context(xmlDocResponse);
> + ws_xml_destroy_doc(xmlDocResponse);
> +
> + /* Pull using filter and enum context */
> + xmlDocResponse = wsmc_action_pull(priv->client, resourceUri, options,
> + filter, enumContext);
> +
> + if (hypervVerifyResponse(priv->client, xmlDocResponse, "pull") < 0)
> + goto cleanup;
> +
> + /* drill down and extract EPR node children */
> + if (!(xmlNodeTemp = ws_xml_get_soap_body(xmlDocResponse))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get SOAP body"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ENUMERATION,
> + WSENUM_PULL_RESP))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get response"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ENUMERATION, WSENUM_ITEMS))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get response items"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ADDRESSING, WSA_EPR))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get EPR items"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeAddr = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ADDRESSING,
> + WSA_ADDRESS))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get EPR address"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeAddrPtr = xmlDocCopyNode((xmlNodePtr) xmlNodeAddr, docPtr, 1))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not copy EPR address"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeRef = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ADDRESSING,
> + WSA_REFERENCE_PARAMETERS))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not lookup EPR item reference parameters"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeRefPtr = xmlDocCopyNode((xmlNodePtr) xmlNodeRef, docPtr, 1))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not copy EPR item reference parameters"));
> + goto cleanup;
> + }
> +
> + /* now build a new xml doc with the EPR node children */
> + if (!(xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri,
> + p->epr.name, NULL))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add child node to xmlNodeParam"));
> + goto cleanup;
> + }
> +
> + if (!(ns = ws_xml_ns_add(xmlNodeParam,
> + "http://schemas.xmlsoap.org/ws/2004/08/addressing", "a"))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not set namespace address for xmlNodeParam"));
> + goto cleanup;
> + }
> +
> + ns = NULL;
> + if (!(ns = ws_xml_ns_add(xmlNodeParam,
> + "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", "w"))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not set wsman namespace address for xmlNodeParam"));
> + goto cleanup;
> + }
> +
> + if (xmlAddChild((xmlNodePtr) *methodNode, (xmlNodePtr) xmlNodeParam) == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add child to xml parent node"));
> + goto cleanup;
> + }
> +
> + if (xmlAddChild((xmlNodePtr) xmlNodeParam, xmlNodeAddrPtr) == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add child to xml parent node"));
> + goto cleanup;
> + }
> +
> + if (xmlAddChild((xmlNodePtr) xmlNodeParam, xmlNodeRefPtr) == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add child to xml parent node"));
> + goto cleanup;
> + }
> +
> + /* we did it! */
> + result = 0;
> +
> + cleanup:
> + if (options != NULL)
> + wsmc_options_destroy(options);
> + if (filter != NULL)
> + filter_destroy(filter);
> + ws_xml_destroy_doc(xmlDocResponse);
> + VIR_FREE(enumContext);
> + VIR_FREE(query_string);
> + return result;
> +}
> +
> +static int
> +hypervSerializeEmbeddedParam(hypervParamPtr p, const char *resourceUri,
> + WsXmlNodeH *methodNode)
> +{
> + int result = -1;
> + WsXmlNodeH xmlNodeInstance = NULL,
> + xmlNodeProperty = NULL,
> + xmlNodeParam = NULL,
> + xmlNodeArray = NULL;
> + WsXmlDocH xmlDocTemp = NULL,
> + xmlDocCdata = NULL;
> + xmlBufferPtr xmlBufferNode = NULL;
> + const xmlChar *xmlCharCdataContent = NULL;
> + xmlNodePtr xmlNodeCdata = NULL;
> + hypervWmiClassInfoPtr classInfo = p->embedded.info;
> + virHashKeyValuePairPtr items = NULL;
> + hypervCimTypePtr property = NULL;
> + int numKeys = -1;
> + int len = 0, i = 0;
> + if (!(xmlNodeParam = ws_xml_add_child(*methodNode, resourceUri, p->embedded.name,
> + NULL))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not add child node %s"),
> + p->embedded.name);
> + goto cleanup;
> + }
> +
> + /* create the temp xml doc */
> +
> + /* start with the INSTANCE node */
> + if (!(xmlDocTemp = ws_xml_create_doc(NULL, "INSTANCE"))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not create temporary xml doc"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeInstance = xml_parser_get_root(xmlDocTemp))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not get temp xml doc root"));
> + goto cleanup;
> + }
> +
> + /* add CLASSNAME node to INSTANCE node */
> + if (ws_xml_add_node_attr(xmlNodeInstance, NULL, "CLASSNAME",
> + classInfo->name) == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add attribute to node"));
> + goto cleanup;
> + }
> +
> + /* retrieve parameters out of hash table */
> + numKeys = virHashSize(p->embedded.table);
> + items = virHashGetItems(p->embedded.table, NULL);
> + if (!items) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not read embedded param hash table"));
> + goto cleanup;
> + }
> +
> + /* Add the parameters */
> + if (numKeys > 0) {
This if is unnecessary.
> + for (i = 0; i < numKeys; i++) {
> + const char *name = items[i].key;
> + const char *value = items[i].value;
> +
> + if (value != NULL) {
> + if (hypervGetCimTypeInfo(classInfo->propertyInfo, name,
> + &property) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not read type information"));
> + goto cleanup;
> + }
> +
> + if (!(xmlNodeProperty = ws_xml_add_child(xmlNodeInstance, NULL,
> + property->isArray ? "PROPERTY.ARRAY" : "PROPERTY",
> + NULL))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add child to XML node"));
> + goto cleanup;
> + }
> +
> + if (ws_xml_add_node_attr(xmlNodeProperty, NULL, "NAME", name) == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add attribute to XML node"));
> + goto cleanup;
> + }
> +
> + if (ws_xml_add_node_attr(xmlNodeProperty, NULL, "TYPE", property->type) == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add attribute to XML node"));
> + goto cleanup;
> + }
> +
> + /* If this attribute is an array, add VALUE.ARRAY node */
> + if (property->isArray) {
> + if (!(xmlNodeArray = ws_xml_add_child(xmlNodeProperty, NULL,
> + "VALUE.ARRAY", NULL))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add child to XML node"));
> + goto cleanup;
> + }
> + }
> +
> + /* add the child */
> + if (ws_xml_add_child(property->isArray ? xmlNodeArray : xmlNodeProperty,
> + NULL, "VALUE", value) == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add child to XML node"));
> + goto cleanup;
> + }
> +
> + xmlNodeArray = NULL;
> + xmlNodeProperty = NULL;
> + }
> + }
> + }
> +
> + /* create CDATA node */
> + xmlBufferNode = xmlBufferCreate();
> + if (xmlNodeDump(xmlBufferNode, (xmlDocPtr) xmlDocTemp->parserDoc,
> + (xmlNodePtr) xmlNodeInstance, 0, 0) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not get root of temp XML doc"));
> + goto cleanup;
> + }
> +
> + len = xmlBufferLength(xmlBufferNode);
> + xmlCharCdataContent = xmlBufferContent(xmlBufferNode);
> + if (!(xmlNodeCdata = xmlNewCDataBlock((xmlDocPtr) xmlDocCdata,
> + xmlCharCdataContent, len))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not create CDATA element"));
> + goto cleanup;
> + }
> +
> + /* Add CDATA node to the doc root */
> + if (xmlAddChild((xmlNodePtr) xmlNodeParam, xmlNodeCdata) == NULL) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not add CDATA to doc root"));
> + goto cleanup;
> + }
> +
> + /* we did it! */
> + result = 0;
> +
> + cleanup:
> + VIR_FREE(items);
> + ws_xml_destroy_doc(xmlDocCdata);
> + ws_xml_destroy_doc(xmlDocTemp);
> + if (xmlBufferNode)
> + xmlBufferFree(xmlBufferNode);
> + return result;
> +}
> +
> +
> +/*
> + * hypervInvokeMethod:
> + * @priv: hypervPrivate object associated with the connection
> + * @params: object containing the all necessary information for method
> + * invocation
> + * @res: Optional out parameter to contain the response XML.
> + *
> + * Performs an invocation described by @params, and optionally returns the
> + * XML containing the result. Returns -1 on failure, 0 on success.
> + */
> +int
> +hypervInvokeMethod(hypervPrivate *priv, hypervInvokeParamsListPtr params,
> + WsXmlDocH *res)
> +{
> + int result = -1;
> + size_t i = 0;
> + int returnCode;
> + WsXmlDocH paramsDocRoot = NULL;
> + client_opt_t *options = NULL;
> + WsXmlDocH response = NULL;
> + WsXmlNodeH methodNode = NULL;
> + char *returnValue_xpath = NULL;
> + char *jobcode_instance_xpath = NULL;
> + char *returnValue = NULL;
> + char *instanceID = NULL;
> + bool completed = false;
> + virBuffer query = VIR_BUFFER_INITIALIZER;
> + Msvm_ConcreteJob *job = NULL;
> + int jobState = -1;
> + hypervParamPtr p = NULL;
> +
> + if (hypervCreateInvokeXmlDoc(params, ¶msDocRoot) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Could not create XML document"));
> + goto cleanup;
> + }
> +
> + methodNode = xml_parser_get_root(paramsDocRoot);
> + if (methodNode == NULL)
> + goto cleanup;
> +
> + /* Serialize parameters */
> + for (i = 0; i < params->nbParams; i++) {
> + p = &(params->params[i]);
> +
> + switch (p->type) {
> + case HYPERV_SIMPLE_PARAM:
> + if (hypervSerializeSimpleParam(p, params->resourceUri,
> + &methodNode) < 0)
> + goto cleanup;
> + break;
> + case HYPERV_EPR_PARAM:
> + if (hypervSerializeEprParam(p, priv, params->resourceUri,
> + paramsDocRoot, &methodNode) < 0)
> + goto cleanup;
> + break;
> + case HYPERV_EMBEDDED_PARAM:
> + if (hypervSerializeEmbeddedParam(p, params->resourceUri,
> + &methodNode) < 0)
> + goto cleanup;
> + break;
> + default:
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Unknown parameter type"));
> + goto cleanup;
> + }
> + }
> +
> + /* Invoke the method and get the response */
> +
> + options = wsmc_options_init();
> +
> + if (!options) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not init options"));
> + goto cleanup;
> + }
> +
> + wsmc_add_selectors_from_str(options, params->selector);
> +
> + /* do the invoke */
> + response = wsmc_action_invoke(priv->client, params->resourceUri, options,
> + params->method, paramsDocRoot);
> +
> + /* check return code of invocation */
> + if (virAsprintf(&returnValue_xpath, "/s:Envelope/s:Body/p:%s_OUTPUT/p:ReturnValue",
> + params->method) < 0)
> + goto cleanup;
> +
> + returnValue = ws_xml_get_xpath_value(response, returnValue_xpath);
> + if (!returnValue) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("Could not get return value for %s invocation"),
> + params->method);
Missing goto cleanup here?
> + }
> +
> + if (virStrToLong_i(returnValue, NULL, 10, &returnCode) < 0)
> + goto cleanup;
> +
> + if (returnCode == CIM_RETURNCODE_TRANSITION_STARTED) {
> + if (virAsprintf(&jobcode_instance_xpath,
> + "/s:Envelope/s:Body/p:%s_OUTPUT/p:Job/a:ReferenceParameters/"
> + "w:SelectorSet/w:Selector[@Name='InstanceID']",
> + params->method) < 0) {
> + goto cleanup;
> + }
> +
> + instanceID = ws_xml_get_xpath_value(response, jobcode_instance_xpath);
> + if (!instanceID) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("Could not get instance ID for %s invocation"),
> + params->method);
> + goto cleanup;
> + }
> +
> + /* Poll every 100 ms until the job completes or fails */
> + while (!completed) {
> + virBufferAddLit(&query, MSVM_CONCRETEJOB_WQL_SELECT);
> + virBufferAsprintf(&query, "where InstanceID = \"%s\"", instanceID);
> +
> + if (hypervGetMsvmConcreteJobList(priv, &query, &job) < 0
> + || job == NULL)
> + goto cleanup;
> +
> + jobState = job->data.common->JobState;
> + switch (jobState) {
> + case MSVM_CONCRETEJOB_JOBSTATE_NEW:
> + case MSVM_CONCRETEJOB_JOBSTATE_STARTING:
> + case MSVM_CONCRETEJOB_JOBSTATE_RUNNING:
> + case MSVM_CONCRETEJOB_JOBSTATE_SHUTTING_DOWN:
> + hypervFreeObject(priv, (hypervObject *) job);
> + job = NULL;
> + usleep(100 * 1000); /* sleep 100 ms */
> + continue;
> + case MSVM_CONCRETEJOB_JOBSTATE_COMPLETED:
> + completed = true;
> + break;
> + case MSVM_CONCRETEJOB_JOBSTATE_TERMINATED:
> + case MSVM_CONCRETEJOB_JOBSTATE_KILLED:
> + case MSVM_CONCRETEJOB_JOBSTATE_EXCEPTION:
> + case MSVM_CONCRETEJOB_JOBSTATE_SERVICE:
> + goto cleanup;
> + default:
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("Unknown invocation state"));
> + goto cleanup;
> + }
> + }
How do you avoid waiting forever for a job to finish?
> + } else if (returnCode != CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, _("Invocation of %s returned an error: %s (%d)"),
> + params->method, hypervReturnCodeToString(returnCode),
> + returnCode);
> + goto cleanup;
> + }
> +
> + if (res != NULL)
Your pointer checking style is inconsistent. libvirt favors the short
form "if (ptr)" and "if (!ptr)" for new code over the long format "if
(ptr != NULL)" and "if (ptr == NULL)".
> + *res = response;
> +
> + result = 0;
> +
> + cleanup:
> + if (options)
> + wsmc_options_destroy(options);
> + if (response && (res == NULL))
> + ws_xml_destroy_doc(response);
> + if (paramsDocRoot)
> + ws_xml_destroy_doc(paramsDocRoot);
> + VIR_FREE(returnValue_xpath);
> + VIR_FREE(jobcode_instance_xpath);
> + VIR_FREE(returnValue);
> + VIR_FREE(instanceID);
> + virBufferFreeAndReset(&query);
> + hypervFreeObject(priv, (hypervObject *) job);
> + hypervFreeInvokeParams(params);
> + return result;
> +}
--
Matthias Bolte
http://photron.blogspot.com
More information about the libvir-list
mailing list