/*
 * 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>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 */

#include <config.h>

#include <wsman-soap.h>
#include <wsman-xml.h>
#include <wsman-xml-binding.h>
#include <u/syslog.h>

#include "internal.h"
#include "virerror.h"
#include "datatypes.h"
#include "viralloc.h"
#include "viruuid.h"
#include "virbuffer.h"
#include "hyperv_private.h"
#include "hyperv_wmi.h"
#include "virstring.h"
#include "virlog.h"
#include "virhash.h"

#define VIR_FROM_THIS VIR_FROM_HYPERV

#define HYPERV_JOB_TIMEOUT_MS 300000

VIR_LOG_INIT("hyperv.hyperv_wmi");

int facility = LOG_DAEMON;

int
hypervGetWmiClassList(hypervPrivate *priv, hypervWmiClassInfo *wmiInfo,
                      virBuffer *query, hypervObject **wmiClass)
{
    hypervWqlQuery wqlQuery = HYPERV_WQL_QUERY_INITIALIZER;

    wqlQuery.info = wmiInfo;
    wqlQuery.query = query;

    return hypervEnumAndPull(priv, &wqlQuery, wmiClass);
}


int
hypervVerifyResponse(WsManClient *client, WsXmlDocH response,
                     const char *detail)
{
    int lastError = wsmc_get_last_error(client);
    int responseCode = wsmc_get_response_code(client);
    WsManFault *fault;

    if (lastError != WS_LASTERR_OK) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Transport error during %1$s: %2$s (%3$d)"),
                       detail, wsman_transport_get_last_error_string(lastError),
                       lastError);
        return -1;
    }

    /* Check the HTTP response code and report an error if it's not 200 (OK),
     * 400 (Bad Request) or 500 (Internal Server Error) */
    if (responseCode != 200 && responseCode != 400 && responseCode != 500) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unexpected HTTP response during %1$s: %2$d"),
                       detail, responseCode);
        return -1;
    }

    if (response == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Empty response during %1$s"), detail);
        return -1;
    }

    if (wsmc_check_for_fault(response)) {
        if (!(fault = wsmc_fault_new()))
            abort();

        wsmc_get_fault_data(response, fault);

        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("SOAP fault during %1$s: code '%2$s', subcode '%3$s', reason '%4$s', detail '%5$s'"),
                       detail, NULLSTR(fault->code), NULLSTR(fault->subcode),
                       NULLSTR(fault->reason), NULLSTR(fault->fault_detail));

        wsmc_fault_destroy(fault);
        return -1;
    }

    return 0;
}


/*
 * Methods to work with method invocation parameters
 */

/*
 * hypervCreateInvokeParamsList:
 * @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.
 */
hypervInvokeParamsList *
hypervCreateInvokeParamsList(const char *method,
                             const char *selector,
                             hypervWmiClassInfo *info)
{
    hypervInvokeParamsList *params = NULL;

    params = g_new0(hypervInvokeParamsList, 1);

    params->params = g_new0(hypervParam, HYPERV_DEFAULT_PARAM_COUNT);

    params->method = method;
    params->ns = info->rootUri;
    params->resourceUri = info->resourceUri;
    params->selector = selector;
    params->nbParams = 0;
    params->nbAvailParams = HYPERV_DEFAULT_PARAM_COUNT;

    return params;
}


/*
 * hypervFreeInvokeParams:
 * @params: Params object to be freed
 *
 */
void
hypervFreeInvokeParams(hypervInvokeParamsList *params)
{
    hypervParam *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"));
        }
    }

    g_free(params->params);
    g_free(params);
}


static void
hypervCheckParams(hypervInvokeParamsList *params)
{
    if (params->nbParams + 1 > params->nbAvailParams) {
        VIR_EXPAND_N(params->params, params->nbAvailParams, 5);
    }
}


/*
 * 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.
 */
void
hypervAddSimpleParam(hypervInvokeParamsList *params, const char *name,
                     const char *value)
{
    hypervParam *p = NULL;

    hypervCheckParams(params);

    p = &params->params[params->nbParams];
    p->type = HYPERV_SIMPLE_PARAM;

    p->simple.name = name;
    p->simple.value = value;

    params->nbParams++;
}


/*
 * 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.
 */
void
hypervAddEprParam(hypervInvokeParamsList *params,
                  const char *name,
                  virBuffer *query,
                  hypervWmiClassInfo *classInfo)
{
    hypervParam *p = NULL;

    hypervCheckParams(params);

    p = &params->params[params->nbParams];
    p->type = HYPERV_EPR_PARAM;
    p->epr.name = name;
    p->epr.query = query;
    p->epr.info = classInfo;
    params->nbParams++;
}


/*
 * hypervCreateEmbeddedParam:
 * @info: WmiInfo of the object type to serialize
 *
 * Instantiates a GHashTable 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 GHashTable on success, otherwise NULL.
 */
GHashTable *
hypervCreateEmbeddedParam(hypervWmiClassInfo *classInfo)
{
    size_t i;
    g_autoptr(GHashTable) table = virHashNew(NULL);
    XmlSerializerInfo *typeinfo = NULL;

    typeinfo = classInfo->serializerInfo;

    for (i = 0; typeinfo[i].name != NULL; i++) {
        XmlSerializerInfo *item = &typeinfo[i];

        if (virHashAddEntry(table, item->name, NULL) < 0)
            return NULL;
    }

    return g_steal_pointer(&table);
}


