1
0
mirror of https://gitlab.com/libvirt/libvirt.git synced 2024-12-23 21:34:54 +03:00

parallels: add functions to list domains and get info

Parallels driver is 'stateless', like vmware or openvz drivers.
It collects information about domains during startup using
command-line utility prlctl. VMs in Parallels are identified by UUIDs
or unique names, which can be used as respective fields in
virDomainDef structure. Currently only basic info, like
description, virtual cpus number and memory amount, is implemented.
Querying devices information will be added in the next patches.

Parallels doesn't support non-persistent domains - you can't run
a domain having only disk image, it must always be registered
in system.

Functions for querying domain info have been just copied from
test driver with some changes - they extract needed data from
previously created list of virDomainObj objects.

Signed-off-by: Dmitry Guryanov <dguryanov@parallels.com>
This commit is contained in:
Dmitry Guryanov 2012-07-31 22:56:07 +04:00 committed by Daniel Veillard
parent 4033df7ed8
commit e93c33a987
5 changed files with 748 additions and 4 deletions

View File

@ -66,6 +66,7 @@ src/openvz/openvz_conf.c
src/openvz/openvz_driver.c src/openvz/openvz_driver.c
src/openvz/openvz_util.c src/openvz/openvz_util.c
src/parallels/parallels_driver.c src/parallels/parallels_driver.c
src/parallels/parallels_utils.c
src/phyp/phyp_driver.c src/phyp/phyp_driver.c
src/qemu/qemu_agent.c src/qemu/qemu_agent.c
src/qemu/qemu_bridge_filter.c src/qemu/qemu_bridge_filter.c

View File

@ -531,7 +531,8 @@ HYPERV_DRIVER_EXTRA_DIST = \
PARALLELS_DRIVER_SOURCES = \ PARALLELS_DRIVER_SOURCES = \
parallels/parallels_driver.h \ parallels/parallels_driver.h \
parallels/parallels_driver.c parallels/parallels_driver.c \
parallels/parallels_utils.c
NETWORK_DRIVER_SOURCES = \ NETWORK_DRIVER_SOURCES = \
network/bridge_driver.h network/bridge_driver.c network/bridge_driver.h network/bridge_driver.c

View File

