/*
 * vz_sdk.c: core driver functions for managing
 * Parallels Cloud Server hosts
 *
 * Copyright (C) 2014 Parallels, Inc.
 *
 * 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 <stdarg.h>

#include "virerror.h"
#include "viralloc.h"
#include "virstring.h"
#include "nodeinfo.h"
#include "virlog.h"
#include "datatypes.h"
#include "domain_conf.h"
#include "virtime.h"

#include "vz_sdk.h"

#define VIR_FROM_THIS VIR_FROM_PARALLELS
#define JOB_INFINIT_WAIT_TIMEOUT UINT_MAX

VIR_LOG_INIT("parallels.sdk");

/*
 * Log error description
 */
static void
logPrlErrorHelper(PRL_RESULT err, const char *filename,
                  const char *funcname, size_t linenr)
{
    char *msg1 = NULL, *msg2 = NULL;
    PRL_UINT32 len = 0;

    /* Get required buffer length */
    PrlApi_GetResultDescription(err, PRL_TRUE, PRL_FALSE, NULL, &len);

    if (VIR_ALLOC_N(msg1, len) < 0)
        goto cleanup;

    /* get short error description */
    PrlApi_GetResultDescription(err, PRL_TRUE, PRL_FALSE, msg1, &len);

    PrlApi_GetResultDescription(err, PRL_FALSE, PRL_FALSE, NULL, &len);

    if (VIR_ALLOC_N(msg2, len) < 0)
        goto cleanup;

    /* get long error description */
    PrlApi_GetResultDescription(err, PRL_FALSE, PRL_FALSE, msg2, &len);

    virReportErrorHelper(VIR_FROM_THIS, VIR_ERR_INTERNAL_ERROR,
                         filename, funcname, linenr,
                         _("%s %s"), msg1, msg2);

 cleanup:
    VIR_FREE(msg1);
    VIR_FREE(msg2);
}

#define logPrlError(code)                          \
    logPrlErrorHelper(code, __FILE__,              \
                      __FUNCTION__, __LINE__)

#define prlsdkCheckRetGoto(ret, label)             \
    do {                                           \
        if (PRL_FAILED(ret)) {                     \
            logPrlError(ret);                      \
            goto label;                            \
        }                                          \
    } while (0)

static PRL_RESULT
logPrlEventErrorHelper(PRL_HANDLE event, const char *filename,
                       const char *funcname, size_t linenr)
{
    PRL_RESULT ret, retCode;
    char *msg1 = NULL, *msg2 = NULL;
    PRL_UINT32 len = 0;
    int err = -1;

    if ((ret = PrlEvent_GetErrCode(event, &retCode))) {
        logPrlError(ret);
        return ret;
    }

    PrlEvent_GetErrString(event, PRL_TRUE, PRL_FALSE, NULL, &len);

    if (VIR_ALLOC_N(msg1, len) < 0)
        goto cleanup;

    PrlEvent_GetErrString(event, PRL_TRUE, PRL_FALSE, msg1, &len);

    PrlEvent_GetErrString(event, PRL_FALSE, PRL_FALSE, NULL, &len);

    if (VIR_ALLOC_N(msg2, len) < 0)
        goto cleanup;

    PrlEvent_GetErrString(event, PRL_FALSE, PRL_FALSE, msg2, &len);

    virReportErrorHelper(VIR_FROM_THIS, VIR_ERR_INTERNAL_ERROR,
                         filename, funcname, linenr,
                         _("%s %s"), msg1, msg2);
    err = 0;

 cleanup:
    VIR_FREE(msg1);
    VIR_FREE(msg2);

    return err;
}

#define logPrlEventError(event)                    \
    logPrlEventErrorHelper(event, __FILE__,        \
                           __FUNCTION__, __LINE__)

static PRL_RESULT
getJobResultHelper(PRL_HANDLE job, unsigned int timeout, PRL_HANDLE *result,
                   const char *filename, const char *funcname,
                   size_t linenr)
{
    PRL_RESULT ret, retCode;

    if ((ret = PrlJob_Wait(job, timeout))) {
        logPrlErrorHelper(ret, filename, funcname, linenr);
        goto cleanup;
    }

    if ((ret = PrlJob_GetRetCode(job, &retCode))) {
        logPrlErrorHelper(ret, filename, funcname, linenr);
        goto cleanup;
    }

    if (retCode) {
        PRL_HANDLE err_handle;

        /* Sometimes it's possible to get additional error info. */
        if ((ret = PrlJob_GetError(job, &err_handle))) {
            logPrlErrorHelper(ret, filename, funcname, linenr);
            goto cleanup;
        }

        if (logPrlEventErrorHelper(err_handle, filename, funcname, linenr))
            logPrlErrorHelper(retCode, filename, funcname, linenr);

        PrlHandle_Free(err_handle);
        ret = retCode;
    } else {
        ret = PrlJob_GetResult(job, result);
        if (PRL_FAILED(ret)) {
            logPrlErrorHelper(ret, filename, funcname, linenr);
            PrlHandle_Free(*result);
            *result = NULL;
            goto cleanup;
        }

        ret = PRL_ERR_SUCCESS;
    }

 cleanup:
    PrlHandle_Free(job);
    return ret;
}

#define getJobResult(job, result)                       \
    getJobResultHelper(job, JOB_INFINIT_WAIT_TIMEOUT,   \
                       result, __FILE__, __FUNCTION__, __LINE__)

static PRL_RESULT
waitJobHelper(PRL_HANDLE job, unsigned int timeout,
              const char *filename, const char *funcname,
              size_t linenr)
{
    PRL_HANDLE result = PRL_INVALID_HANDLE;
    PRL_RESULT ret;

    ret = getJobResultHelper(job, timeout, &result,
                             filename, funcname, linenr);
    PrlHandle_Free(result);
    return ret;
}

#define waitJob(job)                                        \
    waitJobHelper(job, JOB_INFINIT_WAIT_TIMEOUT, __FILE__,  \
                  __FUNCTION__, __LINE__)


int
prlsdkInit(void)
{
    PRL_RESULT ret;

    /* Disable console output */
    PrlApi_SwitchConsoleLogging(0);

    ret = PrlApi_InitEx(PARALLELS_API_VER, PAM_SERVER, 0, 0);
    if (PRL_FAILED(ret)) {
        logPrlError(ret);
        return -1;
    }

    return 0;
};

void
prlsdkDeinit(void)
{
    PrlApi_Deinit();
};

int
prlsdkConnect(vzConnPtr privconn)
{
    PRL_RESULT ret;
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    ret = PrlSrv_Create(&privconn->server);
    if (PRL_FAILED(ret)) {
        logPrlError(ret);
        return -1;
    }

    job = PrlSrv_LoginLocalEx(privconn->server, NULL, 0,
                              PSL_HIGH_SECURITY, PACF_NON_INTERACTIVE_MODE);

    if (waitJob(job)) {
        PrlHandle_Free(privconn->server);
        return -1;
    }

    return 0;
}

void
prlsdkDisconnect(vzConnPtr privconn)
{
    PRL_HANDLE job;

    job = PrlSrv_Logoff(privconn->server);
    waitJob(job);

    PrlHandle_Free(privconn->server);
}

static int
prlsdkSdkDomainLookup(vzConnPtr privconn,
                      const char *id,
                      unsigned int flags,
                      PRL_HANDLE *sdkdom)
{
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    PRL_HANDLE result = PRL_INVALID_HANDLE;
    PRL_RESULT pret = PRL_ERR_UNINITIALIZED;
    int ret = -1;

    job = PrlSrv_GetVmConfig(privconn->server, id, flags);
    if (PRL_FAILED(getJobResult(job, &result)))
        goto cleanup;

    pret = PrlResult_GetParamByIndex(result, 0, sdkdom);
    prlsdkCheckRetGoto(pret, cleanup);

    ret = 0;

 cleanup:
    PrlHandle_Free(result);
    return ret;
}

static void
prlsdkUUIDFormat(const unsigned char *uuid, char *uuidstr)
{
    virUUIDFormat(uuid, uuidstr + 1);

    uuidstr[0] = '{';
    uuidstr[VIR_UUID_STRING_BUFLEN] = '}';
    uuidstr[VIR_UUID_STRING_BUFLEN + 1] = '\0';
}

static PRL_HANDLE
prlsdkSdkDomainLookupByUUID(vzConnPtr privconn, const unsigned char *uuid)
{
    char uuidstr[VIR_UUID_STRING_BUFLEN + 2];
    PRL_HANDLE sdkdom = PRL_INVALID_HANDLE;

    prlsdkUUIDFormat(uuid, uuidstr);

    if (prlsdkSdkDomainLookup(privconn, uuidstr,
                              PGVC_SEARCH_BY_UUID, &sdkdom) < 0) {
        virUUIDFormat(uuid, uuidstr);
        virReportError(VIR_ERR_NO_DOMAIN,
                       _("no domain with matching uuid '%s'"), uuidstr);
        return PRL_INVALID_HANDLE;
    }

    return sdkdom;
}

static int
prlsdkUUIDParse(const char *uuidstr, unsigned char *uuid)
{
    char *tmp = NULL;
    int ret = -1;

    virCheckNonNullArgGoto(uuidstr, error);
    virCheckNonNullArgGoto(uuid, error);

    if (VIR_STRDUP(tmp, uuidstr) < 0)
        goto error;

    tmp[strlen(tmp) - 1] = '\0';

    /* trim curly braces */
    if (virUUIDParse(tmp + 1, uuid) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("UUID in config file malformed"));
        ret = -1;
        goto error;
    }

    ret = 0;
 error:
    VIR_FREE(tmp);
    return ret;
}

static int
prlsdkGetDomainIds(PRL_HANDLE sdkdom,
                   char **name,
                   unsigned char *uuid)
{
    char uuidstr[VIR_UUID_STRING_BUFLEN + 2];
    PRL_UINT32 len;
    PRL_RESULT pret;

    len = 0;
    /* get name length */
    pret = PrlVmCfg_GetName(sdkdom, NULL, &len);
    prlsdkCheckRetGoto(pret, error);

    if (VIR_ALLOC_N(*name, len) < 0)
        goto error;

    PrlVmCfg_GetName(sdkdom, *name, &len);
    prlsdkCheckRetGoto(pret, error);

    len = sizeof(uuidstr);
    PrlVmCfg_GetUuid(sdkdom, uuidstr, &len);
    prlsdkCheckRetGoto(pret, error);

    if (prlsdkUUIDParse(uuidstr, uuid) < 0)
        goto error;

    return 0;

 error:
    VIR_FREE(*name);
    return -1;
}

static int
prlsdkGetDomainState(PRL_HANDLE sdkdom, VIRTUAL_MACHINE_STATE_PTR vmState)
{
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    PRL_HANDLE result = PRL_INVALID_HANDLE;
    PRL_HANDLE vmInfo = PRL_INVALID_HANDLE;
    PRL_RESULT pret;
    int ret = -1;

    job = PrlVm_GetState(sdkdom);

    if (PRL_FAILED(getJobResult(job, &result)))
        goto cleanup;

    pret = PrlResult_GetParamByIndex(result, 0, &vmInfo);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmInfo_GetState(vmInfo, vmState);
    prlsdkCheckRetGoto(pret, cleanup);

    ret = 0;

 cleanup:
    PrlHandle_Free(vmInfo);
    PrlHandle_Free(result);
    return ret;
}

static void
prlsdkDomObjFreePrivate(void *p)
{
    vzDomObjPtr pdom = p;

    if (!pdom)
        return;

    PrlHandle_Free(pdom->sdkdom);
    PrlHandle_Free(pdom->cache.stats);
    virCondDestroy(&pdom->cache.cond);
    VIR_FREE(pdom->uuid);
    VIR_FREE(pdom->home);
    VIR_FREE(p);
};

static int
prlsdkAddDomainVideoInfo(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    virDomainVideoDefPtr video = NULL;
    virDomainVideoAccelDefPtr accel = NULL;
    PRL_RESULT ret;
    PRL_UINT32 videoRam;

    /* video info */
    ret = PrlVmCfg_GetVideoRamSize(sdkdom, &videoRam);
    prlsdkCheckRetGoto(ret, error);

    if (VIR_ALLOC(video) < 0)
        goto error;

    if (VIR_ALLOC(accel) < 0)
        goto error;

    if (VIR_APPEND_ELEMENT_COPY(def->videos, def->nvideos, video) < 0)
        goto error;

    video->type = VIR_DOMAIN_VIDEO_TYPE_VGA;
    video->vram = videoRam << 10; /* from mbibytes to kbibytes */
    video->heads = 1;
    video->accel = accel;

    return 0;

 error:
    VIR_FREE(accel);
    virDomainVideoDefFree(video);
    return -1;
}

