libvirt/src/parallels/parallels_driver.c
Daniel P. Berrange abe038cfc0 Extend previous check to validate driver struct field names
Ensure that the driver struct field names match the public
API names. For an API virXXXX we must have a driver struct
field xXXXX. ie strip the leading 'vir' and lowercase any
leading uppercase letters.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-04-24 10:59:53 +01:00

2448 lines
68 KiB
C

/*
* 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
* <http://www.gnu.org/licenses/>.
*
*/
#include <config.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <paths.h>
#include <pwd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/statvfs.h>
#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 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;
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;
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 = true;
} else {
if (virJSONValueObjectGetNumberUint(jobj, "port", &port) < 0) {
parallelsParseError();
goto cleanup;
}
gr->data.vnc.autoport = false;
}
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 (!(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;
}
virDomainDefParserConfig parallelsDomainDefParserConfig = {
.macPrefix = {0x42, 0x1C, 0x00},
};
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(&parallelsDomainDefParserConfig,
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",
.connectOpen = parallelsOpen, /* 0.10.0 */
.connectClose = parallelsClose, /* 0.10.0 */
.connectGetVersion = parallelsGetVersion, /* 0.10.0 */
.connectGetHostname = virGetHostname, /* 0.10.0 */
.nodeGetInfo = nodeGetInfo, /* 0.10.0 */
.connectGetCapabilities = parallelsGetCapabilities, /* 0.10.0 */
.connectListDomains = parallelsListDomains, /* 0.10.0 */
.connectNumOfDomains = parallelsNumOfDomains, /* 0.10.0 */
.connectListDefinedDomains = parallelsListDefinedDomains, /* 0.10.0 */
.connectNumOfDefinedDomains = parallelsNumOfDefinedDomains, /* 0.10.0 */
.connectListAllDomains = 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(&parallelsDriver) < 0)
return -1;
if (parallelsStorageRegister())
return -1;
if (parallelsNetworkRegister())
return -1;
return 0;
}