
/*
 * esx_vi.c: client for the VMware VI API 2.5 to manage ESX hosts
 *
 * Copyright (C) 2009 Matthias Bolte <matthias.bolte@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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#include <config.h>

#define __STDC_FORMAT_MACROS
#include <inttypes.h>

#include <libxml/parser.h>
#include <libxml/xpathInternals.h>

#include "buf.h"
#include "memory.h"
#include "logging.h"
#include "util.h"
#include "uuid.h"
#include "virterror_internal.h"
#include "esx_vi.h"
#include "esx_vi_methods.h"
#include "esx_util.h"

#define VIR_FROM_THIS VIR_FROM_ESX

#define ESX_VI_ERROR(conn, code, fmt...)                                      \
    virReportErrorHelper(conn, VIR_FROM_ESX, code, __FILE__, __FUNCTION__,    \
                         __LINE__, fmt)

#define ESX_VI__SOAP__REQUEST_HEADER                                          \
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"                            \
    "<soapenv:Envelope "                                                      \
      "xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" "          \
      "xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\" "          \
      "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "              \
      "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"                       \
    "<soapenv:Body>"

#define ESX_VI__SOAP__REQUEST_FOOTER                                          \
    "</soapenv:Body>"                                                         \
    "</soapenv:Envelope>"

#define ESX_VI__SOAP__RESPONSE_XPATH(_type)                                   \
    ((char *)"/soapenv:Envelope/soapenv:Body/"                                \
               "vim:"_type"Response/vim:returnval")

#define ESV_VI__XML_TAG__OPEN(_buffer, _element, _type)                       \
    do {                                                                      \
        virBufferAddLit(_buffer, "<");                                        \
        virBufferAdd(_buffer, _element, -1);                                  \
        virBufferAddLit(_buffer, " xmlns=\"urn:vim25\" xsi:type=\"");         \
        virBufferAdd(_buffer, _type, -1);                                     \
        virBufferAddLit(_buffer, "\">");                                      \
    } while (0)

#define ESV_VI__XML_TAG__CLOSE(_buffer, _element)                             \
    do {                                                                      \
        virBufferAddLit(_buffer, "</");                                       \
        virBufferAdd(_buffer, _element, -1);                                  \
        virBufferAddLit(_buffer, ">");                                        \
    } while (0)

#define ESX_VI__TEMPLATE__ALLOC(_type)                                        \
    int                                                                       \
    esxVI_##_type##_Alloc(virConnectPtr conn, esxVI_##_type **ptrptr)         \
    {                                                                         \
        return esxVI_Alloc(conn, (void **)ptrptr, sizeof (esxVI_##_type));    \
    }

#define ESX_VI__TEMPLATE__FREE(_type, _body)                                  \
    void                                                                      \
    esxVI_##_type##_Free(esxVI_##_type **ptrptr)                              \
    {                                                                         \
        esxVI_##_type *item = NULL;                                           \
                                                                              \
        if (ptrptr == NULL || *ptrptr == NULL) {                              \
            return;                                                           \
        }                                                                     \
                                                                              \
        item = *ptrptr;                                                       \
                                                                              \
        _body                                                                 \
                                                                              \
        VIR_FREE(*ptrptr);                                                    \
    }



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Context
 */

/* esxVI_Context_Alloc */
ESX_VI__TEMPLATE__ALLOC(Context);

/* esxVI_Context_Free */
ESX_VI__TEMPLATE__FREE(Context,
{
    VIR_FREE(item->url);

    if (item->curl_handle != NULL) {
        curl_easy_cleanup(item->curl_handle);
    }

    if (item->curl_headers != NULL) {
        curl_slist_free_all(item->curl_headers);
    }

    virMutexDestroy(&item->curl_lock);

    VIR_FREE(item->username);
    VIR_FREE(item->password);
    esxVI_ServiceContent_Free(&item->service);
    esxVI_UserSession_Free(&item->session);
    esxVI_ManagedObjectReference_Free(&item->datacenter);
    esxVI_ManagedObjectReference_Free(&item->vmFolder);
    esxVI_ManagedObjectReference_Free(&item->hostFolder);
    esxVI_SelectionSpec_Free(&item->fullTraversalSpecList);
});

static size_t
_esxVI_CURL_WriteBuffer(char *data, size_t size, size_t nmemb, void *buffer)
{
    if (buffer != NULL) {
        virBufferAdd((virBufferPtr) buffer, data, size * nmemb);

        return size * nmemb;
    }

    return 0;
}

#define ESX_VI__CURL__ENABLE_DEBUG_OUTPUT 0

#if ESX_VI__CURL__ENABLE_DEBUG_OUTPUT
static int
_esxVI_CURL_Debug(CURL *curl ATTRIBUTE_UNUSED, curl_infotype type,
                  char *info, size_t size, void *data ATTRIBUTE_UNUSED)
{
    switch (type) {
      case CURLINFO_TEXT:
        VIR_DEBUG0("CURLINFO_TEXT");
        fwrite(info, 1, size, stderr);
        printf("\n\n");
        break;

      case CURLINFO_HEADER_IN:
        VIR_DEBUG0("CURLINFO_HEADER_IN");
        break;

      case CURLINFO_HEADER_OUT:
        VIR_DEBUG0("CURLINFO_HEADER_OUT");
        break;

      case CURLINFO_DATA_IN:
        VIR_DEBUG0("CURLINFO_DATA_IN");
        break;

      case CURLINFO_DATA_OUT:
        VIR_DEBUG0("CURLINFO_DATA_OUT");
        break;

      default:
        VIR_DEBUG0("unknown");
        break;
    }

    return 0;
}
#endif