static int
prlsdkGetDiskInfo(PRL_HANDLE prldisk,
                  virDomainDiskDefPtr disk,
                  bool isCdrom,
                  bool isCt)
{
    char *buf = NULL;
    PRL_UINT32 buflen = 0;
    PRL_RESULT pret;
    PRL_UINT32 emulatedType;
    PRL_UINT32 ifType;
    PRL_UINT32 pos;
    virDomainDeviceDriveAddressPtr address;
    int ret = -1;

    pret = PrlVmDev_GetEmulatedType(prldisk, &emulatedType);
    prlsdkCheckRetGoto(pret, cleanup);
    if (emulatedType == PDT_USE_IMAGE_FILE) {
        virDomainDiskSetType(disk, VIR_STORAGE_TYPE_FILE);
        if (isCdrom)
            virDomainDiskSetFormat(disk, VIR_STORAGE_FILE_RAW);
        else
            virDomainDiskSetFormat(disk, VIR_STORAGE_FILE_PLOOP);
    } else {
        virDomainDiskSetType(disk, VIR_STORAGE_TYPE_BLOCK);
        virDomainDiskSetFormat(disk, VIR_STORAGE_FILE_RAW);
    }

    if (isCdrom) {
        disk->device = VIR_DOMAIN_DISK_DEVICE_CDROM;
        disk->src->readonly = true;
    } else {
        disk->device = VIR_DOMAIN_DISK_DEVICE_DISK;
    }

    pret = PrlVmDev_GetFriendlyName(prldisk, NULL, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    if (VIR_ALLOC_N(buf, buflen) < 0)
        goto cleanup;

    pret = PrlVmDev_GetFriendlyName(prldisk, buf, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    if (virDomainDiskSetSource(disk, buf) < 0)
        goto cleanup;

    /* Let physical devices added to CT look like SATA disks */
    if (isCt) {
        ifType = PMS_SATA_DEVICE;
    } else {
        pret = PrlVmDev_GetIfaceType(prldisk, &ifType);
        prlsdkCheckRetGoto(pret, cleanup);
    }

    pret = PrlVmDev_GetStackIndex(prldisk, &pos);
    prlsdkCheckRetGoto(pret, cleanup);

    address = &disk->info.addr.drive;
    switch (ifType) {
    case PMS_IDE_DEVICE:
        disk->bus = VIR_DOMAIN_DISK_BUS_IDE;
        disk->dst = virIndexToDiskName(pos, "hd");
        address->bus = pos / 2;
        address->target = 0;
        address->unit = pos % 2;
        break;
    case PMS_SCSI_DEVICE:
        disk->bus = VIR_DOMAIN_DISK_BUS_SCSI;
        disk->dst = virIndexToDiskName(pos, "sd");
        address->bus = 0;
        address->target = 0;
        address->unit = pos;
        break;
    case PMS_SATA_DEVICE:
        disk->bus = VIR_DOMAIN_DISK_BUS_SATA;
        disk->dst = virIndexToDiskName(pos, "sd");
        address->bus = 0;
        address->target = 0;
        address->unit = pos;
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown disk bus: %X"), ifType);
        goto cleanup;
        break;
    }

    if (!disk->dst)
        goto cleanup;

    disk->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;

    ret = 0;

 cleanup:
    VIR_FREE(buf);
    return ret;
}

static int
prlsdkGetFSInfo(PRL_HANDLE prldisk,
                virDomainFSDefPtr fs)
{
    char *buf = NULL;
    PRL_UINT32 buflen = 0;
    PRL_RESULT pret;
    int ret = -1;

    fs->type = VIR_DOMAIN_FS_TYPE_FILE;
    fs->fsdriver = VIR_DOMAIN_FS_DRIVER_TYPE_PLOOP;
    fs->accessmode = VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH;
    fs->wrpolicy = VIR_DOMAIN_FS_WRPOLICY_DEFAULT;
    fs->format = VIR_STORAGE_FILE_PLOOP;

    fs->readonly = false;
    fs->symlinksResolved = false;

    pret = PrlVmDev_GetImagePath(prldisk, NULL, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    if (VIR_ALLOC_N(buf, buflen) < 0)
        goto cleanup;

    pret = PrlVmDev_GetImagePath(prldisk, buf, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    fs->src = buf;
    buf = NULL;

    pret = PrlVmDevHd_GetMountPoint(prldisk, NULL, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    if (VIR_ALLOC_N(buf, buflen) < 0)
        goto cleanup;

    pret = PrlVmDevHd_GetMountPoint(prldisk, buf, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    fs->dst = buf;
    buf = NULL;

    ret = 0;

 cleanup:
    VIR_FREE(buf);
    return ret;
}

static int
prlsdkAddDomainHardDisksInfo(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    PRL_RESULT pret;
    PRL_UINT32 hddCount;
    PRL_UINT32 i;
    PRL_HANDLE hdd = PRL_INVALID_HANDLE;
    virDomainDiskDefPtr disk = NULL;
    virDomainFSDefPtr fs = NULL;

    pret = PrlVmCfg_GetHardDisksCount(sdkdom, &hddCount);
    prlsdkCheckRetGoto(pret, error);

    for (i = 0; i < hddCount; ++i) {

        PRL_UINT32 emulatedType;

        pret = PrlVmCfg_GetHardDisk(sdkdom, i, &hdd);
        prlsdkCheckRetGoto(pret, error);

        pret = PrlVmDev_GetEmulatedType(hdd, &emulatedType);
        prlsdkCheckRetGoto(pret, error);

        if (PDT_USE_REAL_DEVICE != emulatedType && IS_CT(def)) {

            if (VIR_ALLOC(fs) < 0)
                goto error;

            if (prlsdkGetFSInfo(hdd, fs) < 0)
                goto error;

            if (virDomainFSInsert(def, fs) < 0)
                goto error;

            fs = NULL;
            PrlHandle_Free(hdd);
            hdd = PRL_INVALID_HANDLE;
        } else {
            if (!(disk = virDomainDiskDefNew(NULL)))
                goto error;

            if (prlsdkGetDiskInfo(hdd, disk, false, IS_CT(def)) < 0)
                goto error;

            if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
                goto error;

            disk = NULL;
            PrlHandle_Free(hdd);
            hdd = PRL_INVALID_HANDLE;
        }
    }

    return 0;

 error:
    PrlHandle_Free(hdd);
    virDomainDiskDefFree(disk);
    virDomainFSDefFree(fs);
    return -1;
}

static int
prlsdkAddDomainOpticalDisksInfo(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    PRL_RESULT pret;
    PRL_UINT32 cdromsCount;
    PRL_UINT32 i;
    PRL_HANDLE cdrom = PRL_INVALID_HANDLE;
    virDomainDiskDefPtr disk = NULL;

    pret = PrlVmCfg_GetOpticalDisksCount(sdkdom, &cdromsCount);
    prlsdkCheckRetGoto(pret, error);

    for (i = 0; i < cdromsCount; ++i) {
        pret = PrlVmCfg_GetOpticalDisk(sdkdom, i, &cdrom);
        prlsdkCheckRetGoto(pret, error);

        if (!(disk = virDomainDiskDefNew(NULL)))
            goto error;

        if (prlsdkGetDiskInfo(cdrom, disk, true, IS_CT(def)) < 0)
            goto error;

        PrlHandle_Free(cdrom);
        cdrom = PRL_INVALID_HANDLE;

        if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
            goto error;
    }

    return 0;

 error:
    PrlHandle_Free(cdrom);
    virDomainDiskDefFree(disk);
    return -1;
}

static int
prlsdkGetNetInfo(PRL_HANDLE netAdapter, virDomainNetDefPtr net, bool isCt)
{
    char macstr[VIR_MAC_STRING_BUFLEN];
    PRL_UINT32 buflen;
    PRL_UINT32 netAdapterIndex;
    PRL_UINT32 emulatedType;
    PRL_RESULT pret;
    PRL_BOOL isConnected;
    int ret = -1;

    net->type = VIR_DOMAIN_NET_TYPE_NETWORK;


    /* use device name, shown by prlctl as target device
     * for identifying network adapter in virDomainDefineXML */
    pret = PrlVmDevNet_GetHostInterfaceName(netAdapter, NULL, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    if (VIR_ALLOC_N(net->ifname, buflen) < 0)
        goto cleanup;

    pret = PrlVmDevNet_GetHostInterfaceName(netAdapter, net->ifname, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_GetIndex(netAdapter, &netAdapterIndex);
    prlsdkCheckRetGoto(pret, cleanup);

    if (isCt && netAdapterIndex == (PRL_UINT32) -1) {
        /* venet devices don't have mac address and
         * always up */
        net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_UP;
        if (VIR_STRDUP(net->data.network.name,
                       PARALLELS_DOMAIN_ROUTED_NETWORK_NAME) < 0)
            goto cleanup;
        return 0;
    }

    buflen = ARRAY_CARDINALITY(macstr);
    if (VIR_ALLOC_N(macstr, buflen))
        goto cleanup;
    pret = PrlVmDevNet_GetMacAddressCanonical(netAdapter, macstr, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    if (virMacAddrParse(macstr, &net->mac) < 0)
        goto cleanup;

    pret = PrlVmDev_GetEmulatedType(netAdapter, &emulatedType);
    prlsdkCheckRetGoto(pret, cleanup);

    if (emulatedType == PNA_ROUTED) {
        if (VIR_STRDUP(net->data.network.name,
                       PARALLELS_DOMAIN_ROUTED_NETWORK_NAME) < 0)
            goto cleanup;
    } else {
        pret = PrlVmDevNet_GetVirtualNetworkId(netAdapter, NULL, &buflen);
        prlsdkCheckRetGoto(pret, cleanup);

        if (VIR_ALLOC_N(net->data.network.name, buflen) < 0)
            goto cleanup;

        pret = PrlVmDevNet_GetVirtualNetworkId(netAdapter,
                                               net->data.network.name,
                                               &buflen);
        prlsdkCheckRetGoto(pret, cleanup);

        /*
         * We use VIR_DOMAIN_NET_TYPE_NETWORK for all network adapters
         * except those whose Virtual Network Id differ from Parallels
         * predefined ones such as PARALLELS_DOMAIN_BRIDGED_NETWORK_NAME
         * and PARALLELS_DONAIN_ROUTED_NETWORK_NAME
         */
        if (STRNEQ(net->data.network.name, PARALLELS_DOMAIN_BRIDGED_NETWORK_NAME))
            net->type = VIR_DOMAIN_NET_TYPE_BRIDGE;

    }

    if (!isCt) {
        PRL_VM_NET_ADAPTER_TYPE type;
        pret = PrlVmDevNet_GetAdapterType(netAdapter, &type);
        prlsdkCheckRetGoto(pret, cleanup);

        switch (type) {
        case PNT_RTL:
            if (VIR_STRDUP(net->model, "rtl8139") < 0)
                goto cleanup;
            break;
        case PNT_E1000:
            if (VIR_STRDUP(net->model, "e1000") < 0)
                goto cleanup;
            break;
        case PNT_VIRTIO:
            if (VIR_STRDUP(net->model, "virtio") < 0)
                goto cleanup;
            break;
        default:
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Unknown adapter type: %X"), type);
            goto cleanup;
        }
    }

    pret = PrlVmDev_IsConnected(netAdapter, &isConnected);
    prlsdkCheckRetGoto(pret, cleanup);

    if (isConnected)
        net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_UP;
    else
        net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN;

    ret = 0;
 cleanup:
    return ret;
}

static int
prlsdkAddDomainNetInfo(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    virDomainNetDefPtr net = NULL;
    PRL_RESULT ret;
    PRL_HANDLE netAdapter;
    PRL_UINT32 netAdaptersCount;
    PRL_UINT32 i;

    ret = PrlVmCfg_GetNetAdaptersCount(sdkdom, &netAdaptersCount);
    prlsdkCheckRetGoto(ret, error);
    for (i = 0; i < netAdaptersCount; ++i) {
        ret = PrlVmCfg_GetNetAdapter(sdkdom, i, &netAdapter);
        prlsdkCheckRetGoto(ret, error);

        if (VIR_ALLOC(net) < 0)
            goto error;

        if (prlsdkGetNetInfo(netAdapter, net, IS_CT(def)) < 0)
            goto error;

        PrlHandle_Free(netAdapter);
        netAdapter = PRL_INVALID_HANDLE;

        if (VIR_APPEND_ELEMENT(def->nets, def->nnets, net) < 0)
            goto error;
    }

    return 0;

 error:
    PrlHandle_Free(netAdapter);
    virDomainNetDefFree(net);
    return -1;
}

static int
prlsdkGetSerialInfo(PRL_HANDLE serialPort, virDomainChrDefPtr chr)
{
    PRL_RESULT pret;
    PRL_UINT32 serialPortIndex;
    PRL_UINT32 emulatedType;
    char *friendlyName = NULL;
    PRL_UINT32 buflen;

    chr->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL;
    chr->targetTypeAttr = false;
    pret = PrlVmDev_GetIndex(serialPort, &serialPortIndex);
    prlsdkCheckRetGoto(pret, error);
    chr->target.port = serialPortIndex;

    pret = PrlVmDev_GetEmulatedType(serialPort, &emulatedType);
    prlsdkCheckRetGoto(pret, error);

    pret = PrlVmDev_GetFriendlyName(serialPort, NULL, &buflen);
    prlsdkCheckRetGoto(pret, error);

    if (VIR_ALLOC_N(friendlyName, buflen) < 0)
        goto error;

    pret = PrlVmDev_GetFriendlyName(serialPort, friendlyName, &buflen);
    prlsdkCheckRetGoto(pret, error);

    switch (emulatedType) {
    case PDT_USE_OUTPUT_FILE:
        chr->source.type = VIR_DOMAIN_CHR_TYPE_FILE;
        chr->source.data.file.path = friendlyName;
        break;
    case PDT_USE_SERIAL_PORT_SOCKET_MODE:
        chr->source.type = VIR_DOMAIN_CHR_TYPE_UNIX;
        chr->source.data.nix.path = friendlyName;
        break;
    case PDT_USE_REAL_DEVICE:
        chr->source.type = VIR_DOMAIN_CHR_TYPE_DEV;
        chr->source.data.file.path = friendlyName;
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown serial type: %X"), emulatedType);
        goto error;
        break;
    }

    return 0;
 error:
    VIR_FREE(friendlyName);
    return -1;
}


static int
prlsdkAddSerialInfo(PRL_HANDLE sdkdom,
                    virDomainChrDefPtr **serials,
                    size_t *nserials)
{
    PRL_RESULT ret;
    PRL_HANDLE serialPort;
    PRL_UINT32 serialPortsCount;
    PRL_UINT32 i;
    virDomainChrDefPtr chr = NULL;

    ret = PrlVmCfg_GetSerialPortsCount(sdkdom, &serialPortsCount);
    prlsdkCheckRetGoto(ret, cleanup);
    for (i = 0; i < serialPortsCount; ++i) {
        ret = PrlVmCfg_GetSerialPort(sdkdom, i, &serialPort);
        prlsdkCheckRetGoto(ret, cleanup);

        if (!(chr = virDomainChrDefNew()))
            goto cleanup;

        if (prlsdkGetSerialInfo(serialPort, chr))
            goto cleanup;

        PrlHandle_Free(serialPort);
        serialPort = PRL_INVALID_HANDLE;

        if (VIR_APPEND_ELEMENT(*serials, *nserials, chr) < 0)
            goto cleanup;
    }

    return 0;

 cleanup:
    PrlHandle_Free(serialPort);
    virDomainChrDefFree(chr);
    return -1;
}


static int
prlsdkAddDomainHardware(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    if (!IS_CT(def))
        if (prlsdkAddDomainVideoInfo(sdkdom, def) < 0)
            goto error;

    if (prlsdkAddDomainHardDisksInfo(sdkdom, def) < 0)
        goto error;

    if (prlsdkAddDomainOpticalDisksInfo(sdkdom, def) < 0)
        goto error;

    if (prlsdkAddDomainNetInfo(sdkdom, def) < 0)
        goto error;

    if (prlsdkAddSerialInfo(sdkdom,
                            &def->serials,
                            &def->nserials) < 0)
        goto error;

    return 0;
 error:
    return -1;
}


static int
prlsdkAddVNCInfo(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    virDomainGraphicsDefPtr gr = NULL;
    PRL_VM_REMOTE_DISPLAY_MODE vncMode;
    PRL_UINT32 port;
    PRL_UINT32 buflen = 0;
    PRL_RESULT pret;

    pret = PrlVmCfg_GetVNCMode(sdkdom, &vncMode);
    prlsdkCheckRetGoto(pret, error);

    if (vncMode == PRD_DISABLED)
        return 0;

    if (VIR_ALLOC(gr) < 0)
        goto error;

    pret = PrlVmCfg_GetVNCPort(sdkdom, &port);
    prlsdkCheckRetGoto(pret, error);

    gr->data.vnc.autoport = (vncMode == PRD_AUTO);
    gr->type = VIR_DOMAIN_GRAPHICS_TYPE_VNC;
    gr->data.vnc.port = port;
    gr->data.vnc.keymap = NULL;
    gr->data.vnc.socket = NULL;
    gr->data.vnc.auth.passwd = NULL;
    gr->data.vnc.auth.expires = false;
    gr->data.vnc.auth.connected = 0;

    if (VIR_ALLOC(gr->listens) < 0)
        goto error;

    gr->nListens = 1;

    pret = PrlVmCfg_GetVNCHostName(sdkdom, NULL, &buflen);
    prlsdkCheckRetGoto(pret, error);

    if (VIR_ALLOC_N(gr->listens[0].address, buflen) < 0)
        goto error;

    pret = PrlVmCfg_GetVNCHostName(sdkdom, gr->listens[0].address, &buflen);
    prlsdkCheckRetGoto(pret, error);

    gr->listens[0].type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS;

    if (VIR_APPEND_ELEMENT(def->graphics, def->ngraphics, gr) < 0)
        goto error;

    if (IS_CT(def)) {
        virDomainVideoDefPtr video;
        if (VIR_ALLOC(video) < 0)
            goto error;
        video->type = virDomainVideoDefaultType(def);
        if (video->type < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("cannot determine default video type"));
            VIR_FREE(video);
            goto error;
        }
        video->vram = virDomainVideoDefaultRAM(def, video->type);
        video->heads = 1;
        if (VIR_ALLOC_N(def->videos, 1) < 0) {
            virDomainVideoDefFree(video);
            goto error;
        }
        def->videos[def->nvideos++] = video;
    }
    return 0;

 error:
    virDomainGraphicsDefFree(gr);
    return -1;
}

static int
prlsdkConvertDomainState(VIRTUAL_MACHINE_STATE domainState,
                         PRL_UINT32 envId,
                         virDomainObjPtr dom)
{
    switch (domainState) {
    case VMS_STOPPED:
    case VMS_MOUNTED:
        virDomainObjSetState(dom, VIR_DOMAIN_SHUTOFF,
                             VIR_DOMAIN_SHUTOFF_SHUTDOWN);
        dom->def->id = -1;
        break;
    case VMS_STARTING:
    case VMS_COMPACTING:
    case VMS_RESETTING:
    case VMS_PAUSING:
    case VMS_RECONNECTING:
    case VMS_RUNNING:
        virDomainObjSetState(dom, VIR_DOMAIN_RUNNING,
                             VIR_DOMAIN_RUNNING_BOOTED);
        dom->def->id = envId;
        break;
    case VMS_PAUSED:
        virDomainObjSetState(dom, VIR_DOMAIN_PAUSED,
                             VIR_DOMAIN_PAUSED_USER);
        dom->def->id = envId;
        break;
    case VMS_SUSPENDED:
    case VMS_DELETING_STATE:
    case VMS_SUSPENDING_SYNC:
        virDomainObjSetState(dom, VIR_DOMAIN_SHUTOFF,
                             VIR_DOMAIN_SHUTOFF_SAVED);
        dom->def->id = -1;
        break;
    case VMS_STOPPING:
        virDomainObjSetState(dom, VIR_DOMAIN_SHUTDOWN,
                             VIR_DOMAIN_SHUTDOWN_USER);
        dom->def->id = envId;
        break;
    case VMS_SNAPSHOTING:
        virDomainObjSetState(dom, VIR_DOMAIN_PAUSED,
                             VIR_DOMAIN_PAUSED_SNAPSHOT);
        dom->def->id = envId;
        break;
    case VMS_MIGRATING:
        virDomainObjSetState(dom, VIR_DOMAIN_PAUSED,
                             VIR_DOMAIN_PAUSED_MIGRATION);
        dom->def->id = envId;
        break;
    case VMS_SUSPENDING:
        virDomainObjSetState(dom, VIR_DOMAIN_PAUSED,
                             VIR_DOMAIN_PAUSED_SAVE);
        dom->def->id = envId;
        break;
    case VMS_RESTORING:
        virDomainObjSetState(dom, VIR_DOMAIN_RUNNING,
                             VIR_DOMAIN_RUNNING_RESTORED);
        dom->def->id = envId;
        break;
    case VMS_CONTINUING:
        virDomainObjSetState(dom, VIR_DOMAIN_RUNNING,
                             VIR_DOMAIN_RUNNING_UNPAUSED);
        dom->def->id = envId;
        break;
    case VMS_RESUMING:
        virDomainObjSetState(dom, VIR_DOMAIN_RUNNING,
                             VIR_DOMAIN_RUNNING_RESTORED);
        dom->def->id = envId;
        break;
    case VMS_UNKNOWN:
        virDomainObjSetState(dom, VIR_DOMAIN_NOSTATE,
                             VIR_DOMAIN_NOSTATE_UNKNOWN);
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown domain state: %X"), domainState);
        return -1;
        break;
    }

    return 0;
}

static int
prlsdkConvertCpuInfo(PRL_HANDLE sdkdom,
                     virDomainDefPtr def)
{
    char *buf;
    PRL_UINT32 buflen = 0;
    int hostcpus;
    PRL_UINT32 cpuCount;
    PRL_RESULT pret;
    int ret = -1;

    if ((hostcpus = nodeGetCPUCount()) < 0)
        goto cleanup;

    /* get number of CPUs */
    pret = PrlVmCfg_GetCpuCount(sdkdom, &cpuCount);
    prlsdkCheckRetGoto(pret, cleanup);

    if (cpuCount > hostcpus)
        cpuCount = hostcpus;

    def->vcpus = cpuCount;
    def->maxvcpus = cpuCount;

    pret = PrlVmCfg_GetCpuMask(sdkdom, NULL, &buflen);
    prlsdkCheckRetGoto(pret, cleanup);

    if (VIR_ALLOC_N(buf, buflen) < 0)
        goto cleanup;

    pret = PrlVmCfg_GetCpuMask(sdkdom, buf, &buflen);

    if (strlen(buf) == 0) {
        if (!(def->cpumask = virBitmapNew(hostcpus)))
            goto cleanup;
        virBitmapSetAll(def->cpumask);
    } else {
        if (virBitmapParse(buf, 0, &def->cpumask, hostcpus) < 0)
            goto cleanup;
    }

    ret = 0;
 cleanup:
    VIR_FREE(buf);
    return ret;
}

static int
prlsdkConvertDomainType(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    PRL_VM_TYPE domainType;
    PRL_RESULT pret;

    pret = PrlVmCfg_GetVmType(sdkdom, &domainType);
    prlsdkCheckRetGoto(pret, error);

    switch (domainType) {
    case PVT_VM:
        def->os.type = VIR_DOMAIN_OSTYPE_HVM;
        break;
    case PVT_CT:
        def->os.type = VIR_DOMAIN_OSTYPE_EXE;
        if (VIR_STRDUP(def->os.init, "/sbin/init") < 0)
            return -1;
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown domain type: %X"), domainType);
        return -1;
    }

    return 0;

 error:
    return -1;
}

static int
prlsdkConvertCpuMode(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    PRL_RESULT pret;
    PRL_CPU_MODE cpuMode;

    pret = PrlVmCfg_GetCpuMode(sdkdom, &cpuMode);
    prlsdkCheckRetGoto(pret, error);

    switch (cpuMode) {
    case PCM_CPU_MODE_32:
        def->os.arch = VIR_ARCH_I686;
        break;
    case PCM_CPU_MODE_64:
        def->os.arch = VIR_ARCH_X86_64;
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown CPU mode: %X"), cpuMode);
        return -1;
    }

    return 0;
 error:
    return -1;
}

/*
 * This function retrieves information about domain.
 * If the domains is already in the domains list
 * privconn->domains, then locked 'olddom' must be
 * provided. If the domains must be added to the list,
 * olddom must be NULL.
 *
 * The function return a pointer to a locked virDomainObj.
 */
static virDomainObjPtr
prlsdkLoadDomain(vzConnPtr privconn,
                 PRL_HANDLE sdkdom,
                 virDomainObjPtr olddom)
{
    virDomainObjPtr dom = NULL;
    virDomainDefPtr def = NULL;
    vzDomObjPtr pdom = NULL;
    VIRTUAL_MACHINE_STATE domainState;

    PRL_UINT32 buflen = 0;
    PRL_RESULT pret;
    PRL_UINT32 ram;
    PRL_UINT32 envId;
    PRL_VM_AUTOSTART_OPTION autostart;

    virCheckNonNullArgGoto(privconn, error);
    virCheckNonNullArgGoto(sdkdom, error);

    if (!(def = virDomainDefNew()))
        goto error;

    if (!olddom) {
        if (VIR_ALLOC(pdom) < 0)
            goto error;
    } else {
        pdom = olddom->privateData;
    }

    if (STREQ(privconn->drivername, "vz"))
        def->virtType = VIR_DOMAIN_VIRT_VZ;
    else
        def->virtType = VIR_DOMAIN_VIRT_PARALLELS;

    def->id = -1;

    /* we will remove this field in the near future, so let's set it
     * to NULL temporarily */
    pdom->uuid = NULL;

    pdom->cache.stats = PRL_INVALID_HANDLE;
    pdom->cache.count = -1;
    if (virCondInit(&pdom->cache.cond) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot initialize condition"));
        goto error;
    }

    if (prlsdkGetDomainIds(sdkdom, &def->name, def->uuid) < 0)
        goto error;

    def->onReboot = VIR_DOMAIN_LIFECYCLE_RESTART;
    def->onPoweroff = VIR_DOMAIN_LIFECYCLE_DESTROY;
    def->onCrash = VIR_DOMAIN_LIFECYCLE_CRASH_DESTROY;

    /* get RAM parameters */
    pret = PrlVmCfg_GetRamSize(sdkdom, &ram);
    prlsdkCheckRetGoto(pret, error);
    virDomainDefSetMemoryInitial(def, ram << 10); /* RAM size obtained in Mbytes,
                                                     convert to Kbytes */
    def->mem.cur_balloon = ram << 10;

    if (prlsdkConvertCpuInfo(sdkdom, def) < 0)
        goto error;

    if (prlsdkConvertCpuMode(sdkdom, def) < 0)
        goto error;

    if (prlsdkConvertDomainType(sdkdom, def) < 0)
        goto error;

    if (prlsdkAddDomainHardware(sdkdom, def) < 0)
        goto error;

    if (prlsdkAddVNCInfo(sdkdom, def) < 0)
        goto error;

    pret = PrlVmCfg_GetEnvId(sdkdom, &envId);
    prlsdkCheckRetGoto(pret, error);
    pdom->id = envId;

    buflen = 0;
    pret = PrlVmCfg_GetHomePath(sdkdom, NULL, &buflen);
    prlsdkCheckRetGoto(pret, error);

    VIR_FREE(pdom->home);
    if (VIR_ALLOC_N(pdom->home, buflen) < 0)
        goto error;

    pret = PrlVmCfg_GetHomePath(sdkdom, pdom->home, &buflen);
    prlsdkCheckRetGoto(pret, error);

    /* For VMs pdom->home is actually /directory/config.pvs */
    if (!IS_CT(def)) {
        /* Get rid of /config.pvs in path string */
        char *s = strrchr(pdom->home, '/');
        if (s)
            *s = '\0';
    }

    pret = PrlVmCfg_GetAutoStart(sdkdom, &autostart);
    prlsdkCheckRetGoto(pret, error);

    if (prlsdkGetDomainState(sdkdom, &domainState) < 0)
        goto error;

    if (virDomainDefAddImplicitControllers(def) < 0)
        goto error;

    if (def->ngraphics > 0) {
        int bus = IS_CT(def) ? VIR_DOMAIN_INPUT_BUS_PARALLELS :
                               VIR_DOMAIN_INPUT_BUS_PS2;

        if (virDomainDefMaybeAddInput(def,
                                      VIR_DOMAIN_INPUT_TYPE_MOUSE,
                                      bus) < 0)
            goto error;

        if (virDomainDefMaybeAddInput(def,
                                      VIR_DOMAIN_INPUT_TYPE_KBD,
                                      bus) < 0)
            goto error;
    }

    if (olddom) {
        /* assign new virDomainDef without any checks */
        /* we can't use virDomainObjAssignDef, because it checks
         * for state and domain name */
        dom = olddom;
        virDomainDefFree(dom->def);
        dom->def = def;
    } else {
        if (!(dom = virDomainObjListAdd(privconn->domains, def,
                                        privconn->xmlopt,
                                        0, NULL)))
            goto error;
    }
    /* dom is locked here */

    dom->privateData = pdom;
    dom->privateDataFreeFunc = prlsdkDomObjFreePrivate;
    dom->persistent = 1;

    switch (autostart) {
    case PAO_VM_START_ON_LOAD:
        dom->autostart = 1;
        break;
    case PAO_VM_START_MANUAL:
        dom->autostart = 0;
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown autostart mode: %X"), autostart);
        goto error;
    }

    if (prlsdkConvertDomainState(domainState, envId, dom) < 0)
        goto error;

    if (!pdom->sdkdom) {
        pret = PrlHandle_AddRef(sdkdom);
        prlsdkCheckRetGoto(pret, error);
        pdom->sdkdom = sdkdom;
    }

    return dom;
 error:
    if (dom && !olddom) {
        /* Domain isn't persistent means that we haven't yet set
         * prlsdkDomObjFreePrivate and should call it manually
         */
        if (!dom->persistent)
            prlsdkDomObjFreePrivate(pdom);

        virDomainObjListRemove(privconn->domains, dom);
    }
    /* Delete newly allocated def only if we haven't assigned it to domain
     * Otherwise we will end up with domain having invalid def within it
     */
    if (!dom)
        virDomainDefFree(def);

    return NULL;
}

int
prlsdkLoadDomains(vzConnPtr privconn)
{
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    PRL_HANDLE result;
    PRL_HANDLE sdkdom;
    PRL_UINT32 paramsCount;
    PRL_RESULT pret;
    size_t i = 0;
    virDomainObjPtr dom;

    job = PrlSrv_GetVmListEx(privconn->server, PVTF_VM | PVTF_CT);

    if (PRL_FAILED(getJobResult(job, &result)))
        return -1;

    pret = PrlResult_GetParamsCount(result, &paramsCount);
    prlsdkCheckRetGoto(pret, error);

    for (i = 0; i < paramsCount; i++) {
        pret = PrlResult_GetParamByIndex(result, i, &sdkdom);
        if (PRL_FAILED(pret)) {
            logPrlError(pret);
            PrlHandle_Free(sdkdom);
            goto error;
        }

        dom = prlsdkLoadDomain(privconn, sdkdom, NULL);
        PrlHandle_Free(sdkdom);

        if (!dom)
            goto error;
        else
            virObjectUnlock(dom);
    }

    PrlHandle_Free(result);
    return 0;

 error:
    PrlHandle_Free(result);
    return -1;
}

virDomainObjPtr
prlsdkAddDomain(vzConnPtr privconn, const unsigned char *uuid)
{
    PRL_HANDLE sdkdom = PRL_INVALID_HANDLE;
    virDomainObjPtr dom;

    dom = virDomainObjListFindByUUID(privconn->domains, uuid);
    if (dom) {
        /* domain is already in the list */
        return dom;
    }

    sdkdom = prlsdkSdkDomainLookupByUUID(privconn, uuid);
    if (sdkdom == PRL_INVALID_HANDLE)
        return NULL;

    dom = prlsdkLoadDomain(privconn, sdkdom, NULL);
    PrlHandle_Free(sdkdom);
    return dom;
}

int
prlsdkUpdateDomain(vzConnPtr privconn, virDomainObjPtr dom)
{
    PRL_HANDLE job;
    virDomainObjPtr retdom = NULL;
    vzDomObjPtr pdom = dom->privateData;

    job = PrlVm_RefreshConfig(pdom->sdkdom);
    if (waitJob(job))
        return -1;

    retdom = prlsdkLoadDomain(privconn, pdom->sdkdom, dom);
    return retdom ? 0 : -1;
}

static int prlsdkSendEvent(vzConnPtr privconn,
                           virDomainObjPtr dom,
                           virDomainEventType lvEventType,
                           int lvEventTypeDetails)
{
    virObjectEventPtr event = NULL;

    event = virDomainEventLifecycleNewFromObj(dom,
                                              lvEventType,
                                              lvEventTypeDetails);
    if (!event)
        return -1;

    virObjectEventStateQueue(privconn->domainEventState, event);
    return 0;
}

static void
prlsdkNewStateToEvent(VIRTUAL_MACHINE_STATE domainState,
                      virDomainEventType *lvEventType,
                      int *lvEventTypeDetails)
{
    /* We skip all intermediate states here, because
     * libvirt doesn't have correspoding event types for
     * them */
    switch (domainState) {
    case VMS_STOPPED:
    case VMS_MOUNTED:
        *lvEventType = VIR_DOMAIN_EVENT_STOPPED;
        *lvEventTypeDetails = VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN;
        break;
    case VMS_RUNNING:
        *lvEventType = VIR_DOMAIN_EVENT_STARTED;
        *lvEventTypeDetails = VIR_DOMAIN_EVENT_STARTED_BOOTED;
        break;
    case VMS_PAUSED:
        *lvEventType = VIR_DOMAIN_EVENT_SUSPENDED;
        *lvEventTypeDetails = VIR_DOMAIN_EVENT_SUSPENDED_PAUSED;
        break;
    case VMS_SUSPENDED:
        *lvEventType = VIR_DOMAIN_EVENT_STOPPED;
        *lvEventTypeDetails = VIR_DOMAIN_EVENT_STOPPED_SAVED;
        break;
    default:
        VIR_DEBUG("Skip sending event about changing state to %X",
                  domainState);
        break;
    }
}

static void
prlsdkHandleVmStateEvent(vzConnPtr privconn,
                         PRL_HANDLE prlEvent,
                         unsigned char *uuid)
{
    PRL_RESULT pret = PRL_ERR_FAILURE;
    PRL_HANDLE eventParam = PRL_INVALID_HANDLE;
    PRL_INT32 domainState;
    virDomainObjPtr dom = NULL;
    vzDomObjPtr pdom;
    virDomainEventType lvEventType = 0;
    int lvEventTypeDetails = 0;

    dom = virDomainObjListFindByUUID(privconn->domains, uuid);
    if (dom == NULL)
        return;

    pret = PrlEvent_GetParamByName(prlEvent, "vminfo_vm_state", &eventParam);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlEvtPrm_ToInt32(eventParam, &domainState);
    prlsdkCheckRetGoto(pret, cleanup);

    pdom = dom->privateData;
    if (prlsdkConvertDomainState(domainState, pdom->id, dom) < 0)
        goto cleanup;

    prlsdkNewStateToEvent(domainState,
                          &lvEventType,
                          &lvEventTypeDetails);

    prlsdkSendEvent(privconn, dom, lvEventType, lvEventTypeDetails);

 cleanup:
    virObjectUnlock(dom);
    return;
}

static void
prlsdkHandleVmConfigEvent(vzConnPtr privconn,
                          unsigned char *uuid)
{
    virDomainObjPtr dom = NULL;

    dom = virDomainObjListFindByUUID(privconn->domains, uuid);
    if (dom == NULL)
        return;

    if (prlsdkUpdateDomain(privconn, dom) < 0)
        goto cleanup;

    prlsdkSendEvent(privconn, dom, VIR_DOMAIN_EVENT_DEFINED,
                    VIR_DOMAIN_EVENT_DEFINED_UPDATED);

 cleanup:
    virObjectUnlock(dom);
    return;
}

static void
prlsdkHandleVmAddedEvent(vzConnPtr privconn,
                         unsigned char *uuid)
{
    virDomainObjPtr dom = NULL;

    dom = prlsdkAddDomain(privconn, uuid);
    if (dom == NULL)
        return;

    prlsdkSendEvent(privconn, dom, VIR_DOMAIN_EVENT_DEFINED,
                    VIR_DOMAIN_EVENT_DEFINED_ADDED);

    virObjectUnlock(dom);
    return;
}

static void
prlsdkHandleVmRemovedEvent(vzConnPtr privconn,
                           unsigned char *uuid)
{
    virDomainObjPtr dom = NULL;

    dom = virDomainObjListFindByUUID(privconn->domains, uuid);
    /* domain was removed from the list from the libvirt
     * API function in current connection */
    if (dom == NULL)
        return;

    prlsdkSendEvent(privconn, dom, VIR_DOMAIN_EVENT_UNDEFINED,
                    VIR_DOMAIN_EVENT_UNDEFINED_REMOVED);

    virDomainObjListRemove(privconn->domains, dom);
    return;
}

#define PARALLELS_STATISTICS_DROP_COUNT 3

static PRL_RESULT
prlsdkHandlePerfEvent(vzConnPtr privconn,
                      PRL_HANDLE event,
                      unsigned char *uuid)
{
    virDomainObjPtr dom = NULL;
    vzDomObjPtr privdom = NULL;
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    dom = virDomainObjListFindByUUID(privconn->domains, uuid);
    if (dom == NULL)
        goto cleanup;
    privdom = dom->privateData;

    /* delayed event after unsubscribe */
    if (privdom->cache.count == -1)
        goto cleanup;

    PrlHandle_Free(privdom->cache.stats);
    privdom->cache.stats = PRL_INVALID_HANDLE;

    if (privdom->cache.count > PARALLELS_STATISTICS_DROP_COUNT) {
        job = PrlVm_UnsubscribeFromPerfStats(privdom->sdkdom);
        if (PRL_FAILED(waitJob(job)))
            goto cleanup;
        /* change state to unsubscribed */
        privdom->cache.count = -1;
    } else {
        ++privdom->cache.count;
        privdom->cache.stats = event;
        /* thus we get own of event handle */
        event = PRL_INVALID_HANDLE;
        virCondSignal(&privdom->cache.cond);
    }

 cleanup:
    PrlHandle_Free(event);
    if (dom)
        virObjectUnlock(dom);

    return PRL_ERR_SUCCESS;
}

static void
prlsdkHandleVmEvent(vzConnPtr privconn, PRL_HANDLE prlEvent)
{
    PRL_RESULT pret = PRL_ERR_FAILURE;
    char uuidstr[VIR_UUID_STRING_BUFLEN + 2];
    unsigned char uuid[VIR_UUID_BUFLEN];
    PRL_UINT32 bufsize = ARRAY_CARDINALITY(uuidstr);
    PRL_EVENT_TYPE prlEventType;

    pret = PrlEvent_GetType(prlEvent, &prlEventType);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlEvent_GetIssuerId(prlEvent, uuidstr, &bufsize);
    prlsdkCheckRetGoto(pret, cleanup);

    if (prlsdkUUIDParse(uuidstr, uuid) < 0)
        goto cleanup;

    switch (prlEventType) {
    case PET_DSP_EVT_VM_STATE_CHANGED:
        prlsdkHandleVmStateEvent(privconn, prlEvent, uuid);
        break;
    case PET_DSP_EVT_VM_CONFIG_CHANGED:
        prlsdkHandleVmConfigEvent(privconn, uuid);
        break;
    case PET_DSP_EVT_VM_CREATED:
    case PET_DSP_EVT_VM_ADDED:
        prlsdkHandleVmAddedEvent(privconn, uuid);
        break;
    case PET_DSP_EVT_VM_DELETED:
    case PET_DSP_EVT_VM_UNREGISTERED:
        prlsdkHandleVmRemovedEvent(privconn, uuid);
        break;
    case PET_DSP_EVT_VM_PERFSTATS:
        prlsdkHandlePerfEvent(privconn, prlEvent, uuid);
        /* above function takes own of event */
        prlEvent = PRL_INVALID_HANDLE;
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Can't handle event of type %d"), prlEventType);
    }

 cleanup:
    PrlHandle_Free(prlEvent);
    return;
}

static PRL_RESULT
prlsdkEventsHandler(PRL_HANDLE prlEvent, PRL_VOID_PTR opaque)
{
    vzConnPtr privconn = opaque;
    PRL_RESULT pret = PRL_ERR_FAILURE;
    PRL_HANDLE_TYPE handleType;
    PRL_EVENT_ISSUER_TYPE prlIssuerType = PIE_UNKNOWN;

    pret = PrlHandle_GetType(prlEvent, &handleType);
    prlsdkCheckRetGoto(pret, cleanup);

    /* Currently, there is no need to handle anything but events */
    if (handleType != PHT_EVENT)
        goto cleanup;

    if (privconn == NULL)
        goto cleanup;

    PrlEvent_GetIssuerType(prlEvent, &prlIssuerType);
    prlsdkCheckRetGoto(pret, cleanup);

    switch (prlIssuerType) {
    case PIE_VIRTUAL_MACHINE:
        prlsdkHandleVmEvent(privconn, prlEvent);
        /* above function takes own of event */
        prlEvent = PRL_INVALID_HANDLE;
        break;
    default:
        VIR_DEBUG("Skipping event of issuer type %d", prlIssuerType);
    }

 cleanup:
    PrlHandle_Free(prlEvent);
    return PRL_ERR_SUCCESS;
}


int prlsdkSubscribeToPCSEvents(vzConnPtr privconn)
{
    PRL_RESULT pret = PRL_ERR_UNINITIALIZED;

    pret = PrlSrv_RegEventHandler(privconn->server,
                                  prlsdkEventsHandler,
                                  privconn);
    prlsdkCheckRetGoto(pret, error);
    return 0;

 error:
    return -1;
}

void prlsdkUnsubscribeFromPCSEvents(vzConnPtr privconn)
{
    PRL_RESULT ret = PRL_ERR_UNINITIALIZED;
    ret = PrlSrv_UnregEventHandler(privconn->server,
                                   prlsdkEventsHandler,
                                   privconn);
    if (PRL_FAILED(ret))
        logPrlError(ret);
}

PRL_RESULT prlsdkStart(PRL_HANDLE sdkdom)
{
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    job = PrlVm_StartEx(sdkdom, PSM_VM_START, 0);
    return waitJob(job);
}

static PRL_RESULT prlsdkStopEx(PRL_HANDLE sdkdom, PRL_UINT32 mode)
{
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    job = PrlVm_StopEx(sdkdom, mode, 0);
    return waitJob(job);
}

PRL_RESULT prlsdkKill(PRL_HANDLE sdkdom)
{
    return prlsdkStopEx(sdkdom, PSM_KILL);
}

PRL_RESULT prlsdkStop(PRL_HANDLE sdkdom)
{
    return prlsdkStopEx(sdkdom, PSM_SHUTDOWN);
}

PRL_RESULT prlsdkPause(PRL_HANDLE sdkdom)
{
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    job = PrlVm_Pause(sdkdom, false);
    return waitJob(job);
}

PRL_RESULT prlsdkResume(PRL_HANDLE sdkdom)
{
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    job = PrlVm_Resume(sdkdom);
    return waitJob(job);
}

PRL_RESULT prlsdkSuspend(PRL_HANDLE sdkdom)
{
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    job = PrlVm_Suspend(sdkdom);
    return waitJob(job);
}

int
prlsdkDomainChangeStateLocked(vzConnPtr privconn,
                              virDomainObjPtr dom,
                              prlsdkChangeStateFunc chstate)
{
    vzDomObjPtr pdom;
    PRL_RESULT pret;
    virErrorNumber virerr;

    pdom = dom->privateData;
    pret = chstate(pdom->sdkdom);
    if (PRL_FAILED(pret)) {
        virResetLastError();

        switch (pret) {
        case PRL_ERR_DISP_VM_IS_NOT_STARTED:
        case PRL_ERR_DISP_VM_IS_NOT_STOPPED:
            virerr = VIR_ERR_OPERATION_INVALID;
            break;
        default:
            virerr = VIR_ERR_OPERATION_FAILED;
        }

        virReportError(virerr, "%s", _("Can't change domain state."));
        return -1;
    }

    return prlsdkUpdateDomain(privconn, dom);
}

int
prlsdkDomainChangeState(virDomainPtr domain,
                        prlsdkChangeStateFunc chstate)
{
    vzConnPtr privconn = domain->conn->privateData;
    virDomainObjPtr dom;
    int ret = -1;

    if (!(dom = vzDomObjFromDomain(domain)))
        return -1;

    ret = prlsdkDomainChangeStateLocked(privconn, dom, chstate);
    virObjectUnlock(dom);
    return ret;
}

static int
prlsdkCheckUnsupportedParams(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    size_t i;
    PRL_VM_TYPE vmType;
    PRL_RESULT pret;
    virDomainNumatuneMemMode memMode;

    if (def->title) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("titles are not supported by vz driver"));
        return -1;
    }

    if (def->blkio.ndevices > 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("blkio parameters are not supported "
                         "by vz driver"));
        return -1;
    }

    if (virDomainDefGetMemoryActual(def) != def->mem.cur_balloon) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("changing balloon parameters is not supported "
                         "by vz driver"));
        return -1;
    }

    if (virDomainDefGetMemoryActual(def) % (1 << 10) != 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Memory size should be multiple of 1Mb."));
        return -1;
    }

    if (def->mem.nhugepages ||
        virMemoryLimitIsSet(def->mem.hard_limit) ||
        virMemoryLimitIsSet(def->mem.soft_limit) ||
        def->mem.min_guarantee ||
        virMemoryLimitIsSet(def->mem.swap_hard_limit)) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Memory parameter is not supported "
                         "by vz driver"));
        return -1;
    }

    if (def->vcpus != def->maxvcpus) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("current vcpus must be equal to maxvcpus"));
        return -1;
    }

    if (def->placement_mode) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("changing cpu placement mode is not supported "
                         "by vz driver"));
        return -1;
    }

    if (def->cputune.shares ||
        def->cputune.sharesSpecified ||
        def->cputune.period ||
        def->cputune.quota) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("cputune is not supported by vz driver"));
        return -1;
    }

    if (def->cputune.vcpupin) {
        for (i = 0; i < def->vcpus; i++) {
            if (!virBitmapEqual(def->cpumask,
                                def->cputune.vcpupin[i]->cpumask)) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               "%s", _("vcpupin cpumask differs from default cpumask"));
                return -1;
            }
        }
    }


    /*
     * Though we don't support NUMA configuration at the moment
     * virDomainDefPtr always contain non zero NUMA configuration
     * So, just make sure this configuration does't differ from auto generated.
     */
    if ((virDomainNumatuneGetMode(def->numa, -1, &memMode) == 0 &&
         memMode == VIR_DOMAIN_NUMATUNE_MEM_STRICT) ||
        virDomainNumatuneHasPerNodeBinding(def->numa)) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("numa parameters are not supported "
                         "by vz driver"));
        return -1;
    }

    if (def->onReboot != VIR_DOMAIN_LIFECYCLE_RESTART ||
        def->onPoweroff != VIR_DOMAIN_LIFECYCLE_DESTROY ||
        def->onCrash != VIR_DOMAIN_LIFECYCLE_CRASH_DESTROY) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("on_reboot, on_poweroff and on_crash parameters "
                         "are not supported by vz driver"));
        return -1;
    }

    /* we fill only type and arch fields in vzLoadDomain for
     * hvm type and also init for containers, so we can check that all
     * other paramenters are null and boot devices config is default */

    if (def->os.machine != NULL || def->os.bootmenu != 0 ||
        def->os.kernel != NULL || def->os.initrd != NULL ||
        def->os.cmdline != NULL || def->os.root != NULL ||
        def->os.loader != NULL || def->os.bootloader != NULL ||
        def->os.bootloaderArgs != NULL || def->os.smbios_mode != 0 ||
        def->os.bios.useserial != 0) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("changing OS parameters is not supported "
                         "by vz driver"));
        return -1;
    }

    pret = PrlVmCfg_GetVmType(sdkdom, &vmType);
    if (PRL_FAILED(pret)) {
        logPrlError(pret);
        return -1;
    }

    if (!(vmType == PVT_VM && !IS_CT(def)) &&
        !(vmType == PVT_CT && IS_CT(def))) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("changing OS type is not supported "
                         "by vz driver"));
        return -1;
    }

    if (!IS_CT(def)) {
        if (def->os.nBootDevs != 1 ||
            def->os.bootDevs[0] != VIR_DOMAIN_BOOT_DISK ||
            def->os.init != NULL || def->os.initargv != NULL) {

            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("changing OS parameters is not supported "
                             "by vz driver"));
            return -1;
        }
    } else {
        if (def->os.nBootDevs != 0 ||
            !STREQ_NULLABLE(def->os.init, "/sbin/init") ||
            (def->os.initargv != NULL && def->os.initargv[0] != NULL)) {

            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("changing OS parameters is not supported "
                             "by vz driver"));
            return -1;
        }
    }

    if (def->emulator) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("changing emulator is not supported "
                         "by vz driver"));
        return -1;
    }

    for (i = 0; i < VIR_DOMAIN_FEATURE_LAST; i++) {
        if (def->features[i]) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("changing features is not supported "
                             "by vz driver"));
            return -1;
        }
    }

    if (def->clock.offset != VIR_DOMAIN_CLOCK_OFFSET_UTC ||
        def->clock.ntimers != 0) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("changing clock parameters is not supported "
                         "by vz driver"));
        return -1;
    }

    if (!IS_CT(def) && def->nfss != 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Filesystems in VMs are not supported "
                         "by vz driver"));
        return -1;
    }

    if (def->nsounds != 0 || def->nhostdevs != 0 ||
        def->nredirdevs != 0 || def->nsmartcards != 0 ||
        def->nparallels || def->nchannels != 0 ||
        def->nleases != 0 || def->nhubs != 0) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("changing devices parameters is not supported "
                         "by vz driver"));
        return -1;
    }

    /* there may be one auto-input */
    if (def->ninputs != 0 &&
        (def->ninputs != 2 &&
         def->inputs[0]->type != VIR_DOMAIN_INPUT_TYPE_MOUSE &&
         def->inputs[0]->bus != VIR_DOMAIN_INPUT_BUS_PS2 &&
         def->inputs[1]->type != VIR_DOMAIN_INPUT_TYPE_KBD &&
         def->inputs[1]->bus != VIR_DOMAIN_INPUT_BUS_PS2)) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("changing input devices parameters is not supported "
                         "by vz driver"));
        return -1;
    }

    return 0;
}