/**
 * hypervSetEmbeddedProperty:
 * @table: hash table allocated earlier by hypervCreateEmbeddedParam()
 * @name: name of the property
 * @value: value of the property
 *
 * For given table of properties, set property of @name to @value.
 * Please note, that the hash table does NOT become owner of the @value and
 * thus caller must ensure the pointer validity.
 *
 * Returns: 0 on success,
 *         -1 otherwise.
 */
int
hypervSetEmbeddedProperty(GHashTable *table,
                          const char *name,
                          const char *value)
{
    return virHashUpdateEntry(table, name, (void*) value);
}


/*
 * hypervAddEmbeddedParam:
 * @params: Params list to add to
 * @name: Name of the parameter
 * @table: pointer to table of properties to add
 * @info: WmiInfo of the object to serialize
 *
 * Add a GHashTable containing object properties as an embedded param to
 * an invocation list.
 *
 * Upon return the @table is consumed and the pointer is cleared out.
 */
void
hypervAddEmbeddedParam(hypervInvokeParamsList *params,
                       const char *name,
                       GHashTable **table,
                       hypervWmiClassInfo *classInfo)
{
    hypervParam *p = NULL;

    hypervCheckParams(params);

    p = &params->params[params->nbParams];
    p->type = HYPERV_EMBEDDED_PARAM;
    p->embedded.name = name;
    p->embedded.table = g_steal_pointer(table);
    p->embedded.info = classInfo;
    params->nbParams++;
}


/*
 * hypervFreeEmbeddedParam:
 * @param: Pointer to embedded param to free
 *
 * Free the embedded param hash table.
 */
void
hypervFreeEmbeddedParam(GHashTable *p)
{
    g_clear_pointer(&p, g_hash_table_unref);
}


/*
 * Serializing parameters to XML and invoking methods
 */
static int
hypervGetCimTypeInfo(hypervCimType *typemap, const char *name,
                     hypervCimType **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(hypervInvokeParamsList *params, WsXmlDocH *docRoot)
{
    g_autofree char *method = g_strdup_printf("%s_INPUT", params->method);
    g_auto(WsXmlDocH) invokeXmlDocRoot = ws_xml_create_doc(NULL, method);
    WsXmlNodeH xmlNodeMethod = NULL;

    if (!invokeXmlDocRoot) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not instantiate XML document"));
        return -1;
    }

    xmlNodeMethod = xml_parser_get_root(invokeXmlDocRoot);
    if (!xmlNodeMethod) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get root node of XML document"));
        return -1;
    }

    /* add resource URI as namespace */
    ws_xml_set_ns(xmlNodeMethod, params->resourceUri, "p");

    *docRoot = g_steal_pointer(&invokeXmlDocRoot);

    return 0;
}


static int
hypervSerializeSimpleParam(hypervParam *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(hypervParam *p, hypervPrivate *priv,
                        const char *resourceUri, WsXmlNodeH *methodNode)
{
    WsXmlNodeH xmlNodeParam = NULL,
               xmlNodeTemp = NULL,
               xmlNodeAddr = NULL,
               xmlNodeRef = NULL;
    g_auto(WsXmlDocH) xmlDocResponse = NULL;
    g_autoptr(client_opt_t) options = NULL;
    g_autoptr(filter_t) filter = NULL;
    g_autofree char *enumContext = NULL;
    g_autofree 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"));
        return -1;
    }
    wsmc_set_action_option(options, FLAG_ENUMERATION_ENUM_EPR);

    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"));
        return -1;
    }

    /* 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)
        return -1;

    /* 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)
        return -1;

    /* 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"));
        return -1;
    }

    if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ENUMERATION,
                                         WSENUM_PULL_RESP))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get response"));
        return -1;
    }

    if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ENUMERATION, WSENUM_ITEMS))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get response items"));
        return -1;
    }

    if (!(xmlNodeTemp = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ADDRESSING, WSA_EPR))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get EPR items"));
        return -1;
    }

    if (!(xmlNodeAddr = ws_xml_get_child(xmlNodeTemp, 0, XML_NS_ADDRESSING,
                                         WSA_ADDRESS))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not get EPR address"));
        return -1;
    }

    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"));
        return -1;
    }

    /* 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 methodNode"));
        return -1;
    }

    if (!ws_xml_ns_add(xmlNodeParam, XML_NS_ADDRESSING, "a")) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not set namespace address for xmlNodeParam"));
        return -1;
    }

    if (!ws_xml_ns_add(xmlNodeParam, XML_NS_WS_MAN, "w")) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not set wsman namespace address for xmlNodeParam"));
        return -1;
    }

    ws_xml_duplicate_tree(xmlNodeParam, xmlNodeAddr);
    ws_xml_duplicate_tree(xmlNodeParam, xmlNodeRef);

    /* we did it! */
    return 0;
}