int
esxVI_Context_Connect(virConnectPtr conn, esxVI_Context *ctx, const char *url,
                      const char *username, const char *password, int noVerify)
{
    int result = 0;
    esxVI_String *propertyNameList = NULL;
    esxVI_ObjectContent *datacenterList = NULL;
    esxVI_DynamicProperty *dynamicProperty = NULL;

    if (ctx == NULL || url == NULL || username == NULL || password == NULL ||
        ctx->url != NULL || ctx->service != NULL || ctx->curl_handle != NULL ||
        ctx->curl_headers != NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        goto failure;
    }

    if (esxVI_String_DeepCopyValue(conn, &ctx->url, url) < 0) {
        goto failure;
    }

    ctx->curl_handle = curl_easy_init();

    if (ctx->curl_handle == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "Could not initialize CURL");
        goto failure;
    }

    ctx->curl_headers = curl_slist_append(ctx->curl_headers, "Content-Type: "
                                          "text/xml; charset=UTF-8");

    /*
     * Add a dummy expect header to stop CURL from waiting for a response code
     * 100 (Continue) from the server before continuing the POST operation.
     * Waiting for this response would slowdown each communication with the
     * server by approx. 2 sec, because the server doesn't send the expected
     * 100 (Continue) response and the wait times out resulting in wasting
     * approx. 2 sec per POST operation.
     */
    ctx->curl_headers = curl_slist_append(ctx->curl_headers,
                                          "Expect: nothing");

    if (ctx->curl_headers == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "Could not build CURL header list");
        goto failure;
    }

    curl_easy_setopt(ctx->curl_handle, CURLOPT_URL, ctx->url);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_USERAGENT, "libvirt-esx");
    curl_easy_setopt(ctx->curl_handle, CURLOPT_HEADER, 0);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_FOLLOWLOCATION, 1);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_SSL_VERIFYPEER, noVerify ? 0 : 1);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_SSL_VERIFYHOST, noVerify ? 0 : 2);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_COOKIEFILE, "");
    curl_easy_setopt(ctx->curl_handle, CURLOPT_HTTPHEADER, ctx->curl_headers);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_WRITEFUNCTION,
                     _esxVI_CURL_WriteBuffer);
#if ESX_VI__CURL__ENABLE_DEBUG_OUTPUT
    curl_easy_setopt(ctx->curl_handle, CURLOPT_DEBUGFUNCTION,
                     _esxVI_CURL_Debug);
#endif

    if (virMutexInit(&ctx->curl_lock) < 0) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "Could not initialize CURL mutex");
        goto failure;
    }

    ctx->username = strdup(username);
    ctx->password = strdup(password);

    if (ctx->username == NULL || ctx->password == NULL) {
        virReportOOMError(conn);
        goto failure;
    }

    if (esxVI_RetrieveServiceContent(conn, ctx, &ctx->service) < 0) {
        goto failure;
    }

    if (STREQ(ctx->service->about->apiType, "HostAgent") ||
        STREQ(ctx->service->about->apiType, "VirtualCenter")) {
        if (STRPREFIX(ctx->service->about->apiVersion, "2.5")) {
            ctx->apiVersion = 25;
        } else if (STRPREFIX(ctx->service->about->apiVersion, "4.0")) {
            ctx->apiVersion = 40;
        } else {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Expecting VI API major/minor version '2.5' or '4.0' "
                         "but found '%s'", ctx->service->about->apiVersion);
            goto failure;
        }

        if (STRPREFIX(ctx->service->about->version, "3.5")) {
            ctx->serverVersion = 35;
        } else if (STRPREFIX(ctx->service->about->version, "4.0")) {
            ctx->serverVersion = 40;
        } else {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Expecting server major/minor version '3.5' or '4.0' "
                         "but found '%s'", ctx->service->about->version);
            goto failure;
        }
    } else {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "Expecting VI API type 'HostAgent' or 'VirtualCenter' "
                     "but found '%s'", ctx->service->about->apiType);
        goto failure;
    }

    if (esxVI_Login(conn, ctx, username, password, &ctx->session) < 0) {
        goto failure;
    }

    esxVI_BuildFullTraversalSpecList(conn, &ctx->fullTraversalSpecList);

    if (esxVI_String_AppendValueListToList(conn, &propertyNameList,
                                           "vmFolder\0"
                                           "hostFolder\0") < 0) {
        goto failure;
    }

    /* Get pointer to Datacenter for later use */
    if (esxVI_GetObjectContent(conn, ctx, ctx->service->rootFolder,
                               "Datacenter", propertyNameList,
                               esxVI_Boolean_True, &datacenterList) < 0) {
        goto failure;
    }

    if (datacenterList == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "Could not retrieve the 'datacenter' object from the VI "
                     "host/center");
        goto failure;
    }

    ctx->datacenter = datacenterList->obj;
    datacenterList->obj = NULL;

    /* Get pointer to vmFolder and hostFolder for later use */
    for (dynamicProperty = datacenterList->propSet; dynamicProperty != NULL;
         dynamicProperty = dynamicProperty->_next) {
        if (STREQ(dynamicProperty->name, "vmFolder")) {
            if (esxVI_ManagedObjectReference_CastFromAnyType
                  (conn, dynamicProperty->val, &ctx->vmFolder, "Folder")) {
                goto failure;
            }
        } else if (STREQ(dynamicProperty->name, "hostFolder")) {
            if (esxVI_ManagedObjectReference_CastFromAnyType
                  (conn, dynamicProperty->val, &ctx->hostFolder, "Folder")) {
                goto failure;
            }
        } else {
            VIR_WARN("Unexpected '%s' property", dynamicProperty->name);
        }
    }

    if (ctx->vmFolder == NULL || ctx->hostFolder == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "The 'datacenter' object is missing the "
                     "'vmFolder'/'hostFolder' propoerty");
        goto failure;
    }

  cleanup:
    esxVI_String_Free(&propertyNameList);
    esxVI_ObjectContent_Free(&datacenterList);

    return result;

  failure:
    result = -1;

    goto cleanup;
}

int
esxVI_Context_Download(virConnectPtr conn, esxVI_Context *ctx, const char *url,
                       char **content)
{
    virBuffer buffer = VIR_BUFFER_INITIALIZER;
    CURLcode error_code;
    long response_code;

    if (content == NULL || *content != NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        goto failure;
    }

    virMutexLock(&ctx->curl_lock);

    curl_easy_setopt(ctx->curl_handle, CURLOPT_URL, url);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_WRITEDATA, &buffer);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_HTTPGET, 1);

    error_code = curl_easy_perform(ctx->curl_handle);

    if (error_code != CURLE_OK) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "curl_easy_perform() returned an error: %s (%d)",
                     curl_easy_strerror(error_code), error_code);
        goto unlock;
    }

    error_code = curl_easy_getinfo(ctx->curl_handle, CURLINFO_RESPONSE_CODE,
                                   &response_code);

    if (error_code != CURLE_OK) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "curl_easy_getinfo() returned an error: %s (%d)",
                     curl_easy_strerror(error_code), error_code);
        goto unlock;
    }

    virMutexUnlock(&ctx->curl_lock);

    if (virBufferError(&buffer)) {
        virReportOOMError(conn);
        goto failure;
    }

    *content = virBufferContentAndReset(&buffer);

    if (response_code != 200) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "HTTP response code %d", (int)response_code);
        goto failure;
    }

    return 0;

  failure:
    free(virBufferContentAndReset(&buffer));

    return -1;

  unlock:
    virMutexUnlock(&ctx->curl_lock);

    goto failure;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * RemoteRequest
 */

/* esxVI_RemoteRequest_Alloc */
ESX_VI__TEMPLATE__ALLOC(RemoteRequest);