static int prlsdkClearDevices(PRL_HANDLE sdkdom)
{
    PRL_RESULT pret;
    PRL_UINT32 n, i;
    PRL_HANDLE devList;
    PRL_HANDLE dev;
    int ret = -1;

    pret = PrlVmCfg_SetVNCMode(sdkdom, PRD_DISABLED);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmCfg_GetAllDevices(sdkdom, &devList);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlHndlList_GetItemsCount(devList, &n);
    prlsdkCheckRetGoto(pret, cleanup);

    for (i = 0; i < n; i++) {
        pret = PrlHndlList_GetItem(devList, i, &dev);
        prlsdkCheckRetGoto(pret, cleanup);

        pret = PrlVmDev_Remove(dev);
        PrlHandle_Free(dev);
    }

    ret = 0;
 cleanup:
    PrlHandle_Free(devList);
    return ret;
}

static int
prlsdkRemoveBootDevices(PRL_HANDLE sdkdom)
{
    PRL_RESULT pret;
    PRL_UINT32 i, devCount;
    PRL_HANDLE dev = PRL_INVALID_HANDLE;
    PRL_DEVICE_TYPE devType;

    pret = PrlVmCfg_GetBootDevCount(sdkdom, &devCount);
    prlsdkCheckRetGoto(pret, error);

    for (i = 0; i < devCount; i++) {

        /* always get device by index 0, because device list resort after delete */
        pret = PrlVmCfg_GetBootDev(sdkdom, 0, &dev);
        prlsdkCheckRetGoto(pret, error);

        pret = PrlBootDev_GetType(dev, &devType);
        prlsdkCheckRetGoto(pret, error);

        pret = PrlBootDev_Remove(dev);
        prlsdkCheckRetGoto(pret, error);
    }

    return 0;

 error:
    return -1;
}