static int
hypervSerializeEmbeddedParam(hypervParam *p, const char *resourceUri,
                             WsXmlNodeH *methodNode)
{
    WsXmlNodeH xmlNodeInstance = NULL,
               xmlNodeProperty = NULL,
               xmlNodeParam = NULL,
               xmlNodeArray = NULL;
    g_auto(WsXmlDocH) xmlDocTemp = NULL;
    g_auto(WsXmlDocH) xmlDocCdata = NULL;
    g_autofree char *cdataContent = NULL;
    xmlNodePtr xmlNodeCdata = NULL;
    hypervWmiClassInfo *classInfo = p->embedded.info;
    g_autofree virHashKeyValuePair *items = NULL;
    hypervCimType *property = NULL;
    size_t numKeys = 0;
    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 %1$s"),
                       p->embedded.name);
        return -1;
    }

    /* 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"));
        return -1;
    }

    if (!(xmlNodeInstance = xml_parser_get_root(xmlDocTemp))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not get temp xml doc root"));
        return -1;
    }

    /* 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"));
        return -1;
    }

    /* retrieve parameters out of hash table */
    items = virHashGetItems(p->embedded.table, &numKeys, false);
    if (!items) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not read embedded param hash table"));
        return -1;
    }

    /* 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"));
                return -1;
            }

            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"));
                return -1;
            }

            if (!(ws_xml_add_node_attr(xmlNodeProperty, NULL, "NAME", name))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("Could not add attribute to XML node"));
                return -1;
            }

            if (!(ws_xml_add_node_attr(xmlNodeProperty, NULL, "TYPE", property->type))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("Could not add attribute to XML node"));
                return -1;
            }

            /* 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"));
                    return -1;
                }
            }

            /* 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"));
                return -1;
            }

            xmlNodeArray = NULL;
            xmlNodeProperty = NULL;
        }
    }

    /* create CDATA node */
    ws_xml_dump_memory_node_tree(xmlNodeInstance, &cdataContent, &len);

    if (!(xmlNodeCdata = xmlNewCDataBlock((xmlDocPtr) xmlDocCdata,
                                          (xmlChar *)cdataContent, len))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not create CDATA element"));
        return -1;
    }

    /*
     * Add CDATA node to the doc root
     *
     * FIXME: there is no openwsman wrapper for xmlNewCDataBlock, so instead
     * silence clang alignment warnings by casting to a void pointer first
     */
    if (!(xmlAddChild((xmlNodePtr)(void *)xmlNodeParam, xmlNodeCdata))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not add CDATA to doc root"));
        return -1;
    }

    /* we did it! */
    return 0;
}


/*
 * hypervInvokeMethod:
 * @priv: hypervPrivate object associated with the connection
 * @paramsPtr: pointer to object containing the all necessary information for
 *             method invocation (consumed on invocation)
 * @res: Optional out parameter to contain the response XML.
 *
 * Performs an invocation described by object at @paramsPtr, and optionally
 * returns the XML containing the result.
 *
 * Please note that, object at @paramsPtr is consumed by this function and the
 * pointer is cleared out, regardless of returning success or failure.
 *
 * Returns -1 on failure, 0 on success.
 */
int
hypervInvokeMethod(hypervPrivate *priv,
                   hypervInvokeParamsList **paramsPtr,
                   WsXmlDocH *res)
{
    g_autoptr(hypervInvokeParamsList) params = *paramsPtr;
    size_t i = 0;
    int returnCode;
    g_auto(WsXmlDocH) paramsDocRoot = NULL;
    g_autoptr(client_opt_t) options = NULL;
    g_auto(WsXmlDocH) response = NULL;
    WsXmlNodeH methodNode = NULL;
    g_autofree char *returnValue_xpath = NULL;
    g_autofree char *jobcode_instance_xpath = NULL;
    g_autofree char *returnValue = NULL;
    g_autofree char *instanceID = NULL;
    bool completed = false;
    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
    g_autoptr(Msvm_ConcreteJob) job = NULL;
    int jobState = -1;
    hypervParam *p = NULL;
    int timeout = HYPERV_JOB_TIMEOUT_MS;

    *paramsPtr = NULL;

    if (hypervCreateInvokeXmlDoc(params, &paramsDocRoot) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not create XML document"));
        return -1;
    }

    methodNode = xml_parser_get_root(paramsDocRoot);
    if (!methodNode) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not get root of XML document"));
        return -1;
    }

    /* 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)
                return -1;
            break;
        case HYPERV_EPR_PARAM:
            if (hypervSerializeEprParam(p, priv, params->resourceUri,
                                        &methodNode) < 0)
                return -1;
            break;
        case HYPERV_EMBEDDED_PARAM:
            if (hypervSerializeEmbeddedParam(p, params->resourceUri,
                                             &methodNode) < 0)
                return -1;
            break;
        default:
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Unknown parameter type"));
            return -1;
        }
    }

    /* Invoke the method and get the response */

    options = wsmc_options_init();
    if (!options) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not init options"));
        return -1;
    }
    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 */
    returnValue_xpath = g_strdup_printf("/s:Envelope/s:Body/p:%s_OUTPUT/p:ReturnValue",
                                        params->method);

    returnValue = ws_xml_get_xpath_value(response, returnValue_xpath);
    if (!returnValue) {
        g_autofree char *faultReason_xpath = g_strdup("/s:Envelope/s:Body/s:Fault/s:Reason/s:Text");
        g_autofree char *faultReason = ws_xml_get_xpath_value(response, faultReason_xpath);

        if (faultReason)
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("WS-Management fault during %1$s invocation: %2$s"),
                           params->method, faultReason);
        else
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Could not get return value for %1$s invocation"),
                           params->method);

        return -1;
    }

    if (virStrToLong_i(returnValue, NULL, 10, &returnCode) < 0)
        return -1;

    if (returnCode == CIM_RETURNCODE_TRANSITION_STARTED) {
        jobcode_instance_xpath = g_strdup_printf("/s:Envelope/s:Body/p:%s_OUTPUT/p:Job/a:ReferenceParameters/"
                                                 "w:SelectorSet/w:Selector[@Name='InstanceID']",
                                                 params->method);

        instanceID = ws_xml_get_xpath_value(response, jobcode_instance_xpath);
        if (!instanceID) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Could not get instance ID for %1$s invocation"),
                           params->method);
            return -1;
        }

        /*
         * 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) {
            virBufferEscapeSQL(&query,
                               MSVM_CONCRETEJOB_WQL_SELECT
                               "WHERE InstanceID = '%s'", instanceID);

            if (hypervGetWmiClass(Msvm_ConcreteJob, &job) < 0 || !job)
                return -1;

            jobState = job->data->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((hypervObject *)job);
                job = NULL;
                g_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:
                return -1;
            default:
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("Unknown invocation state"));
                return -1;
            }
        }
        if (!completed && timeout < 0) {
            virReportError(VIR_ERR_OPERATION_TIMEOUT,
                           _("Timeout waiting for %1$s invocation"), params->method);
            return -1;
        }
    } else if (returnCode != CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR) {
        virReportError(VIR_ERR_INTERNAL_ERROR, _("Invocation of %1$s returned an error: %2$s (%3$d)"),
                       params->method, hypervReturnCodeToString(returnCode),
                       returnCode);
        return -1;
    }

    if (res)
        *res = g_steal_pointer(&response);

    return 0;
}

static char*
hypervOutputParamReferenceId(WsXmlNodeH node)
{
    WsXmlNodeH selector_set = NULL;
    WsXmlNodeH selector = NULL;
    size_t i = 0;

    if (node)
        selector_set = ws_xml_find_in_tree(node, XML_NS_WS_MAN, "SelectorSet", TRUE);

    if (selector_set) {
        for (i = 0; (selector = ws_xml_get_child(selector_set, i, XML_NS_WS_MAN, "Selector")) != NULL; i++) {
            if (STREQ_NULLABLE(ws_xml_find_attr_value(selector, NULL, "Name"), "InstanceID")) {
                return ws_xml_get_node_text(selector);
            }
        }
    }
    return NULL;
}


/*
 * hypervResponseGetOutputParam()
 *
 * Extracts an output parameter from a WMI method response and retrieves the
 * referenced object.
 *
 * Handles both synchronous and asynchronous cases. When a method is
 * synchronous, the parameter will be directly present in the response document.
 * When the method returns asynchronously, we need to fetch the result parameter
 * via its associations with the Msvm_ConcreteJob object referenced in the
 * response document.
 *
 * Parameters:
 *   @priv: hypervPrivate object associated with the connection
 *   response: The WMI method response document
 *   paramName: The output parameter name (e.g., "ResultingSnapshot")
 *   paramClass: The WMI class info for the expected result type
 *   outParam: return location for the named output parameter
 *
 * Returns 0 on success, -1 on failure.
 */