/* esxVI_RemoteRequest_Free */
ESX_VI__TEMPLATE__FREE(RemoteRequest,
{
    VIR_FREE(item->request);
    VIR_FREE(item->xpathExpression);
});

int
esxVI_RemoteRequest_Execute(virConnectPtr conn, esxVI_Context *ctx,
                            esxVI_RemoteRequest *remoteRequest,
                            esxVI_RemoteResponse **remoteResponse)
{
    virBuffer buffer = VIR_BUFFER_INITIALIZER;
    esxVI_Fault *fault = NULL;
    CURLcode error_code;

    if (remoteRequest == NULL || remoteRequest->request == NULL ||
        remoteResponse == NULL || *remoteResponse != NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        goto failure;
    }

    if (esxVI_RemoteResponse_Alloc(conn, remoteResponse) < 0) {
        goto failure;
    }

    virMutexLock(&ctx->curl_lock);

    curl_easy_setopt(ctx->curl_handle, CURLOPT_URL, ctx->url);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_WRITEDATA, &buffer);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_POSTFIELDS,
                     remoteRequest->request);
    curl_easy_setopt(ctx->curl_handle, CURLOPT_POSTFIELDSIZE,
                     strlen(remoteRequest->request));

    error_code = curl_easy_perform(ctx->curl_handle);

    if (error_code != CURLE_OK) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "curl_easy_perform() returned an error: %s (%d)",
                     curl_easy_strerror(error_code), error_code);
        goto unlock;
    }

    error_code = curl_easy_getinfo(ctx->curl_handle, CURLINFO_RESPONSE_CODE,
                                   &(*remoteResponse)->response_code);

    if (error_code != CURLE_OK) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "curl_easy_getinfo() returned an error: %s (%d)",
                     curl_easy_strerror(error_code), error_code);
        goto unlock;
    }

    virMutexUnlock(&ctx->curl_lock);

    if (virBufferError(&buffer)) {
        virReportOOMError(conn);
        goto failure;
    }

    (*remoteResponse)->response = virBufferContentAndReset(&buffer);

    if ((*remoteResponse)->response_code == 500 ||
        (remoteRequest->xpathExpression != NULL &&
         (*remoteResponse)->response_code == 200)) {
        (*remoteResponse)->document =
          xmlReadDoc(BAD_CAST(*remoteResponse)->response, "", NULL,
                     XML_PARSE_NONET);

        if ((*remoteResponse)->document == NULL) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Could not parse XML response");
            goto failure;
        }

        if (xmlDocGetRootElement((*remoteResponse)->document) == NULL) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "XML response is an empty document");
            goto failure;
        }

        (*remoteResponse)->xpathContext =
            xmlXPathNewContext((*remoteResponse)->document);

        if ((*remoteResponse)->xpathContext == NULL) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Could not create XPath context");
            goto failure;
        }

        xmlXPathRegisterNs((*remoteResponse)->xpathContext, BAD_CAST "soapenv",
                           BAD_CAST "http://schemas.xmlsoap.org/soap/envelope/");
        xmlXPathRegisterNs((*remoteResponse)->xpathContext, BAD_CAST "vim",
                           BAD_CAST "urn:vim25");

        if ((*remoteResponse)->response_code == 500) {
            (*remoteResponse)->xpathObject =
                xmlXPathEval(BAD_CAST
                             "/soapenv:Envelope/soapenv:Body/soapenv:Fault",
                             (*remoteResponse)->xpathContext);

            if (esxVI_RemoteResponse_DeserializeXPathObject
                  (conn, *remoteResponse,
                   (esxVI_RemoteResponse_DeserializeFunc)
                     esxVI_Fault_Deserialize,
                   (void **)&fault) < 0) {
                goto failure;
            }

            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "HTTP response code %d. VI Fault: %s - %s",
                         (int)(*remoteResponse)->response_code,
                         fault->faultcode, fault->faultstring);

            goto failure;
        } else {
            (*remoteResponse)->xpathObject =
              xmlXPathEval(BAD_CAST remoteRequest->xpathExpression,
                           (*remoteResponse)->xpathContext);
        }
    } else if ((*remoteResponse)->response_code != 200) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "HTTP response code %d",
                     (int)(*remoteResponse)->response_code);

        goto failure;
    }

    return 0;

  failure:
    free(virBufferContentAndReset(&buffer));
    esxVI_RemoteResponse_Free(remoteResponse);
    esxVI_Fault_Free(&fault);

    return -1;

  unlock:
    virMutexUnlock(&ctx->curl_lock);

    goto failure;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * RemoteResponse
 */

/* esxVI_RemoteResponse_Alloc */
ESX_VI__TEMPLATE__ALLOC(RemoteResponse);

/* esxVI_RemoteResponse_Free */
ESX_VI__TEMPLATE__FREE(RemoteResponse,
{
    VIR_FREE(item->response);

    xmlXPathFreeObject(item->xpathObject);
    xmlXPathFreeContext(item->xpathContext);

    if (item->document != NULL) {
        xmlFreeDoc(item->document);
    }
});

int
esxVI_RemoteResponse_DeserializeXPathObject
  (virConnectPtr conn, esxVI_RemoteResponse *remoteResponse,
   esxVI_RemoteResponse_DeserializeFunc deserializeFunc, void **item)
{
    xmlNodePtr node = NULL;

    if (remoteResponse->xpathObject != NULL &&
        remoteResponse->xpathObject->type == XPATH_NODESET) {
        if (remoteResponse->xpathObject->nodesetval->nodeNr != 1) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Expecting 1 XML node but found '%d'",
                         remoteResponse->xpathObject->nodesetval->nodeNr);
            return -1;
        }

        node = remoteResponse->xpathObject->nodesetval->nodeTab[0];

        if (node->type != XML_ELEMENT_NODE) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Wrong XML element type %d", node->type);
            return -1;
        }

        if (deserializeFunc(conn, node, item) < 0) {
            return -1;
        }
    } else {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "XPath error");
        return -1;
    }

    return 0;
}

int
esxVI_RemoteResponse_DeserializeXPathObjectList
  (virConnectPtr conn, esxVI_RemoteResponse *remoteResponse,
   esxVI_RemoteResponse_DeserializeListFunc deserializeListFunc,
   esxVI_List **list)
{
    xmlNodePtr node = NULL;

    if (remoteResponse->xpathObject != NULL &&
        remoteResponse->xpathObject->type == XPATH_NODESET) {
        if (remoteResponse->xpathObject->nodesetval->nodeNr > 0) {
            node = remoteResponse->xpathObject->nodesetval->nodeTab[0];

            if (node->type != XML_ELEMENT_NODE) {
                ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                             "Wrong XML element type %d", node->type);
                return -1;
            }
        } else {
            node = NULL;
        }

        if (deserializeListFunc(conn, node, list) < 0) {
            return -1;
        }
    } else {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "XPath error");
        return -1;
    }

    return 0;
}