static int
prlsdkAddDeviceToBootList(PRL_HANDLE sdkdom,
                          PRL_UINT32 devIndex,
                          PRL_DEVICE_TYPE devType,
                          PRL_UINT32 bootSequence)
{
    PRL_RESULT pret;
    PRL_HANDLE bootDev = PRL_INVALID_HANDLE;

    pret = PrlVmCfg_CreateBootDev(sdkdom, &bootDev);
    prlsdkCheckRetGoto(pret, error);

    pret = PrlBootDev_SetIndex(bootDev, devIndex);
    prlsdkCheckRetGoto(pret, error);

    pret = PrlBootDev_SetType(bootDev, devType);
    prlsdkCheckRetGoto(pret, error);

    pret = PrlBootDev_SetSequenceIndex(bootDev, bootSequence);
    prlsdkCheckRetGoto(pret, error);

    pret = PrlBootDev_SetInUse(bootDev, PRL_TRUE);
    prlsdkCheckRetGoto(pret, error);

    return 0;

 error:
    if (bootDev != PRL_INVALID_HANDLE)
        PrlBootDev_Remove(bootDev);

    return -1;
}

static int prlsdkCheckGraphicsUnsupportedParams(virDomainDefPtr def)
{
    virDomainGraphicsDefPtr gr;

    if (def->ngraphics == 0)
        return 0;

    if (def->ngraphics > 1) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver supports only "
                         "one VNC per domain."));
        return -1;
    }

    gr = def->graphics[0];

    if (gr->type != VIR_DOMAIN_GRAPHICS_TYPE_VNC) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver supports only "
                         "VNC graphics."));
        return -1;
    }

    if (gr->data.vnc.websocket != 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "websockets for VNC graphics."));
        return -1;
    }

    if (gr->data.vnc.keymap != 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "keymap setting for VNC graphics."));
        return -1;
    }

    if (gr->data.vnc.sharePolicy == VIR_DOMAIN_GRAPHICS_VNC_SHARE_ALLOW_EXCLUSIVE) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "exclusive share policy for VNC graphics."));
        return -1;
    }

    if (gr->data.vnc.socket) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "VNC graphics over unix sockets."));
        return -1;
    }

    if (gr->data.vnc.auth.connected == VIR_DOMAIN_GRAPHICS_AUTH_CONNECTED_FAIL ||
        gr->data.vnc.auth.connected == VIR_DOMAIN_GRAPHICS_AUTH_CONNECTED_KEEP) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "given action in case of password change."));
        return -1;
    }

    if (gr->data.vnc.auth.expires) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "setting password expire time."));
        return -1;
    }

    if (gr->nListens > 1) {
        virReportError(VIR_ERR_INVALID_ARG, "%s",
                       _("vz driver doesn't support more than "
                         "one listening VNC server per domain"));
        return -1;
    }

    if (gr->nListens == 1 &&
        virDomainGraphicsListenGetType(gr, 0) != VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS) {
        virReportError(VIR_ERR_INVALID_ARG, "%s",
                       _("vz driver supports only address-based VNC listening"));
        return -1;
    }

    return 0;
}

