/* * parallels_driver.c: core driver functions for managing * Parallels Cloud Server hosts * * Copyright (C) 2012 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 * . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "datatypes.h" #include "virerror.h" #include "viralloc.h" #include "virutil.h" #include "virlog.h" #include "vircommand.h" #include "configmake.h" #include "virstoragefile.h" #include "nodeinfo.h" #include "c-ctype.h" #include "parallels_driver.h" #include "parallels_utils.h" #define VIR_FROM_THIS VIR_FROM_PARALLELS #define PRLCTL "prlctl" #define PRLSRVCTL "prlsrvctl" #define parallelsDomNotFoundError(domain) \ do { \ char uuidstr[VIR_UUID_STRING_BUFLEN]; \ virUUIDFormat(domain->uuid, uuidstr); \ virReportError(VIR_ERR_NO_DOMAIN, \ _("no domain with matching uuid '%s'"), uuidstr); \ } while (0) #define IS_CT(def) (STREQ_NULLABLE(def->os.type, "exe")) static int parallelsClose(virConnectPtr conn); static const char * parallelsGetDiskBusName(int bus) { switch (bus) { case VIR_DOMAIN_DISK_BUS_IDE: return "ide"; case VIR_DOMAIN_DISK_BUS_SATA: return "sata"; case VIR_DOMAIN_DISK_BUS_SCSI: return "scsi"; default: return NULL; } } void parallelsDriverLock(parallelsConnPtr driver) { virMutexLock(&driver->lock); } void parallelsDriverUnlock(parallelsConnPtr driver) { virMutexUnlock(&driver->lock); } static int parallelsDefaultConsoleType(const char *ostype ATTRIBUTE_UNUSED, virArch arch ATTRIBUTE_UNUSED) { return VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL; } static void parallelsDomObjFreePrivate(void *p) { parallelsDomObjPtr pdom = p; if (!pdom) return; VIR_FREE(pdom->uuid); VIR_FREE(pdom->home); VIR_FREE(p); }; static virCapsPtr parallelsBuildCapabilities(void) { virCapsPtr caps; virCapsGuestPtr guest; if ((caps = virCapabilitiesNew(virArchFromHost(), 0, 0)) == NULL) goto no_memory; if (nodeCapsInitNUMA(caps) < 0) goto no_memory; virCapabilitiesSetMacPrefix(caps, (unsigned char[]) { 0x42, 0x1C, 0x00}); if ((guest = virCapabilitiesAddGuest(caps, "hvm", VIR_ARCH_X86_64, "parallels", NULL, 0, NULL)) == NULL) goto no_memory; if (virCapabilitiesAddGuestDomain(guest, "parallels", NULL, NULL, 0, NULL) == NULL) goto no_memory; if ((guest = virCapabilitiesAddGuest(caps, "exe", VIR_ARCH_X86_64, "parallels", NULL, 0, NULL)) == NULL) goto no_memory; if (virCapabilitiesAddGuestDomain(guest, "parallels", NULL, NULL, 0, NULL) == NULL) goto no_memory; caps->defaultConsoleTargetType = parallelsDefaultConsoleType; return caps; no_memory: virReportOOMError(); virObjectUnref(caps); return NULL; } static char * parallelsGetCapabilities(virConnectPtr conn) { parallelsConnPtr privconn = conn->privateData; char *xml; parallelsDriverLock(privconn); if ((xml = virCapabilitiesFormatXML(privconn->caps)) == NULL) virReportOOMError(); parallelsDriverUnlock(privconn); return xml; } static int parallelsGetSerialInfo(virDomainChrDefPtr chr, const char *name, virJSONValuePtr value) { const char *tmp; chr->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL; chr->targetType = VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL; if (virStrToLong_i(name + strlen("serial"), NULL, 10, &chr->target.port) < 0) { parallelsParseError(); return -1; } if (virJSONValueObjectHasKey(value, "output")) { chr->source.type = VIR_DOMAIN_CHR_TYPE_FILE; tmp = virJSONValueObjectGetString(value, "output"); if (!tmp) { parallelsParseError(); return -1; } if (!(chr->source.data.file.path = strdup(tmp))) goto no_memory; } else if (virJSONValueObjectHasKey(value, "socket")) { chr->source.type = VIR_DOMAIN_CHR_TYPE_UNIX; tmp = virJSONValueObjectGetString(value, "socket"); if (!tmp) { parallelsParseError(); return -1; } if (!(chr->source.data.nix.path = strdup(tmp))) goto no_memory; chr->source.data.nix.listen = false; } else if (virJSONValueObjectHasKey(value, "real")) { chr->source.type = VIR_DOMAIN_CHR_TYPE_DEV; tmp = virJSONValueObjectGetString(value, "real"); if (!tmp) { parallelsParseError(); return -1; } if (!(chr->source.data.file.path = strdup(tmp))) goto no_memory; } else { parallelsParseError(); return -1; } return 0; no_memory: virReportOOMError(); return -1; } static int parallelsAddSerialInfo(virDomainChrDefPtr **serials, size_t *nserials, const char *key, virJSONValuePtr value) { virDomainChrDefPtr chr = NULL; if (!(chr = virDomainChrDefNew())) goto no_memory; if (parallelsGetSerialInfo(chr, key, value)) goto cleanup; if (VIR_REALLOC_N(*serials, *nserials + 1) < 0) goto no_memory; (*serials)[(*nserials)++] = chr; return 0; no_memory: virReportOOMError(); cleanup: virDomainChrDefFree(chr); return -1; } static int parallelsAddVideoInfo(virDomainDefPtr def, virJSONValuePtr value) { virDomainVideoDefPtr video = NULL; virDomainVideoAccelDefPtr accel = NULL; const char *tmp; char *endptr; unsigned long mem; if (!(tmp = virJSONValueObjectGetString(value, "size"))) { parallelsParseError(); goto error; } if (virStrToLong_ul(tmp, &endptr, 10, &mem) < 0) { parallelsParseError(); goto error; } if (!STREQ(endptr, "Mb")) { parallelsParseError(); goto error; } if (VIR_ALLOC(video) < 0) goto no_memory; if (VIR_ALLOC(accel) < 0) goto no_memory; if (VIR_REALLOC_N(def->videos, def->nvideos + 1) < 0) goto no_memory; def->videos[def->nvideos++] = video; video->type = VIR_DOMAIN_VIDEO_TYPE_VGA; video->vram = mem << 20; video->heads = 1; video->accel = accel; return 0; no_memory: virReportOOMError(); VIR_FREE(accel); virDomainVideoDefFree(video); error: return -1; } static int parallelsGetHddInfo(virDomainDefPtr def, virDomainDiskDefPtr disk, const char *key, virJSONValuePtr value) { const char *tmp; unsigned int idx; disk->device = VIR_DOMAIN_DISK_DEVICE_DISK; if (virJSONValueObjectHasKey(value, "real") == 1) { disk->type = VIR_DOMAIN_DISK_TYPE_BLOCK; if (!(tmp = virJSONValueObjectGetString(value, "real"))) { parallelsParseError(); return -1; } if (!(disk->src = strdup(tmp))) { virReportOOMError(); return -1; } } else { disk->type = VIR_DOMAIN_DISK_TYPE_FILE; if (!(tmp = virJSONValueObjectGetString(value, "image"))) { parallelsParseError(); return -1; } if (!(disk->src = strdup(tmp))) { virReportOOMError(); return -1; } } tmp = virJSONValueObjectGetString(value, "port"); if (!tmp && !IS_CT(def)) { parallelsParseError(); return -1; } if (tmp) { if (STRPREFIX(tmp, "ide")) { disk->bus = VIR_DOMAIN_DISK_BUS_IDE; } else if (STRPREFIX(tmp, "sata")) { disk->bus = VIR_DOMAIN_DISK_BUS_SATA; } else if (STRPREFIX(tmp, "scsi")) { disk->bus = VIR_DOMAIN_DISK_BUS_SCSI; } else { parallelsParseError(); return -1; } char *colonp; unsigned int pos; if (!(colonp = strchr(tmp, ':'))) { parallelsParseError(); return -1; } if (virStrToLong_ui(colonp + 1, NULL, 10, &pos) < 0) { parallelsParseError(); return -1; } disk->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE; disk->info.addr.drive.target = pos; } else { /* Actually there are no disk devices in containers, but in * in Parallels Cloud Server we mount disk images as container's * root fs during start, so it looks like a disk device. */ disk->bus = VIR_DOMAIN_DISK_BUS_IDE; } if (virStrToLong_ui(key + strlen("hdd"), NULL, 10, &idx) < 0) { parallelsParseError(); return -1; } if (!(disk->dst = virIndexToDiskName(idx, "sd"))) return -1; return 0; } static int parallelsAddHddInfo(virDomainDefPtr def, const char *key, virJSONValuePtr value) { virDomainDiskDefPtr disk = NULL; if (VIR_ALLOC(disk) < 0) goto no_memory; if (parallelsGetHddInfo(def, disk, key, value)) goto error; if (VIR_REALLOC_N(def->disks, def->ndisks + 1) < 0) goto no_memory; def->disks[def->ndisks++] = disk; return 0; no_memory: virReportOOMError(); error: virDomainDiskDefFree(disk); return -1; } static inline unsigned char hex2int(char c) { if (c <= '9') return c - '0'; else return 10 + c - 'A'; } /* * Parse MAC address in format XXXXXXXXXXXX. */ static int parallelsMacAddrParse(const char *str, virMacAddrPtr addr) { int i; if (strlen(str) != 12) goto error; for (i = 0; i < 6; i++) { if (!c_isxdigit(str[2 * i]) || !c_isxdigit(str[2 * i + 1])) goto error; addr->addr[i] = (hex2int(str[2 * i]) << 4) + hex2int(str[2 * i + 1]); } return 0; error: virReportError(VIR_ERR_INVALID_ARG, _("Invalid MAC address format '%s'"), str); return -1; } static int parallelsGetNetInfo(virDomainNetDefPtr net, const char *key, virJSONValuePtr value) { const char *tmp; /* use device name, shown by prlctl as target device * for identifying network adapter in virDomainDefineXML */ if (!(net->ifname = strdup(key))) { virReportOOMError(); goto error; } net->type = VIR_DOMAIN_NET_TYPE_NETWORK; if (!(tmp = virJSONValueObjectGetString(value, "mac"))) { parallelsParseError(); return -1; } if (parallelsMacAddrParse(tmp, &net->mac) < 0) { parallelsParseError(); goto error; } if (virJSONValueObjectHasKey(value, "network")) { if (!(tmp = virJSONValueObjectGetString(value, "network"))) { parallelsParseError(); goto error; } if (!(net->data.network.name = strdup(tmp))) { virReportOOMError(); goto error; } } else if (virJSONValueObjectHasKey(value, "type")) { if (!(tmp = virJSONValueObjectGetString(value, "type"))) { parallelsParseError(); goto error; } if (!STREQ(tmp, "routed")) { parallelsParseError(); goto error; } if (!(net->data.network.name = strdup(PARALLELS_ROUTED_NETWORK_NAME))) { virReportOOMError(); goto error; } } else { parallelsParseError(); goto error; } net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_UP; if ((tmp = virJSONValueObjectGetString(value, "state")) && STREQ(tmp, "disconnected")) { net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN; } return 0; error: return -1; } static int parallelsAddNetInfo(virDomainDefPtr def, const char *key, virJSONValuePtr value) { virDomainNetDefPtr net = NULL; if (VIR_ALLOC(net) < 0) goto no_memory; if (parallelsGetNetInfo(net, key, value)) goto error; if (VIR_EXPAND_N(def->nets, def->nnets, 1) < 0) goto no_memory; def->nets[def->nnets - 1] = net; return 0; no_memory: virReportOOMError(); error: virDomainNetDefFree(net); return -1; } static int parallelsAddDomainHardware(virDomainDefPtr def, virJSONValuePtr jobj) { int n; size_t i; virJSONValuePtr value; const char *key; n = virJSONValueObjectKeysNumber(jobj); if (n < 1) goto cleanup; for (i = 0; i < n; i++) { key = virJSONValueObjectGetKey(jobj, i); value = virJSONValueObjectGetValue(jobj, i); if (STRPREFIX(key, "serial")) { if (parallelsAddSerialInfo(&def->serials, &def->nserials, key, value)) goto cleanup; if (def->nconsoles == 0) { if (parallelsAddSerialInfo(&def->consoles, &def->nconsoles, key, value)) goto cleanup; } } else if (STREQ(key, "video")) { if (parallelsAddVideoInfo(def, value)) goto cleanup; } else if (STRPREFIX(key, "hdd")) { if (parallelsAddHddInfo(def, key, value)) goto cleanup; } else if (STRPREFIX(key, "net")) { if (parallelsAddNetInfo(def, key, value)) goto cleanup; } } return 0; cleanup: return -1; } static int parallelsAddVNCInfo(virDomainDefPtr def, virJSONValuePtr jobj_root) { const char *tmp; unsigned int port; virJSONValuePtr jobj; int ret = -1; virDomainGraphicsDefPtr gr = NULL; jobj = virJSONValueObjectGet(jobj_root, "Remote display"); if (!jobj) { parallelsParseError(); goto cleanup; } tmp = virJSONValueObjectGetString(jobj, "mode"); if (!tmp) { parallelsParseError(); goto cleanup; } if (STREQ(tmp, "off")) { ret = 0; goto cleanup; } if (VIR_ALLOC(gr) < 0) goto no_memory; if (STREQ(tmp, "auto")) { if (virJSONValueObjectGetNumberUint(jobj, "port", &port) < 0) port = 0; gr->data.vnc.autoport = 1; } else { if (virJSONValueObjectGetNumberUint(jobj, "port", &port) < 0) { parallelsParseError(); goto cleanup; } gr->data.vnc.autoport = 0; } 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 = 0; gr->data.vnc.auth.connected = 0; if (!(tmp = virJSONValueObjectGetString(jobj, "address"))) { parallelsParseError(); goto cleanup; } if (VIR_ALLOC(gr->listens) < 0) goto no_memory; gr->nListens = 1; if (!(gr->listens[0].address = strdup(tmp))) goto no_memory; gr->listens[0].type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS; if (VIR_REALLOC_N(def->graphics, def->ngraphics + 1) < 0) goto no_memory; def->graphics[def->ngraphics++] = gr; return 0; no_memory: virReportOOMError(); cleanup: virDomainGraphicsDefFree(gr); return ret; } /* * Must be called with privconn->lock held */ static virDomainObjPtr parallelsLoadDomain(parallelsConnPtr privconn, virJSONValuePtr jobj) { virDomainObjPtr dom = NULL; virDomainDefPtr def = NULL; parallelsDomObjPtr pdom = NULL; virJSONValuePtr jobj2, jobj3; const char *tmp; char *endptr; unsigned long mem; unsigned int x; const char *autostart; const char *state; if (VIR_ALLOC(def) < 0) goto no_memory; def->virtType = VIR_DOMAIN_VIRT_PARALLELS; def->id = -1; if (!(tmp = virJSONValueObjectGetString(jobj, "Name"))) { parallelsParseError(); goto cleanup; } if (!(def->name = strdup(tmp))) goto no_memory; if (!(tmp = virJSONValueObjectGetString(jobj, "ID"))) { parallelsParseError(); goto cleanup; } if (virUUIDParse(tmp, def->uuid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("UUID in config file malformed")); goto cleanup; } if (!(tmp = virJSONValueObjectGetString(jobj, "Description"))) { parallelsParseError(); goto cleanup; } if (!(def->description = strdup(tmp))) goto no_memory; if (!(jobj2 = virJSONValueObjectGet(jobj, "Hardware"))) { parallelsParseError(); goto cleanup; } if (!(jobj3 = virJSONValueObjectGet(jobj2, "cpu"))) { parallelsParseError(); goto cleanup; } if (virJSONValueObjectGetNumberUint(jobj3, "cpus", &x) == 0) { def->vcpus = x; def->maxvcpus = x; } else if ((tmp = virJSONValueObjectGetString(jobj3, "cpus"))) { if (STREQ(tmp, "unlimited")) { virNodeInfo nodeinfo; if (nodeGetInfo(NULL, &nodeinfo) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Can't get node info")); goto cleanup; } def->vcpus = nodeinfo.cpus; def->maxvcpus = def->vcpus; } else { parallelsParseError(); goto cleanup; } } else { parallelsParseError(); goto cleanup; } if (!(jobj3 = virJSONValueObjectGet(jobj2, "memory"))) { parallelsParseError(); goto cleanup; } if (!(tmp = virJSONValueObjectGetString(jobj3, "size"))) { parallelsParseError(); goto cleanup; } if (virStrToLong_ul(tmp, &endptr, 10, &mem) < 0) { parallelsParseError(); goto cleanup; } if (!STREQ(endptr, "Mb")) { parallelsParseError(); goto cleanup; } def->mem.max_balloon = mem; def->mem.max_balloon <<= 10; def->mem.cur_balloon = def->mem.max_balloon; if (!(tmp = virJSONValueObjectGetString(jobj, "Type"))) { parallelsParseError(); goto cleanup; } if (STREQ(tmp, "CT")) { if (!(def->os.type = strdup("exe"))) goto no_memory; if (!(def->os.init = strdup("/sbin/init"))) goto no_memory; } else if (STREQ(tmp, "VM")) { if (!(def->os.type = strdup("hvm"))) goto no_memory; } def->os.arch = VIR_ARCH_X86_64; if (VIR_ALLOC(pdom) < 0) goto no_memory; if (virJSONValueObjectGetNumberUint(jobj, "EnvID", &x) < 0) goto cleanup; pdom->id = x; if (!(tmp = virJSONValueObjectGetString(jobj, "ID"))) { parallelsParseError(); goto cleanup; } if (!(pdom->uuid = strdup(tmp))) goto no_memory; if (!(tmp = virJSONValueObjectGetString(jobj, "Home"))) { parallelsParseError(); goto cleanup; } if (!(pdom->home = strdup(tmp))) goto no_memory; if (!(state = virJSONValueObjectGetString(jobj, "State"))) { parallelsParseError(); goto cleanup; } if (!(autostart = virJSONValueObjectGetString(jobj, "Autostart"))) { parallelsParseError(); goto cleanup; } if (parallelsAddDomainHardware(def, jobj2) < 0) goto cleanup; if (parallelsAddVNCInfo(def, jobj) < 0) goto cleanup; if (!(dom = virDomainObjListAdd(privconn->domains, def, privconn->xmlopt, 0, NULL))) goto cleanup; /* dom is locked here */ dom->privateDataFreeFunc = parallelsDomObjFreePrivate; dom->privateData = pdom; dom->persistent = 1; /* TODO: handle all possible states */ if (STREQ(state, "running")) { virDomainObjSetState(dom, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_BOOTED); def->id = pdom->id; } if (STREQ(autostart, "on")) dom->autostart = 1; else dom->autostart = 0; virObjectUnlock(dom); return dom; no_memory: virReportOOMError(); cleanup: virDomainDefFree(def); parallelsDomObjFreePrivate(pdom); return NULL; } /* * Must be called with privconn->lock held * * if domain_name is NULL - load information about all * registered domains. */ static int parallelsLoadDomains(parallelsConnPtr privconn, const char *domain_name) { int count, i; virJSONValuePtr jobj; virJSONValuePtr jobj2; virDomainObjPtr dom = NULL; int ret = -1; jobj = parallelsParseOutput(PRLCTL, "list", "-j", "-a", "-i", "-H", "--vmtype", "all", domain_name, NULL); if (!jobj) { parallelsParseError(); goto cleanup; } count = virJSONValueArraySize(jobj); if (count < 0) { parallelsParseError(); goto cleanup; } for (i = 0; i < count; i++) { jobj2 = virJSONValueArrayGet(jobj, i); if (!jobj2) { parallelsParseError(); goto cleanup; } dom = parallelsLoadDomain(privconn, jobj2); if (!dom) goto cleanup; } ret = 0; cleanup: virJSONValueFree(jobj); return ret; } static int parallelsOpenDefault(virConnectPtr conn) { parallelsConnPtr privconn; if (VIR_ALLOC(privconn) < 0) { virReportOOMError(); return VIR_DRV_OPEN_ERROR; } if (virMutexInit(&privconn->lock) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot initialize mutex")); goto error; } if (!(privconn->caps = parallelsBuildCapabilities())) goto error; if (!(privconn->xmlopt = virDomainXMLOptionNew(NULL, NULL, NULL))) goto error; if (!(privconn->domains = virDomainObjListNew())) goto error; conn->privateData = privconn; if (parallelsLoadDomains(privconn, NULL)) goto error; return VIR_DRV_OPEN_SUCCESS; error: virObjectUnref(privconn->domains); virObjectUnref(privconn->caps); virStoragePoolObjListFree(&privconn->pools); VIR_FREE(privconn); return VIR_DRV_OPEN_ERROR; } static virDrvOpenStatus parallelsOpen(virConnectPtr conn, virConnectAuthPtr auth ATTRIBUTE_UNUSED, unsigned int flags) { int ret; virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); if (!conn->uri) return VIR_DRV_OPEN_DECLINED; if (!conn->uri->scheme || STRNEQ(conn->uri->scheme, "parallels")) return VIR_DRV_OPEN_DECLINED; /* Remote driver should handle these. */ if (conn->uri->server) return VIR_DRV_OPEN_DECLINED; /* From this point on, the connection is for us. */ if (!STREQ_NULLABLE(conn->uri->path, "/system")) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected Parallels URI path '%s', try parallels:///system"), conn->uri->path); return VIR_DRV_OPEN_ERROR; } if ((ret = parallelsOpenDefault(conn)) != VIR_DRV_OPEN_SUCCESS) return ret; return VIR_DRV_OPEN_SUCCESS; } static int parallelsClose(virConnectPtr conn) { parallelsConnPtr privconn = conn->privateData; parallelsDriverLock(privconn); virObjectUnref(privconn->caps); virObjectUnref(privconn->xmlopt); virObjectUnref(privconn->domains); conn->privateData = NULL; parallelsDriverUnlock(privconn); virMutexDestroy(&privconn->lock); VIR_FREE(privconn); return 0; } static int parallelsGetVersion(virConnectPtr conn ATTRIBUTE_UNUSED, unsigned long *hvVer) { char *output, *sVer, *tmp; const char *searchStr = "prlsrvctl version "; int ret = -1; output = parallelsGetOutput(PRLSRVCTL, "--help", NULL); if (!output) { parallelsParseError(); goto cleanup; } if (!(sVer = strstr(output, searchStr))) { parallelsParseError(); goto cleanup; } sVer = sVer + strlen(searchStr); /* parallels server has versions number like 6.0.17977.782218, * so libvirt can handle only first two numbers. */ if (!(tmp = strchr(sVer, '.'))) { parallelsParseError(); goto cleanup; } if (!(tmp = strchr(tmp + 1, '.'))) { parallelsParseError(); goto cleanup; } tmp[0] = '\0'; if (virParseVersionString(sVer, hvVer, true) < 0) { parallelsParseError(); goto cleanup; } ret = 0; cleanup: VIR_FREE(output); return ret; } static int parallelsListDomains(virConnectPtr conn, int *ids, int maxids) { parallelsConnPtr privconn = conn->privateData; int n; parallelsDriverLock(privconn); n = virDomainObjListGetActiveIDs(privconn->domains, ids, maxids); parallelsDriverUnlock(privconn); return n; } static int parallelsNumOfDomains(virConnectPtr conn) { parallelsConnPtr privconn = conn->privateData; int count; parallelsDriverLock(privconn); count = virDomainObjListNumOfDomains(privconn->domains, 1); parallelsDriverUnlock(privconn); return count; } static int parallelsListDefinedDomains(virConnectPtr conn, char **const names, int maxnames) { parallelsConnPtr privconn = conn->privateData; int n; parallelsDriverLock(privconn); memset(names, 0, sizeof(*names) * maxnames); n = virDomainObjListGetInactiveNames(privconn->domains, names, maxnames); parallelsDriverUnlock(privconn); return n; } static int parallelsNumOfDefinedDomains(virConnectPtr conn) { parallelsConnPtr privconn = conn->privateData; int count; parallelsDriverLock(privconn); count = virDomainObjListNumOfDomains(privconn->domains, 0); parallelsDriverUnlock(privconn); return count; } static int parallelsListAllDomains(virConnectPtr conn, virDomainPtr **domains, unsigned int flags) { parallelsConnPtr privconn = conn->privateData; int ret = -1; virCheckFlags(VIR_CONNECT_LIST_DOMAINS_FILTERS_ALL, -1); parallelsDriverLock(privconn); ret = virDomainObjListExport(privconn->domains, conn, domains, flags); parallelsDriverUnlock(privconn); return ret; } static virDomainPtr parallelsLookupDomainByID(virConnectPtr conn, int id) { parallelsConnPtr privconn = conn->privateData; virDomainPtr ret = NULL; virDomainObjPtr dom; parallelsDriverLock(privconn); dom = virDomainObjListFindByID(privconn->domains, id); parallelsDriverUnlock(privconn); if (dom == NULL) { virReportError(VIR_ERR_NO_DOMAIN, NULL); goto cleanup; } ret = virGetDomain(conn, dom->def->name, dom->def->uuid); if (ret) ret->id = dom->def->id; cleanup: if (dom) virObjectUnlock(dom); return ret; } static virDomainPtr parallelsLookupDomainByUUID(virConnectPtr conn, const unsigned char *uuid) { parallelsConnPtr privconn = conn->privateData; virDomainPtr ret = NULL; virDomainObjPtr dom; parallelsDriverLock(privconn); dom = virDomainObjListFindByUUID(privconn->domains, uuid); parallelsDriverUnlock(privconn); if (dom == NULL) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(uuid, uuidstr); virReportError(VIR_ERR_NO_DOMAIN, _("no domain with matching uuid '%s'"), uuidstr); goto cleanup; } ret = virGetDomain(conn, dom->def->name, dom->def->uuid); if (ret) ret->id = dom->def->id; cleanup: if (dom) virObjectUnlock(dom); return ret; } static virDomainPtr parallelsLookupDomainByName(virConnectPtr conn, const char *name) { parallelsConnPtr privconn = conn->privateData; virDomainPtr ret = NULL; virDomainObjPtr dom; parallelsDriverLock(privconn); dom = virDomainObjListFindByName(privconn->domains, name); parallelsDriverUnlock(privconn); if (dom == NULL) { virReportError(VIR_ERR_NO_DOMAIN, _("no domain with matching name '%s'"), name); goto cleanup; } ret = virGetDomain(conn, dom->def->name, dom->def->uuid); if (ret) ret->id = dom->def->id; cleanup: if (dom) virObjectUnlock(dom); return ret; } static int parallelsGetDomainInfo(virDomainPtr domain, virDomainInfoPtr info) { parallelsConnPtr privconn = domain->conn->privateData; virDomainObjPtr privdom; int ret = -1; parallelsDriverLock(privconn); privdom = virDomainObjListFindByUUID(privconn->domains, domain->uuid); parallelsDriverUnlock(privconn); if (privdom == NULL) { parallelsDomNotFoundError(domain); goto cleanup; } info->state = virDomainObjGetState(privdom, NULL); info->memory = privdom->def->mem.cur_balloon; info->maxMem = privdom->def->mem.max_balloon; info->nrVirtCpu = privdom->def->vcpus; info->cpuTime = 0; ret = 0; cleanup: if (privdom) virObjectUnlock(privdom); return ret; } static char * parallelsGetOSType(virDomainPtr domain) { parallelsConnPtr privconn = domain->conn->privateData; virDomainObjPtr privdom; char *ret = NULL; parallelsDriverLock(privconn); privdom = virDomainObjListFindByUUID(privconn->domains, domain->uuid); if (privdom == NULL) { parallelsDomNotFoundError(domain); goto cleanup; } if (!(ret = strdup(privdom->def->os.type))) virReportOOMError(); cleanup: if (privdom) virObjectUnlock(privdom); parallelsDriverUnlock(privconn); return ret; } static int parallelsDomainIsPersistent(virDomainPtr domain) { parallelsConnPtr privconn = domain->conn->privateData; virDomainObjPtr privdom; int ret = -1; parallelsDriverLock(privconn); privdom = virDomainObjListFindByUUID(privconn->domains, domain->uuid); if (privdom == NULL) { parallelsDomNotFoundError(domain); goto cleanup; } ret = 1; cleanup: if (privdom) virObjectUnlock(privdom); parallelsDriverUnlock(privconn); return ret; } static int parallelsDomainGetState(virDomainPtr domain, int *state, int *reason, unsigned int flags) { parallelsConnPtr privconn = domain->conn->privateData; virDomainObjPtr privdom; int ret = -1; virCheckFlags(0, -1); parallelsDriverLock(privconn); privdom = virDomainObjListFindByUUID(privconn->domains, domain->uuid); parallelsDriverUnlock(privconn); if (privdom == NULL) { parallelsDomNotFoundError(domain); goto cleanup; } *state = virDomainObjGetState(privdom, reason); ret = 0; cleanup: if (privdom) virObjectUnlock(privdom); return ret; } static char * parallelsDomainGetXMLDesc(virDomainPtr domain, unsigned int flags) { parallelsConnPtr privconn = domain->conn->privateData; virDomainDefPtr def; virDomainObjPtr privdom; char *ret = NULL; /* Flags checked by virDomainDefFormat */ parallelsDriverLock(privconn); privdom = virDomainObjListFindByUUID(privconn->domains, domain->uuid); parallelsDriverUnlock(privconn); if (privdom == NULL) { parallelsDomNotFoundError(domain); goto cleanup; } def = (flags & VIR_DOMAIN_XML_INACTIVE) && privdom->newDef ? privdom->newDef : privdom->def; ret = virDomainDefFormat(def, flags); cleanup: if (privdom) virObjectUnlock(privdom); return ret; } static int parallelsDomainGetAutostart(virDomainPtr domain, int *autostart) { parallelsConnPtr privconn = domain->conn->privateData; virDomainObjPtr privdom; int ret = -1; parallelsDriverLock(privconn); privdom = virDomainObjListFindByUUID(privconn->domains, domain->uuid); parallelsDriverUnlock(privconn); if (privdom == NULL) { parallelsDomNotFoundError(domain); goto cleanup; } *autostart = privdom->autostart; ret = 0; cleanup: if (privdom) virObjectUnlock(privdom); return ret; } typedef int (*parallelsChangeStateFunc)(virDomainObjPtr privdom); #define PARALLELS_UUID(x) (((parallelsDomObjPtr)(x->privateData))->uuid) static int parallelsDomainChangeState(virDomainPtr domain, virDomainState req_state, const char *req_state_name, parallelsChangeStateFunc chstate, virDomainState new_state, int reason) { parallelsConnPtr privconn = domain->conn->privateData; virDomainObjPtr privdom; int state; int ret = -1; parallelsDriverLock(privconn); privdom = virDomainObjListFindByUUID(privconn->domains, domain->uuid); parallelsDriverUnlock(privconn); if (privdom == NULL) { parallelsDomNotFoundError(domain); goto cleanup; } state = virDomainObjGetState(privdom, NULL); if (state != req_state) { virReportError(VIR_ERR_INTERNAL_ERROR, _("domain '%s' not %s"), privdom->def->name, req_state_name); goto cleanup; } if (chstate(privdom)) goto cleanup; virDomainObjSetState(privdom, new_state, reason); ret = 0; cleanup: if (privdom) virObjectUnlock(privdom); return ret; } static int parallelsPause(virDomainObjPtr privdom) { return parallelsCmdRun(PRLCTL, "pause", PARALLELS_UUID(privdom), NULL); } static int parallelsPauseDomain(virDomainPtr domain) { return parallelsDomainChangeState(domain, VIR_DOMAIN_RUNNING, "running", parallelsPause, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_USER); } static int parallelsResume(virDomainObjPtr privdom) { return parallelsCmdRun(PRLCTL, "resume", PARALLELS_UUID(privdom), NULL); } static int parallelsResumeDomain(virDomainPtr domain) { return parallelsDomainChangeState(domain, VIR_DOMAIN_PAUSED, "paused", parallelsResume, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_UNPAUSED); } static int parallelsStart(virDomainObjPtr privdom) { return parallelsCmdRun(PRLCTL, "start", PARALLELS_UUID(privdom), NULL); } static int parallelsDomainCreate(virDomainPtr domain) { return parallelsDomainChangeState(domain, VIR_DOMAIN_SHUTOFF, "stopped", parallelsStart, VIR_DOMAIN_RUNNING, VIR_DOMAIN_EVENT_STARTED_BOOTED); } static int parallelsKill(virDomainObjPtr privdom) { return parallelsCmdRun(PRLCTL, "stop", PARALLELS_UUID(privdom), "--kill", NULL); } static int parallelsDestroyDomain(virDomainPtr domain) { return parallelsDomainChangeState(domain, VIR_DOMAIN_RUNNING, "running", parallelsKill, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_DESTROYED); } static int parallelsStop(virDomainObjPtr privdom) { return parallelsCmdRun(PRLCTL, "stop", PARALLELS_UUID(privdom), NULL); } static int parallelsShutdownDomain(virDomainPtr domain) { return parallelsDomainChangeState(domain, VIR_DOMAIN_RUNNING, "running", parallelsStop, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_SHUTDOWN); } static int parallelsApplyGraphicsParams(virDomainGraphicsDefPtr *oldgraphics, int nold, virDomainGraphicsDefPtr *newgraphics, int nnew) { virDomainGraphicsDefPtr new, old; /* parallels server supports only 1 VNC display per VM */ if (nold != nnew || nnew > 1) goto error; if (nnew == 0) return 0; if (newgraphics[0]->type != VIR_DOMAIN_GRAPHICS_TYPE_VNC) goto error; old = oldgraphics[0]; new = newgraphics[0]; if (old->data.vnc.port != new->data.vnc.port && (old->data.vnc.port != 0 && new->data.vnc.port != 0)) { goto error; } else if (old->data.vnc.autoport != new->data.vnc.autoport || new->data.vnc.keymap != NULL || new->data.vnc.socket != NULL || !STREQ_NULLABLE(old->data.vnc.auth.passwd, new->data.vnc.auth.passwd) || old->data.vnc.auth.expires != new->data.vnc.auth.expires || old->data.vnc.auth.validTo != new->data.vnc.auth.validTo || old->data.vnc.auth.connected != new->data.vnc.auth.connected) { goto error; } else if (old->nListens != new->nListens || new->nListens > 1 || old->listens[0].type != new->listens[0].type || !STREQ_NULLABLE(old->listens[0].address, new->listens[0].address) || !STREQ_NULLABLE(old->listens[0].network, new->listens[0].network)) { goto error; } return 0; error: virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing display parameters is not supported " "by parallels driver")); return -1; } static int parallelsApplySerialParams(virDomainChrDefPtr *oldserials, int nold, virDomainChrDefPtr *newserials, int nnew) { int i, j; if (nold != nnew) goto error; for (i = 0; i < nold; i++) { virDomainChrDefPtr oldserial = oldserials[i]; virDomainChrDefPtr newserial = NULL; for (j = 0; j < nnew; j++) { if (newserials[j]->target.port == oldserial->target.port) { newserial = newserials[j]; break; } } if (!newserial) goto error; if (oldserial->source.type != newserial->source.type) goto error; if ((newserial->source.type == VIR_DOMAIN_CHR_TYPE_DEV || newserial->source.type == VIR_DOMAIN_CHR_TYPE_FILE) && !STREQ_NULLABLE(oldserial->source.data.file.path, newserial->source.data.file.path)) goto error; if (newserial->source.type == VIR_DOMAIN_CHR_TYPE_UNIX && (!STREQ_NULLABLE(oldserial->source.data.nix.path, newserial->source.data.nix.path) || oldserial->source.data.nix.listen == newserial->source.data.nix.listen)) { goto error; } } return 0; error: virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing serial device parameters is " "not supported by parallels driver")); return -1; } static int parallelsApplyVideoParams(parallelsDomObjPtr pdom, virDomainVideoDefPtr *oldvideos, int nold, virDomainVideoDefPtr *newvideos, int nnew) { virDomainVideoDefPtr old, new; char str_vram[32]; if (nold != 1 || nnew != 1) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Only one video device is " "supported by parallels driver")); return -1; } old = oldvideos[0]; new = newvideos[0]; if (new->type != VIR_DOMAIN_VIDEO_TYPE_VGA) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Only VGA video device is " "supported by parallels driver")); return -1; } if (new->heads != 1) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Only one monitor is supported by parallels driver")); return -1; } /* old->accel must be always non-NULL */ if (new->accel == NULL || old->accel->support2d != new->accel->support2d || old->accel->support3d != new->accel->support3d) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Changing video acceleration parameters is " "not supported by parallels driver")); return -1; } if (old->vram != new->vram) { if (new->vram % (1 << 20) != 0) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Video RAM size should be multiple of 1Mb.")); return -1; } snprintf(str_vram, 31, "%d", new->vram >> 20); str_vram[31] = '\0'; if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, "--videosize", str_vram, NULL)) return -1; } return 0; } static int parallelsAddHddByVolume(parallelsDomObjPtr pdom, virDomainDiskDefPtr disk, virStoragePoolObjPtr pool, virStorageVolDefPtr voldef) { int ret = -1; const char *strbus; virCommandPtr cmd = virCommandNewArgList(PRLCTL, "set", pdom->uuid, "--device-add", "hdd", NULL); virCommandAddArgFormat(cmd, "--size=%lluM", voldef->capacity >> 20); if (!(strbus = parallelsGetDiskBusName(disk->bus))) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, _("Invalid disk bus: %d"), disk->bus); goto cleanup; } virCommandAddArgFormat(cmd, "--iface=%s", strbus); if (disk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE) virCommandAddArgFormat(cmd, "--position=%d", disk->info.addr.drive.target); if (virCommandRun(cmd, NULL) < 0) goto cleanup; if (parallelsStorageVolumeDefRemove(pool, voldef)) goto cleanup; ret = 0; cleanup: virCommandFree(cmd); return ret; } static int parallelsAddHdd(virConnectPtr conn, parallelsDomObjPtr pdom, virDomainDiskDefPtr disk) { parallelsConnPtr privconn = conn->privateData; virStorageVolDefPtr voldef = NULL; virStoragePoolObjPtr pool = NULL; virStorageVolPtr vol = NULL; int ret = -1; if (!(vol = parallelsStorageVolumeLookupByPathLocked(conn, disk->src))) { virReportError(VIR_ERR_INVALID_ARG, _("Can't find volume with path '%s'"), disk->src); return -1; } pool = virStoragePoolObjFindByName(&privconn->pools, vol->pool); if (!pool) { virReportError(VIR_ERR_INVALID_ARG, _("Can't find storage pool with name '%s'"), vol->pool); goto cleanup; } voldef = virStorageVolDefFindByPath(pool, disk->src); if (!voldef) { virReportError(VIR_ERR_INVALID_ARG, _("Can't find storage volume definition for path '%s'"), disk->src); goto cleanup; } ret = parallelsAddHddByVolume(pdom, disk, pool, voldef); cleanup: if (pool) virStoragePoolObjUnlock(pool); virObjectUnref(vol); return ret; } static int parallelsRemoveHdd(parallelsDomObjPtr pdom, virDomainDiskDefPtr disk) { char prlname[16]; prlname[15] = '\0'; snprintf(prlname, 15, "hdd%d", virDiskNameToIndex(disk->dst)); if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, "--device-del", prlname, "--detach-only", NULL)) return -1; return 0; } static int parallelsApplyDisksParams(virConnectPtr conn, parallelsDomObjPtr pdom, virDomainDiskDefPtr *olddisks, int nold, virDomainDiskDefPtr *newdisks, int nnew) { int i, j; for (i = 0; i < nold; i++) { virDomainDiskDefPtr newdisk = NULL; virDomainDiskDefPtr olddisk = olddisks[i]; for (j = 0; j < nnew; j++) { if (STREQ_NULLABLE(newdisks[j]->dst, olddisk->dst)) { newdisk = newdisks[j]; break; } } if (!newdisk) { if (parallelsRemoveHdd(pdom, olddisk)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, _("Can't remove disk '%s' " "in the specified config"), olddisks[i]->serial); return -1; } continue; } if (olddisk->bus != newdisk->bus || olddisk->info.addr.drive.target != newdisk->info.addr.drive.target || !STREQ_NULLABLE(olddisk->src, newdisk->src)) { char prlname[16]; char strpos[16]; const char *strbus; prlname[15] = '\0'; snprintf(prlname, 15, "hdd%d", virDiskNameToIndex(newdisk->dst)); strpos[15] = '\0'; snprintf(strpos, 15, "%d", newdisk->info.addr.drive.target); if (!(strbus = parallelsGetDiskBusName(newdisk->bus))) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, _("Unsupported disk bus: %d"), newdisk->bus); return -1; } if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, "--device-set", prlname, "--iface", strbus, "--position", strpos, "--image", newdisk->src, NULL)) return -1; } } for (i = 0; i < nnew; i++) { virDomainDiskDefPtr newdisk = newdisks[i]; bool found = false; for (j = 0; j < nold; j++) if (STREQ_NULLABLE(olddisks[j]->dst, newdisk->dst)) found = true; if (found) continue; if (parallelsAddHdd(conn, pdom, newdisk)) return -1; } return 0; } static int parallelsApplyIfaceParams(parallelsDomObjPtr pdom, virDomainNetDefPtr oldnet, virDomainNetDefPtr newnet) { bool create = false; bool is_changed = false; virCommandPtr cmd = NULL; char strmac[VIR_MAC_STRING_BUFLEN]; int i; int ret = -1; if (!oldnet) { create = true; if (VIR_ALLOC(oldnet) < 0) { virReportOOMError(); return -1; } } if (!create && oldnet->type != newnet->type) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Changing network type is not supported")); goto cleanup; } if (!STREQ_NULLABLE(oldnet->model, newnet->model)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Changing network device model is not supported")); goto cleanup; } if (!STREQ_NULLABLE(oldnet->data.network.portgroup, newnet->data.network.portgroup)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Changing network portgroup is not supported")); goto cleanup; } if (!virNetDevVPortProfileEqual(oldnet->virtPortProfile, newnet->virtPortProfile)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Changing virtual port profile is not supported")); goto cleanup; } if (newnet->tune.sndbuf_specified) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Setting send buffer size is not supported")); goto cleanup; } if (!STREQ_NULLABLE(oldnet->script, newnet->script)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Setting startup script is not supported")); goto cleanup; } if (!STREQ_NULLABLE(oldnet->filter, newnet->filter)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Changing filter params is not supported")); goto cleanup; } if (newnet->bandwidth != NULL) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Setting bandwidth params is not supported")); goto cleanup; } for (i = 0; i < sizeof(newnet->vlan); i++) { if (((char *)&newnet->vlan)[i] != 0) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Setting vlan params is not supported")); goto cleanup; } } /* Here we know, that there are no differences, that are forbidden. * Check is something changed, if no - do nothing */ if (create) { cmd = virCommandNewArgList(PRLCTL, "set", pdom->uuid, "--device-add", "net", NULL); } else { cmd = virCommandNewArgList(PRLCTL, "set", pdom->uuid, "--device-set", newnet->ifname, NULL); } if (virMacAddrCmp(&oldnet->mac, &newnet->mac)) { virMacAddrFormat(&newnet->mac, strmac); virCommandAddArgFormat(cmd, "--mac=%s", strmac); is_changed = true; } if (!STREQ_NULLABLE(oldnet->data.network.name, newnet->data.network.name)) { if (STREQ_NULLABLE(newnet->data.network.name, PARALLELS_ROUTED_NETWORK_NAME)) { virCommandAddArgFormat(cmd, "--type=routed"); } else { virCommandAddArgFormat(cmd, "--network=%s", newnet->data.network.name); } is_changed = true; } if (oldnet->linkstate != newnet->linkstate) { if (newnet->linkstate == VIR_DOMAIN_NET_INTERFACE_LINK_STATE_UP) { virCommandAddArgFormat(cmd, "--connect"); } else if (newnet->linkstate == VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN) { virCommandAddArgFormat(cmd, "--disconnect"); } is_changed = true; } if (!create && !is_changed) { /* nothing changed - no need to run prlctl */ ret = 0; goto cleanup; } if (virCommandRun(cmd, NULL) < 0) goto cleanup; ret = 0; cleanup: if (create) VIR_FREE(oldnet); virCommandFree(cmd); return ret; } static int parallelsApplyIfacesParams(parallelsDomObjPtr pdom, virDomainNetDefPtr *oldnets, int nold, virDomainNetDefPtr *newnets, int nnew) { int i, j; virDomainNetDefPtr newnet; virDomainNetDefPtr oldnet; bool found; for (i = 0; i < nold; i++) { newnet = NULL; oldnet = oldnets[i]; for (j = 0; j < nnew; j++) { if (STREQ_NULLABLE(newnets[j]->ifname, oldnet->ifname)) { newnet = newnets[j]; break; } } if (!newnet) { if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, "--device-del", oldnet->ifname, NULL) < 0) return -1; continue; } if (parallelsApplyIfaceParams(pdom, oldnet, newnet) < 0) return -1; } for (i = 0; i < nnew; i++) { newnet = newnets[i]; found = false; for (j = 0; j < nold; j++) if (STREQ_NULLABLE(oldnets[j]->ifname, newnet->ifname)) found = true; if (found) continue; if (parallelsApplyIfaceParams(pdom, NULL, newnet)) return -1; } return 0; } static int parallelsApplyChanges(virConnectPtr conn, virDomainObjPtr dom, virDomainDefPtr new) { char buf[32]; virDomainDefPtr old = dom->def; parallelsDomObjPtr pdom = dom->privateData; if (new->description && !STREQ_NULLABLE(old->description, new->description)) { if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, "--description", new->description, NULL)) return -1; } if (new->name && !STREQ_NULLABLE(old->name, new->name)) { if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, "--name", new->name, NULL)) return -1; } if (new->title && !STREQ_NULLABLE(old->title, new->title)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("titles are not supported by parallels driver")); return -1; } if (new->blkio.ndevices > 0) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("blkio parameters are not supported " "by parallels driver")); return -1; } if (old->mem.max_balloon != new->mem.max_balloon) { if (new->mem.max_balloon != new->mem.cur_balloon) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing balloon parameters is not supported " "by parallels driver")); return -1; } if (new->mem.max_balloon % (1 << 10) != 0) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Memory size should be multiple of 1Mb.")); return -1; } snprintf(buf, 31, "%llu", new->mem.max_balloon >> 10); buf[31] = '\0'; if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, "--memsize", buf, NULL)) return -1; } if (old->mem.hugepage_backed != new->mem.hugepage_backed || old->mem.hard_limit != new->mem.hard_limit || old->mem.soft_limit != new->mem.soft_limit || old->mem.min_guarantee != new->mem.min_guarantee || old->mem.swap_hard_limit != new->mem.swap_hard_limit) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("Memory parameter is not supported " "by parallels driver")); return -1; } if (old->vcpus != new->vcpus) { if (new->vcpus != new->maxvcpus) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("current vcpus must be equal to maxvcpus")); return -1; } snprintf(buf, 31, "%d", new->vcpus); buf[31] = '\0'; if (parallelsCmdRun(PRLCTL, "set", pdom->uuid, "--cpus", buf, NULL)) return -1; } if (old->placement_mode != new->placement_mode) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing cpu placement mode is not supported " "by parallels driver")); return -1; } if ((old->cpumask != NULL || new->cpumask != NULL) && (old->cpumask == NULL || new->cpumask == NULL || !virBitmapEqual(old->cpumask, new->cpumask))) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing cpu mask is not supported " "by parallels driver")); return -1; } if (old->cputune.shares != new->cputune.shares || old->cputune.period != new->cputune.period || old->cputune.quota != new->cputune.quota || old->cputune.nvcpupin != new->cputune.nvcpupin) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("cputune is not supported by parallels driver")); return -1; } if (old->numatune.memory.mode != new->numatune.memory.mode || old->numatune.memory.placement_mode != new->numatune.memory.placement_mode || ((old->numatune.memory.nodemask != NULL || new->numatune.memory.nodemask != NULL) && (old->numatune.memory.nodemask == NULL || new->numatune.memory.nodemask == NULL || !virBitmapEqual(old->numatune.memory.nodemask, new->numatune.memory.nodemask)))){ virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("numa parameters are not supported " "by parallels driver")); return -1; } if (old->onReboot != new->onReboot || old->onPoweroff != new->onPoweroff || old->onCrash != new->onCrash) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("on_reboot, on_poweroff and on_crash parameters " "are not supported by parallels driver")); return -1; } /* we fill only type and arch fields in parallelsLoadDomain 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 (!STREQ_NULLABLE(old->os.type, new->os.type) || old->os.arch != new->os.arch || new->os.machine != NULL || new->os.bootmenu != 0 || new->os.kernel != NULL || new->os.initrd != NULL || new->os.cmdline != NULL || new->os.root != NULL || new->os.loader != NULL || new->os.bootloader != NULL || new->os.bootloaderArgs != NULL || new->os.smbios_mode != 0 || new->os.bios.useserial != 0) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing OS parameters is not supported " "by parallels driver")); return -1; } if (STREQ(new->os.type, "hvm")) { if (new->os.nBootDevs != 1 || new->os.bootDevs[0] != VIR_DOMAIN_BOOT_DISK || new->os.init != NULL || new->os.initargv != NULL) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing OS parameters is not supported " "by parallels driver")); return -1; } } else { if (new->os.nBootDevs != 0 || !STREQ_NULLABLE(old->os.init, new->os.init) || (new->os.initargv != NULL && new->os.initargv[0] != NULL)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing OS parameters is not supported " "by parallels driver")); return -1; } } if (!STREQ_NULLABLE(old->emulator, new->emulator)) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing emulator is not supported " "by parallels driver")); return -1; } if (old->features != new->features) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing features is not supported " "by parallels driver")); return -1; } if (new->clock.offset != VIR_DOMAIN_CLOCK_OFFSET_UTC || new->clock.ntimers != 0) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing clock parameters is not supported " "by parallels driver")); return -1; } if (parallelsApplyGraphicsParams(old->graphics, old->ngraphics, new->graphics, new->ngraphics) < 0) return -1; if (new->nfss != 0 || new->nsounds != 0 || new->nhostdevs != 0 || new->nredirdevs != 0 || new->nsmartcards != 0 || new->nparallels || new->nchannels != 0 || new->nleases != 0 || new->nhubs != 0) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing devices parameters is not supported " "by parallels driver")); return -1; } /* there may be one auto-input */ if (new->ninputs > 1 || (new->ninputs > 1 && (new->inputs[0]->type != VIR_DOMAIN_INPUT_TYPE_MOUSE || new->inputs[0]->bus != VIR_DOMAIN_INPUT_BUS_PS2))) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("changing input devices parameters is not supported " "by parallels driver")); } if (parallelsApplySerialParams(old->serials, old->nserials, new->serials, new->nserials) < 0) return -1; if (parallelsApplySerialParams(old->consoles, old->nconsoles, new->consoles, new->nconsoles) < 0) return -1; if (parallelsApplyVideoParams(pdom, old->videos, old->nvideos, new->videos, new->nvideos) < 0) return -1; if (parallelsApplyDisksParams(conn, pdom, old->disks, old->ndisks, new->disks, new->ndisks) < 0) return -1; if (parallelsApplyIfacesParams(pdom, old->nets, old->nnets, new->nets, new->nnets) < 0) return -1; return 0; } static int parallelsCreateVm(virConnectPtr conn, virDomainDefPtr def) { parallelsConnPtr privconn = conn->privateData; int i; virStorageVolDefPtr privvol = NULL; virStoragePoolObjPtr pool = NULL; virStorageVolPtr vol = NULL; char uuidstr[VIR_UUID_STRING_BUFLEN]; for (i = 0; i < def->ndisks; i++) { if (def->disks[i]->device != VIR_DOMAIN_DISK_DEVICE_DISK) continue; vol = parallelsStorageVolumeLookupByPathLocked(conn, def->disks[i]->src); if (!vol) { virReportError(VIR_ERR_INVALID_ARG, _("Can't find volume with path '%s'"), def->disks[i]->src); return -1; } break; } if (!vol) { /* We determine path to VM directory from volume, so * let's report error if no disk until better solution * will be found */ virReportError(VIR_ERR_INVALID_ARG, _("Can't create VM '%s' without hard disks"), def->name ? def->name : _("(unnamed)")); return -1; } pool = virStoragePoolObjFindByName(&privconn->pools, vol->pool); if (!pool) { virReportError(VIR_ERR_INVALID_ARG, _("Can't find storage pool with name '%s'"), vol->pool); goto error; } privvol = virStorageVolDefFindByPath(pool, def->disks[i]->src); if (!privvol) { virReportError(VIR_ERR_INVALID_ARG, _("Can't find storage volume definition for path '%s'"), def->disks[i]->src); goto error2; } virUUIDFormat(def->uuid, uuidstr); if (parallelsCmdRun(PRLCTL, "create", def->name, "--dst", pool->def->target.path, "--no-hdd", "--uuid", uuidstr, NULL) < 0) goto error2; if (parallelsCmdRun(PRLCTL, "set", def->name, "--vnc-mode", "auto", NULL) < 0) goto error2; virStoragePoolObjUnlock(pool); virObjectUnref(vol); return 0; error2: virStoragePoolObjUnlock(pool); error: virObjectUnref(vol); return -1; } static int parallelsCreateCt(virConnectPtr conn ATTRIBUTE_UNUSED, virDomainDefPtr def) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(def->uuid, uuidstr); if (def->nfss != 1 || def->fss[0]->type != VIR_DOMAIN_FS_TYPE_TEMPLATE) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("There must be only 1 template FS for " "container creation")); goto error; } if (parallelsCmdRun(PRLCTL, "create", def->name, "--vmtype", "ct", "--uuid", uuidstr, "--ostemplate", def->fss[0]->src, NULL) < 0) goto error; if (parallelsCmdRun(PRLCTL, "set", def->name, "--vnc-mode", "auto", NULL) < 0) goto error; return 0; error: return -1; } static virDomainPtr parallelsDomainDefineXML(virConnectPtr conn, const char *xml) { parallelsConnPtr privconn = conn->privateData; virDomainPtr ret = NULL; virDomainDefPtr def; virDomainObjPtr dom = NULL, olddom = NULL; parallelsDriverLock(privconn); if ((def = virDomainDefParseString(xml, privconn->caps, privconn->xmlopt, 1 << VIR_DOMAIN_VIRT_PARALLELS, VIR_DOMAIN_XML_INACTIVE)) == NULL) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("Can't parse XML desc")); goto cleanup; } olddom = virDomainObjListFindByUUID(privconn->domains, def->uuid); if (olddom == NULL) { virResetLastError(); if (STREQ(def->os.type, "hvm")) { if (parallelsCreateVm(conn, def)) goto cleanup; } else if (STREQ(def->os.type, "exe")) { if (parallelsCreateCt(conn, def)) goto cleanup; } else { virReportError(VIR_ERR_INVALID_ARG, _("Unsupported OS type: %s"), def->os.type); goto cleanup; } if (parallelsLoadDomains(privconn, def->name)) goto cleanup; olddom = virDomainObjListFindByName(privconn->domains, def->name); if (!olddom) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Domain for '%s' is not defined after creation"), def->name ? def->name : _("(unnamed)")); goto cleanup; } } if (parallelsApplyChanges(conn, olddom, def) < 0) { virObjectUnlock(olddom); goto cleanup; } virObjectUnlock(olddom); if (!(dom = virDomainObjListAdd(privconn->domains, def, privconn->xmlopt, 0, NULL))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Can't allocate domobj")); goto cleanup; } def = NULL; ret = virGetDomain(conn, dom->def->name, dom->def->uuid); if (ret) ret->id = dom->def->id; cleanup: virDomainDefFree(def); if (dom) virObjectUnlock(dom); parallelsDriverUnlock(privconn); return ret; } static virDriver parallelsDriver = { .no = VIR_DRV_PARALLELS, .name = "Parallels", .open = parallelsOpen, /* 0.10.0 */ .close = parallelsClose, /* 0.10.0 */ .version = parallelsGetVersion, /* 0.10.0 */ .getHostname = virGetHostname, /* 0.10.0 */ .nodeGetInfo = nodeGetInfo, /* 0.10.0 */ .getCapabilities = parallelsGetCapabilities, /* 0.10.0 */ .listDomains = parallelsListDomains, /* 0.10.0 */ .numOfDomains = parallelsNumOfDomains, /* 0.10.0 */ .listDefinedDomains = parallelsListDefinedDomains, /* 0.10.0 */ .numOfDefinedDomains = parallelsNumOfDefinedDomains, /* 0.10.0 */ .listAllDomains = parallelsListAllDomains, /* 0.10.0 */ .domainLookupByID = parallelsLookupDomainByID, /* 0.10.0 */ .domainLookupByUUID = parallelsLookupDomainByUUID, /* 0.10.0 */ .domainLookupByName = parallelsLookupDomainByName, /* 0.10.0 */ .domainGetOSType = parallelsGetOSType, /* 0.10.0 */ .domainGetInfo = parallelsGetDomainInfo, /* 0.10.0 */ .domainGetState = parallelsDomainGetState, /* 0.10.0 */ .domainGetXMLDesc = parallelsDomainGetXMLDesc, /* 0.10.0 */ .domainIsPersistent = parallelsDomainIsPersistent, /* 0.10.0 */ .domainGetAutostart = parallelsDomainGetAutostart, /* 0.10.0 */ .domainSuspend = parallelsPauseDomain, /* 0.10.0 */ .domainResume = parallelsResumeDomain, /* 0.10.0 */ .domainDestroy = parallelsDestroyDomain, /* 0.10.0 */ .domainShutdown = parallelsShutdownDomain, /* 0.10.0 */ .domainCreate = parallelsDomainCreate, /* 0.10.0 */ .domainDefineXML = parallelsDomainDefineXML, /* 0.10.0 */ }; /** * parallelsRegister: * * Registers the parallels driver */ int parallelsRegister(void) { char *prlctl_path; prlctl_path = virFindFileInPath(PRLCTL); if (!prlctl_path) { VIR_DEBUG("%s", _("Can't find prlctl command in the PATH env")); return 0; } VIR_FREE(prlctl_path); if (virRegisterDriver(¶llelsDriver) < 0) return -1; if (parallelsStorageRegister()) return -1; if (parallelsNetworkRegister()) return -1; return 0; }