int
hypervResponseGetOutputParam(hypervPrivate *priv,
                             WsXmlDocH response,
                             const char *paramName,
                             hypervWmiClassInfo *paramClassInfo,
                             hypervObject **outParam)
{
    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
    WsXmlNodeH body = NULL;
    WsXmlNodeH output_node = NULL;
    char *output_name = NULL;
    char *provider_ns = NULL;
    const char *rv_str = NULL;
    int return_code;
    size_t i = 0;

    body = ws_xml_get_soap_body(response);
    if (!body) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not find SOAP Body in response"));
        return -1;
    }

    /* Find the $(METHOD)_OUTPUT node in the SOAP Body */
    for (i = 0; (output_node = ws_xml_get_child(body, i, NULL, NULL)) != NULL; i++) {
        output_name = ws_xml_get_node_local_name(output_node);
        if (output_name && g_str_has_suffix(output_name, "_OUTPUT"))
            break;
        output_node = NULL;
    }

    if (!output_node) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not find _OUTPUT node in method response"));
        return -1;
    }

    provider_ns = ws_xml_get_node_name_ns(output_node);

    rv_str = ws_xml_get_node_text(ws_xml_get_child(output_node, 0, provider_ns, "ReturnValue"));
    if (!rv_str || virStrToLong_i(rv_str, NULL, 10, &return_code) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not get ReturnValue from method response"));
        return -1;
    }

    if (return_code == CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR) {
        /* if this was a synchronous response, the output parameter should contain
         * the id of an object, so we can simply look it up by its instance ID */
        WsXmlNodeH param_node = ws_xml_get_child(output_node, 0, provider_ns, paramName);
        const char *out_param_id = NULL;

        if (param_node)
            out_param_id = hypervOutputParamReferenceId(param_node);

        if (!out_param_id) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Could not find output parameter '%1$s' in response"),
                           paramName);
            return -1;
        }
        VIR_DEBUG("Method response was synchronous: %1$s = %2$s", paramName, out_param_id);
        virBufferAsprintf(&query, "SELECT * FROM %s ", paramClassInfo->name);
        virBufferEscapeSQL(&query, "WHERE InstanceID='%s'", out_param_id);
    } else if (return_code == CIM_RETURNCODE_TRANSITION_STARTED) {
        /* if this was an asynchronous response, we have to get the output
         * parameter from its association with the async job */
        WsXmlNodeH job_node = ws_xml_get_child(output_node, 0, provider_ns, "Job");
        const char *job_id = NULL;

        if (job_node)
            job_id = hypervOutputParamReferenceId(job_node);

        if (!job_id) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Could not find Job ID in async method response"));
            return -1;
        }
        VIR_DEBUG("Method response was asynchronous. Job ID = %1$s", job_id);
        virBufferEscapeSQL(&query, "ASSOCIATORS OF {Msvm_ConcreteJob.InstanceID='%s'} ", job_id);
        virBufferAsprintf(&query, "WHERE AssocClass = Msvm_AffectedJobElement ResultClass = %s",
                          paramClassInfo->name);
    } else {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unexpected return code %1$d in method response"),
                       return_code);
        return -1;
    }

    return hypervGetWmiClassList(priv, paramClassInfo, &query, outParam);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Object
 */