int
esxVI_RemoteResponse_DeserializeXPathObjectAsManagedObjectReference
  (virConnectPtr conn, esxVI_RemoteResponse *remoteResponse,
   esxVI_ManagedObjectReference **managedObjectReference,
   const char *expectedType)
{
    xmlNodePtr node = NULL;

    if (remoteResponse->xpathObject != NULL &&
        remoteResponse->xpathObject->type == XPATH_NODESET) {
        if (remoteResponse->xpathObject->nodesetval->nodeNr != 1) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Expecting 1 XML node but found '%d'",
                         remoteResponse->xpathObject->nodesetval->nodeNr);
            return -1;
        }

        node = remoteResponse->xpathObject->nodesetval->nodeTab[0];

        if (node->type != XML_ELEMENT_NODE) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Wrong XML element type %d", node->type);
            return -1;
        }

        if (esxVI_ManagedObjectReference_Deserialize(conn, node,
                                                     managedObjectReference,
                                                     expectedType) < 0) {
            return -1;
        }
    } else {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "XPath error");
        return -1;
    }

    return 0;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Enumeration
 */

int
esxVI_Enumeration_CastFromAnyType(virConnectPtr conn,
                                  const esxVI_Enumeration *enumeration,
                                  esxVI_AnyType *anyType, int *value)
{
    int i;

    if (anyType == NULL || value == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    *value = 0; /* undefined */

    if (STRNEQ(anyType->other, enumeration->type)) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "Expecting type '%s' but found '%s'", enumeration->type,
                     anyType->other);
        return -1;
    }

    for (i = 0; enumeration->values[i].name != NULL; ++i) {
        if (STREQ(anyType->value, enumeration->values[i].name)) {
            *value = enumeration->values[i].value;
            return 0;
        }
    }

    ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                 "Unknown value '%s' for %s", anyType->value,
                 enumeration->type);

    return -1;
}