static int prlsdkCheckVideoUnsupportedParams(virDomainDefPtr def)
{
    virDomainVideoDefPtr v;

    if (IS_CT(def)) {
        if (def->nvideos == 0) {
            return 0;
        } else {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("Video adapters are not supported "
                             "int containers."));
            return -1;
        }
    } else {
        if (def->nvideos != 1) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("vz driver supports "
                             "only one video adapter."));
            return -1;
        }
    }

    v = def->videos[0];

    if (v->type != VIR_DOMAIN_VIDEO_TYPE_VGA) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver supports "
                         "only VGA video adapters."));
        return -1;
    }

    if (v->heads != 1) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "multihead video adapters."));
        return -1;
    }

    if (v->accel != NULL && (v->accel->support2d || v->accel->support3d)) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "setting video acceleration parameters."));
        return -1;
    }

    return 0;
}

static int prlsdkCheckSerialUnsupportedParams(virDomainChrDefPtr chr)
{
    if (chr->deviceType != VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Specified character device type is not supported "
                         "by vz driver."));
        return -1;
    }

    if (chr->targetTypeAttr) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Specified character device target type is not "
                         "supported by vz driver."));
        return -1;
    }

    if (chr->source.type != VIR_DOMAIN_CHR_TYPE_DEV &&
        chr->source.type != VIR_DOMAIN_CHR_TYPE_FILE &&
        chr->source.type != VIR_DOMAIN_CHR_TYPE_UNIX) {


        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Specified character device source type is not "
                         "supported by vz driver."));
        return -1;
    }

    if (chr->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting device info for character devices is not "
                         "supported by vz driver."));
        return -1;
    }

    if (chr->nseclabels > 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting security labels is not "
                         "supported by vz driver."));
        return -1;
    }

    return 0;
}

static int prlsdkCheckNetUnsupportedParams(virDomainNetDefPtr net)
{
    if (net->type != VIR_DOMAIN_NET_TYPE_NETWORK &&
        net->type != VIR_DOMAIN_NET_TYPE_BRIDGE) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Specified network adapter type is not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->backend.tap || net->backend.vhost) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Interface backend parameters are not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->data.network.portgroup) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Virtual network portgroups are not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->tune.sndbuf_specified) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting interface sndbuf is not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->script) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting interface script is not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->ifname_guest) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting guest interface name is not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting device info for network devices is not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->filter) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting network filter is not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->bandwidth) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting network bandwidth is not "
                         "supported by vz driver."));
        return -1;
    }

    if (net->vlan.trunk) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting up vlans is not "
                         "supported by vz driver."));
        return -1;
    }

    return 0;
}

static int prlsdkCheckDiskUnsupportedParams(virDomainDiskDefPtr disk)
{
    if (disk->device != VIR_DOMAIN_DISK_DEVICE_DISK &&
        disk->device != VIR_DOMAIN_DISK_DEVICE_CDROM) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Only hard disks and cdroms are supported "
                         "by vz driver."));
        return -1;
    }

    if (disk->blockio.logical_block_size ||
        disk->blockio.physical_block_size) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting disk block sizes is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->blkdeviotune.total_bytes_sec ||
        disk->blkdeviotune.read_bytes_sec ||
        disk->blkdeviotune.write_bytes_sec ||
        disk->blkdeviotune.total_iops_sec ||
        disk->blkdeviotune.read_iops_sec ||
        disk->blkdeviotune.write_iops_sec) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting disk io limits is not "
                         "supported by vz driver yet."));
        return -1;
    }

    if (disk->serial) {
        VIR_INFO("%s", _("Setting disk serial number is not "
                         "supported by vz driver."));
    }

    if (disk->wwn) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting disk wwn id is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->vendor) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting disk vendor is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->product) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting disk product id is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->error_policy != VIR_DOMAIN_DISK_ERROR_POLICY_DEFAULT) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting disk error policy is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->iomode) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting disk io mode is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->copy_on_read) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Disk copy_on_read is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->startupPolicy != VIR_DOMAIN_STARTUP_POLICY_DEFAULT) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting up disk startup policy is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->transient) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Transient disks are not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->discard) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting up disk discard parameter is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->iothread) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting up disk io thread # is not "
                         "supported by vz driver."));
        return -1;
    }

    if (disk->src->type != VIR_STORAGE_TYPE_FILE &&
        disk->src->type != VIR_STORAGE_TYPE_BLOCK) {

        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Only disk and block storage types are "
                         "supported by vz driver."));
        return -1;

    }

    return 0;
}