/* This function guarantees that wqlQuery->query is reset, even on failure */
int
hypervEnumAndPull(hypervPrivate *priv, hypervWqlQuery *wqlQuery,
                  hypervObject **list)
{
    WsSerializerContextH serializerContext;
    g_autoptr(client_opt_t) options = NULL;
    g_autofree char *query_string = NULL;
    hypervWmiClassInfo *wmiInfo = wqlQuery->info;
    g_autoptr(filter_t) filter = NULL;
    g_auto(WsXmlDocH) response = NULL;
    g_autofree char *enumContext = NULL;
    g_autoptr(hypervObject) head = NULL;
    hypervObject *tail = NULL;
    WsXmlNodeH node = NULL;
    hypervObject *object;

    query_string = virBufferContentAndReset(wqlQuery->query);

    if (list == NULL || *list != NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid argument"));
        VIR_FREE(query_string);
        return -1;
    }

    serializerContext = wsmc_get_serialization_context(priv->client);

    options = wsmc_options_init();

    if (options == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not initialize options"));
        return -1;
    }

    filter = filter_create_simple(WSM_WQL_FILTER_DIALECT, query_string);

    if (filter == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not create filter"));
        return -1;
    }

    response = wsmc_action_enumerate(priv->client, wmiInfo->rootUri, options,
                                     filter);

    if (hypervVerifyResponse(priv->client, response, "enumeration") < 0)
        return -1;

    enumContext = wsmc_get_enum_context(response);

    g_clear_pointer(&response, ws_xml_destroy_doc);

    while (enumContext != NULL && *enumContext != '\0') {
        XML_TYPE_PTR data = NULL;

        response = wsmc_action_pull(priv->client, wmiInfo->resourceUri, options,
                                    filter, enumContext);

        if (hypervVerifyResponse(priv->client, response, "pull") < 0)
            return -1;

        node = ws_xml_get_soap_body(response);

        if (node == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Could not lookup SOAP body"));
            return -1;
        }

        node = ws_xml_get_child(node, 0, XML_NS_ENUMERATION, WSENUM_PULL_RESP);

        if (node == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Could not lookup pull response"));
            return -1;
        }

        node = ws_xml_get_child(node, 0, XML_NS_ENUMERATION, WSENUM_ITEMS);

        if (node == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Could not lookup pull response items"));
            return -1;
        }

        if (ws_xml_get_child(node, 0, wmiInfo->resourceUri,
                             wmiInfo->name) == NULL)
            break;

        data = ws_deserialize(serializerContext, node, wmiInfo->serializerInfo,
                              wmiInfo->name, wmiInfo->resourceUri, NULL, 0, 0);

        if (data == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Could not deserialize pull response item"));
            return -1;
        }

        object = g_new0(hypervObject, 1);
        object->info = wmiInfo;
        object->data = data;
        object->priv = priv;

        if (head == NULL) {
            head = object;
        } else {
            tail->next = object;
        }

        tail = object;

        VIR_FREE(enumContext);
        enumContext = wsmc_get_enum_context(response);

        g_clear_pointer(&response, ws_xml_destroy_doc);
    }

    *list = g_steal_pointer(&head);

    return 0;
}