int
esxVI_Enumeration_Serialize(virConnectPtr conn,
                            const esxVI_Enumeration *enumeration,
                            int value, const char *element,
                            virBufferPtr output, esxVI_Boolean required)
{
    int i;
    const char *name = NULL;

    if (element == NULL || output == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (value == 0) { /* undefined */
        return esxVI_CheckSerializationNecessity(conn, element, required);
    }

    for (i = 0; enumeration->values[i].name != NULL; ++i) {
        if (value == enumeration->values[i].value) {
            name = enumeration->values[i].name;
            break;
        }
    }

    if (name == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    ESV_VI__XML_TAG__OPEN(output, element, enumeration->type);

    virBufferAdd(output, name, -1);

    ESV_VI__XML_TAG__CLOSE(output, element);

    return 0;
}

int
esxVI_Enumeration_Deserialize(virConnectPtr conn,
                              const esxVI_Enumeration *enumeration,
                              xmlNodePtr node, int *value)
{
    int i;
    int result = 0;
    char *name = NULL;

    if (value == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        goto failure;
    }

    *value = 0; /* undefined */

    if (esxVI_String_DeserializeValue(conn, node, &name) < 0) {
        goto failure;
    }

    for (i = 0; enumeration->values[i].name != NULL; ++i) {
        if (STREQ(name, enumeration->values[i].name)) {
            *value = enumeration->values[i].value;
            goto cleanup;
        }
    }

    ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Unknown value '%s' for %s",
                 name, enumeration->type);

  cleanup:
    VIR_FREE(name);

    return result;

  failure:
    goto cleanup;

    result = -1;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * List
 */

int
esxVI_List_Append(virConnectPtr conn, esxVI_List **list, esxVI_List *item)
{
    esxVI_List *next = NULL;

    if (list == NULL || item == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (*list == NULL) {
        *list = item;
        return 0;
    }

    next = *list;

    while (next->_next != NULL) {
        next = next->_next;
    }

    next->_next = item;

    return 0;
}

int
esxVI_List_DeepCopy(virConnectPtr conn, esxVI_List **destList,
                    esxVI_List *srcList,
                    esxVI_List_DeepCopyFunc deepCopyFunc,
                    esxVI_List_FreeFunc freeFunc)
{
    esxVI_List *dest = NULL;
    esxVI_List *src = NULL;

    if (destList == NULL || *destList != NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        goto failure;
    }

    for (src = srcList; src != NULL; src = src->_next) {
        if (deepCopyFunc(conn, &dest, src) < 0 ||
            esxVI_List_Append(conn, destList, dest) < 0) {
            goto failure;
        }

        dest = NULL;
    }

    return 0;

  failure:
    freeFunc(&dest);
    freeFunc(destList);

    return -1;
}

int
esxVI_List_Serialize(virConnectPtr conn, esxVI_List *list, const char *element,
                     virBufferPtr output, esxVI_Boolean required,
                     esxVI_List_SerializeFunc serializeFunc)
{
    esxVI_List *item = NULL;

    if (element == NULL || output == NULL || serializeFunc == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (list == NULL) {
        return esxVI_CheckSerializationNecessity(conn, element, required);
    }

    for (item = list; item != NULL; item = item->_next) {
        if (serializeFunc(conn, item, element, output,
                          esxVI_Boolean_True) < 0) {
            return -1;
        }
    }

    return 0;
}

int
esxVI_List_Deserialize(virConnectPtr conn, xmlNodePtr node, esxVI_List **list,
                       esxVI_List_DeserializeFunc deserializeFunc,
                       esxVI_List_FreeFunc freeFunc)
{
    esxVI_List *item = NULL;

    if (list == NULL || *list != NULL ||
        deserializeFunc == NULL || freeFunc == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (node == NULL) {
        return 0;
    }

    for (; node != NULL; node = node->next) {
        if (node->type != XML_ELEMENT_NODE) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Wrong XML element type %d", node->type);
            goto failure;
        }

        item = NULL;

        if (deserializeFunc(conn, node, &item) < 0) {
            goto failure;
        }

        if (esxVI_List_Append(conn, list, item) < 0) {
            goto failure;
        }
    }

    return 0;

  failure:
    freeFunc(list);

    return -1;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Utility and Convenience Functions
 */

int
esxVI_Alloc(virConnectPtr conn, void **ptrptr, size_t size)
{
    if (ptrptr == NULL || *ptrptr != NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (virAllocN(ptrptr, size, 1) < 0) {
        virReportOOMError(conn);
        return -1;
    }

    return 0;
}

int
esxVI_CheckSerializationNecessity(virConnectPtr conn, const char *element,
                                  esxVI_Boolean required)
{
    if (element == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (required == esxVI_Boolean_True) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "Required property missing while trying to serialize "
                     "'%s'", element);
        return -1;
    } else {
        return 0;
    }
}



int
esxVI_BuildFullTraversalSpecItem(virConnectPtr conn,
                                 esxVI_SelectionSpec **fullTraversalSpecList,
                                 const char *name, const char *type,
                                 const char *path, const char *selectSetNames)
{
    esxVI_TraversalSpec *traversalSpec = NULL;
    esxVI_SelectionSpec *selectionSpec = NULL;
    const char *currentSelectSetName = NULL;

    if (fullTraversalSpecList == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (esxVI_TraversalSpec_Alloc(conn, &traversalSpec) < 0 ||
        esxVI_String_DeepCopyValue(conn, &traversalSpec->_base->name,
                                   name) < 0 ||
        esxVI_String_DeepCopyValue(conn, &traversalSpec->type, type) < 0 ||
        esxVI_String_DeepCopyValue(conn, &traversalSpec->path, path) < 0) {
        goto failure;
    }

    traversalSpec->skip = esxVI_Boolean_False;

    if (selectSetNames != NULL) {
        currentSelectSetName = selectSetNames;

        while (currentSelectSetName != NULL && *currentSelectSetName != '\0') {
            selectionSpec = NULL;

            if (esxVI_SelectionSpec_Alloc(conn, &selectionSpec) < 0 ||
                esxVI_String_DeepCopyValue(conn, &selectionSpec->name,
                                           currentSelectSetName) < 0 ||
                esxVI_SelectionSpec_AppendToList(conn,
                                                 &traversalSpec->selectSet,
                                                 selectionSpec) < 0) {
                goto failure;
            }

            currentSelectSetName += strlen(currentSelectSetName) + 1;
        }
    }

    if (esxVI_SelectionSpec_AppendToList(conn, fullTraversalSpecList,
                                         traversalSpec->_base) < 0) {
        goto failure;
    }

    return 0;

  failure:
    esxVI_TraversalSpec_Free(&traversalSpec);

    return -1;
}



int
esxVI_BuildFullTraversalSpecList(virConnectPtr conn,
                                 esxVI_SelectionSpec **fullTraversalSpecList)
{
    if (fullTraversalSpecList == NULL || *fullTraversalSpecList != NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (esxVI_BuildFullTraversalSpecItem(conn, fullTraversalSpecList,
                                         "visitFolders",
                                         "Folder", "childEntity",
                                         "visitFolders\0"
                                         "datacenterToVmFolder\0"
                                         "datacenterToHostFolder\0"
                                         "computeResourceToHost\0"
                                         "computeResourceToResourcePool\0"
                                         "HostSystemToVm\0"
                                         "resourcePoolToVm\0") < 0) {
        goto failure;
    }

    /* Traversal through vmFolder branch */
    if (esxVI_BuildFullTraversalSpecItem(conn, fullTraversalSpecList,
                                         "datacenterToVmFolder",
                                         "Datacenter", "vmFolder",
                                         "visitFolders\0") < 0) {
        goto failure;
    }

    /* Traversal through hostFolder branch  */
    if (esxVI_BuildFullTraversalSpecItem(conn, fullTraversalSpecList,
                                         "datacenterToHostFolder",
                                         "Datacenter", "hostFolder",
                                         "visitFolders\0") < 0) {
        goto failure;
    }

    /* Traversal through host branch  */
    if (esxVI_BuildFullTraversalSpecItem(conn, fullTraversalSpecList,
                                         "computeResourceToHost",
                                         "ComputeResource", "host",
                                         NULL) < 0) {
        goto failure;
    }

    /* Traversal through resourcePool branch */
    if (esxVI_BuildFullTraversalSpecItem(conn, fullTraversalSpecList,
                                         "computeResourceToResourcePool",
                                         "ComputeResource", "resourcePool",
                                         "resourcePoolToResourcePool\0"
                                         "resourcePoolToVm\0") < 0) {
        goto failure;
    }

    /* Recurse through all resource pools */
    if (esxVI_BuildFullTraversalSpecItem(conn, fullTraversalSpecList,
                                         "resourcePoolToResourcePool",
                                         "ResourcePool", "resourcePool",
                                         "resourcePoolToResourcePool\0"
                                         "resourcePoolToVm\0") < 0) {
        goto failure;
    }

    /* Recurse through all hosts */
    if (esxVI_BuildFullTraversalSpecItem(conn, fullTraversalSpecList,
                                         "HostSystemToVm",
                                         "HostSystem", "vm",
                                         "visitFolders\0") < 0) {
        goto failure;
    }

    /* Recurse through all resource pools */
    if (esxVI_BuildFullTraversalSpecItem(conn, fullTraversalSpecList,
                                         "resourcePoolToVm",
                                         "ResourcePool", "vm", NULL) < 0) {
        goto failure;
    }

    return 0;

  failure:
    esxVI_SelectionSpec_Free(fullTraversalSpecList);

    return -1;
}



/*
 * Can't use the SessionIsActive() function here, because at least
 * 'ESX Server 3.5.0 build-64607' returns an 'method not implemented' fault if
 * you try to call it. Query the session manager for the current session of
 * this connection instead and re-login if there is no current session for this
 * connection.
 */
#define ESX_VI_USE_SESSION_IS_ACTIVE 0

int
esxVI_EnsureSession(virConnectPtr conn, esxVI_Context *ctx)
{
    int result = 0;
#if ESX_VI_USE_SESSION_IS_ACTIVE
    esxVI_Boolean active = esxVI_Boolean_Undefined;
#else
    esxVI_String *propertyNameList = NULL;
    esxVI_ObjectContent *sessionManager = NULL;
    esxVI_DynamicProperty *dynamicProperty = NULL;
    esxVI_UserSession *currentSession = NULL;
#endif

    if (ctx->session == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid call");
        return -1;
    }

#if ESX_VI_USE_SESSION_IS_ACTIVE
    if (esxVI_SessionIsActive(conn, ctx, ctx->session->key,
                              ctx->session->userName, &active) < 0) {
        return -1;
    }

    if (active != esxVI_Boolean_True) {
        esxVI_UserSession_Free(&ctx->session);

        if (esxVI_Login(conn, ctx, ctx->username, ctx->password,
                        &ctx->session) < 0) {
            return -1;
        }
    }
#else
    if (esxVI_String_AppendValueToList(conn, &propertyNameList,
                                       "currentSession") < 0 ||
        esxVI_GetObjectContent(conn, ctx, ctx->service->sessionManager,
                               "SessionManager", propertyNameList,
                               esxVI_Boolean_False, &sessionManager) < 0) {
        goto failure;
    }

    for (dynamicProperty = sessionManager->propSet; dynamicProperty != NULL;
         dynamicProperty = dynamicProperty->_next) {
        if (STREQ(dynamicProperty->name, "currentSession")) {
            if (esxVI_UserSession_CastFromAnyType(conn, dynamicProperty->val,
                                                  &currentSession) < 0) {
                goto failure;
            }

            break;
        } else {
            VIR_WARN("Unexpected '%s' property", dynamicProperty->name);
        }
    }

    if (currentSession == NULL) {
        esxVI_UserSession_Free(&ctx->session);

        if (esxVI_Login(conn, ctx, ctx->username, ctx->password,
                        &ctx->session) < 0) {
            goto failure;
        }
    } else if (STRNEQ(ctx->session->key, currentSession->key)) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "Key of the current session differs from the key at "
                     "last login");
        goto failure;
    }
#endif

  cleanup:
#if ESX_VI_USE_SESSION_IS_ACTIVE
    /* nothing */
#else
    esxVI_String_Free(&propertyNameList);
    esxVI_ObjectContent_Free(&sessionManager);
    esxVI_UserSession_Free(&currentSession);
#endif

    return result;

  failure:
    result = -1;

    goto cleanup;

    return 0;
}



int
esxVI_GetObjectContent(virConnectPtr conn, esxVI_Context *ctx,
                       esxVI_ManagedObjectReference *root,
                       const char *type, esxVI_String *propertyNameList,
                       esxVI_Boolean recurse,
                       esxVI_ObjectContent **objectContentList)
{
    int result = 0;
    esxVI_ObjectSpec *objectSpec = NULL;
    esxVI_PropertySpec *propertySpec = NULL;
    esxVI_PropertyFilterSpec *propertyFilterSpec = NULL;

    if (ctx->fullTraversalSpecList == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid call");
        return -1;
    }

    if (esxVI_ObjectSpec_Alloc(conn, &objectSpec) < 0) {
        goto failure;
    }

    objectSpec->obj = root;
    objectSpec->skip = esxVI_Boolean_False;

    if (recurse == esxVI_Boolean_True) {
        objectSpec->selectSet = ctx->fullTraversalSpecList;
    }

    if (esxVI_PropertySpec_Alloc(conn, &propertySpec) < 0) {
        goto failure;
    }

    propertySpec->type = (char *)type;
    propertySpec->pathSet = propertyNameList;

    if (esxVI_PropertyFilterSpec_Alloc(conn, &propertyFilterSpec) < 0 ||
        esxVI_PropertySpec_AppendToList(conn, &propertyFilterSpec->propSet,
                                        propertySpec) < 0 ||
        esxVI_ObjectSpec_AppendToList(conn, &propertyFilterSpec->objectSet,
                                      objectSpec) < 0) {
        goto failure;
    }

    result = esxVI_RetrieveProperties(conn, ctx, propertyFilterSpec,
                                      objectContentList);

  cleanup:
    /*
     * Remove values given by the caller from the data structures to prevent
     * them from being freed by the call to esxVI_PropertyFilterSpec_Free().
     */
    if (objectSpec != NULL) {
        objectSpec->obj = NULL;
        objectSpec->selectSet = NULL;
    }

    if (propertySpec != NULL) {
        propertySpec->type = NULL;
        propertySpec->pathSet = NULL;
    }

    esxVI_PropertyFilterSpec_Free(&propertyFilterSpec);

    return result;

  failure:
    result = -1;

    goto cleanup;
}



int
esxVI_GetVirtualMachinePowerState(virConnectPtr conn,
                                  esxVI_ObjectContent *virtualMachine,
                                  esxVI_VirtualMachinePowerState *powerState)
{
    esxVI_DynamicProperty *dynamicProperty;

    for (dynamicProperty = virtualMachine->propSet; dynamicProperty != NULL;
         dynamicProperty = dynamicProperty->_next) {
        if (STREQ(dynamicProperty->name, "runtime.powerState")) {
            return esxVI_VirtualMachinePowerState_CastFromAnyType
                     (conn, dynamicProperty->val, powerState);
        }
    }

    ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                 "Missing 'runtime.powerState' property");

    return -1;
}



int
esxVI_GetNumberOfDomainsByPowerState(virConnectPtr conn, esxVI_Context *ctx,
                                     esxVI_VirtualMachinePowerState powerState,
                                     esxVI_Boolean inverse)
{
    esxVI_String *propertyNameList = NULL;
    esxVI_ObjectContent *virtualMachineList = NULL;
    esxVI_ObjectContent *virtualMachine = NULL;
    esxVI_DynamicProperty *dynamicProperty = NULL;
    esxVI_VirtualMachinePowerState powerState_;
    int numberOfDomains = 0;

    if (esxVI_String_AppendValueToList(conn, &propertyNameList,
                                       "runtime.powerState") < 0 ||
        esxVI_GetObjectContent(conn, ctx, ctx->vmFolder, "VirtualMachine",
                               propertyNameList, esxVI_Boolean_True,
                               &virtualMachineList) < 0) {
        goto failure;
    }

    for (virtualMachine = virtualMachineList; virtualMachine != NULL;
         virtualMachine = virtualMachine->_next) {
        for (dynamicProperty = virtualMachine->propSet;
             dynamicProperty != NULL;
             dynamicProperty = dynamicProperty->_next) {
            if (STREQ(dynamicProperty->name, "runtime.powerState")) {
                if (esxVI_VirtualMachinePowerState_CastFromAnyType
                      (conn, dynamicProperty->val, &powerState_) < 0) {
                    goto failure;
                }

                if ((inverse != esxVI_Boolean_True &&
                     powerState_ == powerState) ||
                    (inverse == esxVI_Boolean_True &&
                     powerState_ != powerState)) {
                    numberOfDomains++;
                }
            } else {
                VIR_WARN("Unexpected '%s' property", dynamicProperty->name);
            }
        }
    }

  cleanup:
    esxVI_String_Free(&propertyNameList);
    esxVI_ObjectContent_Free(&virtualMachineList);

    return numberOfDomains;

  failure:
    numberOfDomains = -1;

    goto cleanup;
}



int
esxVI_GetVirtualMachineIdentity(virConnectPtr conn,
                                esxVI_ObjectContent *virtualMachine,
                                int *id, char **name, unsigned char *uuid)
{
    const char *uuid_string = NULL;
    esxVI_DynamicProperty *dynamicProperty = NULL;

    if (STRNEQ(virtualMachine->obj->type, "VirtualMachine")) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                     "ObjectContent does not reference a virtual machine");
        return -1;
    }

    if (id != NULL) {
        if (esxUtil_ParseVirtualMachineIDString
              (virtualMachine->obj->value, id) < 0 || *id <= 0) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Could not parse positive integer from '%s'",
                         virtualMachine->obj->value);
            goto failure;
        }
    }

    if (name != NULL) {
        if (*name != NULL) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
            goto failure;
        }

        for (dynamicProperty = virtualMachine->propSet;
             dynamicProperty != NULL;
             dynamicProperty = dynamicProperty->_next) {
            if (STREQ(dynamicProperty->name, "name")) {
                if (esxVI_AnyType_ExpectType(conn, dynamicProperty->val,
                                             esxVI_Type_String) < 0) {
                    goto failure;
                }

                *name = strdup(dynamicProperty->val->string);

                if (*name == NULL) {
                    virReportOOMError(conn);
                    goto failure;
                }

                break;
            }
        }

        if (*name == NULL) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Could not get name of virtual machine");
            goto failure;
        }
    }

    if (uuid != NULL) {
        for (dynamicProperty = virtualMachine->propSet;
             dynamicProperty != NULL;
             dynamicProperty = dynamicProperty->_next) {
            if (STREQ(dynamicProperty->name, "summary.config.uuid")) {
                if (esxVI_AnyType_ExpectType(conn, dynamicProperty->val,
                                             esxVI_Type_String) < 0) {
                    goto failure;
                }

                uuid_string = dynamicProperty->val->string;
                break;
            }
        }

        if (uuid_string == NULL) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Could not get UUID of virtual machine");
            goto failure;
        }

        if (virUUIDParse(uuid_string, uuid) < 0) {
            ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR,
                         "Could not parse UUID from string '%s'", uuid_string);
            goto failure;
        }
    }

    return 0;

  failure:
    if (name != NULL) {
        VIR_FREE(*name);
    }

    return -1;
}



