[libvirt] [PATCH v7 0/6] Hyper-V method invocation

Changes from v6: * Added news entry Sri Ramanujam (6): hyperv: Functions to work with invocation parameters. hyperv: Generate object property type information. hyperv: add hypervInvokeMethod hyperv: support virDomainSendKey hyperv: Add support for virDomainSetMemory news: Update news for new Hyper-V APIs docs/news.xml | 5 + src/hyperv/hyperv_driver.c | 237 +++++++++ src/hyperv/hyperv_wmi.c | 910 ++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 95 +++- src/hyperv/hyperv_wmi_classes.h | 19 + src/hyperv/hyperv_wmi_generator.input | 116 +++++ src/hyperv/hyperv_wmi_generator.py | 15 +- src/hyperv/openwsman.h | 4 + 8 files changed, 1399 insertions(+), 2 deletions(-) -- 2.9.4

This commit introduces functionality for creating and working with invoke parameters. This commit does not include any code for serializing and actually performing the method invocations; it merely defines the functions and API for using invocation parameters in driver code. HYPERV_DEFAULT_PARAM_COUNT was chosen because almost no method invocations have more than 4 parameters. Functions added: * hypervInitInvokeParamsList * hypervFreeInvokeParams * hypervAddSimpleParam * hypervAddEprParam * hypervCreateEmbeddedParam * hypervSetEmbeddedProperty * hypervAddEmbeddedParam * hypervFreeEmbeddedParam --- src/hyperv/hyperv_wmi.c | 262 ++++++++++++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 79 ++++++++++++++- 2 files changed, 340 insertions(+), 1 deletion(-) diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index a3c7dc0..2732db3 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c @@ -2,6 +2,7 @@ * hyperv_wmi.c: general WMI over WSMAN related functions and structures for * managing Microsoft Hyper-V hosts * + * Copyright (C) 2017 Datto Inc * Copyright (C) 2014 Red Hat, Inc. * Copyright (C) 2011 Matthias Bolte <matthias.bolte@googlemail.com> * Copyright (C) 2009 Michael Sievers <msievers83@googlemail.com> @@ -142,6 +143,267 @@ hypervVerifyResponse(WsManClient *client, WsXmlDocH response, } +/* + * Methods to work with method invocation parameters + */ + +/* + * hypervCreateInvokeParamsList: + * @priv: hypervPrivate object associated with the connection. + * @method: The name of the method you are calling + * @selector: The selector for the object you are invoking the method on + * @obj: The WmiInfo of the object class you are invoking the method on. + * + * Create a new InvokeParamsList object for the method call. + * + * Returns a pointer to the newly instantiated object on success, which should + * be freed by hypervInvokeMethod. Otherwise returns NULL. + */ +hypervInvokeParamsListPtr +hypervCreateInvokeParamsList(hypervPrivate *priv, const char *method, + const char *selector, hypervWmiClassInfoListPtr obj) +{ + hypervInvokeParamsListPtr params = NULL; + hypervWmiClassInfoPtr info = NULL; + + if (hypervGetWmiClassInfo(priv, obj, &info) < 0) + goto cleanup; + + if (VIR_ALLOC(params) < 0) + goto cleanup; + + if (VIR_ALLOC_N(params->params, + HYPERV_DEFAULT_PARAM_COUNT) < 0) { + VIR_FREE(params); + goto cleanup; + } + + params->method = method; + params->ns = info->rootUri; + params->resourceUri = info->resourceUri; + params->selector = selector; + params->nbParams = 0; + params->nbAvailParams = HYPERV_DEFAULT_PARAM_COUNT; + + cleanup: + return params; +} + +/* + * hypervFreeInvokeParams: + * @params: Params object to be freed + * + */ +void +hypervFreeInvokeParams(hypervInvokeParamsListPtr params) +{ + hypervParamPtr p = NULL; + size_t i = 0; + + if (params == NULL) + return; + + for (i = 0; i < params->nbParams; i++) { + p = &(params->params[i]); + + switch (p->type) { + case HYPERV_SIMPLE_PARAM: + break; + case HYPERV_EPR_PARAM: + virBufferFreeAndReset(p->epr.query); + break; + case HYPERV_EMBEDDED_PARAM: + hypervFreeEmbeddedParam(p->embedded.table); + break; + default: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Invalid parameter type passed to free")); + } + } + + VIR_DISPOSE_N(params->params, params->nbAvailParams); + VIR_FREE(params); +} + +static inline int +hypervCheckParams(hypervInvokeParamsListPtr params) +{ + if (params->nbParams + 1 > params->nbAvailParams) { + if (VIR_EXPAND_N(params->params, params->nbAvailParams, 5) < 0) + return -1; + } + + return 0; +} + +/* + * hypervAddSimpleParam: + * @params: Params object to add to + * @name: Name of the parameter + * @value: Value of the parameter + * + * Add a param of type HYPERV_SIMPLE_PARAM, which is essentially a serialized + * key/value pair. + * + * Returns -1 on failure, 0 on success. + */ +int +hypervAddSimpleParam(hypervInvokeParamsListPtr params, const char *name, + const char *value) +{ + int result = -1; + hypervParamPtr p = NULL; + + if (hypervCheckParams(params) < 0) + goto cleanup; + + p = ¶ms->params[params->nbParams]; + p->type = HYPERV_SIMPLE_PARAM; + + p->simple.name = name; + p->simple.value = value; + + params->nbParams++; + + result = 0; + + cleanup: + return result; +} + +/* + * hypervAddEprParam: + * @params: Params object to add to + * @name: Parameter name + * @priv: hypervPrivate object associated with the connection + * @query: WQL filter + * @eprInfo: WmiInfo of the object being filtered + * + * Adds an EPR param to the params list. Returns -1 on failure, 0 on success. + */ +int +hypervAddEprParam(hypervInvokeParamsListPtr params, const char *name, + hypervPrivate *priv, virBufferPtr query, + hypervWmiClassInfoListPtr eprInfo) +{ + hypervParamPtr p = NULL; + hypervWmiClassInfoPtr classInfo = NULL; + + if (hypervGetWmiClassInfo(priv, eprInfo, &classInfo) < 0 || + hypervCheckParams(params) < 0) + return -1; + + p = ¶ms->params[params->nbParams]; + p->type = HYPERV_EPR_PARAM; + p->epr.name = name; + p->epr.query = query; + p->epr.info = classInfo; + params->nbParams++; + + return 0; +} + +/* + * hypervCreateEmbeddedParam: + * @priv: hypervPrivate object associated with the connection + * @info: WmiInfo of the object type to serialize + * + * Instantiates a virHashTable pre-filled with all the properties pre-added + * a key/value pairs set to NULL. The user then sets only those properties that + * they wish to serialize, and passes the table via hypervAddEmbeddedParam. + * + * Returns a pointer to the virHashTable on success, otherwise NULL. + */ +virHashTablePtr +hypervCreateEmbeddedParam(hypervPrivate *priv, hypervWmiClassInfoListPtr info) +{ + size_t i; + int count = 0; + virHashTablePtr table = NULL; + XmlSerializerInfo *typeinfo = NULL; + XmlSerializerInfo *item = NULL; + hypervWmiClassInfoPtr classInfo = NULL; + + /* Get the typeinfo out of the class info list */ + if (hypervGetWmiClassInfo(priv, info, &classInfo) < 0) + goto error; + + typeinfo = classInfo->serializerInfo; + + /* loop through the items to find out how many fields there are */ + for (i = 0; typeinfo[i].name != NULL; i++) {} + count = i; + + table = virHashCreate(count, NULL); + if (table == NULL) + goto error; + + for (i = 0; typeinfo[i].name != NULL; i++) { + item = &typeinfo[i]; + + if (virHashAddEntry(table, item->name, NULL) < 0) + goto error; + } + + return table; + + error: + virHashFree(table); + return NULL; +} + +int +hypervSetEmbeddedProperty(virHashTablePtr table, const char *name, char *value) +{ + return virHashUpdateEntry(table, name, value); +} + +/* + * hypervAddEmbeddedParam: + * @params: Params list to add to + * @priv: hypervPrivate object associated with the connection + * @name: Name of the parameter + * @table: table of properties to add + * @info: WmiInfo of the object to serialize + * + * Add a virHashTable containing object properties as an embedded param to + * an invocation list. Returns -1 on failure, 0 on success. + */ +int +hypervAddEmbeddedParam(hypervInvokeParamsListPtr params, hypervPrivate *priv, + const char *name, virHashTablePtr table, hypervWmiClassInfoListPtr info) +{ + hypervParamPtr p = NULL; + hypervWmiClassInfoPtr classInfo = NULL; + + if (hypervCheckParams(params) < 0) + return -1; + + /* Get the typeinfo out of the class info list */ + if (hypervGetWmiClassInfo(priv, info, &classInfo) < 0) + return -1; + + p = ¶ms->params[params->nbParams]; + p->type = HYPERV_EMBEDDED_PARAM; + p->embedded.name = name; + p->embedded.table = table; + p->embedded.info = classInfo; + params->nbParams++; + + return 0; +} + +/* + * hypervFreeEmbeddedParam: + * @param: Pointer to embedded param to free + * + * Free the embedded param hash table. + */ +void +hypervFreeEmbeddedParam(virHashTablePtr p) +{ + virHashFree(p); +} /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Object diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index edb8efa..54ce5b1 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -28,11 +28,13 @@ # include "hyperv_private.h" # include "hyperv_wmi_classes.h" # include "openwsman.h" - +# include "virhash.h" # define HYPERV_WQL_QUERY_INITIALIZER { NULL, NULL } +# define HYPERV_DEFAULT_PARAM_COUNT 5 + int hypervVerifyResponse(WsManClient *client, WsXmlDocH response, const char *detail); @@ -74,7 +76,82 @@ int hypervEnumAndPull(hypervPrivate *priv, hypervWqlQueryPtr wqlQuery, void hypervFreeObject(hypervPrivate *priv, hypervObject *object); +/* + * Invoke + */ + +typedef enum { + HYPERV_SIMPLE_PARAM, + HYPERV_EPR_PARAM, + HYPERV_EMBEDDED_PARAM +} hypervStorageType; + +struct _hypervSimpleParam { + const char *name; + const char *value; +}; +typedef struct _hypervSimpleParam hypervSimpleParam; + +struct _hypervEprParam { + const char *name; + virBufferPtr query; + hypervWmiClassInfoPtr info; // info of the object this param represents +}; +typedef struct _hypervEprParam hypervEprParam; + +struct _hypervEmbeddedParam { + const char *name; + virHashTablePtr table; + hypervWmiClassInfoPtr info; // info of the object this param represents +}; +typedef struct _hypervEmbeddedParam hypervEmbeddedParam; + +struct _hypervParam { + hypervStorageType type; + union { + hypervSimpleParam simple; + hypervEprParam epr; + hypervEmbeddedParam embedded; + }; +}; +typedef struct _hypervParam hypervParam; +typedef hypervParam *hypervParamPtr; + +struct _hypervInvokeParamsList { + const char *method; + const char *ns; + const char *resourceUri; + const char *selector; + hypervParamPtr params; + size_t nbParams; + size_t nbAvailParams; +}; +typedef struct _hypervInvokeParamsList hypervInvokeParamsList; +typedef hypervInvokeParamsList *hypervInvokeParamsListPtr; + + +hypervInvokeParamsListPtr hypervCreateInvokeParamsList(hypervPrivate *priv, + const char *method, const char *selector, hypervWmiClassInfoListPtr obj); + +void hypervFreeInvokeParams(hypervInvokeParamsListPtr params); + +int hypervAddSimpleParam(hypervInvokeParamsListPtr params, const char *name, + const char *value); + +int hypervAddEprParam(hypervInvokeParamsListPtr params, const char *name, + hypervPrivate *priv, virBufferPtr query, + hypervWmiClassInfoListPtr eprInfo); + +virHashTablePtr hypervCreateEmbeddedParam(hypervPrivate *priv, + hypervWmiClassInfoListPtr info); + +int hypervSetEmbeddedProperty(virHashTablePtr table, const char *name, + char *value); + +int hypervAddEmbeddedParam(hypervInvokeParamsListPtr params, hypervPrivate *priv, + const char *name, virHashTablePtr table, hypervWmiClassInfoListPtr info); +void hypervFreeEmbeddedParam(virHashTablePtr p); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * CIM/Msvm_ReturnCode */ -- 2.9.4

Update the generator to generate basic property type information for each CIM object representation. Right now, it generates arrays of hypervCimType structs: struct _hypervCimType { const char *name; const char *type; bool isArray; }; --- src/hyperv/hyperv_wmi_classes.h | 19 +++++++++++++++++++ src/hyperv/hyperv_wmi_generator.py | 15 ++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/hyperv/hyperv_wmi_classes.h b/src/hyperv/hyperv_wmi_classes.h index f7d596f..ce4643e 100644 --- a/src/hyperv/hyperv_wmi_classes.h +++ b/src/hyperv/hyperv_wmi_classes.h @@ -1,6 +1,7 @@ /* * hyperv_wmi_classes.h: WMI classes for managing Microsoft Hyper-V hosts * + * Copyright (C) 2017 Datto Inc * Copyright (C) 2011 Matthias Bolte <matthias.bolte@googlemail.com> * Copyright (C) 2009 Michael Sievers <msievers83@googlemail.com> * @@ -23,6 +24,7 @@ #ifndef __HYPERV_WMI_CLASSES_H__ # define __HYPERV_WMI_CLASSES_H__ +# include "internal.h" # include "openwsman.h" # include "hyperv_wmi_classes.generated.typedef" @@ -96,6 +98,21 @@ enum _Msvm_ConcreteJob_JobState { }; +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * WMI + */ + +typedef struct _hypervCimType hypervCimType; +typedef hypervCimType *hypervCimTypePtr; +struct _hypervCimType { + /* Parameter name */ + const char *name; + /* Parameter type */ + const char *type; + /* whether parameter is an array type */ + bool isArray; +}; + typedef struct _hypervWmiClassInfo hypervWmiClassInfo; typedef hypervWmiClassInfo *hypervWmiClassInfoPtr; struct _hypervWmiClassInfo { @@ -109,6 +126,8 @@ struct _hypervWmiClassInfo { const char *resourceUri; /* The wsman serializer info - one of the *_TypeInfo structs */ XmlSerializerInfo *serializerInfo; + /* Property type information */ + hypervCimTypePtr propertyInfo; }; diff --git a/src/hyperv/hyperv_wmi_generator.py b/src/hyperv/hyperv_wmi_generator.py index 9aee0b9..9c0acce 100755 --- a/src/hyperv/hyperv_wmi_generator.py +++ b/src/hyperv/hyperv_wmi_generator.py @@ -122,6 +122,14 @@ class WmiClass: source += "SER_END_ITEMS(%s_Data);\n\n" % cls.name + # also generate typemap data while we're here + source += "hypervCimType %s_Typemap[] = {\n" % cls.name + + for property in cls.properties: + source += property.generate_typemap() + source += ' { "", "", 0 },\n' # null terminated + source += '};\n\n' + source += self._define_WmiInfo_struct() source += "\n\n" @@ -222,7 +230,8 @@ class WmiClass: source += " .version = NULL,\n" source += " .rootUri = %s,\n" % cls.uri_info.rootUri source += " .resourceUri = %s_RESOURCE_URI,\n" % cls.name.upper() - source += " .serializerInfo = %s_Data_TypeInfo\n" % cls.name + source += " .serializerInfo = %s_Data_TypeInfo,\n" % cls.name + source += " .propertyInfo = %s_Typemap\n" % cls.name source += " },\n" source += " }\n" @@ -374,6 +383,10 @@ class Property: % (Property.typemap[self.type], class_name.upper(), self.name) + def generate_typemap(self): + return ' { "%s", "%s", %s },\n' % (self.name, self.type.lower(), str(self.is_array).lower()) + + def open_and_print(filename): if filename.startswith("./"): -- 2.9.4

This commit adds support for invoking methods on remote objects via hypervInvokeMethod. --- src/hyperv/hyperv_wmi.c | 590 ++++++++++++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 8 +- src/hyperv/openwsman.h | 4 + 3 files changed, 600 insertions(+), 2 deletions(-) diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index 2732db3..f944b14 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,16 @@ #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 +#define HYPERV_JOB_TIMEOUT_MS 5000 + +VIR_LOG_INIT("hyperv.hyperv_wmi"); static int hypervGetWmiClassInfo(hypervPrivate *priv, hypervWmiClassInfoListPtr list, @@ -405,6 +411,590 @@ hypervFreeEmbeddedParam(virHashTablePtr p) virHashFree(p); } +/* + * 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 cleanup; + + *docRoot = ws_xml_create_doc(NULL, method); + if (*docRoot == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not instantiate XML document")); + goto cleanup; + } + + xmlNodeMethod = xml_parser_get_root(*docRoot); + if (xmlNodeMethod == NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not get root node of XML document")); + goto cleanup; + } + + /* add resource URI as namespace */ + ws_xml_set_ns(xmlNodeMethod, params->resourceUri, "p"); + + 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) +{ + 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")); + return -1; + } + + return 0; +} + +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 */ + if (virBufferCheckError(p->epr.query) < 0) { + virBufferFreeAndReset(p->epr.query); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid query")); + goto cleanup; + } + 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; + } + + 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; + ssize_t 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))) { + 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 */ + 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))) { + 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))) { + 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))) { + 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))) { + 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; + int timeout = HYPERV_JOB_TIMEOUT_MS; + + 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) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not get root of XML document")); + 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); + goto cleanup; + } + + 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 Hyper-V about the job until either the job completes or fails, + * or 5 minutes have elapsed. + * + * Windows has its own timeout on running WMI method calls (it calls + * these "jobs"), by default set to 1 minute. The administrator can + * change this to whatever they want, however, so we can't rely on it. + * + * Therefore, to avoid waiting in this loop for a very long-running job + * to complete, we instead bail after 5 minutes no matter what. NOTE that + * this does not mean that the remote job has terminated on the Windows + * side! That is up to Windows to control, we don't do anything about it. + */ + while (!completed && timeout >= 0) { + 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 */ + timeout -= 100; + 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; + } + } + if (!completed && timeout < 0) { + virReportError(VIR_ERR_OPERATION_TIMEOUT, + _("Timeout waiting for %s invocation"), params->method); + goto cleanup; + } + } 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) + *res = response; + + result = 0; + + cleanup: + if (options) + wsmc_options_destroy(options); + if (response && (!res)) + 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; +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Object */ diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index 54ce5b1..25cc470 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -95,14 +95,14 @@ typedef struct _hypervSimpleParam hypervSimpleParam; struct _hypervEprParam { const char *name; virBufferPtr query; - hypervWmiClassInfoPtr info; // info of the object this param represents + hypervWmiClassInfoPtr info; /* info of the object this param represents */ }; typedef struct _hypervEprParam hypervEprParam; struct _hypervEmbeddedParam { const char *name; virHashTablePtr table; - hypervWmiClassInfoPtr info; // info of the object this param represents + hypervWmiClassInfoPtr info; /* info of the object this param represents */ }; typedef struct _hypervEmbeddedParam hypervEmbeddedParam; @@ -152,6 +152,10 @@ int hypervAddEmbeddedParam(hypervInvokeParamsListPtr params, hypervPrivate *priv const char *name, virHashTablePtr table, hypervWmiClassInfoListPtr info); void hypervFreeEmbeddedParam(virHashTablePtr p); + +int hypervInvokeMethod(hypervPrivate *priv, hypervInvokeParamsListPtr params, + WsXmlDocH *res); + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * CIM/Msvm_ReturnCode */ diff --git a/src/hyperv/openwsman.h b/src/hyperv/openwsman.h index f66ed86..fc2958f 100644 --- a/src/hyperv/openwsman.h +++ b/src/hyperv/openwsman.h @@ -43,4 +43,8 @@ # define SER_NS_INT64(ns, n, x) SER_NS_INT64_FLAGS(ns, n, x, 0) # endif +/* wsman-xml.h */ +WsXmlDocH ws_xml_create_doc(const char *rootNsUri, const char *rootName); +WsXmlNodeH xml_parser_get_root(WsXmlDocH doc); + #endif /* __OPENWSMAN_H__ */ -- 2.9.4

On Tue, 2017-06-27 at 15:13 -0400, Sri Ramanujam wrote:
This commit adds support for invoking methods on remote objects via hypervInvokeMethod. --- src/hyperv/hyperv_wmi.c | 590 ++++++++++++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 8 +- src/hyperv/openwsman.h | 4 + 3 files changed, 600 insertions(+), 2 deletions(-) diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index 2732db3..f944b14 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c [...] +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; [...] + if (!(xmlNodeAddrPtr = xmlDocCopyNode((xmlNodePtr) xmlNodeAddr, docPtr, 1))) {
Here you're casting a WsXmlNodeH to a xmlNodePtr, and clang doesn't like it one bit: hyperv/hyperv_wmi.c:576:43: error: cast from 'WsXmlNodeH' (aka 'struct __WsXmlNode *') to 'xmlNodePtr' (aka 'struct _xmlNode *') increases required alignment from 4 to 8 [-Werror,-Wcast-align] Any idea how to unbreak it? -- Andrea Bolognani / Red Hat / Virtualization

2017-07-18 18:54 GMT+02:00 Andrea Bolognani <abologna@redhat.com>:
On Tue, 2017-06-27 at 15:13 -0400, Sri Ramanujam wrote:
This commit adds support for invoking methods on remote objects via hypervInvokeMethod. --- src/hyperv/hyperv_wmi.c | 590 ++++++++++++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 8 +- src/hyperv/openwsman.h | 4 + 3 files changed, 600 insertions(+), 2 deletions(-)
diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index 2732db3..f944b14 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c [...] +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; [...] + if (!(xmlNodeAddrPtr = xmlDocCopyNode((xmlNodePtr) xmlNodeAddr, docPtr, 1))) {
Here you're casting a WsXmlNodeH to a xmlNodePtr, and clang doesn't like it one bit:
hyperv/hyperv_wmi.c:576:43: error: cast from 'WsXmlNodeH' (aka 'struct __WsXmlNode *') to 'xmlNodePtr' (aka 'struct _xmlNode *') increases required alignment from 4 to 8 [-Werror,-Wcast-align]
Any idea how to unbreak it?
The problem here is that the driver is mixing direct libxml2 calls with calls to the libxml2 wrapper of openwsman. The openwsman wrapper type WsXmlNodeH is actually a xmlNodePtr, but that is hidden to the compiler. I checked if the openwsman libxml2 wrapper is complete enough to get rid of this API mixing. I could replace all direct libxml2 calls with openwsman wrapper call except xmlNewCDataBlock. A hack for this last offender is to cast to a void pointer first, instead of a direct cast. See attached patch for a quick fix, compile-tested only. Another possibility is to do all the XML building using direct libxml2 calls, format the XML document and reparse it with the openwsman wrapper. But I don't have time to work on that at the moment. -- Matthias Bolte http://photron.blogspot.com

Just tested this patch quickly, and it causes invalid free()s when the codepath is invoked against a Hyper-V 2008 system, and causes the operation to fail (but does not crash virsh) against Hyper-V 2012. I'm away from my usual setup atm (on vacation), so I can help look into this further next week when I'm back. On Wed, Jul 19, 2017 at 1:21 AM, Matthias Bolte <matthias.bolte@googlemail.com> wrote:
2017-07-18 18:54 GMT+02:00 Andrea Bolognani <abologna@redhat.com>:
On Tue, 2017-06-27 at 15:13 -0400, Sri Ramanujam wrote:
This commit adds support for invoking methods on remote objects via hypervInvokeMethod. --- src/hyperv/hyperv_wmi.c | 590 ++++++++++++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 8 +- src/hyperv/openwsman.h | 4 + 3 files changed, 600 insertions(+), 2 deletions(-)
diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index 2732db3..f944b14 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c [...] +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; [...] + if (!(xmlNodeAddrPtr = xmlDocCopyNode((xmlNodePtr) xmlNodeAddr, docPtr, 1))) {
Here you're casting a WsXmlNodeH to a xmlNodePtr, and clang doesn't like it one bit:
hyperv/hyperv_wmi.c:576:43: error: cast from 'WsXmlNodeH' (aka 'struct __WsXmlNode *') to 'xmlNodePtr' (aka 'struct _xmlNode *') increases required alignment from 4 to 8 [-Werror,-Wcast-align]
Any idea how to unbreak it?
The problem here is that the driver is mixing direct libxml2 calls with calls to the libxml2 wrapper of openwsman. The openwsman wrapper type WsXmlNodeH is actually a xmlNodePtr, but that is hidden to the compiler.
I checked if the openwsman libxml2 wrapper is complete enough to get rid of this API mixing. I could replace all direct libxml2 calls with openwsman wrapper call except xmlNewCDataBlock. A hack for this last offender is to cast to a void pointer first, instead of a direct cast. See attached patch for a quick fix, compile-tested only.
Another possibility is to do all the XML building using direct libxml2 calls, format the XML document and reparse it with the openwsman wrapper. But I don't have time to work on that at the moment.
-- Matthias Bolte http://photron.blogspot.com
-- Sri Ramanujam Software Engineer Datto, Inc.

[Please don't top-post on technical mailing lists] 2017-07-19 14:13 GMT+02:00 Sri Ramanujam <sramanujam@datto.com>:
Just tested this patch quickly, and it causes invalid free()s when the codepath is invoked against a Hyper-V 2008 system, and causes the operation to fail (but does not crash virsh) against Hyper-V 2012. I'm away from my usual setup atm (on vacation), so I can help look into this further next week when I'm back.
On Wed, Jul 19, 2017 at 1:21 AM, Matthias Bolte <matthias.bolte@googlemail.com> wrote:
2017-07-18 18:54 GMT+02:00 Andrea Bolognani <abologna@redhat.com>:
On Tue, 2017-06-27 at 15:13 -0400, Sri Ramanujam wrote:
This commit adds support for invoking methods on remote objects via hypervInvokeMethod. --- src/hyperv/hyperv_wmi.c | 590 ++++++++++++++++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 8 +- src/hyperv/openwsman.h | 4 + 3 files changed, 600 insertions(+), 2 deletions(-)
diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index 2732db3..f944b14 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c [...] +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; [...] + if (!(xmlNodeAddrPtr = xmlDocCopyNode((xmlNodePtr) xmlNodeAddr, docPtr, 1))) {
Here you're casting a WsXmlNodeH to a xmlNodePtr, and clang doesn't like it one bit:
hyperv/hyperv_wmi.c:576:43: error: cast from 'WsXmlNodeH' (aka 'struct __WsXmlNode *') to 'xmlNodePtr' (aka 'struct _xmlNode *') increases required alignment from 4 to 8 [-Werror,-Wcast-align]
Any idea how to unbreak it?
The problem here is that the driver is mixing direct libxml2 calls with calls to the libxml2 wrapper of openwsman. The openwsman wrapper type WsXmlNodeH is actually a xmlNodePtr, but that is hidden to the compiler.
I checked if the openwsman libxml2 wrapper is complete enough to get rid of this API mixing. I could replace all direct libxml2 calls with openwsman wrapper call except xmlNewCDataBlock. A hack for this last offender is to cast to a void pointer first, instead of a direct cast. See attached patch for a quick fix, compile-tested only.
Another possibility is to do all the XML building using direct libxml2 calls, format the XML document and reparse it with the openwsman wrapper. But I don't have time to work on that at the moment.
The patch was not meant to fix the issue, it was meant to illustrate what I tried to investigate a potential way to fix the problem. I think, there are three ways to go about this: 1. Just cast to a void pointer first. This silences clang, but still relies on the implementation detail that openwsman is using libxml2 internally. This is the quick and dirty workaround for the immediate problem at hand. 2. Clean up the API mix and only use the public ws_xml_* functions. That will probably not work because the openwsman wrapper doesn't provide a function to create a CDATA block. 3. Clean up the API mix and only use libxml2 functions to build the XML and then format and reparse it to obtain an WsXmlDocH. Regards, Matthias -- Matthias Bolte http://photron.blogspot.com

This commit adds support for virDomainSendKey. It also serves as an example of how to use the new method invocation APIs with a single "simple" type parameter. --- src/hyperv/hyperv_driver.c | 123 ++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.c | 7 ++ src/hyperv/hyperv_wmi.h | 3 +- src/hyperv/hyperv_wmi_generator.input | 86 ++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 1 deletion(-) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 0ca5971..7ee3d9a 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -35,6 +35,8 @@ #include "hyperv_wmi.h" #include "openwsman.h" #include "virstring.h" +#include "virkeycode.h" +#include "intprops.h" #define VIR_FROM_THIS VIR_FROM_HYPERV @@ -1373,6 +1375,126 @@ hypervConnectListAllDomains(virConnectPtr conn, #undef MATCH +static int +hypervDomainSendKey(virDomainPtr domain, unsigned int codeset, + unsigned int holdtime, unsigned int *keycodes, int nkeycodes, + unsigned int flags) +{ + int result = -1; + size_t i = 0; + int keycode = 0; + int *translatedKeycodes = NULL; + hypervPrivate *priv = domain->conn->privateData; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + char *selector = NULL; + Msvm_ComputerSystem *computerSystem = NULL; + Msvm_Keyboard *keyboard = NULL; + virBuffer query = VIR_BUFFER_INITIALIZER; + hypervInvokeParamsListPtr params = NULL; + char keycodeStr[INT_BUFSIZE_BOUND(int)]; + + virCheckFlags(0, -1); + + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervMsvmComputerSystemFromDomain(domain, &computerSystem) < 0) + goto cleanup; + + virBufferAsprintf(&query, + "associators of " + "{Msvm_ComputerSystem.CreationClassName=\"Msvm_ComputerSystem\"," + "Name=\"%s\"} " + "where ResultClass = Msvm_Keyboard", + uuid_string); + + if (hypervGetMsvmKeyboardList(priv, &query, &keyboard) < 0) + goto cleanup; + + if (VIR_ALLOC_N(translatedKeycodes, nkeycodes) < 0) + goto cleanup; + + /* translate keycodes to win32 and generate keyup scancodes. */ + for (i = 0; i < nkeycodes; i++) { + if (codeset != VIR_KEYCODE_SET_WIN32) { + keycode = virKeycodeValueTranslate(codeset, VIR_KEYCODE_SET_WIN32, + keycodes[i]); + + if (keycode < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not translate keycode")); + goto cleanup; + } + translatedKeycodes[i] = keycode; + } + } + + if (virAsprintf(&selector, + "CreationClassName=Msvm_Keyboard&DeviceID=%s&" + "SystemCreationClassName=Msvm_ComputerSystem&" + "SystemName=%s", keyboard->data.common->DeviceID, uuid_string) < 0) + goto cleanup; + + /* press the keys */ + for (i = 0; i < nkeycodes; i++) { + snprintf(keycodeStr, sizeof(keycodeStr), "%d", translatedKeycodes[i]); + + params = hypervCreateInvokeParamsList(priv, "PressKey", selector, + Msvm_Keyboard_WmiInfo); + + if (!params) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create param")); + goto cleanup; + } + + if (hypervAddSimpleParam(params, "keyCode", keycodeStr) < 0) { + hypervFreeInvokeParams(params); + goto cleanup; + } + + if (hypervInvokeMethod(priv, params, NULL) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not press key %d"), + translatedKeycodes[i]); + goto cleanup; + } + } + + /* simulate holdtime by sleeping */ + if (holdtime > 0) + usleep(holdtime * 1000); + + /* release the keys */ + for (i = 0; i < nkeycodes; i++) { + snprintf(keycodeStr, sizeof(keycodeStr), "%d", translatedKeycodes[i]); + params = hypervCreateInvokeParamsList(priv, "ReleaseKey", selector, + Msvm_Keyboard_WmiInfo); + + if (!params) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create param")); + goto cleanup; + } + + if (hypervAddSimpleParam(params, "keyCode", keycodeStr) < 0) { + hypervFreeInvokeParams(params); + goto cleanup; + } + + if (hypervInvokeMethod(priv, params, NULL) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("Could not release key %s"), + keycodeStr); + goto cleanup; + } + } + + result = 0; + + cleanup: + VIR_FREE(translatedKeycodes); + VIR_FREE(selector); + hypervFreeObject(priv, (hypervObject *) keyboard); + hypervFreeObject(priv, (hypervObject *) computerSystem); + virBufferFreeAndReset(&query); + return result; +} static virHypervisorDriver hypervHypervisorDriver = { @@ -1408,6 +1530,7 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainManagedSave = hypervDomainManagedSave, /* 0.9.5 */ .domainHasManagedSaveImage = hypervDomainHasManagedSaveImage, /* 0.9.5 */ .domainManagedSaveRemove = hypervDomainManagedSaveRemove, /* 0.9.5 */ + .domainSendKey = hypervDomainSendKey, /* TODO: version */ .connectIsAlive = hypervConnectIsAlive, /* 0.9.8 */ }; diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index f944b14..36d3c2e 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c @@ -1342,6 +1342,13 @@ hypervGetMsvmMemorySettingDataList(hypervPrivate *priv, virBufferPtr query, (hypervObject **) list); } +int hypervGetMsvmKeyboardList(hypervPrivate *priv, virBufferPtr query, + Msvm_Keyboard **list) +{ + return hypervGetWmiClassList(priv, Msvm_Keyboard_WmiInfo, query, + (hypervObject **) list); +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index 25cc470..5c9da04 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -219,7 +219,8 @@ int hypervGetMsvmProcessorSettingDataList(hypervPrivate *priv, int hypervGetMsvmMemorySettingDataList(hypervPrivate *priv, virBufferPtr query, Msvm_MemorySettingData **list); - +int hypervGetMsvmKeyboardList(hypervPrivate *priv, virBufferPtr query, + Msvm_Keyboard **list); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Msvm_ComputerSystem diff --git a/src/hyperv/hyperv_wmi_generator.input b/src/hyperv/hyperv_wmi_generator.input index d7f819e..4ccda04 100644 --- a/src/hyperv/hyperv_wmi_generator.input +++ b/src/hyperv/hyperv_wmi_generator.input @@ -956,3 +956,89 @@ class Msvm_VirtualHardDiskSettingData uint32 PhysicalSectorSize string VirtualDiskId end + +class Msvm_Keyboard + string Caption + string Description + string ElementName + datetime InstallDate + string Name + uint16 OperationalStatus[] + string StatusDescriptions[] + string Status + uint16 HealthState + uint16 EnabledState + string OtherEnabledState + uint16 RequestedState + uint16 EnabledDefault + datetime TimeOfLastStateChange + string SystemCreationClassName + string SystemName + string CreationClassName + string DeviceID + boolean PowerManagementSupported + uint16 PowerManagementCapabilities[] + uint16 Availability + uint16 StatusInfo + uint32 LastErrorCode + string ErrorDescription + boolean ErrorCleared + string OtherIdentifyingInfo[] + uint64 PowerOnHours + uint64 TotalPowerOnHours + string IdentifyingDescriptions[] + uint16 AdditionalAvailability[] + uint64 MaxQuiesceTime + uint16 LocationIndicator + boolean IsLocked + string Layout + uint16 NumberOfFunctionKeys + uint16 Password +end + + +class v2/Msvm_Keyboard + string InstanceID + string Caption + string Description + string ElementName + datetime InstallDate + string Name + uint16 OperationalStatus[] + string StatusDescriptions[] + string Status + uint16 HealthState + uint16 CommunicationStatus + uint16 DetailedStatus + uint16 OperatingStatus + uint16 PrimaryStatus + uint16 EnabledState + string OtherEnabledState + uint16 RequestedState + uint16 EnabledDefault + datetime TimeOfLastStateChange + uint16 AvailableRequestedStates[] + uint16 TransitioningToState + string SystemCreationClassName + string SystemName + string CreationClassName + string DeviceID + boolean PowerManagementSupported + uint16 PowerManagementCapabilities[] + uint16 Availability + uint16 StatusInfo + uint32 LastErrorCode + string ErrorDescription + boolean ErrorCleared + string OtherIdentifyingInfo[] + uint64 PowerOnHours + uint64 TotalPowerOnHours + string IdentifyingDescriptions[] + uint16 AdditionalAvailability[] + uint64 MaxQuiesceTime + boolean IsLocked + string Layout + uint16 NumberOfFunctionKeys + uint16 Password + boolean UnicodeSupported +end -- 2.9.4

Introduces support for virDomainSetMemory. This also serves an an example for how to use the new method invocation API with a more complicated method, this time including an EPR and embedded param. --- src/hyperv/hyperv_driver.c | 114 ++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.c | 51 +++++++++++++++ src/hyperv/hyperv_wmi.h | 11 ++++ src/hyperv/hyperv_wmi_generator.input | 30 +++++++++ 4 files changed, 206 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 7ee3d9a..a069afd 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -1497,6 +1497,118 @@ hypervDomainSendKey(virDomainPtr domain, unsigned int codeset, } +static int +hypervDomainSetMemoryFlags(virDomainPtr domain, unsigned long memory, + unsigned int flags) +{ + int result = -1; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + hypervPrivate *priv = domain->conn->privateData; + char *memory_str = NULL; + hypervInvokeParamsListPtr params = NULL; + unsigned long memory_mb = VIR_ROUND_UP(VIR_DIV_UP(memory, 1024), 2); + Msvm_VirtualSystemSettingData *vssd = NULL; + Msvm_MemorySettingData *memsd = NULL; + virBuffer eprQuery = VIR_BUFFER_INITIALIZER; + virHashTablePtr memResource = NULL; + + virCheckFlags(0, -1); + + if (virAsprintf(&memory_str, "%lu", memory_mb) < 0) + goto cleanup; + + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervGetMsvmVirtualSystemSettingDataFromUUID(priv, uuid_string, &vssd) < 0) + goto cleanup; + + if (hypervGetMsvmMemorySettingDataFromVSSD(priv, vssd->data.common->InstanceID, + &memsd) < 0) + goto cleanup; + + if (priv->wmiVersion == HYPERV_WMI_VERSION_V1) { + params = hypervCreateInvokeParamsList(priv, "ModifyVirtualSystemResources", + MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR, + Msvm_VirtualSystemManagementService_WmiInfo); + + if (!params) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create params")); + goto cleanup; + } + + virBufferAddLit(&eprQuery, MSVM_COMPUTERSYSTEM_WQL_SELECT); + virBufferAsprintf(&eprQuery, "where Name = \"%s\"", uuid_string); + + if (hypervAddEprParam(params, "ComputerSystem", priv, &eprQuery, + Msvm_ComputerSystem_WmiInfo) < 0) + goto params_cleanup; + } else if (priv->wmiVersion == HYPERV_WMI_VERSION_V2) { + params = hypervCreateInvokeParamsList(priv, "ModifyResourceSettings", + MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR, + Msvm_VirtualSystemManagementService_WmiInfo); + + if (!params) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create params")); + goto cleanup; + } + } + + memResource = hypervCreateEmbeddedParam(priv, Msvm_MemorySettingData_WmiInfo); + if (!memResource) + goto params_cleanup; + + if (hypervSetEmbeddedProperty(memResource, "VirtualQuantity", memory_str) < 0) { + hypervFreeEmbeddedParam(memResource); + goto params_cleanup; + } + + if (hypervSetEmbeddedProperty(memResource, "InstanceID", + memsd->data.common->InstanceID) < 0) { + hypervFreeEmbeddedParam(memResource); + goto params_cleanup; + } + + if (priv->wmiVersion == HYPERV_WMI_VERSION_V1) { + if (hypervAddEmbeddedParam(params, priv, "ResourceSettingData", + memResource, Msvm_MemorySettingData_WmiInfo) < 0) { + hypervFreeEmbeddedParam(memResource); + goto params_cleanup; + } + + } else if (priv->wmiVersion == HYPERV_WMI_VERSION_V2) { + if (hypervAddEmbeddedParam(params, priv, "ResourceSettings", + memResource, Msvm_MemorySettingData_WmiInfo) < 0) { + hypervFreeEmbeddedParam(memResource); + goto params_cleanup; + } + } + + if (hypervInvokeMethod(priv, params, NULL) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not set memory")); + goto cleanup; + } + + result = 0; + goto cleanup; + + params_cleanup: + hypervFreeInvokeParams(params); + virBufferFreeAndReset(&eprQuery); + cleanup: + VIR_FREE(memory_str); + hypervFreeObject(priv, (hypervObject *) vssd); + hypervFreeObject(priv, (hypervObject *) memsd); + return result; +} + + +static int +hypervDomainSetMemory(virDomainPtr domain, unsigned long memory) +{ + return hypervDomainSetMemoryFlags(domain, memory, 0); +} + + static virHypervisorDriver hypervHypervisorDriver = { .name = "Hyper-V", .connectOpen = hypervConnectOpen, /* 0.9.5 */ @@ -1531,6 +1643,8 @@ static virHypervisorDriver hypervHypervisorDriver = { .domainHasManagedSaveImage = hypervDomainHasManagedSaveImage, /* 0.9.5 */ .domainManagedSaveRemove = hypervDomainManagedSaveRemove, /* 0.9.5 */ .domainSendKey = hypervDomainSendKey, /* TODO: version */ + .domainSetMemory = hypervDomainSetMemory, /* TODO: version */ + .domainSetMemoryFlags = hypervDomainSetMemoryFlags, /* TODO: version */ .connectIsAlive = hypervConnectIsAlive, /* 0.9.8 */ }; diff --git a/src/hyperv/hyperv_wmi.c b/src/hyperv/hyperv_wmi.c index 36d3c2e..8a61703 100644 --- a/src/hyperv/hyperv_wmi.c +++ b/src/hyperv/hyperv_wmi.c @@ -1635,3 +1635,54 @@ hypervMsvmComputerSystemFromDomain(virDomainPtr domain, return 0; } + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Msvm_VirtualSystemSettingData + */ + +int +hypervGetMsvmVirtualSystemSettingDataFromUUID(hypervPrivate *priv, + const char *uuid_string, Msvm_VirtualSystemSettingData **list) +{ + virBuffer query = VIR_BUFFER_INITIALIZER; + + virBufferAsprintf(&query, + "associators of " + "{Msvm_ComputerSystem.CreationClassName=\"Msvm_ComputerSystem\"," + "Name=\"%s\"} " + "where AssocClass = Msvm_SettingsDefineState " + "ResultClass = Msvm_VirtualSystemSettingData", + uuid_string); + + if (hypervGetWmiClassList(priv, Msvm_VirtualSystemSettingData_WmiInfo, &query, + (hypervObject **) list) < 0 || *list == NULL) + return -1; + + return 0; +} + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Msvm_MemorySettingData + */ + +int +hypervGetMsvmMemorySettingDataFromVSSD(hypervPrivate *priv, + const char *vssd_instanceid, Msvm_MemorySettingData **list) +{ + virBuffer query = VIR_BUFFER_INITIALIZER; + + virBufferAsprintf(&query, + "associators of " + "{Msvm_VirtualSystemSettingData.InstanceID=\"%s\"} " + "where AssocClass = Msvm_VirtualSystemSettingDataComponent " + "ResultClass = Msvm_MemorySettingData", + vssd_instanceid); + + if (hypervGetWmiClassList(priv, Msvm_MemorySettingData_WmiInfo, &query, + (hypervObject **) list) < 0 || *list == NULL) + return -1; + + return 0; +} diff --git a/src/hyperv/hyperv_wmi.h b/src/hyperv/hyperv_wmi.h index 5c9da04..cc53078 100644 --- a/src/hyperv/hyperv_wmi.h +++ b/src/hyperv/hyperv_wmi.h @@ -35,6 +35,9 @@ # define HYPERV_DEFAULT_PARAM_COUNT 5 +# define MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR \ + "CreationClassName=Msvm_VirtualSystemManagementService" + int hypervVerifyResponse(WsManClient *client, WsXmlDocH response, const char *detail); @@ -212,6 +215,10 @@ int hypervGetMsvmVirtualSystemSettingDataList(hypervPrivate *priv, virBufferPtr query, Msvm_VirtualSystemSettingData **list); +int hypervGetMsvmVirtualSystemSettingDataFromUUID(hypervPrivate *priv, + const char *uuid_string, + Msvm_VirtualSystemSettingData **list); + int hypervGetMsvmProcessorSettingDataList(hypervPrivate *priv, virBufferPtr query, Msvm_ProcessorSettingData **list); @@ -219,6 +226,10 @@ int hypervGetMsvmProcessorSettingDataList(hypervPrivate *priv, int hypervGetMsvmMemorySettingDataList(hypervPrivate *priv, virBufferPtr query, Msvm_MemorySettingData **list); +int hypervGetMsvmMemorySettingDataFromVSSD(hypervPrivate *priv, + const char *vssd_instanceid, + Msvm_MemorySettingData **list); + int hypervGetMsvmKeyboardList(hypervPrivate *priv, virBufferPtr query, Msvm_Keyboard **list); diff --git a/src/hyperv/hyperv_wmi_generator.input b/src/hyperv/hyperv_wmi_generator.input index 4ccda04..da19765 100644 --- a/src/hyperv/hyperv_wmi_generator.input +++ b/src/hyperv/hyperv_wmi_generator.input @@ -787,6 +787,36 @@ class Msvm_VirtualSystemManagementService boolean Started end +class v2/Msvm_VirtualSystemManagementService + string InstanceID + string Caption + string Description + string ElementName + datetime InstallDate + string Name + uint16 OperationalStatus[] + string StatusDescriptions[] + string Status + uint16 HealthState + uint16 CommunicationStatus + uint16 DetailedStatus + uint16 OperatingStatus + uint16 PrimaryStatus + uint16 EnabledState + string OtherEnabledState + uint16 RequestedState + uint16 EnabledDefault + datetime TimeOfLastStateChange + uint16 AvailableRequestedStates[] + uint16 TransitioningToState + string SystemCreationClassName + string SystemName + string CreationClassName + string PrimaryOwnerName + string PrimaryOwnerContact + string StartMode + boolean Started +end class Msvm_VirtualSystemGlobalSettingData string Caption -- 2.9.4

--- docs/news.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/news.xml b/docs/news.xml index 27562e1..77d167c 100644 --- a/docs/news.xml +++ b/docs/news.xml @@ -48,6 +48,11 @@ </change> </section> <section title="Improvements"> + <change> + <summary> + hyperv: Implement virDomainSetMemory and virDomainSendKey APIs + </summary> + </change> </section> <section title="Bug fixes"> </section> -- 2.9.4

2017-06-27 21:13 GMT+02:00 Sri Ramanujam <sramanujam@datto.com>:
Changes from v6: * Added news entry
Sri Ramanujam (6): hyperv: Functions to work with invocation parameters. hyperv: Generate object property type information. hyperv: add hypervInvokeMethod hyperv: support virDomainSendKey hyperv: Add support for virDomainSetMemory news: Update news for new Hyper-V APIs
docs/news.xml | 5 + src/hyperv/hyperv_driver.c | 237 +++++++++ src/hyperv/hyperv_wmi.c | 910 ++++++++++++++++++++++++++++++++++ src/hyperv/hyperv_wmi.h | 95 +++- src/hyperv/hyperv_wmi_classes.h | 19 + src/hyperv/hyperv_wmi_generator.input | 116 +++++ src/hyperv/hyperv_wmi_generator.py | 15 +- src/hyperv/openwsman.h | 4 + 8 files changed, 1399 insertions(+), 2 deletions(-)
ACK to the whole series. I've pushed it now, thanks! -- Matthias Bolte http://photron.blogspot.com
participants (3)
-
Andrea Bolognani
-
Matthias Bolte
-
Sri Ramanujam