void
hypervFreeObject(void *object)
{
    hypervObject *next;
    WsSerializerContextH serializerContext;

    if (object == NULL)
        return;

    serializerContext = wsmc_get_serialization_context(((hypervObject *)object)->priv->client);

    while (object != NULL) {
        next = ((hypervObject *)object)->next;

        ((hypervObject *)object)->priv = NULL;

        if (ws_serializer_free_mem(serializerContext, ((hypervObject *)object)->data,
                                   ((hypervObject *)object)->info->serializerInfo) < 0) {
            VIR_ERROR(_("Could not free deserialized data"));
        }

        VIR_FREE(object);

        object = next;
    }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * CIM/Msvm_ReturnCode
 */

const char *
hypervReturnCodeToString(int returnCode)
{
    switch (returnCode) {
    case CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR:
        return _("Completed with no error");

    case CIM_RETURNCODE_NOT_SUPPORTED:
        return _("Not supported");

    case CIM_RETURNCODE_UNKNOWN_ERROR:
        return _("Unknown error");

    case CIM_RETURNCODE_CANNOT_COMPLETE_WITHIN_TIMEOUT_PERIOD:
        return _("Cannot complete within timeout period");

    case CIM_RETURNCODE_FAILED:
        return _("Failed");

    case CIM_RETURNCODE_INVALID_PARAMETER:
        return _("Invalid parameter");

    case CIM_RETURNCODE_IN_USE:
        return _("In use");

    case CIM_RETURNCODE_TRANSITION_STARTED:
        return _("Transition started");

    case CIM_RETURNCODE_INVALID_STATE_TRANSITION:
        return _("Invalid state transition");

    case CIM_RETURNCODE_TIMEOUT_PARAMETER_NOT_SUPPORTED:
        return _("Timeout parameter not supported");

    case CIM_RETURNCODE_BUSY:
        return _("Busy");

    case MSVM_RETURNCODE_FAILED:
        return _("Failed");

    case MSVM_RETURNCODE_ACCESS_DENIED:
        return _("Access denied");

    case MSVM_RETURNCODE_NOT_SUPPORTED:
        return _("Not supported");

    case MSVM_RETURNCODE_STATUS_IS_UNKNOWN:
        return _("Status is unknown");

    case MSVM_RETURNCODE_TIMEOUT:
        return _("Timeout");

    case MSVM_RETURNCODE_INVALID_PARAMETER:
        return _("Invalid parameter");

    case MSVM_RETURNCODE_SYSTEM_IS_IN_USE:
        return _("System is in use");

    case MSVM_RETURNCODE_INVALID_STATE_FOR_THIS_OPERATION:
        return _("Invalid state for this operation");

    case MSVM_RETURNCODE_INCORRECT_DATA_TYPE:
        return _("Incorrect data type");

    case MSVM_RETURNCODE_SYSTEM_IS_NOT_AVAILABLE:
        return _("System is not available");

    case MSVM_RETURNCODE_OUT_OF_MEMORY:
        return _("Out of memory");

    default:
        return _("Unknown return code");
    }
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Msvm_ComputerSystem
 */

int
hypervInvokeMsvmComputerSystemRequestStateChange(virDomainPtr domain,
                                                 int requestedState)
{
    hypervPrivate *priv = domain->conn->privateData;
    char uuid_string[VIR_UUID_STRING_BUFLEN];
    g_auto(WsXmlDocH) response = NULL;
    g_autoptr(client_opt_t) options = NULL;
    g_autofree char *selector = NULL;
    g_autofree char *properties = NULL;
    g_autofree char *returnValue = NULL;
    int returnCode;
    g_autofree char *instanceID = NULL;
    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
    g_autoptr(Msvm_ConcreteJob) concreteJob = NULL;
    bool completed = false;

    virUUIDFormat(domain->uuid, uuid_string);

    selector = g_strdup_printf("Name=%s&CreationClassName=Msvm_ComputerSystem", uuid_string);
    properties = g_strdup_printf("RequestedState=%d", requestedState);

    options = wsmc_options_init();

    if (options == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not initialize options"));
        return -1;
    }

    wsmc_add_selectors_from_str(options, selector);
    wsmc_add_prop_from_str(options, properties);

    /* Invoke method */
    response = wsmc_action_invoke(priv->client, MSVM_COMPUTERSYSTEM_RESOURCE_URI,
                                  options, "RequestStateChange", NULL);

    if (hypervVerifyResponse(priv->client, response, "invocation") < 0)
        return -1;

    /* Check return value */
    returnValue = ws_xml_get_xpath_value(response, (char *)"/s:Envelope/s:Body/p:RequestStateChange_OUTPUT/p:ReturnValue");

    if (returnValue == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not lookup %1$s for %2$s invocation"),
                       "ReturnValue", "RequestStateChange");
        return -1;
    }

    if (virStrToLong_i(returnValue, NULL, 10, &returnCode) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not parse return code from '%1$s'"), returnValue);
        return -1;
    }

    if (returnCode == CIM_RETURNCODE_TRANSITION_STARTED) {
        /* Get concrete job object */
        instanceID = ws_xml_get_xpath_value(response, (char *)"/s:Envelope/s:Body/p:RequestStateChange_OUTPUT/p:Job/a:ReferenceParameters/w:SelectorSet/w:Selector[@Name='InstanceID']");

        if (instanceID == NULL) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Could not lookup %1$s for %2$s invocation"),
                           "InstanceID", "RequestStateChange");
            return -1;
        }

        /* FIXME: Poll every 100ms until the job completes or fails. There
         *        seems to be no other way than polling. */
        while (!completed) {
            virBufferAsprintf(&query,
                              MSVM_CONCRETEJOB_WQL_SELECT
                              "WHERE InstanceID = '%s'", instanceID);

            if (hypervGetWmiClass(Msvm_ConcreteJob, &concreteJob) < 0)
                return -1;

            if (concreteJob == NULL) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Could not lookup %1$s for %2$s invocation"),
                               "Msvm_ConcreteJob", "RequestStateChange");
                return -1;
            }

            switch (concreteJob->data->JobState) {
            case MSVM_CONCRETEJOB_JOBSTATE_NEW:
            case MSVM_CONCRETEJOB_JOBSTATE_STARTING:
            case MSVM_CONCRETEJOB_JOBSTATE_RUNNING:
            case MSVM_CONCRETEJOB_JOBSTATE_SHUTTING_DOWN:
                hypervFreeObject((hypervObject *)concreteJob);
                concreteJob = NULL;

                g_usleep(100 * 1000);
                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:
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Concrete job for %1$s invocation is in error state"),
                               "RequestStateChange");
                return -1;

            default:
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("Concrete job for %1$s invocation is in unknown state"),
                               "RequestStateChange");
                return -1;
            }
        }
    } else if (returnCode != CIM_RETURNCODE_COMPLETED_WITH_NO_ERROR) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Invocation of %1$s returned an error: %2$s (%3$d)"),
                       "RequestStateChange", hypervReturnCodeToString(returnCode),
                       returnCode);
        return -1;
    }

    return 0;
}


int
hypervMsvmComputerSystemEnabledStateToDomainState
(Msvm_ComputerSystem *computerSystem)
{
    switch (computerSystem->data->EnabledState) {
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_UNKNOWN:
        return VIR_DOMAIN_NOSTATE;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_ENABLED:
        return VIR_DOMAIN_RUNNING;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_DISABLED:
        return VIR_DOMAIN_SHUTOFF;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_PAUSED:
        return VIR_DOMAIN_PAUSED;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SUSPENDED: /* managed save */
        return VIR_DOMAIN_SHUTOFF;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_STARTING:
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SNAPSHOTTING:
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SAVING:
        return VIR_DOMAIN_RUNNING;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_STOPPING:
        return VIR_DOMAIN_SHUTDOWN;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_PAUSING:
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_RESUMING:
        return VIR_DOMAIN_RUNNING;

    default:
        return VIR_DOMAIN_NOSTATE;
    }
}