static int prlsdkCheckFSUnsupportedParams(virDomainFSDefPtr fs)
{
    if (fs->type != VIR_DOMAIN_FS_TYPE_FILE) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Only file based filesystems are "
                         "supported by vz driver."));
        return -1;
    }

    if (fs->fsdriver != VIR_DOMAIN_FS_DRIVER_TYPE_PLOOP) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Only ploop fs driver is "
                         "supported by vz driver."));
        return -1;
    }

    if (fs->accessmode != VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Changing fs access mode is not "
                         "supported by vz driver."));
        return -1;
    }

    if (fs->wrpolicy != VIR_DOMAIN_FS_WRPOLICY_DEFAULT) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Changing fs write policy is not "
                         "supported by vz driver."));
        return -1;
    }

    if (fs->format != VIR_STORAGE_FILE_PLOOP) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Only ploop disk images are "
                         "supported by vz driver."));
        return -1;
    }

    if (fs->readonly) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting readonly for filesystems is "
                         "supported by vz driver."));
        return -1;
    }

    if (fs->space_hard_limit || fs->space_soft_limit) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Setting fs quotas is not "
                         "supported by vz driver."));
        return -1;
    }

    return 0;
}

static int prlsdkApplyGraphicsParams(PRL_HANDLE sdkdom, virDomainDefPtr def)
{
    virDomainGraphicsDefPtr gr;
    PRL_RESULT pret;
    int ret  = -1;
    const char *listenAddr = NULL;

    if (prlsdkCheckGraphicsUnsupportedParams(def))
        return -1;

    if (def->ngraphics == 0)
        return 0;

    gr = def->graphics[0];

    if (gr->data.vnc.autoport) {
        pret = PrlVmCfg_SetVNCMode(sdkdom, PRD_AUTO);
        prlsdkCheckRetGoto(pret, cleanup);
    } else {
        pret = PrlVmCfg_SetVNCMode(sdkdom, PRD_MANUAL);
        prlsdkCheckRetGoto(pret, cleanup);

        pret = PrlVmCfg_SetVNCPort(sdkdom, gr->data.vnc.port);
        prlsdkCheckRetGoto(pret, cleanup);
    }

    if (gr->nListens == 1) {
        listenAddr = virDomainGraphicsListenGetAddress(gr, 0);
        if (!listenAddr)
            goto cleanup;
        pret = PrlVmCfg_SetVNCHostName(sdkdom, listenAddr);
        prlsdkCheckRetGoto(pret, cleanup);
    }

    ret = 0;
 cleanup:
    return ret;
}

static int prlsdkApplyVideoParams(PRL_HANDLE sdkdom ATTRIBUTE_UNUSED, virDomainDefPtr def)
{
    PRL_RESULT pret;

    if (def->nvideos == 0)
        return 0;

    if (IS_CT(def)) {
        /* ignore video parameters */
        return 0;
    }

    if (prlsdkCheckVideoUnsupportedParams(def))
        return -1;

    pret = PrlVmCfg_SetVideoRamSize(sdkdom, def->videos[0]->vram >> 10);
    prlsdkCheckRetGoto(pret, error);

    return 0;
 error:
    return -1;
}

static int prlsdkAddSerial(PRL_HANDLE sdkdom, virDomainChrDefPtr chr)
{
    PRL_RESULT pret;
    PRL_HANDLE sdkchr = PRL_INVALID_HANDLE;
    PRL_VM_DEV_EMULATION_TYPE emutype;
    PRL_SERIAL_PORT_SOCKET_OPERATION_MODE socket_mode = PSP_SERIAL_SOCKET_SERVER;
    char *path;
    int ret = -1;

    if (prlsdkCheckSerialUnsupportedParams(chr) < 0)
        return -1;

    pret = PrlVmCfg_CreateVmDev(sdkdom, PDE_SERIAL_PORT, &sdkchr);
    prlsdkCheckRetGoto(pret, cleanup);

    switch (chr->source.type) {
    case VIR_DOMAIN_CHR_TYPE_DEV:
        emutype = PDT_USE_REAL_DEVICE;
        path = chr->source.data.file.path;
        break;
    case VIR_DOMAIN_CHR_TYPE_FILE:
        emutype = PDT_USE_OUTPUT_FILE;
        path = chr->source.data.file.path;
        break;
    case VIR_DOMAIN_CHR_TYPE_UNIX:
        emutype = PDT_USE_SERIAL_PORT_SOCKET_MODE;
        path = chr->source.data.nix.path;
        if (chr->source.data.nix.listen)
            socket_mode = PSP_SERIAL_SOCKET_SERVER;
        else
            socket_mode = PSP_SERIAL_SOCKET_CLIENT;
        break;
    default:
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("vz driver doesn't support "
                         "specified serial source type."));
        goto cleanup;
    }

    pret = PrlVmDev_SetEmulatedType(sdkchr, emutype);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetSysName(sdkchr, path);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetFriendlyName(sdkchr, path);
    prlsdkCheckRetGoto(pret, cleanup);

    if (chr->source.type == VIR_DOMAIN_CHR_TYPE_UNIX) {
        pret = PrlVmDevSerial_SetSocketMode(sdkchr, socket_mode);
        prlsdkCheckRetGoto(pret, cleanup);
    }

    pret = PrlVmDev_SetEnabled(sdkchr, 1);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetIndex(sdkchr, chr->target.port);
    prlsdkCheckRetGoto(pret, cleanup);

    ret = 0;
 cleanup:
    PrlHandle_Free(sdkchr);
    return ret;
}

#define PRL_MAC_STRING_BUFNAME  13

static const char * prlsdkFormatMac(virMacAddrPtr mac, char *macstr)
{
    snprintf(macstr, PRL_MAC_STRING_BUFNAME,
             "%02X%02X%02X%02X%02X%02X",
             mac->addr[0], mac->addr[1], mac->addr[2],
             mac->addr[3], mac->addr[4], mac->addr[5]);
    macstr[PRL_MAC_STRING_BUFNAME - 1] = '\0';
    return macstr;
}

static int prlsdkAddNet(PRL_HANDLE sdkdom,
                        vzConnPtr privconn,
                        virDomainNetDefPtr net,
                        bool isCt)
{
    PRL_RESULT pret;
    PRL_HANDLE sdknet = PRL_INVALID_HANDLE;
    PRL_HANDLE vnet = PRL_INVALID_HANDLE;
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    int ret = -1;
    char macstr[PRL_MAC_STRING_BUFNAME];

    if (prlsdkCheckNetUnsupportedParams(net) < 0)
        return -1;

    pret = PrlVmCfg_CreateVmDev(sdkdom, PDE_GENERIC_NETWORK_ADAPTER, &sdknet);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetEnabled(sdknet, 1);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetConnected(sdknet, net->linkstate !=
                                 VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN);

    prlsdkCheckRetGoto(pret, cleanup);

    if (net->ifname) {
        pret = PrlVmDevNet_SetHostInterfaceName(sdknet, net->ifname);
        prlsdkCheckRetGoto(pret, cleanup);
    }

    prlsdkFormatMac(&net->mac, macstr);
    pret = PrlVmDevNet_SetMacAddress(sdknet, macstr);
    prlsdkCheckRetGoto(pret, cleanup);

    if (isCt) {
        if (net->model)
            VIR_WARN("Setting network adapter for containers is not "
                     "supported by vz driver.");
    } else {
        if (STREQ(net->model, "rtl8139")) {
            pret = PrlVmDevNet_SetAdapterType(sdknet, PNT_RTL);
        } else if (STREQ(net->model, "e1000")) {
            pret = PrlVmDevNet_SetAdapterType(sdknet, PNT_E1000);
        } else if (STREQ(net->model, "virtio")) {
            pret = PrlVmDevNet_SetAdapterType(sdknet, PNT_VIRTIO);
        } else {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("Specified network adapter model is not "
                             "supported by vz driver."));
            goto cleanup;
        }
        prlsdkCheckRetGoto(pret, cleanup);
    }

    if (net->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
        if (STREQ(net->data.network.name, PARALLELS_DOMAIN_ROUTED_NETWORK_NAME)) {
            pret = PrlVmDev_SetEmulatedType(sdknet, PNA_ROUTED);
            prlsdkCheckRetGoto(pret, cleanup);
        } else if (STREQ(net->data.network.name, PARALLELS_DOMAIN_BRIDGED_NETWORK_NAME)) {
            pret = PrlVmDev_SetEmulatedType(sdknet, PNA_BRIDGED_ETHERNET);
            prlsdkCheckRetGoto(pret, cleanup);

            pret = PrlVmDevNet_SetVirtualNetworkId(sdknet, net->data.network.name);
            prlsdkCheckRetGoto(pret, cleanup);
        }
    } else if (net->type == VIR_DOMAIN_NET_TYPE_BRIDGE) {
        /*
         * For this type of adapter we create a new
         * Virtual Network assuming that bridge with given name exists
         * Failing creating this means domain creation failure
         */
        pret = PrlVirtNet_Create(&vnet);
        prlsdkCheckRetGoto(pret, cleanup);

        pret = PrlVirtNet_SetNetworkId(vnet, net->data.network.name);
        prlsdkCheckRetGoto(pret, cleanup);

        pret = PrlVirtNet_SetNetworkType(vnet, PVN_BRIDGED_ETHERNET);
        prlsdkCheckRetGoto(pret, cleanup);

        job = PrlSrv_AddVirtualNetwork(privconn->server, vnet, 0);
        if (PRL_FAILED(pret = waitJob(job)))
            goto cleanup;

        pret = PrlVmDev_SetEmulatedType(sdknet, PNA_BRIDGED_ETHERNET);
        prlsdkCheckRetGoto(pret, cleanup);

        pret = PrlVmDevNet_SetVirtualNetworkId(sdknet, net->data.network.name);
        prlsdkCheckRetGoto(pret, cleanup);
    }

    if (net->trustGuestRxFilters == VIR_TRISTATE_BOOL_YES)
        pret = PrlVmDevNet_SetPktFilterPreventMacSpoof(sdknet, 0);
    else if (net->trustGuestRxFilters == VIR_TRISTATE_BOOL_NO)
        pret = PrlVmDevNet_SetPktFilterPreventMacSpoof(sdknet, 1);
    prlsdkCheckRetGoto(pret, cleanup);

    ret = 0;
 cleanup:
    PrlHandle_Free(vnet);
    PrlHandle_Free(sdknet);
    return ret;
}

static int
prlsdkDelNet(vzConnPtr privconn, virDomainNetDefPtr net)
{
    int ret = -1;
    PRL_RESULT pret;
    PRL_HANDLE vnet = PRL_INVALID_HANDLE;
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    if (net->type != VIR_DOMAIN_NET_TYPE_BRIDGE) {
        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                       _("unplugging network device of type %s is not supported"),
                       virDomainNetTypeToString(net->type));
        return ret;
    }

    pret = PrlVirtNet_Create(&vnet);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVirtNet_SetNetworkId(vnet, net->data.network.name);
    prlsdkCheckRetGoto(pret, cleanup);

    job = PrlSrv_DeleteVirtualNetwork(privconn->server, vnet, 0);
    if (PRL_FAILED(pret = waitJob(job)))
        goto cleanup;

    ret = 0;

 cleanup:
    PrlHandle_Free(vnet);
    return ret;
}

int prlsdkAttachNet(virDomainObjPtr dom,
                    vzConnPtr privconn,
                    virDomainNetDefPtr net)
{
    int ret = -1;
    vzDomObjPtr privdom = dom->privateData;
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    if (!IS_CT(dom->def)) {
        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
                       _("network device cannot be attached"));
        return ret;
    }

    job = PrlVm_BeginEdit(privdom->sdkdom);
    if (PRL_FAILED(waitJob(job)))
        return ret;

    ret = prlsdkAddNet(privdom->sdkdom, privconn, net, IS_CT(dom->def));
    if (ret == 0) {
        job = PrlVm_CommitEx(privdom->sdkdom, PVCF_DETACH_HDD_BUNDLE);
        if (PRL_FAILED(waitJob(job)))
            return -1;
    }

    return ret;
}

static PRL_HANDLE
prlsdkFindNetByMAC(PRL_HANDLE sdkdom, virMacAddrPtr mac)
{
    PRL_RESULT pret;
    PRL_UINT32 adaptersCount;
    PRL_UINT32 i;
    PRL_HANDLE adapter = PRL_INVALID_HANDLE;
    PRL_UINT32 len;
    char adapterMac[PRL_MAC_STRING_BUFNAME];
    char expectedMac[PRL_MAC_STRING_BUFNAME];

    prlsdkFormatMac(mac, expectedMac);

    pret = PrlVmCfg_GetNetAdaptersCount(sdkdom, &adaptersCount);
    prlsdkCheckRetGoto(pret, cleanup);

    for (i = 0; i < adaptersCount; ++i) {
        pret = PrlVmCfg_GetNetAdapter(sdkdom, i, &adapter);
        prlsdkCheckRetGoto(pret, cleanup);

        len = sizeof(adapterMac);
        memset(adapterMac, 0, sizeof(adapterMac));
        pret = PrlVmDevNet_GetMacAddress(adapter, adapterMac, &len);
        prlsdkCheckRetGoto(pret, cleanup);

        if (memcmp(adapterMac, expectedMac, PRL_MAC_STRING_BUFNAME) == 0)
            return adapter;

        PrlHandle_Free(adapter);
        adapter = PRL_INVALID_HANDLE;
    }

 cleanup:
    PrlHandle_Free(adapter);
    return adapter;
}

int prlsdkDetachNet(virDomainObjPtr dom,
                    vzConnPtr privconn,
                    virDomainNetDefPtr net)
{
    int ret = -1;
    vzDomObjPtr privdom = dom->privateData;
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    PRL_HANDLE sdknet = PRL_INVALID_HANDLE;
    PRL_RESULT pret;

    if (!IS_CT(dom->def)) {
        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
                       _("network device cannot be detached"));
        goto cleanup;
    }

    job = PrlVm_BeginEdit(privdom->sdkdom);
    if (PRL_FAILED(waitJob(job)))
        goto cleanup;

    sdknet = prlsdkFindNetByMAC(privdom->sdkdom, &net->mac);
    if (sdknet == PRL_INVALID_HANDLE)
        goto cleanup;

    if (prlsdkDelNet(privconn, net) < 0)
        goto cleanup;

    pret = PrlVmDev_Remove(sdknet);
    prlsdkCheckRetGoto(pret, cleanup);

    job = PrlVm_CommitEx(privdom->sdkdom, PVCF_DETACH_HDD_BUNDLE);
    if (PRL_FAILED(waitJob(job)))
        goto cleanup;

    ret = 0;

 cleanup:
    PrlHandle_Free(sdknet);
    return ret;
}

static int prlsdkDelDisk(PRL_HANDLE sdkdom, int idx)
{
    int ret = -1;
    PRL_RESULT pret;
    PRL_HANDLE sdkdisk = PRL_INVALID_HANDLE;

    pret = PrlVmCfg_GetHardDisk(sdkdom, idx, &sdkdisk);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_Remove(sdkdisk);
    prlsdkCheckRetGoto(pret, cleanup);

    ret = 0;

 cleanup:
    PrlHandle_Free(sdkdisk);
    return ret;
}