int
esxVI_LookupHostSystemByIp(virConnectPtr conn, esxVI_Context *ctx,
                           const char *ip, esxVI_String *propertyNameList,
                           esxVI_ObjectContent **hostSystem)
{
    int result = 0;
    esxVI_ManagedObjectReference *managedObjectReference = NULL;

    if (hostSystem == NULL || *hostSystem != NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (esxVI_FindByIp(conn, ctx, ctx->datacenter, ip, esxVI_Boolean_False,
                       &managedObjectReference) < 0) {
        goto failure;
    }

    if (esxVI_GetObjectContent(conn, ctx, managedObjectReference,
                               "HostSystem", propertyNameList,
                               esxVI_Boolean_False, hostSystem) < 0) {
        goto failure;
    }

  cleanup:
    esxVI_ManagedObjectReference_Free(&managedObjectReference);

    return result;

  failure:
    result = -1;

    goto cleanup;
}



int
esxVI_LookupVirtualMachineByUuid(virConnectPtr conn, esxVI_Context *ctx,
                                 const unsigned char *uuid,
                                 esxVI_String *propertyNameList,
                                 esxVI_ObjectContent **virtualMachine)
{
    int result = 0;
    esxVI_ManagedObjectReference *managedObjectReference = NULL;

    if (virtualMachine == NULL || *virtualMachine != NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    if (esxVI_FindByUuid(conn, ctx, ctx->datacenter, uuid, esxVI_Boolean_True,
                         &managedObjectReference) < 0) {
        goto failure;
    }

    if (esxVI_GetObjectContent(conn, ctx, managedObjectReference,
                               "VirtualMachine", propertyNameList,
                               esxVI_Boolean_False, virtualMachine) < 0) {
        goto failure;
    }

  cleanup:
    esxVI_ManagedObjectReference_Free(&managedObjectReference);

    return result;

  failure:
    result = -1;

    goto cleanup;
}



int
esxVI_StartVirtualMachineTask(virConnectPtr conn, esxVI_Context *ctx,
                              const char *name, const char *request,
                              esxVI_ManagedObjectReference **task)
{
    int result = 0;
    esxVI_RemoteRequest *remoteRequest = NULL;
    esxVI_RemoteResponse *remoteResponse = NULL;

    if (esxVI_RemoteRequest_Alloc(conn, &remoteRequest) < 0) {
        goto failure;
    }

    remoteRequest->request = (char *)request;

    if (virAsprintf(&remoteRequest->xpathExpression,
                    ESX_VI__SOAP__RESPONSE_XPATH("%s_Task"), name) < 0) {
        virReportOOMError(conn);
        goto failure;
    }

    if (esxVI_RemoteRequest_Execute(conn, ctx, remoteRequest,
                                    &remoteResponse) < 0 ||
        esxVI_RemoteResponse_DeserializeXPathObjectAsManagedObjectReference
          (conn, remoteResponse, task, "Task") < 0) {
        goto failure;
    }

  cleanup:
    /*
     * Remove values given by the caller from the data structures to prevent
     * them from being freed by the call to esxVI_RemoteRequest_Free().
     */
    if (remoteRequest != NULL) {
        remoteRequest->request = NULL;
    }

    esxVI_RemoteRequest_Free(&remoteRequest);
    esxVI_RemoteResponse_Free(&remoteResponse);

    return result;

  failure:
    result = -1;

    goto cleanup;
}



int
esxVI_StartSimpleVirtualMachineTask
  (virConnectPtr conn, esxVI_Context *ctx, const char *name,
   esxVI_ManagedObjectReference *virtualMachine,
   esxVI_ManagedObjectReference **task)
{
    int result = 0;
    virBuffer buffer = VIR_BUFFER_INITIALIZER;
    char *request = NULL;

    virBufferAddLit(&buffer, ESX_VI__SOAP__REQUEST_HEADER);
    virBufferAddLit(&buffer, "<");
    virBufferAdd(&buffer, name, -1);
    virBufferAddLit(&buffer, "_Task xmlns=\"urn:vim25\">");

    if (esxVI_ManagedObjectReference_Serialize(conn, virtualMachine, "_this",
                                               &buffer,
                                               esxVI_Boolean_True) < 0) {
        goto failure;
    }

    virBufferAddLit(&buffer, "</");
    virBufferAdd(&buffer, name, -1);
    virBufferAddLit(&buffer, "_Task>");
    virBufferAddLit(&buffer, ESX_VI__SOAP__REQUEST_FOOTER);

    if (virBufferError(&buffer)) {
        virReportOOMError(conn);
        goto failure;
    }

    request = virBufferContentAndReset(&buffer);

    if (esxVI_StartVirtualMachineTask(conn, ctx, name, request, task) < 0) {
        goto failure;
    }

  cleanup:
    VIR_FREE(request);

    return result;

  failure:
    free(virBufferContentAndReset(&buffer));

    result = -1;

    goto cleanup;
}



int
esxVI_SimpleVirtualMachineMethod(virConnectPtr conn, esxVI_Context *ctx,
                                const char *name,
                                esxVI_ManagedObjectReference *virtualMachine)
{
    int result = 0;
    esxVI_RemoteRequest *remoteRequest = NULL;
    esxVI_RemoteResponse *remoteResponse = NULL;
    virBuffer buffer = VIR_BUFFER_INITIALIZER;

    if (ctx->service == NULL) {
        ESX_VI_ERROR(conn, VIR_ERR_INTERNAL_ERROR, "Invalid argument");
        return -1;
    }

    virBufferAddLit(&buffer, ESX_VI__SOAP__REQUEST_HEADER);
    virBufferAddLit(&buffer, "<");
    virBufferAdd(&buffer, name, -1);
    virBufferAddLit(&buffer, " xmlns=\"urn:vim25\">");

    if (esxVI_RemoteRequest_Alloc(conn, &remoteRequest) < 0 ||
        esxVI_ManagedObjectReference_Serialize(conn, virtualMachine, "_this",
                                               &buffer,
                                               esxVI_Boolean_True) < 0) {
        goto failure;
    }

    virBufferAddLit(&buffer, "</");
    virBufferAdd(&buffer, name, -1);
    virBufferAddLit(&buffer, ">");
    virBufferAddLit(&buffer, ESX_VI__SOAP__REQUEST_FOOTER);

    if (virBufferError(&buffer)) {
        virReportOOMError(conn);
        goto failure;
    }

    remoteRequest->request = virBufferContentAndReset(&buffer);

    if (esxVI_RemoteRequest_Execute(conn, ctx, remoteRequest,
                                    &remoteResponse) < 0) {
        goto failure;
    }

  cleanup:
    esxVI_RemoteRequest_Free(&remoteRequest);
    esxVI_RemoteResponse_Free(&remoteResponse);

    return result;

  failure:
    free(virBufferContentAndReset(&buffer));

    result = -1;

    goto cleanup;
}



int
esxVI_WaitForTaskCompletion(virConnectPtr conn, esxVI_Context *ctx,
                            esxVI_ManagedObjectReference *task,
                            esxVI_TaskInfoState *finalState)
{
    int result = 0;
    esxVI_ObjectSpec *objectSpec = NULL;
    esxVI_PropertySpec *propertySpec = NULL;
    esxVI_PropertyFilterSpec *propertyFilterSpec = NULL;
    esxVI_ManagedObjectReference *propertyFilter = NULL;
    char *version = NULL;
    esxVI_UpdateSet *updateSet = NULL;
    esxVI_PropertyFilterUpdate *propertyFilterUpdate = NULL;
    esxVI_ObjectUpdate *objectUpdate = NULL;
    esxVI_PropertyChange *propertyChange = NULL;
    esxVI_AnyType *propertyValue = NULL;
    esxVI_TaskInfoState state = esxVI_TaskInfoState_Undefined;

    version = strdup("");

    if (version == NULL) {
        virReportOOMError(conn);
        goto failure;
    }

    if (esxVI_ObjectSpec_Alloc(conn, &objectSpec) < 0) {
        goto failure;
    }

    objectSpec->obj = task;
    objectSpec->skip = esxVI_Boolean_False;

    if (esxVI_PropertySpec_Alloc(conn, &propertySpec) < 0) {
        goto failure;
    }

    propertySpec->type = task->type;

    if (esxVI_String_AppendValueToList(conn, &propertySpec->pathSet,
                                       "info.state") < 0 ||
        esxVI_PropertyFilterSpec_Alloc(conn, &propertyFilterSpec) < 0 ||
        esxVI_PropertySpec_AppendToList(conn, &propertyFilterSpec->propSet,
                                        propertySpec) < 0 ||
        esxVI_ObjectSpec_AppendToList(conn, &propertyFilterSpec->objectSet,
                                      objectSpec) < 0 ||
        esxVI_CreateFilter(conn, ctx, propertyFilterSpec, esxVI_Boolean_True,
                           &propertyFilter) < 0) {
        goto failure;
    }

    while (state != esxVI_TaskInfoState_Success &&
           state != esxVI_TaskInfoState_Error) {
        esxVI_UpdateSet_Free(&updateSet);

        if (esxVI_WaitForUpdates(conn, ctx, version, &updateSet) < 0) {
            goto failure;
        }

        VIR_FREE(version);
        version = strdup(updateSet->version);

        if (version == NULL) {
            virReportOOMError(conn);
            goto failure;
        }

        if (updateSet->filterSet == NULL) {
            continue;
        }

        for (propertyFilterUpdate = updateSet->filterSet;
             propertyFilterUpdate != NULL;
             propertyFilterUpdate = propertyFilterUpdate->_next) {
            for (objectUpdate = propertyFilterUpdate->objectSet;
                 objectUpdate != NULL; objectUpdate = objectUpdate->_next) {
                for (propertyChange = objectUpdate->changeSet;
                     propertyChange != NULL;
                     propertyChange = propertyChange->_next) {
                    if (STREQ(propertyChange->name, "info.state")) {
                        if (propertyChange->op == esxVI_PropertyChangeOp_Add ||
                            propertyChange->op == esxVI_PropertyChangeOp_Assign) {
                            propertyValue = propertyChange->val;
                        } else {
                            propertyValue = NULL;
                        }
                    }
                }
            }
        }

        if (propertyValue == NULL) {
            continue;
        }

        if (esxVI_TaskInfoState_CastFromAnyType(conn, propertyValue,
                                                &state) < 0) {
            goto failure;
        }
    }

    if (esxVI_DestroyPropertyFilter(conn, ctx, propertyFilter) < 0) {
        VIR_DEBUG0("DestroyPropertyFilter failed");
    }

    if (esxVI_TaskInfoState_CastFromAnyType(conn, propertyValue,
                                            finalState) < 0) {
        goto failure;
    }

  cleanup:
    /*
     * Remove values given by the caller from the data structures to prevent
     * them from being freed by the call to esxVI_PropertyFilterSpec_Free().
     */
    if (objectSpec != NULL) {
        objectSpec->obj = NULL;
    }

    if (propertySpec != NULL) {
        propertySpec->type = NULL;
    }

    esxVI_PropertyFilterSpec_Free(&propertyFilterSpec);
    esxVI_ManagedObjectReference_Free(&propertyFilter);
    VIR_FREE(version);
    esxVI_UpdateSet_Free(&updateSet);

    return result;

  failure:
    result = -1;

    goto cleanup;
}