bool
hypervIsMsvmComputerSystemActive(Msvm_ComputerSystem *computerSystem,
                                 bool *in_transition)
{
    if (in_transition != NULL)
        *in_transition = false;

    switch (computerSystem->data->EnabledState) {
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_UNKNOWN:
        return false;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_ENABLED:
        return true;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_DISABLED:
        return false;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_PAUSED:
        return true;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SUSPENDED: /* managed save */
        return false;

    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_STARTING:
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SNAPSHOTTING:
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_SAVING:
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_STOPPING:
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_PAUSING:
    case MSVM_COMPUTERSYSTEM_ENABLEDSTATE_RESUMING:
        if (in_transition != NULL)
            *in_transition = true;

        return true;

    default:
        return false;
    }
}


int
hypervMsvmComputerSystemToDomain(virConnectPtr conn,
                                 Msvm_ComputerSystem *computerSystem,
                                 virDomainPtr *domain)
{
    unsigned char uuid[VIR_UUID_BUFLEN];
    int id = -1;

    if (domain == NULL || *domain != NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid argument"));
        return -1;
    }

    if (virUUIDParse(computerSystem->data->Name, uuid) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not parse UUID from string '%1$s'"),
                       computerSystem->data->Name);
        return -1;
    }

    if (hypervIsMsvmComputerSystemActive(computerSystem, NULL))
        id = computerSystem->data->ProcessID;

    *domain = virGetDomain(conn, computerSystem->data->ElementName, uuid, id);

    return *domain ? 0 : -1;
}


int
hypervMsvmComputerSystemFromUUID(hypervPrivate *priv, const char *uuid,
                                 Msvm_ComputerSystem **computerSystem)
{
    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;

    if (!computerSystem || *computerSystem) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid argument"));
        return -1;
    }

    virBufferEscapeSQL(&query,
                       MSVM_COMPUTERSYSTEM_WQL_SELECT
                       "WHERE " MSVM_COMPUTERSYSTEM_WQL_VIRTUAL
                       "AND Name = '%s'", uuid);

    if (hypervGetWmiClass(Msvm_ComputerSystem, computerSystem) < 0)
        return -1;

    if (!*computerSystem) {
        virReportError(VIR_ERR_NO_DOMAIN, _("No domain with UUID %1$s"), uuid);
        return -1;
    }

    return 0;
}


int
hypervMsvmComputerSystemFromDomain(virDomainPtr domain,
                                   Msvm_ComputerSystem **computerSystem)
{
    hypervPrivate *priv = domain->conn->privateData;
    char uuidString[VIR_UUID_STRING_BUFLEN];

    virUUIDFormat(domain->uuid, uuidString);

    return hypervMsvmComputerSystemFromUUID(priv, uuidString, computerSystem);
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Generic "Get WMI class list" helpers
 */

int
hypervGetMsvmVirtualSystemSettingDataFromUUID(hypervPrivate *priv,
                                              const char *uuid_string,
                                              Msvm_VirtualSystemSettingData **list)
{
    g_auto(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 (hypervGetWmiClass(Msvm_VirtualSystemSettingData, list) < 0 || !*list)
        return -1;

    return 0;
}


int
hypervGetDomainSnapshotsSD(hypervPrivate *priv,
                           const char *domain_uuid_string,
                           Msvm_VirtualSystemSettingData **list)
{
    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;

    virBufferAsprintf(&query,
                      "ASSOCIATORS OF {Msvm_ComputerSystem.CreationClassName='Msvm_ComputerSystem',Name='%s'} "
                      "WHERE AssocClass = Msvm_SnapshotOfVirtualSystem "
                      "ResultClass = Msvm_VirtualSystemSettingData",
                      domain_uuid_string);

    if (hypervGetWmiClass(Msvm_VirtualSystemSettingData, list) < 0)
        return -1;

    return 0;
}


#define hypervGetSettingData(type, id, out) \
    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER; \
    virBufferEscapeSQL(&query, \
                       "ASSOCIATORS OF {Msvm_VirtualSystemSettingData.InstanceID='%s'} " \
                       "WHERE AssocClass = Msvm_VirtualSystemSettingDataComponent " \
                       "ResultClass = " #type, \
                       id); \
    if (hypervGetWmiClass(type, out) < 0) \
        return -1


int
hypervGetResourceAllocationSD(hypervPrivate *priv,
                              const char *id,
                              Msvm_ResourceAllocationSettingData **data)
{
    hypervGetSettingData(Msvm_ResourceAllocationSettingData, id, data);

    if (!*data) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not look up resource allocation setting data with virtual system instance ID '%1$s'"),
                       id);
        return -1;
    }

    return 0;
}


int
hypervGetProcessorSD(hypervPrivate *priv,
                     const char *id,
                     Msvm_ProcessorSettingData **data)
{
    hypervGetSettingData(Msvm_ProcessorSettingData, id, data);

    if (!*data) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not look up processor setting data with virtual system instance ID '%1$s'"),
                       id);
        return -1;
    }

    return 0;
}


int
hypervGetMemorySD(hypervPrivate *priv,
                  const char *vssd_instanceid,
                  Msvm_MemorySettingData **list)
{
    hypervGetSettingData(Msvm_MemorySettingData, vssd_instanceid, list);

    if (!*list)
        return -1;

    return 0;
}


int
hypervGetStorageAllocationSD(hypervPrivate *priv,
                             const char *id,
                             Msvm_StorageAllocationSettingData **data)
{
    hypervGetSettingData(Msvm_StorageAllocationSettingData, id, data);
    return 0;
}