static int prlsdkAddDisk(PRL_HANDLE sdkdom,
                         virDomainDiskDefPtr disk,
                         bool bootDisk,
                         bool isCt)
{
    PRL_RESULT pret;
    PRL_HANDLE sdkdisk = PRL_INVALID_HANDLE;
    int ret = -1;
    PRL_VM_DEV_EMULATION_TYPE emutype;
    PRL_MASS_STORAGE_INTERFACE_TYPE sdkbus;
    int idx;
    virDomainDeviceDriveAddressPtr drive;
    PRL_UINT32 devIndex;
    PRL_DEVICE_TYPE devType;
    char *dst = NULL;

    if (prlsdkCheckDiskUnsupportedParams(disk) < 0)
        return -1;

    if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK)
        devType = PDE_HARD_DISK;
    else
        devType = PDE_OPTICAL_DISK;

    pret = PrlVmCfg_CreateVmDev(sdkdom, devType, &sdkdisk);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetEnabled(sdkdisk, 1);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetConnected(sdkdisk, 1);
    prlsdkCheckRetGoto(pret, cleanup);

    if (disk->src->type == VIR_STORAGE_TYPE_FILE) {
        if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK &&
            virDomainDiskGetFormat(disk) != VIR_STORAGE_FILE_PLOOP) {

            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid format of "
                                                         "disk %s, vz driver supports only "
                                                         "images in ploop format."), disk->src->path);
            goto cleanup;
        }

        emutype = PDT_USE_IMAGE_FILE;
    } else {
        if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK &&
            (virDomainDiskGetFormat(disk) != VIR_STORAGE_FILE_RAW &&
             virDomainDiskGetFormat(disk) != VIR_STORAGE_FILE_NONE &&
             virDomainDiskGetFormat(disk) != VIR_STORAGE_FILE_AUTO)) {

            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid format "
                                                         "of disk %s, it should be either not set, or set "
                                                         "to raw or auto."), disk->src->path);
            goto cleanup;
        }
        emutype = PDT_USE_REAL_DEVICE;
    }

    pret = PrlVmDev_SetEmulatedType(sdkdisk, emutype);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetSysName(sdkdisk, disk->src->path);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetFriendlyName(sdkdisk, disk->src->path);
    prlsdkCheckRetGoto(pret, cleanup);

    drive = &disk->info.addr.drive;
    if (drive->controller > 0) {
        /* We have only one controller of each type */
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive "
                                                     "address of disk %s, vz driver supports "
                                                     "only one controller."), disk->dst);
        goto cleanup;
    }

    if (drive->target > 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive "
                                                     "address of disk %s, vz driver supports "
                                                     "only target 0."), disk->dst);
        goto cleanup;
    }

    switch (disk->bus) {
    case VIR_DOMAIN_DISK_BUS_IDE:
        if (drive->unit > 1) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive "
                                                         "address of disk %s, vz driver supports "
                                                         "only units 0-1 for IDE bus."), disk->dst);
            goto cleanup;
        }
        sdkbus = PMS_IDE_DEVICE;
        idx = 2 * drive->bus + drive->unit;
        dst = virIndexToDiskName(idx, "hd");
        break;
    case VIR_DOMAIN_DISK_BUS_SCSI:
        if (drive->bus > 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive "
                                                         "address of disk %s, vz driver supports "
                                                         "only bus 0 for SCSI bus."), disk->dst);
            goto cleanup;
        }
        sdkbus = PMS_SCSI_DEVICE;
        idx = drive->unit;
        dst = virIndexToDiskName(idx, "sd");
        break;
    case VIR_DOMAIN_DISK_BUS_SATA:
        if (drive->bus > 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive "
                                                         "address of disk %s, vz driver supports "
                                                         "only bus 0 for SATA bus."), disk->dst);
            goto cleanup;
        }
        sdkbus = PMS_SATA_DEVICE;
        idx = drive->unit;
        dst = virIndexToDiskName(idx, "sd");
        break;
    default:
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Specified disk bus is not "
                         "supported by vz driver."));
        goto cleanup;
    }

    if (!dst)
        goto cleanup;

    if (STRNEQ(dst, disk->dst)) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("Invalid drive "
                                                     "address of disk %s, vz driver supports "
                                                     "only defaults address to logical device name."), disk->dst);
        goto cleanup;
    }

    pret = PrlVmDev_SetIfaceType(sdkdisk, sdkbus);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetStackIndex(sdkdisk, idx);
    prlsdkCheckRetGoto(pret, cleanup);

    switch (disk->cachemode) {
    case VIR_DOMAIN_DISK_CACHE_DISABLE:
        pret = PrlVmCfg_SetDiskCacheWriteBack(sdkdom, PRL_FALSE);
        prlsdkCheckRetGoto(pret, cleanup);
        break;
    case VIR_DOMAIN_DISK_CACHE_WRITEBACK:
        pret = PrlVmCfg_SetDiskCacheWriteBack(sdkdom, PRL_TRUE);
        prlsdkCheckRetGoto(pret, cleanup);
        break;
    case VIR_DOMAIN_DISK_CACHE_DEFAULT:
        break;
    default:
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("Specified disk cache mode is not "
                         "supported by vz driver."));
        goto cleanup;
    }

    if (bootDisk == true) {
        pret = PrlVmDev_GetIndex(sdkdisk, &devIndex);
        prlsdkCheckRetGoto(pret, cleanup);

        if (prlsdkAddDeviceToBootList(sdkdom, devIndex, devType, 0) < 0)
            goto cleanup;

        /* If we add physical device as a boot disk to container
         * we have to specify mount point for it */
        if (isCt) {
            pret = PrlVmDevHd_SetMountPoint(sdkdisk, "/");
            prlsdkCheckRetGoto(pret, cleanup);
        }
    }

    return 0;
 cleanup:
    PrlHandle_Free(sdkdisk);
    VIR_FREE(dst);
    return ret;
}

int
prlsdkAttachVolume(virDomainObjPtr dom, virDomainDiskDefPtr disk)
{
    int ret = -1;
    vzDomObjPtr privdom = dom->privateData;
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    job = PrlVm_BeginEdit(privdom->sdkdom);
    if (PRL_FAILED(waitJob(job)))
        goto cleanup;

    ret = prlsdkAddDisk(privdom->sdkdom, disk, false, IS_CT(dom->def));
    if (ret == 0) {
        job = PrlVm_CommitEx(privdom->sdkdom, PVCF_DETACH_HDD_BUNDLE);
        if (PRL_FAILED(waitJob(job))) {
            ret = -1;
            goto cleanup;
        }
    }

 cleanup:
    return ret;
}

static int
prlsdkGetDiskIndex(PRL_HANDLE sdkdom, virDomainDiskDefPtr disk)
{
    int idx = -1;
    char *buf = NULL;
    PRL_UINT32 buflen = 0;
    PRL_RESULT pret;
    PRL_UINT32 hddCount;
    PRL_UINT32 i;
    PRL_HANDLE hdd = PRL_INVALID_HANDLE;

    pret = PrlVmCfg_GetHardDisksCount(sdkdom, &hddCount);
    prlsdkCheckRetGoto(pret, cleanup);

    for (i = 0; i < hddCount; ++i) {

        pret = PrlVmCfg_GetHardDisk(sdkdom, i, &hdd);
        prlsdkCheckRetGoto(pret, cleanup);

        buflen = 0;
        pret = PrlVmDev_GetFriendlyName(hdd, 0, &buflen);
        prlsdkCheckRetGoto(pret, cleanup);

        if (VIR_ALLOC_N(buf, buflen) < 0)
            goto cleanup;

        pret = PrlVmDev_GetFriendlyName(hdd, buf, &buflen);
        prlsdkCheckRetGoto(pret, cleanup);

        if (STRNEQ(disk->src->path, buf)) {

            PrlHandle_Free(hdd);
            hdd = PRL_INVALID_HANDLE;
            VIR_FREE(buf);
            continue;
        }

        VIR_FREE(buf);
        idx = i;
        break;
    }

 cleanup:
    PrlHandle_Free(hdd);
    return idx;
}

int
prlsdkDetachVolume(virDomainObjPtr dom, virDomainDiskDefPtr disk)
{
    int ret = -1, idx;
    vzDomObjPtr privdom = dom->privateData;
    PRL_HANDLE job = PRL_INVALID_HANDLE;

    idx = prlsdkGetDiskIndex(privdom->sdkdom, disk);
    if (idx < 0)
        goto cleanup;

    job = PrlVm_BeginEdit(privdom->sdkdom);
    if (PRL_FAILED(waitJob(job)))
        goto cleanup;

    ret = prlsdkDelDisk(privdom->sdkdom, idx);
    if (ret == 0) {
        job = PrlVm_CommitEx(privdom->sdkdom, PVCF_DETACH_HDD_BUNDLE);
        if (PRL_FAILED(waitJob(job))) {
            ret = -1;
            goto cleanup;
        }
    }

 cleanup:
    return ret;
}

static int
prlsdkAddFS(PRL_HANDLE sdkdom, virDomainFSDefPtr fs)
{
    PRL_RESULT pret;
    PRL_HANDLE sdkdisk = PRL_INVALID_HANDLE;
    int ret = -1;

    if (prlsdkCheckFSUnsupportedParams(fs) < 0)
        return -1;

    pret = PrlVmCfg_CreateVmDev(sdkdom, PDE_HARD_DISK, &sdkdisk);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetEnabled(sdkdisk, 1);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetConnected(sdkdisk, 1);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetEmulatedType(sdkdisk, PDT_USE_IMAGE_FILE);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetSysName(sdkdisk, fs->src);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetImagePath(sdkdisk, fs->src);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDev_SetFriendlyName(sdkdisk, fs->src);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmDevHd_SetMountPoint(sdkdisk, fs->dst);
    prlsdkCheckRetGoto(pret, cleanup);

    ret = 0;

 cleanup:
    PrlHandle_Free(sdkdisk);
    return ret;
}
static int
prlsdkDoApplyConfig(virConnectPtr conn,
                    PRL_HANDLE sdkdom,
                    virDomainDefPtr def,
                    virDomainDefPtr olddef)
{
    PRL_RESULT pret;
    size_t i;
    char uuidstr[VIR_UUID_STRING_BUFLEN + 2];
    bool needBoot = true;
    char *mask = NULL;

    if (prlsdkCheckUnsupportedParams(sdkdom, def) < 0)
        return -1;

    if (def->description) {
        pret = PrlVmCfg_SetDescription(sdkdom, def->description);
        prlsdkCheckRetGoto(pret, error);
    }

    if (def->name) {
        pret = PrlVmCfg_SetName(sdkdom, def->name);
        prlsdkCheckRetGoto(pret, error);
    }

    if (def->uuid) {
        prlsdkUUIDFormat(def->uuid, uuidstr);

        pret = PrlVmCfg_SetUuid(sdkdom, uuidstr);
        prlsdkCheckRetGoto(pret, error);
    }

    pret = PrlVmCfg_SetRamSize(sdkdom, virDomainDefGetMemoryActual(def) >> 10);
    prlsdkCheckRetGoto(pret, error);

    pret = PrlVmCfg_SetCpuCount(sdkdom, def->vcpus);
    prlsdkCheckRetGoto(pret, error);

    if (!(mask = virBitmapFormat(def->cpumask)))
        goto error;

    pret = PrlVmCfg_SetCpuMask(sdkdom, mask);
    prlsdkCheckRetGoto(pret, error);
    VIR_FREE(mask);

    switch (def->os.arch) {
    case VIR_ARCH_X86_64:
        pret = PrlVmCfg_SetCpuMode(sdkdom, PCM_CPU_MODE_64);
        break;
    case VIR_ARCH_I686:
        pret = PrlVmCfg_SetCpuMode(sdkdom, PCM_CPU_MODE_32);
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown CPU mode: %s"),
                       virArchToString(def->os.arch));
        goto error;
    }
    prlsdkCheckRetGoto(pret, error);

    if (prlsdkClearDevices(sdkdom) < 0)
        goto error;

    if (prlsdkRemoveBootDevices(sdkdom) < 0)
        goto error;

    if (olddef) {
        for (i = 0; i < olddef->nnets; i++)
            prlsdkDelNet(conn->privateData, olddef->nets[i]);
    }

    for (i = 0; i < def->nnets; i++) {
        if (prlsdkAddNet(sdkdom, conn->privateData, def->nets[i], IS_CT(def)) < 0)
            goto error;
    }

    if (prlsdkApplyGraphicsParams(sdkdom, def) < 0)
        goto error;

    if (prlsdkApplyVideoParams(sdkdom, def) < 0)
        goto error;

    for (i = 0; i < def->nserials; i++) {
        if (prlsdkAddSerial(sdkdom, def->serials[i]) < 0)
            goto error;
    }

    for (i = 0; i < def->nfss; i++) {
        if (STREQ(def->fss[i]->dst, "/"))
            needBoot = false;
        if (prlsdkAddFS(sdkdom, def->fss[i]) < 0)
            goto error;
    }

    for (i = 0; i < def->ndisks; i++) {
        bool bootDisk = false;

        if (needBoot == true &&
            def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) {

            needBoot = false;
            bootDisk = true;
        }
        if (prlsdkAddDisk(sdkdom, def->disks[i], bootDisk, IS_CT(def)) < 0)
            goto error;
    }

    return 0;

 error:
    VIR_FREE(mask);

    for (i = 0; i < def->nnets; i++)
        prlsdkDelNet(conn->privateData, def->nets[i]);

    return -1;
}

int
prlsdkApplyConfig(virConnectPtr conn,
                  virDomainObjPtr dom,
                  virDomainDefPtr new)
{
    vzConnPtr privconn = conn->privateData;
    PRL_HANDLE sdkdom = PRL_INVALID_HANDLE;
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    int ret;

    sdkdom = prlsdkSdkDomainLookupByUUID(privconn, dom->def->uuid);
    if (sdkdom == PRL_INVALID_HANDLE)
        return -1;

    job = PrlVm_BeginEdit(sdkdom);
    if (PRL_FAILED(waitJob(job)))
        return -1;

    ret = prlsdkDoApplyConfig(conn, sdkdom, new, dom->def);

    if (ret == 0) {
        job = PrlVm_CommitEx(sdkdom, PVCF_DETACH_HDD_BUNDLE);
        if (PRL_FAILED(waitJob(job)))
            ret = -1;
    }

    PrlHandle_Free(sdkdom);

    return ret;
}