@ -53,14 +53,29 @@
#include "nodeinfo.h" #include "nodeinfo.h"
#include "json.h" #include "json.h"
#include "domain_conf.h" #include "domain_conf.h"
#include "virdomainlist.h"
#include "parallels_driver.h" #include "parallels_driver.h"
#include "parallels_utils.h"
#define VIR_FROM_THIS VIR_FROM_PARALLELS #define VIR_FROM_THIS VIR_FROM_PARALLELS
#define PRLCTL "prlctl" #define PRLCTL "prlctl"
#define PRLSRVCTL "prlsrvctl"
#define PARALLELS_DEFAULT_ARCH "x86_64" #define PARALLELS_DEFAULT_ARCH "x86_64"
#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 parallelsParseError() \
virReportErrorHelper(VIR_FROM_TEST, VIR_ERR_OPERATION_FAILED, __FILE__, \
__FUNCTION__, __LINE__, _("Can't parse prlctl output"))
struct _parallelsConn { struct _parallelsConn {
virMutex lock; virMutex lock;
virDomainObjList domains; virDomainObjList domains;
@ -71,6 +86,14 @@ struct _parallelsConn {
typedef struct _parallelsConn parallelsConn; typedef struct _parallelsConn parallelsConn;
typedef struct _parallelsConn *parallelsConnPtr; typedef struct _parallelsConn *parallelsConnPtr;
struct parallelsDomObj {
int id;
char *uuid;
char *os;
};
typedef struct parallelsDomObj *parallelsDomObjPtr;
static int parallelsClose(virConnectPtr conn); static int parallelsClose(virConnectPtr conn);
static void static void
@ -91,6 +114,18 @@ parallelsDefaultConsoleType(const char *ostype ATTRIBUTE_UNUSED)
return VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL; 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(p);
};
static virCapsPtr static virCapsPtr
parallelsBuildCapabilities(void) parallelsBuildCapabilities(void)
{ {
@ -139,6 +174,207 @@ parallelsGetCapabilities(virConnectPtr conn)
return xml; return xml;
} }
/*
* 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) {
parallelsParseError();
goto cleanup;
}
def->vcpus = x;
def->maxvcpus = x;
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 (!(def->os.type = strdup("hvm")))
goto no_memory;
if (!(def->os.arch = strdup(PARALLELS_DEFAULT_ARCH)))
goto no_memory;
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, "OS")))
goto cleanup;
if (!(state = virJSONValueObjectGetString(jobj, "State"))) {
parallelsParseError();
goto cleanup;
}
if (!(autostart = virJSONValueObjectGetString(jobj, "Autostart"))) {
parallelsParseError();
goto cleanup;
}
if (!(dom = virDomainAssignDef(privconn->caps,
&privconn->domains, def, false)))
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;
virDomainObjUnlock(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", "vm", 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 static int
parallelsOpenDefault(virConnectPtr conn) parallelsOpenDefault(virConnectPtr conn)
{ {
@ -162,6 +398,9 @@ parallelsOpenDefault(virConnectPtr conn)
conn->privateData = privconn; conn->privateData = privconn;
if (parallelsLoadDomains(privconn, NULL))
goto error;
return VIR_DRV_OPEN_SUCCESS; return VIR_DRV_OPEN_SUCCESS;
error: error:
@ -232,9 +471,358 @@ parallelsClose(virConnectPtr conn)
static int static int
parallelsGetVersion(virConnectPtr conn ATTRIBUTE_UNUSED, unsigned long *hvVer) parallelsGetVersion(virConnectPtr conn ATTRIBUTE_UNUSED, unsigned long *hvVer)
{ {
/* TODO */ char *output, *sVer, *tmp;
*hvVer = 6; const char *searchStr = "prlsrvctl version ";
return 0; 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_FILTERS_ALL, -1);
parallelsDriverLock(privconn);
ret = virDomainList(conn, privconn->domains.objs, 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 = virDomainFindByID(&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)
virDomainObjUnlock(dom);
return ret;
}
static virDomainPtr
parallelsLookupDomainByUUID(virConnectPtr conn, const unsigned char *uuid)
{
parallelsConnPtr privconn = conn->privateData;
virDomainPtr ret = NULL;
virDomainObjPtr dom;
parallelsDriverLock(privconn);
dom = virDomainFindByUUID(&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)
virDomainObjUnlock(dom);
return ret;
}
static virDomainPtr
parallelsLookupDomainByName(virConnectPtr conn, const char *name)
{
parallelsConnPtr privconn = conn->privateData;
virDomainPtr ret = NULL;
virDomainObjPtr dom;
parallelsDriverLock(privconn);
dom = virDomainFindByName(&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)
virDomainObjUnlock(dom);
return ret;
}
static int
parallelsGetDomainInfo(virDomainPtr domain, virDomainInfoPtr info)
{
parallelsConnPtr privconn = domain->conn->privateData;
virDomainObjPtr privdom;
int ret = -1;
parallelsDriverLock(privconn);
privdom = virDomainFindByUUID(&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)
virDomainObjUnlock(privdom);
return ret;
}
static char *
parallelsGetOSType(virDomainPtr domain)
{
parallelsConnPtr privconn = domain->conn->privateData;
virDomainObjPtr privdom;
char *ret = NULL;
parallelsDriverLock(privconn);
privdom = virDomainFindByUUID(&privconn->domains, domain->uuid);
if (privdom == NULL) {
parallelsDomNotFoundError(domain);
goto cleanup;
}
if (!(ret = strdup(privdom->def->os.type)))
virReportOOMError();
cleanup:
if (privdom)
virDomainObjUnlock(privdom);
parallelsDriverUnlock(privconn);
return ret;
}
static int
parallelsDomainIsPersistent(virDomainPtr domain)
{
parallelsConnPtr privconn = domain->conn->privateData;
virDomainObjPtr privdom;
int ret = -1;
parallelsDriverLock(privconn);
privdom = virDomainFindByUUID(&privconn->domains, domain->uuid);
if (privdom == NULL) {
parallelsDomNotFoundError(domain);
goto cleanup;
}
ret = 1;
cleanup:
if (privdom)
virDomainObjUnlock(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 = virDomainFindByUUID(&privconn->domains, domain->uuid);
parallelsDriverUnlock(privconn);
if (privdom == NULL) {
parallelsDomNotFoundError(domain);
goto cleanup;
}
*state = virDomainObjGetState(privdom, reason);
ret = 0;
cleanup:
if (privdom)
virDomainObjUnlock(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 = virDomainFindByUUID(&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)
virDomainObjUnlock(privdom);
return ret;
}
static int
parallelsDomainGetAutostart(virDomainPtr domain, int *autostart)
{
parallelsConnPtr privconn = domain->conn->privateData;
virDomainObjPtr privdom;
int ret = -1;
parallelsDriverLock(privconn);
privdom = virDomainFindByUUID(&privconn->domains, domain->uuid);
parallelsDriverUnlock(privconn);
if (privdom == NULL) {
parallelsDomNotFoundError(domain);
goto cleanup;
}
*autostart = privdom->autostart;
ret = 0;
cleanup:
if (privdom)
virDomainObjUnlock(privdom);
return ret;
} }
static virDriver parallelsDriver = { static virDriver parallelsDriver = {
@ -246,6 +834,20 @@ static virDriver parallelsDriver = {
.getHostname = virGetHostname, /* 0.10.0 */ .getHostname = virGetHostname, /* 0.10.0 */
.nodeGetInfo = nodeGetInfo, /* 0.10.0 */ .nodeGetInfo = nodeGetInfo, /* 0.10.0 */
.getCapabilities = parallelsGetCapabilities, /* 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 */
}; };
/** /**

View File

@ -0,0 +1,109 @@
/*
* parallels_utils.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 <stdarg.h>
#include "command.h"
#include "virterror_internal.h"
#include "memory.h"
#include "json.h"
#include "parallels_utils.h"
#define VIR_FROM_THIS VIR_FROM_PARALLELS
static int
parallelsDoCmdRun(char **outbuf, const char *binary, va_list list)
{
virCommandPtr cmd = virCommandNewVAList(binary, list);
char *scmd = NULL;
int ret = -1;
if (outbuf)
virCommandSetOutputBuffer(cmd, outbuf);
scmd = virCommandToString(cmd);
if (!scmd)
goto cleanup;
if (virCommandRun(cmd, NULL))
goto cleanup;
ret = 0;
cleanup:
VIR_FREE(scmd);
virCommandFree(cmd);
if (ret)
VIR_FREE(*outbuf);
return ret;
}
/*
* Run command and parse its JSON output, return
* pointer to virJSONValue or NULL in case of error.
*/
virJSONValuePtr
parallelsParseOutput(const char *binary, ...)
{
char *outbuf;
virJSONValuePtr jobj = NULL;
va_list list;
int ret;
va_start(list, binary);
ret = parallelsDoCmdRun(&outbuf, binary, list);
va_end(list);
if (ret)
return NULL;
jobj = virJSONValueFromString(outbuf);
if (!jobj)
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid output from prlctl: %s"), outbuf);
VIR_FREE(outbuf);
return jobj;
}
/*
* Run command and return its output, pointer to
* buffer or NULL in case of error. Caller os responsible
* for freeing the buffer.
*/
char *
parallelsGetOutput(const char *binary, ...)
{
char *outbuf;
va_list list;
int ret;
va_start(list, binary);
ret = parallelsDoCmdRun(&outbuf, binary, list);
va_end(list);
if (ret)
return NULL;
return outbuf;
}

View File

@ -0,0 +1,31 @@
/*
* parallels_utils.h: 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/>.
*
*/
#ifndef PARALLELS_UTILS_H
# define PARALLELS_UTILS_H
virJSONValuePtr parallelsParseOutput(const char *binary, ...)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_SENTINEL;
char * parallelsGetOutput(const char *binary, ...)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_SENTINEL;
#endif