int
hypervGetSerialPortSD(hypervPrivate *priv,
                      const char *id,
                      Msvm_SerialPortSettingData **data)
{
    hypervGetSettingData(Msvm_SerialPortSettingData, id, data);
    return 0;
}


int
hypervGetSyntheticEthernetPortSD(hypervPrivate *priv,
                                 const char *id,
                                 Msvm_SyntheticEthernetPortSettingData **data)
{
    hypervGetSettingData(Msvm_SyntheticEthernetPortSettingData, id, data);
    return 0;
}


int
hypervGetEthernetPortAllocationSD(hypervPrivate *priv,
                                  const char *id,
                                  Msvm_EthernetPortAllocationSettingData **data)
{
    hypervGetSettingData(Msvm_EthernetPortAllocationSettingData, id, data);
    return 0;
}


int
hypervImageManagementServiceGetVHDSD(hypervPrivate *priv,
                                     const char *vhdPath,
                                     WsXmlDocH *settingDataDoc)
{
    hypervInvokeParamsList *params = NULL;
    g_auto(WsXmlDocH) response = NULL;
    g_autofree char *settingDataXmlStr = NULL;

    params = hypervCreateInvokeParamsList("GetVirtualHardDiskSettingData",
                                          MSVM_IMAGEMANAGEMENTSERVICE_SELECTOR,
                                          Msvm_ImageManagementService_WmiInfo);
    hypervAddSimpleParam(params, "Path", vhdPath);

    if (hypervInvokeMethod(priv, &params, &response) < 0)
        return -1;

    settingDataXmlStr = ws_xml_get_xpath_value(response,
        (char *)"/s:Envelope/s:Body/p:GetVirtualHardDiskSettingData_OUTPUT/p:SettingData");

    if (!settingDataXmlStr) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not extract SettingData from response for '%1$s'"), vhdPath);
        return -1;
    }

    /* the method returns an embedded CIM-XML document as a string, so we need
     * to parse it as xml */
    *settingDataDoc = ws_xml_read_memory(settingDataXmlStr, strlen(settingDataXmlStr), "UTF-8", 0);
    if (!*settingDataDoc) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not parse VHD SettingData XML for '%1$s'"), vhdPath);
        return -1;
    }

    return 0;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Msvm_VirtualSystemManagementService
 */

int
hypervMsvmVSMSAddResourceSettings(virDomainPtr domain,
                                  GHashTable **resourceSettingsPtr,
                                  hypervWmiClassInfo *wmiInfo,
                                  WsXmlDocH *response)
{
    hypervPrivate *priv = domain->conn->privateData;
    char uuid_string[VIR_UUID_STRING_BUFLEN];
    g_autoptr(Msvm_VirtualSystemSettingData) vssd = NULL;
    g_autoptr(GHashTable) resourceSettings = *resourceSettingsPtr;
    g_autoptr(hypervInvokeParamsList) params = NULL;
    g_auto(virBuffer) eprQuery = VIR_BUFFER_INITIALIZER;

    *resourceSettingsPtr = NULL;

    virUUIDFormat(domain->uuid, uuid_string);

    if (hypervGetMsvmVirtualSystemSettingDataFromUUID(priv, uuid_string, &vssd) < 0)
        return -1;

    virBufferEscapeSQL(&eprQuery,
                       MSVM_VIRTUALSYSTEMSETTINGDATA_WQL_SELECT "WHERE InstanceID='%s'",
                       vssd->data->InstanceID);

    params = hypervCreateInvokeParamsList("AddResourceSettings",
                                          MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR,
                                          Msvm_VirtualSystemManagementService_WmiInfo);

    if (!params)
        return -1;

    hypervAddEprParam(params, "AffectedConfiguration",
                      &eprQuery, Msvm_VirtualSystemSettingData_WmiInfo);

    hypervAddEmbeddedParam(params, "ResourceSettings", &resourceSettings, wmiInfo);

    if (hypervInvokeMethod(priv, &params, response) < 0)
        return -1;

    return 0;
}


int
hypervMsvmVSMSModifyResourceSettings(hypervPrivate *priv,
                                     GHashTable **resourceSettingsPtr,
                                     hypervWmiClassInfo *wmiInfo)
{
    g_autoptr(GHashTable) resourceSettings = *resourceSettingsPtr;
    g_autoptr(hypervInvokeParamsList) params = NULL;

    *resourceSettingsPtr = NULL;

    params = hypervCreateInvokeParamsList("ModifyResourceSettings",
                                          MSVM_VIRTUALSYSTEMMANAGEMENTSERVICE_SELECTOR,
                                          Msvm_VirtualSystemManagementService_WmiInfo);

    if (!params)
        return -1;

    hypervAddEmbeddedParam(params, "ResourceSettings", &resourceSettings, wmiInfo);

    if (hypervInvokeMethod(priv, &params, NULL) < 0)
        return -1;

    return 0;
}


int
hypervGetSecuritySD(hypervPrivate *priv,
                    const char *vssd_instanceid,
                    Msvm_SecuritySettingData **data)
{
    g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;

    virBufferEscapeSQL(&query,
                       "ASSOCIATORS OF {Msvm_VirtualSystemSettingData.InstanceID='%s'} "
                       "WHERE ResultClass = Msvm_SecuritySettingData",
                       vssd_instanceid);

    if (hypervGetWmiClass(Msvm_SecuritySettingData, data) < 0)
        return -1;

    if (!*data) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Could not look up security setting data with virtual system instance ID '%1$s'"),
                       vssd_instanceid);
        return -1;
    }

    return 0;
}