int
prlsdkCreateVm(virConnectPtr conn, virDomainDefPtr def)
{
    vzConnPtr privconn = conn->privateData;
    PRL_HANDLE sdkdom = PRL_INVALID_HANDLE;
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    PRL_HANDLE result = PRL_INVALID_HANDLE;
    PRL_HANDLE srvconf = PRL_INVALID_HANDLE;
    PRL_RESULT pret;
    int ret = -1;

    pret = PrlSrv_CreateVm(privconn->server, &sdkdom);
    prlsdkCheckRetGoto(pret, cleanup);

    job = PrlSrv_GetSrvConfig(privconn->server);
    if (PRL_FAILED(getJobResult(job, &result)))
        goto cleanup;

    pret = PrlResult_GetParamByIndex(result, 0, &srvconf);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmCfg_SetDefaultConfig(sdkdom, srvconf, PVS_GUEST_VER_LIN_REDHAT, 0);
    prlsdkCheckRetGoto(pret, cleanup);

    pret = PrlVmCfg_SetOfflineManagementEnabled(sdkdom, 0);
    prlsdkCheckRetGoto(pret, cleanup);

    ret = prlsdkDoApplyConfig(conn, sdkdom, def, NULL);
    if (ret)
        goto cleanup;

    job = PrlVm_Reg(sdkdom, "", 1);
    if (PRL_FAILED(waitJob(job)))
        ret = -1;

 cleanup:
    PrlHandle_Free(sdkdom);
    return ret;
}

int
prlsdkCreateCt(virConnectPtr conn, virDomainDefPtr def)
{
    vzConnPtr privconn = conn->privateData;
    PRL_HANDLE sdkdom = PRL_INVALID_HANDLE;
    PRL_GET_VM_CONFIG_PARAM_DATA confParam;
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    PRL_HANDLE result = PRL_INVALID_HANDLE;
    PRL_RESULT pret;
    int ret = -1;
    int useTemplate = 0;
    size_t i;

    if (def->nfss > 1) {
        /* Check all filesystems */
        for (i = 0; i < def->nfss; i++) {
            if (def->fss[i]->type != VIR_DOMAIN_FS_TYPE_FILE) {
                virReportError(VIR_ERR_INVALID_ARG, "%s",
                               _("Unsupported filesystem type."));
                return -1;
            }
        }
    } else if (def->nfss == 1) {
        if (def->fss[0]->type == VIR_DOMAIN_FS_TYPE_TEMPLATE) {
            useTemplate = 1;
        } else if (def->fss[0]->type != VIR_DOMAIN_FS_TYPE_FILE) {
            virReportError(VIR_ERR_INVALID_ARG, "%s",
                           _("Unsupported filesystem type."));
            return -1;
        }
    }

    confParam.nVmType = PVT_CT;
    confParam.sConfigSample = "vswap.1024MB";
    confParam.nOsVersion = 0;

    job = PrlSrv_GetDefaultVmConfig(privconn->server, &confParam, 0);
    if (PRL_FAILED(getJobResult(job, &result)))
        goto cleanup;

    pret = PrlResult_GetParamByIndex(result, 0, &sdkdom);
    prlsdkCheckRetGoto(pret, cleanup);

    if (useTemplate) {
        pret = PrlVmCfg_SetOsTemplate(sdkdom, def->fss[0]->src);
        prlsdkCheckRetGoto(pret, cleanup);

    }

    ret = prlsdkDoApplyConfig(conn, sdkdom, def, NULL);
    if (ret)
        goto cleanup;

    job = PrlVm_RegEx(sdkdom, "",
                      PACF_NON_INTERACTIVE_MODE | PRNVM_PRESERVE_DISK);
    if (PRL_FAILED(waitJob(job)))
        ret = -1;

 cleanup:
    PrlHandle_Free(sdkdom);
    return ret;
}

int
prlsdkUnregisterDomain(vzConnPtr privconn, virDomainObjPtr dom)
{
    vzDomObjPtr privdom = dom->privateData;
    PRL_HANDLE job;
    size_t i;

    for (i = 0; i < dom->def->nnets; i++)
        prlsdkDelNet(privconn, dom->def->nets[i]);

    job = PrlVm_Unreg(privdom->sdkdom);
    if (PRL_FAILED(waitJob(job)))
        return -1;

    if (prlsdkSendEvent(privconn, dom, VIR_DOMAIN_EVENT_UNDEFINED,
                        VIR_DOMAIN_EVENT_UNDEFINED_REMOVED) < 0)
        return -1;

    virDomainObjListRemove(privconn->domains, dom);
    return 0;
}

int
prlsdkDomainManagedSaveRemove(virDomainObjPtr dom)
{
    vzDomObjPtr privdom = dom->privateData;
    PRL_HANDLE job;

    job = PrlVm_DropSuspendedState(privdom->sdkdom);
    if (PRL_FAILED(waitJob(job)))
        return -1;

    return 0;
}

static int
prlsdkExtractStatsParam(PRL_HANDLE sdkstats, const char *name, long long *val)
{
    PRL_HANDLE param = PRL_INVALID_HANDLE;
    PRL_RESULT pret;
    PRL_INT64 pval = 0;
    int ret = -1;

    pret = PrlEvent_GetParamByName(sdkstats, name, &param);
    if (pret == PRL_ERR_NO_DATA) {
        *val = -1;
        ret = 0;
        goto cleanup;
    } else if (PRL_FAILED(pret)) {
        logPrlError(pret);
        goto cleanup;
    }
    pret = PrlEvtPrm_ToInt64(param, &pval);
    prlsdkCheckRetGoto(pret, cleanup);

    *val = pval;
    ret = 0;

 cleanup:
    PrlHandle_Free(param);
    return ret;
}

#define PARALLELS_STATISTICS_TIMEOUT (60 * 1000)

static int
prlsdkGetStatsParam(virDomainObjPtr dom, const char *name, long long *val)
{
    vzDomObjPtr privdom = dom->privateData;
    PRL_HANDLE job = PRL_INVALID_HANDLE;
    unsigned long long now;

    if (privdom->cache.stats != PRL_INVALID_HANDLE) {
        /* reset count to keep subscribtion */
        privdom->cache.count = 0;
        return prlsdkExtractStatsParam(privdom->cache.stats, name, val);
    }

    if (privdom->cache.count == -1) {
        job = PrlVm_SubscribeToPerfStats(privdom->sdkdom, NULL);
        if (PRL_FAILED(waitJob(job)))
            goto error;
    }

    /* change state to subscribed in case of unsubscribed
       or reset count so we stop unsubscribe attempts */
    privdom->cache.count = 0;

    if (virTimeMillisNow(&now) < 0) {
        virReportSystemError(errno, "%s", _("Unable to get current time"));
        goto error;
    }

    while (privdom->cache.stats == PRL_INVALID_HANDLE) {
        if (virCondWaitUntil(&privdom->cache.cond, &dom->parent.lock,
                             now + PARALLELS_STATISTICS_TIMEOUT) < 0) {
            if (errno == ETIMEDOUT) {
                virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s",
                               _("Timeout on waiting statistics event."));
                goto error;
            } else {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("Unable to wait on monitor condition"));
                goto error;
            }
        }
    }

    return prlsdkExtractStatsParam(privdom->cache.stats, name, val);
 error:
    return -1;
}

int
prlsdkGetBlockStats(virDomainObjPtr dom, virDomainDiskDefPtr disk, virDomainBlockStatsPtr stats)
{
    virDomainDeviceDriveAddressPtr address;
    int idx;
    const char *prefix;
    int ret = -1;
    char *name = NULL;

    address = &disk->info.addr.drive;
    switch (disk->bus) {
    case VIR_DOMAIN_DISK_BUS_IDE:
        prefix = "ide";
        idx = address->bus * 2 + address->unit;
        break;
    case VIR_DOMAIN_DISK_BUS_SATA:
        prefix = "sata";
        idx = address->unit;
        break;
    case VIR_DOMAIN_DISK_BUS_SCSI:
        prefix = "scsi";
        idx = address->unit;
        break;
    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown disk bus: %X"), disk->bus);
        goto cleanup;
    }


#define PRLSDK_GET_STAT_PARAM(VAL, TYPE, NAME)                          \
    if (virAsprintf(&name, "devices.%s%d.%s", prefix, idx, NAME) < 0)   \
        goto cleanup;                                                   \
    if (prlsdkGetStatsParam(dom, name, &stats->VAL) < 0)                \
        goto cleanup;                                                   \
    VIR_FREE(name);

    PARALLELS_BLOCK_STATS_FOREACH(PRLSDK_GET_STAT_PARAM)

#undef PRLSDK_GET_STAT_PARAM

    ret = 0;

 cleanup:

    VIR_FREE(name);
    return ret;
}

static PRL_HANDLE
prlsdkFindNetByPath(virDomainObjPtr dom, const char *path)
{
    PRL_UINT32 count = 0;
    vzDomObjPtr privdom = dom->privateData;
    PRL_UINT32 buflen = 0;
    PRL_RESULT pret;
    size_t i;
    char *name = NULL;
    PRL_HANDLE net = PRL_INVALID_HANDLE;

    pret = PrlVmCfg_GetNetAdaptersCount(privdom->sdkdom, &count);
    prlsdkCheckRetGoto(pret, error);

    for (i = 0; i < count; ++i) {
        pret = PrlVmCfg_GetNetAdapter(privdom->sdkdom, i, &net);
        prlsdkCheckRetGoto(pret, error);

        pret = PrlVmDevNet_GetHostInterfaceName(net, NULL, &buflen);
        prlsdkCheckRetGoto(pret, error);

        if (VIR_ALLOC_N(name, buflen) < 0)
            goto error;

        pret = PrlVmDevNet_GetHostInterfaceName(net, name, &buflen);
        prlsdkCheckRetGoto(pret, error);

        if (STREQ(name, path))
            break;

        VIR_FREE(name);
        PrlHandle_Free(net);
        net = PRL_INVALID_HANDLE;
    }

    if (net == PRL_INVALID_HANDLE)
        virReportError(VIR_ERR_INVALID_ARG,
                       _("invalid path, '%s' is not a known interface"), path);
    return net;

 error:
    VIR_FREE(name);
    PrlHandle_Free(net);
    return PRL_INVALID_HANDLE;
}

int
prlsdkGetNetStats(virDomainObjPtr dom, const char *path,
                  virDomainInterfaceStatsPtr stats)
{
    int ret = -1;
    PRL_UINT32 net_index = -1;
    char *name = NULL;
    PRL_RESULT pret;
    PRL_HANDLE net = PRL_INVALID_HANDLE;

    net = prlsdkFindNetByPath(dom, path);
    if (net == PRL_INVALID_HANDLE)
       goto cleanup;

    pret = PrlVmDev_GetIndex(net, &net_index);
    prlsdkCheckRetGoto(pret, cleanup);

#define PRLSDK_GET_NET_COUNTER(VAL, NAME)                           \
    if (virAsprintf(&name, "net.nic%d.%s", net_index, NAME) < 0)    \
        goto cleanup;                                               \
    if (prlsdkGetStatsParam(dom, name, &stats->VAL) < 0)            \
        goto cleanup;                                               \
    VIR_FREE(name);

    PRLSDK_GET_NET_COUNTER(rx_bytes, "bytes_in")
    PRLSDK_GET_NET_COUNTER(rx_packets, "pkts_in")
    PRLSDK_GET_NET_COUNTER(tx_bytes, "bytes_out")
    PRLSDK_GET_NET_COUNTER(tx_packets, "pkts_out")
    stats->rx_errs = -1;
    stats->rx_drop = -1;
    stats->tx_errs = -1;
    stats->tx_drop = -1;

#undef PRLSDK_GET_NET_COUNTER
    ret = 0;

 cleanup:
    VIR_FREE(name);
    PrlHandle_Free(net);

    return ret;
}

int
prlsdkGetVcpuStats(virDomainObjPtr dom, int idx, unsigned long long *vtime)
{
    char *name = NULL;
    long long ptime = 0;
    int ret = -1;

    if (virAsprintf(&name, "guest.vcpu%u.time", (unsigned int)idx) < 0)
        goto cleanup;
    if (prlsdkGetStatsParam(dom, name, &ptime) < 0)
        goto cleanup;
    *vtime = ptime == -1 ? 0 : ptime;
    ret = 0;

 cleanup:
    VIR_FREE(name);
    return ret;
}

int
prlsdkGetMemoryStats(virDomainObjPtr dom,
                     virDomainMemoryStatPtr stats,
                     unsigned int nr_stats)
{
    int ret = -1;
    long long v = 0, t = 0, u = 0;
    size_t i = 0;

#define PRLSDK_GET_COUNTER(NAME, VALUE)                             \
    if (prlsdkGetStatsParam(dom, NAME, &VALUE) < 0)                 \
        goto cleanup;                                               \

#define PRLSDK_MEMORY_STAT_SET(TAG, VALUE)                          \
    if (i < nr_stats) {                                             \
        stats[i].tag = (TAG);                                       \
        stats[i].val = (VALUE);                                     \
        i++;                                                        \
    }

    i = 0;

    // count to kb
    PRLSDK_GET_COUNTER("guest.ram.swap_in", v)
    if (v != -1)
        PRLSDK_MEMORY_STAT_SET(VIR_DOMAIN_MEMORY_STAT_SWAP_IN, v << 12)

    PRLSDK_GET_COUNTER("guest.ram.swap_out", v)
    if (v != -1)
        PRLSDK_MEMORY_STAT_SET(VIR_DOMAIN_MEMORY_STAT_SWAP_OUT, v << 12)

    PRLSDK_GET_COUNTER("guest.ram.minor_fault", v)
    if (v != -1)
        PRLSDK_MEMORY_STAT_SET(VIR_DOMAIN_MEMORY_STAT_MINOR_FAULT, v)

    PRLSDK_GET_COUNTER("guest.ram.major_fault", v)
    if (v != -1)
        PRLSDK_MEMORY_STAT_SET(VIR_DOMAIN_MEMORY_STAT_MAJOR_FAULT, v)

    PRLSDK_GET_COUNTER("guest.ram.total", v)
    if (v != -1)
        PRLSDK_MEMORY_STAT_SET(VIR_DOMAIN_MEMORY_STAT_AVAILABLE, v << 10)

    PRLSDK_GET_COUNTER("guest.ram.balloon_actual", v)
    if (v != -1)
        PRLSDK_MEMORY_STAT_SET(VIR_DOMAIN_MEMORY_STAT_ACTUAL_BALLOON, v << 10)

    PRLSDK_GET_COUNTER("guest.ram.usage", u)
    PRLSDK_GET_COUNTER("guest.ram.total", t)
    if (u != -1 && t != -1)
        PRLSDK_MEMORY_STAT_SET(VIR_DOMAIN_MEMORY_STAT_UNUSED, (t - u) << 10)

#undef PRLSDK_GET_COUNTER
#undef PRLSDK_MEMORY_STAT_SET

    ret = i;
 cleanup:

    return ret;
}
