mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-05 12:35:20 +00:00
f0f986efa8
Start virtiofsd for each <filesystem> device using it. Pre-create the socket for communication with QEMU and pass it to virtiofsd. Note that virtiofsd needs to run as root. https://bugzilla.redhat.com/show_bug.cgi?id=1694166 Introduced by QEMU commit a43efa34c7d7b628cbf1ec0fe60043e5c91043ea Signed-off-by: Ján Tomko <jtomko@redhat.com> Reviewed-by: Peter Krempa <pkrempa@redhat.com> Tested-by: Andrea Bolognani <abologna@redhat.com>
16804 lines
528 KiB
C
16804 lines
528 KiB
C
/*
|
|
* qemu_domain.c: QEMU domain private state
|
|
*
|
|
* Copyright (C) 2006-2019 Red Hat, Inc.
|
|
* Copyright (C) 2006 Daniel P. Berrange
|
|
*
|
|
* 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 "qemu_domain.h"
|
|
#include "qemu_alias.h"
|
|
#include "qemu_block.h"
|
|
#include "qemu_cgroup.h"
|
|
#include "qemu_command.h"
|
|
#include "qemu_dbus.h"
|
|
#include "qemu_process.h"
|
|
#include "qemu_capabilities.h"
|
|
#include "qemu_hostdev.h"
|
|
#include "qemu_migration.h"
|
|
#include "qemu_migration_params.h"
|
|
#include "qemu_security.h"
|
|
#include "qemu_slirp.h"
|
|
#include "qemu_extdevice.h"
|
|
#include "qemu_blockjob.h"
|
|
#include "qemu_checkpoint.h"
|
|
#include "viralloc.h"
|
|
#include "virlog.h"
|
|
#include "virerror.h"
|
|
#include "cpu/cpu.h"
|
|
#include "viruuid.h"
|
|
#include "virfile.h"
|
|
#include "domain_addr.h"
|
|
#include "domain_capabilities.h"
|
|
#include "domain_event.h"
|
|
#include "virtime.h"
|
|
#include "virnetdevopenvswitch.h"
|
|
#include "virstoragefile.h"
|
|
#include "virstring.h"
|
|
#include "virthreadjob.h"
|
|
#include "virprocess.h"
|
|
#include "vircrypto.h"
|
|
#include "virrandom.h"
|
|
#include "virsystemd.h"
|
|
#include "virsecret.h"
|
|
#include "logging/log_manager.h"
|
|
#include "locking/domain_lock.h"
|
|
#include "virdomainsnapshotobjlist.h"
|
|
#include "virdomaincheckpointobjlist.h"
|
|
#include "backup_conf.h"
|
|
#include "virutil.h"
|
|
|
|
#ifdef __linux__
|
|
# include <sys/sysmacros.h>
|
|
#endif
|
|
#include <sys/time.h>
|
|
#include <fcntl.h>
|
|
#if defined(HAVE_SYS_MOUNT_H)
|
|
# include <sys/mount.h>
|
|
#endif
|
|
#ifdef WITH_SELINUX
|
|
# include <selinux/selinux.h>
|
|
#endif
|
|
|
|
#define QEMU_QXL_VGAMEM_DEFAULT 16 * 1024
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
VIR_LOG_INIT("qemu.qemu_domain");
|
|
|
|
VIR_ENUM_IMPL(qemuDomainJob,
|
|
QEMU_JOB_LAST,
|
|
"none",
|
|
"query",
|
|
"destroy",
|
|
"suspend",
|
|
"modify",
|
|
"abort",
|
|
"migration operation",
|
|
"none", /* async job is never stored in job.active */
|
|
"async nested",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(qemuDomainAgentJob,
|
|
QEMU_AGENT_JOB_LAST,
|
|
"none",
|
|
"query",
|
|
"modify",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(qemuDomainAsyncJob,
|
|
QEMU_ASYNC_JOB_LAST,
|
|
"none",
|
|
"migration out",
|
|
"migration in",
|
|
"save",
|
|
"dump",
|
|
"snapshot",
|
|
"start",
|
|
"backup",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(qemuDomainNamespace,
|
|
QEMU_DOMAIN_NS_LAST,
|
|
"mount",
|
|
);
|
|
|
|
|
|
/**
|
|
* qemuDomainObjFromDomain:
|
|
* @domain: Domain pointer that has to be looked up
|
|
*
|
|
* This function looks up @domain and returns the appropriate virDomainObjPtr
|
|
* that has to be released by calling virDomainObjEndAPI().
|
|
*
|
|
* Returns the domain object with incremented reference counter which is locked
|
|
* on success, NULL otherwise.
|
|
*/
|
|
virDomainObjPtr
|
|
qemuDomainObjFromDomain(virDomainPtr domain)
|
|
{
|
|
virDomainObjPtr vm;
|
|
virQEMUDriverPtr driver = domain->conn->privateData;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
|
|
vm = virDomainObjListFindByUUID(driver->domains, domain->uuid);
|
|
if (!vm) {
|
|
virUUIDFormat(domain->uuid, uuidstr);
|
|
virReportError(VIR_ERR_NO_DOMAIN,
|
|
_("no domain with matching uuid '%s' (%s)"),
|
|
uuidstr, domain->name);
|
|
return NULL;
|
|
}
|
|
|
|
return vm;
|
|
}
|
|
|
|
|
|
struct _qemuDomainLogContext {
|
|
virObject parent;
|
|
|
|
int writefd;
|
|
int readfd; /* Only used if manager == NULL */
|
|
off_t pos;
|
|
ino_t inode; /* Only used if manager != NULL */
|
|
char *path;
|
|
virLogManagerPtr manager;
|
|
};
|
|
|
|
static virClassPtr qemuDomainLogContextClass;
|
|
static virClassPtr qemuDomainSaveCookieClass;
|
|
|
|
static void qemuDomainLogContextDispose(void *obj);
|
|
static void qemuDomainSaveCookieDispose(void *obj);
|
|
|
|
|
|
static int
|
|
qemuDomainOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainLogContext, virClassForObject()))
|
|
return -1;
|
|
|
|
if (!VIR_CLASS_NEW(qemuDomainSaveCookie, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomain);
|
|
|
|
static void
|
|
qemuDomainLogContextDispose(void *obj)
|
|
{
|
|
qemuDomainLogContextPtr ctxt = obj;
|
|
VIR_DEBUG("ctxt=%p", ctxt);
|
|
|
|
virLogManagerFree(ctxt->manager);
|
|
VIR_FREE(ctxt->path);
|
|
VIR_FORCE_CLOSE(ctxt->writefd);
|
|
VIR_FORCE_CLOSE(ctxt->readfd);
|
|
}
|
|
|
|
const char *
|
|
qemuDomainAsyncJobPhaseToString(qemuDomainAsyncJob job,
|
|
int phase G_GNUC_UNUSED)
|
|
{
|
|
switch (job) {
|
|
case QEMU_ASYNC_JOB_MIGRATION_OUT:
|
|
case QEMU_ASYNC_JOB_MIGRATION_IN:
|
|
return qemuMigrationJobPhaseTypeToString(phase);
|
|
|
|
case QEMU_ASYNC_JOB_SAVE:
|
|
case QEMU_ASYNC_JOB_DUMP:
|
|
case QEMU_ASYNC_JOB_SNAPSHOT:
|
|
case QEMU_ASYNC_JOB_START:
|
|
case QEMU_ASYNC_JOB_NONE:
|
|
case QEMU_ASYNC_JOB_BACKUP:
|
|
G_GNUC_FALLTHROUGH;
|
|
case QEMU_ASYNC_JOB_LAST:
|
|
break;
|
|
}
|
|
|
|
return "none";
|
|
}
|
|
|
|
int
|
|
qemuDomainAsyncJobPhaseFromString(qemuDomainAsyncJob job,
|
|
const char *phase)
|
|
{
|
|
if (!phase)
|
|
return 0;
|
|
|
|
switch (job) {
|
|
case QEMU_ASYNC_JOB_MIGRATION_OUT:
|
|
case QEMU_ASYNC_JOB_MIGRATION_IN:
|
|
return qemuMigrationJobPhaseTypeFromString(phase);
|
|
|
|
case QEMU_ASYNC_JOB_SAVE:
|
|
case QEMU_ASYNC_JOB_DUMP:
|
|
case QEMU_ASYNC_JOB_SNAPSHOT:
|
|
case QEMU_ASYNC_JOB_START:
|
|
case QEMU_ASYNC_JOB_NONE:
|
|
case QEMU_ASYNC_JOB_BACKUP:
|
|
G_GNUC_FALLTHROUGH;
|
|
case QEMU_ASYNC_JOB_LAST:
|
|
break;
|
|
}
|
|
|
|
if (STREQ(phase, "none"))
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainNamespaceEnabled(virDomainObjPtr vm,
|
|
qemuDomainNamespace ns)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
return priv->namespaces &&
|
|
virBitmapIsBitSet(priv->namespaces, ns);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainEnableNamespace(virDomainObjPtr vm,
|
|
qemuDomainNamespace ns)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
if (!priv->namespaces &&
|
|
!(priv->namespaces = virBitmapNew(QEMU_DOMAIN_NS_LAST)))
|
|
return -1;
|
|
|
|
if (virBitmapSetBit(priv->namespaces, ns) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to enable namespace: %s"),
|
|
qemuDomainNamespaceTypeToString(ns));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainDisableNamespace(virDomainObjPtr vm,
|
|
qemuDomainNamespace ns)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
if (priv->namespaces) {
|
|
ignore_value(virBitmapClearBit(priv->namespaces, ns));
|
|
if (virBitmapIsAllClear(priv->namespaces)) {
|
|
virBitmapFree(priv->namespaces);
|
|
priv->namespaces = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainEventEmitJobCompleted(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virObjectEventPtr event;
|
|
virTypedParameterPtr params = NULL;
|
|
int nparams = 0;
|
|
int type;
|
|
|
|
if (!priv->job.completed)
|
|
return;
|
|
|
|
if (qemuDomainJobInfoToParams(priv->job.completed, &type,
|
|
¶ms, &nparams) < 0) {
|
|
VIR_WARN("Could not get stats for completed job; domain %s",
|
|
vm->def->name);
|
|
}
|
|
|
|
event = virDomainEventJobCompletedNewFromObj(vm, params, nparams);
|
|
virObjectEventStateQueue(driver->domainEventState, event);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjInitJob(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
memset(&priv->job, 0, sizeof(priv->job));
|
|
|
|
if (virCondInit(&priv->job.cond) < 0)
|
|
return -1;
|
|
|
|
if (virCondInit(&priv->job.asyncCond) < 0) {
|
|
virCondDestroy(&priv->job.cond);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
qemuDomainObjResetJob(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
qemuDomainJobObjPtr job = &priv->job;
|
|
|
|
job->active = QEMU_JOB_NONE;
|
|
job->owner = 0;
|
|
job->ownerAPI = NULL;
|
|
job->started = 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainObjResetAgentJob(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
qemuDomainJobObjPtr job = &priv->job;
|
|
|
|
job->agentActive = QEMU_AGENT_JOB_NONE;
|
|
job->agentOwner = 0;
|
|
job->agentOwnerAPI = NULL;
|
|
job->agentStarted = 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainObjResetAsyncJob(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
qemuDomainJobObjPtr job = &priv->job;
|
|
|
|
job->asyncJob = QEMU_ASYNC_JOB_NONE;
|
|
job->asyncOwner = 0;
|
|
job->asyncOwnerAPI = NULL;
|
|
job->asyncStarted = 0;
|
|
job->phase = 0;
|
|
job->mask = QEMU_JOB_DEFAULT_MASK;
|
|
job->abortJob = false;
|
|
job->spiceMigration = false;
|
|
job->spiceMigrated = false;
|
|
job->dumpCompleted = false;
|
|
VIR_FREE(job->error);
|
|
VIR_FREE(job->current);
|
|
qemuMigrationParamsFree(job->migParams);
|
|
job->migParams = NULL;
|
|
job->apiFlags = 0;
|
|
}
|
|
|
|
void
|
|
qemuDomainObjRestoreJob(virDomainObjPtr obj,
|
|
qemuDomainJobObjPtr job)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
|
|
memset(job, 0, sizeof(*job));
|
|
job->active = priv->job.active;
|
|
job->owner = priv->job.owner;
|
|
job->asyncJob = priv->job.asyncJob;
|
|
job->asyncOwner = priv->job.asyncOwner;
|
|
job->phase = priv->job.phase;
|
|
job->migParams = g_steal_pointer(&priv->job.migParams);
|
|
job->apiFlags = priv->job.apiFlags;
|
|
|
|
qemuDomainObjResetJob(priv);
|
|
qemuDomainObjResetAsyncJob(priv);
|
|
}
|
|
|
|
static void
|
|
qemuDomainObjFreeJob(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
qemuDomainObjResetJob(priv);
|
|
qemuDomainObjResetAsyncJob(priv);
|
|
VIR_FREE(priv->job.current);
|
|
VIR_FREE(priv->job.completed);
|
|
virCondDestroy(&priv->job.cond);
|
|
virCondDestroy(&priv->job.asyncCond);
|
|
}
|
|
|
|
static bool
|
|
qemuDomainTrackJob(qemuDomainJob job)
|
|
{
|
|
return (QEMU_DOMAIN_TRACK_JOBS & JOB_MASK(job)) != 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainJobInfoUpdateTime(qemuDomainJobInfoPtr jobInfo)
|
|
{
|
|
unsigned long long now;
|
|
|
|
if (!jobInfo->started)
|
|
return 0;
|
|
|
|
if (virTimeMillisNow(&now) < 0)
|
|
return -1;
|
|
|
|
if (now < jobInfo->started) {
|
|
VIR_WARN("Async job starts in the future");
|
|
jobInfo->started = 0;
|
|
return 0;
|
|
}
|
|
|
|
jobInfo->timeElapsed = now - jobInfo->started;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuDomainJobInfoUpdateDowntime(qemuDomainJobInfoPtr jobInfo)
|
|
{
|
|
unsigned long long now;
|
|
|
|
if (!jobInfo->stopped)
|
|
return 0;
|
|
|
|
if (virTimeMillisNow(&now) < 0)
|
|
return -1;
|
|
|
|
if (now < jobInfo->stopped) {
|
|
VIR_WARN("Guest's CPUs stopped in the future");
|
|
jobInfo->stopped = 0;
|
|
return 0;
|
|
}
|
|
|
|
jobInfo->stats.mig.downtime = now - jobInfo->stopped;
|
|
jobInfo->stats.mig.downtime_set = true;
|
|
return 0;
|
|
}
|
|
|
|
static virDomainJobType
|
|
qemuDomainJobStatusToType(qemuDomainJobStatus status)
|
|
{
|
|
switch (status) {
|
|
case QEMU_DOMAIN_JOB_STATUS_NONE:
|
|
break;
|
|
|
|
case QEMU_DOMAIN_JOB_STATUS_ACTIVE:
|
|
case QEMU_DOMAIN_JOB_STATUS_MIGRATING:
|
|
case QEMU_DOMAIN_JOB_STATUS_QEMU_COMPLETED:
|
|
case QEMU_DOMAIN_JOB_STATUS_POSTCOPY:
|
|
case QEMU_DOMAIN_JOB_STATUS_PAUSED:
|
|
return VIR_DOMAIN_JOB_UNBOUNDED;
|
|
|
|
case QEMU_DOMAIN_JOB_STATUS_COMPLETED:
|
|
return VIR_DOMAIN_JOB_COMPLETED;
|
|
|
|
case QEMU_DOMAIN_JOB_STATUS_FAILED:
|
|
return VIR_DOMAIN_JOB_FAILED;
|
|
|
|
case QEMU_DOMAIN_JOB_STATUS_CANCELED:
|
|
return VIR_DOMAIN_JOB_CANCELLED;
|
|
}
|
|
|
|
return VIR_DOMAIN_JOB_NONE;
|
|
}
|
|
|
|
int
|
|
qemuDomainJobInfoToInfo(qemuDomainJobInfoPtr jobInfo,
|
|
virDomainJobInfoPtr info)
|
|
{
|
|
info->type = qemuDomainJobStatusToType(jobInfo->status);
|
|
info->timeElapsed = jobInfo->timeElapsed;
|
|
|
|
switch (jobInfo->statsType) {
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_MIGRATION:
|
|
info->memTotal = jobInfo->stats.mig.ram_total;
|
|
info->memRemaining = jobInfo->stats.mig.ram_remaining;
|
|
info->memProcessed = jobInfo->stats.mig.ram_transferred;
|
|
info->fileTotal = jobInfo->stats.mig.disk_total +
|
|
jobInfo->mirrorStats.total;
|
|
info->fileRemaining = jobInfo->stats.mig.disk_remaining +
|
|
(jobInfo->mirrorStats.total -
|
|
jobInfo->mirrorStats.transferred);
|
|
info->fileProcessed = jobInfo->stats.mig.disk_transferred +
|
|
jobInfo->mirrorStats.transferred;
|
|
break;
|
|
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_SAVEDUMP:
|
|
info->memTotal = jobInfo->stats.mig.ram_total;
|
|
info->memRemaining = jobInfo->stats.mig.ram_remaining;
|
|
info->memProcessed = jobInfo->stats.mig.ram_transferred;
|
|
break;
|
|
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_MEMDUMP:
|
|
info->memTotal = jobInfo->stats.dump.total;
|
|
info->memProcessed = jobInfo->stats.dump.completed;
|
|
info->memRemaining = info->memTotal - info->memProcessed;
|
|
break;
|
|
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_BACKUP:
|
|
info->fileTotal = jobInfo->stats.backup.total;
|
|
info->fileProcessed = jobInfo->stats.backup.transferred;
|
|
info->fileRemaining = info->fileTotal - info->fileProcessed;
|
|
break;
|
|
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_NONE:
|
|
break;
|
|
}
|
|
|
|
info->dataTotal = info->memTotal + info->fileTotal;
|
|
info->dataRemaining = info->memRemaining + info->fileRemaining;
|
|
info->dataProcessed = info->memProcessed + info->fileProcessed;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainMigrationJobInfoToParams(qemuDomainJobInfoPtr jobInfo,
|
|
int *type,
|
|
virTypedParameterPtr *params,
|
|
int *nparams)
|
|
{
|
|
qemuMonitorMigrationStats *stats = &jobInfo->stats.mig;
|
|
qemuDomainMirrorStatsPtr mirrorStats = &jobInfo->mirrorStats;
|
|
virTypedParameterPtr par = NULL;
|
|
int maxpar = 0;
|
|
int npar = 0;
|
|
unsigned long long mirrorRemaining = mirrorStats->total -
|
|
mirrorStats->transferred;
|
|
|
|
if (virTypedParamsAddInt(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_OPERATION,
|
|
jobInfo->operation) < 0)
|
|
goto error;
|
|
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_TIME_ELAPSED,
|
|
jobInfo->timeElapsed) < 0)
|
|
goto error;
|
|
|
|
if (jobInfo->timeDeltaSet &&
|
|
jobInfo->timeElapsed > jobInfo->timeDelta &&
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_TIME_ELAPSED_NET,
|
|
jobInfo->timeElapsed - jobInfo->timeDelta) < 0)
|
|
goto error;
|
|
|
|
if (stats->downtime_set &&
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DOWNTIME,
|
|
stats->downtime) < 0)
|
|
goto error;
|
|
|
|
if (stats->downtime_set &&
|
|
jobInfo->timeDeltaSet &&
|
|
stats->downtime > jobInfo->timeDelta &&
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DOWNTIME_NET,
|
|
stats->downtime - jobInfo->timeDelta) < 0)
|
|
goto error;
|
|
|
|
if (stats->setup_time_set &&
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_SETUP_TIME,
|
|
stats->setup_time) < 0)
|
|
goto error;
|
|
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DATA_TOTAL,
|
|
stats->ram_total +
|
|
stats->disk_total +
|
|
mirrorStats->total) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DATA_PROCESSED,
|
|
stats->ram_transferred +
|
|
stats->disk_transferred +
|
|
mirrorStats->transferred) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DATA_REMAINING,
|
|
stats->ram_remaining +
|
|
stats->disk_remaining +
|
|
mirrorRemaining) < 0)
|
|
goto error;
|
|
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_TOTAL,
|
|
stats->ram_total) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_PROCESSED,
|
|
stats->ram_transferred) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_REMAINING,
|
|
stats->ram_remaining) < 0)
|
|
goto error;
|
|
|
|
if (stats->ram_bps &&
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_BPS,
|
|
stats->ram_bps) < 0)
|
|
goto error;
|
|
|
|
if (stats->ram_duplicate_set) {
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_CONSTANT,
|
|
stats->ram_duplicate) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_NORMAL,
|
|
stats->ram_normal) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_NORMAL_BYTES,
|
|
stats->ram_normal_bytes) < 0)
|
|
goto error;
|
|
}
|
|
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_DIRTY_RATE,
|
|
stats->ram_dirty_rate) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_ITERATION,
|
|
stats->ram_iteration) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_POSTCOPY_REQS,
|
|
stats->ram_postcopy_reqs) < 0)
|
|
goto error;
|
|
|
|
if (stats->ram_page_size > 0 &&
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_PAGE_SIZE,
|
|
stats->ram_page_size) < 0)
|
|
goto error;
|
|
|
|
/* The remaining stats are disk, mirror, or migration specific
|
|
* so if this is a SAVEDUMP, we can just skip them */
|
|
if (jobInfo->statsType == QEMU_DOMAIN_JOB_STATS_TYPE_SAVEDUMP)
|
|
goto done;
|
|
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DISK_TOTAL,
|
|
stats->disk_total +
|
|
mirrorStats->total) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DISK_PROCESSED,
|
|
stats->disk_transferred +
|
|
mirrorStats->transferred) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DISK_REMAINING,
|
|
stats->disk_remaining +
|
|
mirrorRemaining) < 0)
|
|
goto error;
|
|
|
|
if (stats->disk_bps &&
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_DISK_BPS,
|
|
stats->disk_bps) < 0)
|
|
goto error;
|
|
|
|
if (stats->xbzrle_set) {
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_COMPRESSION_CACHE,
|
|
stats->xbzrle_cache_size) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_COMPRESSION_BYTES,
|
|
stats->xbzrle_bytes) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_COMPRESSION_PAGES,
|
|
stats->xbzrle_pages) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_COMPRESSION_CACHE_MISSES,
|
|
stats->xbzrle_cache_miss) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_COMPRESSION_OVERFLOW,
|
|
stats->xbzrle_overflow) < 0)
|
|
goto error;
|
|
}
|
|
|
|
if (stats->cpu_throttle_percentage &&
|
|
virTypedParamsAddInt(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_AUTO_CONVERGE_THROTTLE,
|
|
stats->cpu_throttle_percentage) < 0)
|
|
goto error;
|
|
|
|
done:
|
|
*type = qemuDomainJobStatusToType(jobInfo->status);
|
|
*params = par;
|
|
*nparams = npar;
|
|
return 0;
|
|
|
|
error:
|
|
virTypedParamsFree(par, npar);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDumpJobInfoToParams(qemuDomainJobInfoPtr jobInfo,
|
|
int *type,
|
|
virTypedParameterPtr *params,
|
|
int *nparams)
|
|
{
|
|
qemuMonitorDumpStats *stats = &jobInfo->stats.dump;
|
|
virTypedParameterPtr par = NULL;
|
|
int maxpar = 0;
|
|
int npar = 0;
|
|
|
|
if (virTypedParamsAddInt(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_OPERATION,
|
|
jobInfo->operation) < 0)
|
|
goto error;
|
|
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_TIME_ELAPSED,
|
|
jobInfo->timeElapsed) < 0)
|
|
goto error;
|
|
|
|
if (virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_TOTAL,
|
|
stats->total) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_PROCESSED,
|
|
stats->completed) < 0 ||
|
|
virTypedParamsAddULLong(&par, &npar, &maxpar,
|
|
VIR_DOMAIN_JOB_MEMORY_REMAINING,
|
|
stats->total - stats->completed) < 0)
|
|
goto error;
|
|
|
|
*type = qemuDomainJobStatusToType(jobInfo->status);
|
|
*params = par;
|
|
*nparams = npar;
|
|
return 0;
|
|
|
|
error:
|
|
virTypedParamsFree(par, npar);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainBackupJobInfoToParams(qemuDomainJobInfoPtr jobInfo,
|
|
int *type,
|
|
virTypedParameterPtr *params,
|
|
int *nparams)
|
|
{
|
|
qemuDomainBackupStats *stats = &jobInfo->stats.backup;
|
|
g_autoptr(virTypedParamList) par = g_new0(virTypedParamList, 1);
|
|
|
|
if (virTypedParamListAddInt(par, jobInfo->operation,
|
|
VIR_DOMAIN_JOB_OPERATION) < 0)
|
|
return -1;
|
|
|
|
if (virTypedParamListAddULLong(par, jobInfo->timeElapsed,
|
|
VIR_DOMAIN_JOB_TIME_ELAPSED) < 0)
|
|
return -1;
|
|
|
|
if (stats->transferred > 0 || stats->total > 0) {
|
|
if (virTypedParamListAddULLong(par, stats->total,
|
|
VIR_DOMAIN_JOB_DISK_TOTAL) < 0)
|
|
return -1;
|
|
|
|
if (virTypedParamListAddULLong(par, stats->transferred,
|
|
VIR_DOMAIN_JOB_DISK_PROCESSED) < 0)
|
|
return -1;
|
|
|
|
if (virTypedParamListAddULLong(par, stats->total - stats->transferred,
|
|
VIR_DOMAIN_JOB_DISK_REMAINING) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (stats->tmp_used > 0 || stats->tmp_total > 0) {
|
|
if (virTypedParamListAddULLong(par, stats->tmp_used,
|
|
VIR_DOMAIN_JOB_DISK_TEMP_USED) < 0)
|
|
return -1;
|
|
|
|
if (virTypedParamListAddULLong(par, stats->tmp_total,
|
|
VIR_DOMAIN_JOB_DISK_TEMP_TOTAL) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (jobInfo->status != QEMU_DOMAIN_JOB_STATUS_ACTIVE &&
|
|
virTypedParamListAddBoolean(par,
|
|
jobInfo->status == QEMU_DOMAIN_JOB_STATUS_COMPLETED,
|
|
VIR_DOMAIN_JOB_SUCCESS) < 0)
|
|
return -1;
|
|
|
|
*nparams = virTypedParamListStealParams(par, params);
|
|
*type = qemuDomainJobStatusToType(jobInfo->status);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainJobInfoToParams(qemuDomainJobInfoPtr jobInfo,
|
|
int *type,
|
|
virTypedParameterPtr *params,
|
|
int *nparams)
|
|
{
|
|
switch (jobInfo->statsType) {
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_MIGRATION:
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_SAVEDUMP:
|
|
return qemuDomainMigrationJobInfoToParams(jobInfo, type, params, nparams);
|
|
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_MEMDUMP:
|
|
return qemuDomainDumpJobInfoToParams(jobInfo, type, params, nparams);
|
|
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_BACKUP:
|
|
return qemuDomainBackupJobInfoToParams(jobInfo, type, params, nparams);
|
|
|
|
case QEMU_DOMAIN_JOB_STATS_TYPE_NONE:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid job statistics type"));
|
|
break;
|
|
|
|
default:
|
|
virReportEnumRangeError(qemuDomainJobStatsType, jobInfo->statsType);
|
|
break;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* qemuDomainGetMasterKeyFilePath:
|
|
* @libDir: Directory path to domain lib files
|
|
*
|
|
* Generate a path to the domain master key file for libDir.
|
|
* It's up to the caller to handle checking if path exists.
|
|
*
|
|
* Returns path to memory containing the name of the file. It is up to the
|
|
* caller to free; otherwise, NULL on failure.
|
|
*/
|
|
char *
|
|
qemuDomainGetMasterKeyFilePath(const char *libDir)
|
|
{
|
|
if (!libDir) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid path for master key file"));
|
|
return NULL;
|
|
}
|
|
return virFileBuildPath(libDir, "master-key.aes", NULL);
|
|
}
|
|
|
|
|
|
/* qemuDomainWriteMasterKeyFile:
|
|
* @driver: qemu driver data
|
|
* @vm: Pointer to the vm object
|
|
*
|
|
* Get the desired path to the masterKey file and store it in the path.
|
|
*
|
|
* Returns 0 on success, -1 on failure with error message indicating failure
|
|
*/
|
|
int
|
|
qemuDomainWriteMasterKeyFile(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
int fd = -1;
|
|
int ret = -1;
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
/* Only gets filled in if we have the capability */
|
|
if (!priv->masterKey)
|
|
return 0;
|
|
|
|
if (!(path = qemuDomainGetMasterKeyFilePath(priv->libDir)))
|
|
return -1;
|
|
|
|
if ((fd = open(path, O_WRONLY|O_TRUNC|O_CREAT, 0600)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to open domain master key file for write"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (safewrite(fd, priv->masterKey, priv->masterKeyLen) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to write master key file for domain"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (qemuSecurityDomainSetPathLabel(driver, vm, path, false) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
VIR_FORCE_CLOSE(fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainMasterKeyFree(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
if (!priv->masterKey)
|
|
return;
|
|
|
|
VIR_DISPOSE_N(priv->masterKey, priv->masterKeyLen);
|
|
}
|
|
|
|
/* qemuDomainMasterKeyReadFile:
|
|
* @priv: pointer to domain private object
|
|
*
|
|
* Expected to be called during qemuProcessReconnect once the domain
|
|
* libDir has been generated through qemuStateInitialize calling
|
|
* virDomainObjListLoadAllConfigs which will restore the libDir path
|
|
* to the domain private object.
|
|
*
|
|
* This function will get the path to the master key file and if it
|
|
* exists, it will read the contents of the file saving it in priv->masterKey.
|
|
*
|
|
* Once the file exists, the validity checks may cause failures; however,
|
|
* if the file doesn't exist or the capability doesn't exist, we just
|
|
* return (mostly) quietly.
|
|
*
|
|
* Returns 0 on success or lack of capability
|
|
* -1 on failure with error message indicating failure
|
|
*/
|
|
int
|
|
qemuDomainMasterKeyReadFile(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
int fd = -1;
|
|
uint8_t *masterKey = NULL;
|
|
ssize_t masterKeyLen = 0;
|
|
|
|
/* If we don't have the capability, then do nothing. */
|
|
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET))
|
|
return 0;
|
|
|
|
if (!(path = qemuDomainGetMasterKeyFilePath(priv->libDir)))
|
|
return -1;
|
|
|
|
if (!virFileExists(path)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("domain master key file doesn't exist in %s"),
|
|
priv->libDir);
|
|
goto error;
|
|
}
|
|
|
|
if ((fd = open(path, O_RDONLY)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to open domain master key file for read"));
|
|
goto error;
|
|
}
|
|
|
|
if (VIR_ALLOC_N(masterKey, 1024) < 0)
|
|
goto error;
|
|
|
|
if ((masterKeyLen = saferead(fd, masterKey, 1024)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("unable to read domain master key file"));
|
|
goto error;
|
|
}
|
|
|
|
if (masterKeyLen != QEMU_DOMAIN_MASTER_KEY_LEN) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("invalid master key read, size=%zd"), masterKeyLen);
|
|
goto error;
|
|
}
|
|
|
|
ignore_value(VIR_REALLOC_N_QUIET(masterKey, masterKeyLen));
|
|
|
|
priv->masterKey = masterKey;
|
|
priv->masterKeyLen = masterKeyLen;
|
|
|
|
VIR_FORCE_CLOSE(fd);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
if (masterKeyLen > 0)
|
|
memset(masterKey, 0, masterKeyLen);
|
|
VIR_FREE(masterKey);
|
|
|
|
VIR_FORCE_CLOSE(fd);
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* qemuDomainMasterKeyRemove:
|
|
* @priv: Pointer to the domain private object
|
|
*
|
|
* Remove the traces of the master key, clear the heap, clear the file,
|
|
* delete the file.
|
|
*/
|
|
void
|
|
qemuDomainMasterKeyRemove(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
|
|
if (!priv->masterKey)
|
|
return;
|
|
|
|
/* Clear the contents */
|
|
qemuDomainMasterKeyFree(priv);
|
|
|
|
/* Delete the master key file */
|
|
path = qemuDomainGetMasterKeyFilePath(priv->libDir);
|
|
unlink(path);
|
|
}
|
|
|
|
|
|
/* qemuDomainMasterKeyCreate:
|
|
* @vm: Pointer to the domain object
|
|
*
|
|
* As long as the underlying qemu has the secret capability,
|
|
* generate and store 'raw' in a file a random 32-byte key to
|
|
* be used as a secret shared with qemu to share sensitive data.
|
|
*
|
|
* Returns: 0 on success, -1 w/ error message on failure
|
|
*/
|
|
int
|
|
qemuDomainMasterKeyCreate(virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
/* If we don't have the capability, then do nothing. */
|
|
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET))
|
|
return 0;
|
|
|
|
if (VIR_ALLOC_N(priv->masterKey, QEMU_DOMAIN_MASTER_KEY_LEN) < 0)
|
|
return -1;
|
|
priv->masterKeyLen = QEMU_DOMAIN_MASTER_KEY_LEN;
|
|
|
|
if (virRandomBytes(priv->masterKey, priv->masterKeyLen) < 0) {
|
|
VIR_DISPOSE_N(priv->masterKey, priv->masterKeyLen);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainSecretPlainClear(qemuDomainSecretPlainPtr secret)
|
|
{
|
|
VIR_FREE(secret->username);
|
|
VIR_DISPOSE_N(secret->secret, secret->secretlen);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainSecretAESClear(qemuDomainSecretAESPtr secret,
|
|
bool keepAlias)
|
|
{
|
|
if (!keepAlias)
|
|
VIR_FREE(secret->alias);
|
|
|
|
VIR_FREE(secret->username);
|
|
VIR_FREE(secret->iv);
|
|
VIR_FREE(secret->ciphertext);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainSecretInfoClear(qemuDomainSecretInfoPtr secinfo,
|
|
bool keepAlias)
|
|
{
|
|
if (!secinfo)
|
|
return;
|
|
|
|
switch ((qemuDomainSecretInfoType) secinfo->type) {
|
|
case VIR_DOMAIN_SECRET_INFO_TYPE_PLAIN:
|
|
qemuDomainSecretPlainClear(&secinfo->s.plain);
|
|
break;
|
|
|
|
case VIR_DOMAIN_SECRET_INFO_TYPE_AES:
|
|
qemuDomainSecretAESClear(&secinfo->s.aes, keepAlias);
|
|
break;
|
|
|
|
case VIR_DOMAIN_SECRET_INFO_TYPE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainSecretInfoFree(qemuDomainSecretInfoPtr *secinfo)
|
|
{
|
|
if (!*secinfo)
|
|
return;
|
|
|
|
qemuDomainSecretInfoClear(*secinfo, false);
|
|
|
|
VIR_FREE(*secinfo);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainSecretInfoDestroy:
|
|
* @secinfo: object to destroy
|
|
*
|
|
* Removes any data unnecessary for further use, but keeps alias allocated.
|
|
*/
|
|
void
|
|
qemuDomainSecretInfoDestroy(qemuDomainSecretInfoPtr secinfo)
|
|
{
|
|
qemuDomainSecretInfoClear(secinfo, true);
|
|
}
|
|
|
|
|
|
static virClassPtr qemuDomainDiskPrivateClass;
|
|
static void qemuDomainDiskPrivateDispose(void *obj);
|
|
|
|
static int
|
|
qemuDomainDiskPrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainDiskPrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainDiskPrivate);
|
|
|
|
static virObjectPtr
|
|
qemuDomainDiskPrivateNew(void)
|
|
{
|
|
qemuDomainDiskPrivatePtr priv;
|
|
|
|
if (qemuDomainDiskPrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainDiskPrivateClass)))
|
|
return NULL;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
static void
|
|
qemuDomainDiskPrivateDispose(void *obj)
|
|
{
|
|
qemuDomainDiskPrivatePtr priv = obj;
|
|
|
|
virObjectUnref(priv->migrSource);
|
|
VIR_FREE(priv->qomName);
|
|
VIR_FREE(priv->nodeCopyOnRead);
|
|
virObjectUnref(priv->blockjob);
|
|
}
|
|
|
|
static virClassPtr qemuDomainStorageSourcePrivateClass;
|
|
static void qemuDomainStorageSourcePrivateDispose(void *obj);
|
|
|
|
static int
|
|
qemuDomainStorageSourcePrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainStorageSourcePrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainStorageSourcePrivate);
|
|
|
|
virObjectPtr
|
|
qemuDomainStorageSourcePrivateNew(void)
|
|
{
|
|
qemuDomainStorageSourcePrivatePtr priv;
|
|
|
|
if (qemuDomainStorageSourcePrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainStorageSourcePrivateClass)))
|
|
return NULL;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainStorageSourcePrivateDispose(void *obj)
|
|
{
|
|
qemuDomainStorageSourcePrivatePtr priv = obj;
|
|
|
|
qemuDomainSecretInfoFree(&priv->secinfo);
|
|
qemuDomainSecretInfoFree(&priv->encinfo);
|
|
}
|
|
|
|
|
|
static virClassPtr qemuDomainVcpuPrivateClass;
|
|
static void qemuDomainVcpuPrivateDispose(void *obj);
|
|
|
|
static int
|
|
qemuDomainVcpuPrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainVcpuPrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainVcpuPrivate);
|
|
|
|
static virObjectPtr
|
|
qemuDomainVcpuPrivateNew(void)
|
|
{
|
|
qemuDomainVcpuPrivatePtr priv;
|
|
|
|
if (qemuDomainVcpuPrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainVcpuPrivateClass)))
|
|
return NULL;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainVcpuPrivateDispose(void *obj)
|
|
{
|
|
qemuDomainVcpuPrivatePtr priv = obj;
|
|
|
|
VIR_FREE(priv->type);
|
|
VIR_FREE(priv->alias);
|
|
virJSONValueFree(priv->props);
|
|
return;
|
|
}
|
|
|
|
|
|
static virClassPtr qemuDomainChrSourcePrivateClass;
|
|
static void qemuDomainChrSourcePrivateDispose(void *obj);
|
|
|
|
static int
|
|
qemuDomainChrSourcePrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainChrSourcePrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainChrSourcePrivate);
|
|
|
|
static virObjectPtr
|
|
qemuDomainChrSourcePrivateNew(void)
|
|
{
|
|
qemuDomainChrSourcePrivatePtr priv;
|
|
|
|
if (qemuDomainChrSourcePrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainChrSourcePrivateClass)))
|
|
return NULL;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainChrSourcePrivateDispose(void *obj)
|
|
{
|
|
qemuDomainChrSourcePrivatePtr priv = obj;
|
|
|
|
qemuDomainSecretInfoFree(&priv->secinfo);
|
|
}
|
|
|
|
|
|
static virClassPtr qemuDomainVsockPrivateClass;
|
|
static void qemuDomainVsockPrivateDispose(void *obj);
|
|
|
|
static int
|
|
qemuDomainVsockPrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainVsockPrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainVsockPrivate);
|
|
|
|
static virObjectPtr
|
|
qemuDomainVsockPrivateNew(void)
|
|
{
|
|
qemuDomainVsockPrivatePtr priv;
|
|
|
|
if (qemuDomainVsockPrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainVsockPrivateClass)))
|
|
return NULL;
|
|
|
|
priv->vhostfd = -1;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainVsockPrivateDispose(void *obj G_GNUC_UNUSED)
|
|
{
|
|
qemuDomainVsockPrivatePtr priv = obj;
|
|
|
|
VIR_FORCE_CLOSE(priv->vhostfd);
|
|
}
|
|
|
|
|
|
static virClassPtr qemuDomainGraphicsPrivateClass;
|
|
static void qemuDomainGraphicsPrivateDispose(void *obj);
|
|
|
|
static int
|
|
qemuDomainGraphicsPrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainGraphicsPrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainGraphicsPrivate);
|
|
|
|
static virObjectPtr
|
|
qemuDomainGraphicsPrivateNew(void)
|
|
{
|
|
qemuDomainGraphicsPrivatePtr priv;
|
|
|
|
if (qemuDomainGraphicsPrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainGraphicsPrivateClass)))
|
|
return NULL;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainGraphicsPrivateDispose(void *obj)
|
|
{
|
|
qemuDomainGraphicsPrivatePtr priv = obj;
|
|
|
|
VIR_FREE(priv->tlsAlias);
|
|
qemuDomainSecretInfoFree(&priv->secinfo);
|
|
}
|
|
|
|
|
|
static virClassPtr qemuDomainNetworkPrivateClass;
|
|
static void qemuDomainNetworkPrivateDispose(void *obj);
|
|
|
|
|
|
static int
|
|
qemuDomainNetworkPrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainNetworkPrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainNetworkPrivate);
|
|
|
|
|
|
static virObjectPtr
|
|
qemuDomainNetworkPrivateNew(void)
|
|
{
|
|
qemuDomainNetworkPrivatePtr priv;
|
|
|
|
if (qemuDomainNetworkPrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainNetworkPrivateClass)))
|
|
return NULL;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainNetworkPrivateDispose(void *obj G_GNUC_UNUSED)
|
|
{
|
|
qemuDomainNetworkPrivatePtr priv = obj;
|
|
|
|
qemuSlirpFree(priv->slirp);
|
|
}
|
|
|
|
|
|
static virClassPtr qemuDomainFSPrivateClass;
|
|
static void qemuDomainFSPrivateDispose(void *obj);
|
|
|
|
|
|
static int
|
|
qemuDomainFSPrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainFSPrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainFSPrivate);
|
|
|
|
|
|
static virObjectPtr
|
|
qemuDomainFSPrivateNew(void)
|
|
{
|
|
qemuDomainFSPrivatePtr priv;
|
|
|
|
if (qemuDomainFSPrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainFSPrivateClass)))
|
|
return NULL;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainFSPrivateDispose(void *obj)
|
|
{
|
|
qemuDomainFSPrivatePtr priv = obj;
|
|
|
|
g_free(priv->vhostuser_fs_sock);
|
|
}
|
|
|
|
static virClassPtr qemuDomainVideoPrivateClass;
|
|
static void qemuDomainVideoPrivateDispose(void *obj);
|
|
|
|
|
|
static int
|
|
qemuDomainVideoPrivateOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(qemuDomainVideoPrivate, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(qemuDomainVideoPrivate);
|
|
|
|
|
|
static virObjectPtr
|
|
qemuDomainVideoPrivateNew(void)
|
|
{
|
|
qemuDomainVideoPrivatePtr priv;
|
|
|
|
if (qemuDomainVideoPrivateInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(priv = virObjectNew(qemuDomainVideoPrivateClass)))
|
|
return NULL;
|
|
|
|
priv->vhost_user_fd = -1;
|
|
|
|
return (virObjectPtr) priv;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainVideoPrivateDispose(void *obj)
|
|
{
|
|
qemuDomainVideoPrivatePtr priv = obj;
|
|
|
|
VIR_FORCE_CLOSE(priv->vhost_user_fd);
|
|
}
|
|
|
|
|
|
/* qemuDomainSecretPlainSetup:
|
|
* @secinfo: Pointer to secret info
|
|
* @usageType: The virSecretUsageType
|
|
* @username: username to use for authentication (may be NULL)
|
|
* @seclookupdef: Pointer to seclookupdef data
|
|
*
|
|
* Taking a secinfo, fill in the plaintext information
|
|
*
|
|
* Returns 0 on success, -1 on failure with error message
|
|
*/
|
|
static int
|
|
qemuDomainSecretPlainSetup(qemuDomainSecretInfoPtr secinfo,
|
|
virSecretUsageType usageType,
|
|
const char *username,
|
|
virSecretLookupTypeDefPtr seclookupdef)
|
|
{
|
|
g_autoptr(virConnect) conn = virGetConnectSecret();
|
|
int ret = -1;
|
|
|
|
if (!conn)
|
|
return -1;
|
|
|
|
secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_PLAIN;
|
|
secinfo->s.plain.username = g_strdup(username);
|
|
|
|
ret = virSecretGetSecretString(conn, seclookupdef, usageType,
|
|
&secinfo->s.plain.secret,
|
|
&secinfo->s.plain.secretlen);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* qemuDomainSecretAESSetup:
|
|
* @priv: pointer to domain private object
|
|
* @secinfo: Pointer to secret info
|
|
* @srcalias: Alias of the disk/hostdev used to generate the secret alias
|
|
* @usageType: The virSecretUsageType
|
|
* @username: username to use for authentication (may be NULL)
|
|
* @seclookupdef: Pointer to seclookupdef data
|
|
* @isLuks: True/False for is for luks (alias generation)
|
|
*
|
|
* Taking a secinfo, fill in the AES specific information using the
|
|
*
|
|
* Returns 0 on success, -1 on failure with error message
|
|
*/
|
|
static int
|
|
qemuDomainSecretAESSetup(qemuDomainObjPrivatePtr priv,
|
|
qemuDomainSecretInfoPtr secinfo,
|
|
const char *srcalias,
|
|
virSecretUsageType usageType,
|
|
const char *username,
|
|
virSecretLookupTypeDefPtr seclookupdef,
|
|
bool isLuks)
|
|
{
|
|
g_autoptr(virConnect) conn = virGetConnectSecret();
|
|
int ret = -1;
|
|
uint8_t *raw_iv = NULL;
|
|
size_t ivlen = QEMU_DOMAIN_AES_IV_LEN;
|
|
uint8_t *secret = NULL;
|
|
size_t secretlen = 0;
|
|
uint8_t *ciphertext = NULL;
|
|
size_t ciphertextlen = 0;
|
|
|
|
if (!conn)
|
|
return -1;
|
|
|
|
secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES;
|
|
secinfo->s.aes.username = g_strdup(username);
|
|
|
|
if (!(secinfo->s.aes.alias = qemuDomainGetSecretAESAlias(srcalias, isLuks)))
|
|
goto cleanup;
|
|
|
|
if (VIR_ALLOC_N(raw_iv, ivlen) < 0)
|
|
goto cleanup;
|
|
|
|
/* Create a random initialization vector */
|
|
if (virRandomBytes(raw_iv, ivlen) < 0)
|
|
goto cleanup;
|
|
|
|
/* Encode the IV and save that since qemu will need it */
|
|
secinfo->s.aes.iv = g_base64_encode(raw_iv, ivlen);
|
|
|
|
/* Grab the unencoded secret */
|
|
if (virSecretGetSecretString(conn, seclookupdef, usageType,
|
|
&secret, &secretlen) < 0)
|
|
goto cleanup;
|
|
|
|
if (virCryptoEncryptData(VIR_CRYPTO_CIPHER_AES256CBC,
|
|
priv->masterKey, QEMU_DOMAIN_MASTER_KEY_LEN,
|
|
raw_iv, ivlen, secret, secretlen,
|
|
&ciphertext, &ciphertextlen) < 0)
|
|
goto cleanup;
|
|
|
|
/* Clear out the secret */
|
|
memset(secret, 0, secretlen);
|
|
|
|
/* Now encode the ciphertext and store to be passed to qemu */
|
|
secinfo->s.aes.ciphertext = g_base64_encode(ciphertext,
|
|
ciphertextlen);
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
VIR_DISPOSE_N(raw_iv, ivlen);
|
|
VIR_DISPOSE_N(secret, secretlen);
|
|
VIR_DISPOSE_N(ciphertext, ciphertextlen);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainSupportsEncryptedSecret:
|
|
* @priv: qemu domain private data
|
|
*
|
|
* Returns true if libvirt can use encrypted 'secret' objects with VM which
|
|
* @priv belongs to.
|
|
*/
|
|
bool
|
|
qemuDomainSupportsEncryptedSecret(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
return virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC) &&
|
|
virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) &&
|
|
priv->masterKey;
|
|
}
|
|
|
|
|
|
/* qemuDomainSecretInfoNewPlain:
|
|
* @usageType: Secret usage type
|
|
* @username: username
|
|
* @lookupDef: lookup def describing secret
|
|
*
|
|
* Helper function to create a secinfo to be used for secinfo consumers. This
|
|
* sets up a 'plain' (unencrypted) secret for legacy consumers.
|
|
*
|
|
* Returns @secinfo on success, NULL on failure. Caller is responsible
|
|
* to eventually free @secinfo.
|
|
*/
|
|
static qemuDomainSecretInfoPtr
|
|
qemuDomainSecretInfoNewPlain(virSecretUsageType usageType,
|
|
const char *username,
|
|
virSecretLookupTypeDefPtr lookupDef)
|
|
{
|
|
qemuDomainSecretInfoPtr secinfo = NULL;
|
|
|
|
if (VIR_ALLOC(secinfo) < 0)
|
|
return NULL;
|
|
|
|
if (qemuDomainSecretPlainSetup(secinfo, usageType, username, lookupDef) < 0) {
|
|
qemuDomainSecretInfoFree(&secinfo);
|
|
return NULL;
|
|
}
|
|
|
|
return secinfo;
|
|
}
|
|
|
|
|
|
/* qemuDomainSecretInfoNew:
|
|
* @priv: pointer to domain private object
|
|
* @srcAlias: Alias base to use for TLS object
|
|
* @usageType: Secret usage type
|
|
* @username: username
|
|
* @looupDef: lookup def describing secret
|
|
* @isLuks: boolean for luks lookup
|
|
*
|
|
* Helper function to create a secinfo to be used for secinfo consumers. This
|
|
* sets up encrypted data to be used with qemu's 'secret' object.
|
|
*
|
|
* Returns @secinfo on success, NULL on failure. Caller is responsible
|
|
* to eventually free @secinfo.
|
|
*/
|
|
static qemuDomainSecretInfoPtr
|
|
qemuDomainSecretInfoNew(qemuDomainObjPrivatePtr priv,
|
|
const char *srcAlias,
|
|
virSecretUsageType usageType,
|
|
const char *username,
|
|
virSecretLookupTypeDefPtr lookupDef,
|
|
bool isLuks)
|
|
{
|
|
qemuDomainSecretInfoPtr secinfo = NULL;
|
|
|
|
if (!qemuDomainSupportsEncryptedSecret(priv)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("encrypted secrets are not supported"));
|
|
return NULL;
|
|
}
|
|
|
|
if (VIR_ALLOC(secinfo) < 0)
|
|
return NULL;
|
|
|
|
if (qemuDomainSecretAESSetup(priv, secinfo, srcAlias, usageType, username,
|
|
lookupDef, isLuks) < 0) {
|
|
qemuDomainSecretInfoFree(&secinfo);
|
|
return NULL;
|
|
}
|
|
|
|
return secinfo;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainSecretInfoTLSNew:
|
|
* @priv: pointer to domain private object
|
|
* @srcAlias: Alias base to use for TLS object
|
|
* @secretUUID: Provide a secretUUID value to look up/create the secretInfo
|
|
*
|
|
* Using the passed @secretUUID, generate a seclookupdef that can be used
|
|
* to generate the returned qemuDomainSecretInfoPtr for a TLS based secret.
|
|
*
|
|
* Returns qemuDomainSecretInfoPtr or NULL on error.
|
|
*/
|
|
qemuDomainSecretInfoPtr
|
|
qemuDomainSecretInfoTLSNew(qemuDomainObjPrivatePtr priv,
|
|
const char *srcAlias,
|
|
const char *secretUUID)
|
|
{
|
|
virSecretLookupTypeDef seclookupdef = {0};
|
|
|
|
if (virUUIDParse(secretUUID, seclookupdef.u.uuid) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("malformed TLS secret uuid '%s' provided"),
|
|
secretUUID);
|
|
return NULL;
|
|
}
|
|
seclookupdef.type = VIR_SECRET_LOOKUP_TYPE_UUID;
|
|
|
|
return qemuDomainSecretInfoNew(priv, srcAlias,
|
|
VIR_SECRET_USAGE_TYPE_TLS, NULL,
|
|
&seclookupdef, false);
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk)
|
|
{
|
|
qemuDomainStorageSourcePrivatePtr srcPriv;
|
|
virStorageSourcePtr n;
|
|
|
|
for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
if ((srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(n))) {
|
|
qemuDomainSecretInfoDestroy(srcPriv->secinfo);
|
|
qemuDomainSecretInfoDestroy(srcPriv->encinfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainStorageSourceHasAuth(virStorageSourcePtr src)
|
|
{
|
|
if (!virStorageSourceIsEmpty(src) &&
|
|
virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_NETWORK &&
|
|
src->auth &&
|
|
(src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI ||
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainDiskHasEncryptionSecret(virStorageSourcePtr src)
|
|
{
|
|
if (!virStorageSourceIsEmpty(src) && src->encryption &&
|
|
src->encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS &&
|
|
src->encryption->nsecrets > 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainSecretStorageSourcePrepare:
|
|
* @priv: domain private object
|
|
* @src: storage source struct to setup
|
|
* @authalias: prefix of the alias for secret holding authentication data
|
|
* @encalias: prefix of the alias for secret holding encryption password
|
|
*
|
|
* Prepares data necessary for encryption and authentication of @src. The two
|
|
* alias prefixes are provided since in the backing chain authentication belongs
|
|
* to the storage protocol data whereas encryption is relevant to the format
|
|
* driver in qemu. The two will have different node names.
|
|
*
|
|
* Returns 0 on success; -1 on error while reporting an libvirt error.
|
|
*/
|
|
static int
|
|
qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivatePtr priv,
|
|
virStorageSourcePtr src,
|
|
const char *authalias,
|
|
const char *encalias)
|
|
{
|
|
qemuDomainStorageSourcePrivatePtr srcPriv;
|
|
bool iscsiHasPS = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_ISCSI_PASSWORD_SECRET);
|
|
bool hasAuth = qemuDomainStorageSourceHasAuth(src);
|
|
bool hasEnc = qemuDomainDiskHasEncryptionSecret(src);
|
|
|
|
if (!hasAuth && !hasEnc)
|
|
return 0;
|
|
|
|
if (!(src->privateData = qemuDomainStorageSourcePrivateNew()))
|
|
return -1;
|
|
|
|
srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
|
|
|
if (hasAuth) {
|
|
virSecretUsageType usageType = VIR_SECRET_USAGE_TYPE_ISCSI;
|
|
|
|
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD)
|
|
usageType = VIR_SECRET_USAGE_TYPE_CEPH;
|
|
|
|
if (!qemuDomainSupportsEncryptedSecret(priv) ||
|
|
(src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI && !iscsiHasPS)) {
|
|
srcPriv->secinfo = qemuDomainSecretInfoNewPlain(usageType,
|
|
src->auth->username,
|
|
&src->auth->seclookupdef);
|
|
} else {
|
|
srcPriv->secinfo = qemuDomainSecretInfoNew(priv, authalias,
|
|
usageType,
|
|
src->auth->username,
|
|
&src->auth->seclookupdef,
|
|
false);
|
|
}
|
|
|
|
if (!srcPriv->secinfo)
|
|
return -1;
|
|
}
|
|
|
|
if (hasEnc) {
|
|
if (!(srcPriv->encinfo =
|
|
qemuDomainSecretInfoNew(priv, encalias,
|
|
VIR_SECRET_USAGE_TYPE_VOLUME, NULL,
|
|
&src->encryption->secrets[0]->seclookupdef,
|
|
true)))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainSecretHostdevDestroy(virDomainHostdevDefPtr hostdev)
|
|
{
|
|
qemuDomainStorageSourcePrivatePtr srcPriv;
|
|
|
|
if (virHostdevIsSCSIDevice(hostdev)) {
|
|
virDomainHostdevSubsysSCSIPtr scsisrc = &hostdev->source.subsys.u.scsi;
|
|
virDomainHostdevSubsysSCSIiSCSIPtr iscsisrc = &scsisrc->u.iscsi;
|
|
|
|
if (scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI) {
|
|
srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(iscsisrc->src);
|
|
if (srcPriv && srcPriv->secinfo)
|
|
qemuDomainSecretInfoFree(&srcPriv->secinfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* qemuDomainSecretHostdevPrepare:
|
|
* @priv: pointer to domain private object
|
|
* @hostdev: Pointer to a hostdev definition
|
|
*
|
|
* For the right host device, generate the qemuDomainSecretInfo structure.
|
|
*
|
|
* Returns 0 on success, -1 on failure
|
|
*/
|
|
int
|
|
qemuDomainSecretHostdevPrepare(qemuDomainObjPrivatePtr priv,
|
|
virDomainHostdevDefPtr hostdev)
|
|
{
|
|
if (virHostdevIsSCSIDevice(hostdev)) {
|
|
virDomainHostdevSubsysSCSIPtr scsisrc = &hostdev->source.subsys.u.scsi;
|
|
virDomainHostdevSubsysSCSIiSCSIPtr iscsisrc = &scsisrc->u.iscsi;
|
|
virStorageSourcePtr src = iscsisrc->src;
|
|
|
|
if (scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI &&
|
|
src->auth) {
|
|
if (qemuDomainSecretStorageSourcePrepare(priv, src,
|
|
hostdev->info->alias, NULL) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainSecretChardevDestroy(virDomainChrSourceDefPtr dev)
|
|
{
|
|
qemuDomainChrSourcePrivatePtr chrSourcePriv =
|
|
QEMU_DOMAIN_CHR_SOURCE_PRIVATE(dev);
|
|
|
|
if (!chrSourcePriv || !chrSourcePriv->secinfo)
|
|
return;
|
|
|
|
qemuDomainSecretInfoFree(&chrSourcePriv->secinfo);
|
|
}
|
|
|
|
|
|
/* qemuDomainSecretChardevPrepare:
|
|
* @cfg: Pointer to driver config object
|
|
* @priv: pointer to domain private object
|
|
* @chrAlias: Alias of the chr device
|
|
* @dev: Pointer to a char source definition
|
|
*
|
|
* For a TCP character device, generate a qemuDomainSecretInfo to be used
|
|
* by the command line code to generate the secret for the tls-creds to use.
|
|
*
|
|
* Returns 0 on success, -1 on failure
|
|
*/
|
|
int
|
|
qemuDomainSecretChardevPrepare(virQEMUDriverConfigPtr cfg,
|
|
qemuDomainObjPrivatePtr priv,
|
|
const char *chrAlias,
|
|
virDomainChrSourceDefPtr dev)
|
|
{
|
|
g_autofree char *charAlias = NULL;
|
|
|
|
if (dev->type != VIR_DOMAIN_CHR_TYPE_TCP)
|
|
return 0;
|
|
|
|
if (dev->data.tcp.haveTLS == VIR_TRISTATE_BOOL_YES &&
|
|
cfg->chardevTLSx509secretUUID) {
|
|
qemuDomainChrSourcePrivatePtr chrSourcePriv =
|
|
QEMU_DOMAIN_CHR_SOURCE_PRIVATE(dev);
|
|
|
|
if (!(charAlias = qemuAliasChardevFromDevAlias(chrAlias)))
|
|
return -1;
|
|
|
|
chrSourcePriv->secinfo =
|
|
qemuDomainSecretInfoTLSNew(priv, charAlias,
|
|
cfg->chardevTLSx509secretUUID);
|
|
|
|
if (!chrSourcePriv->secinfo)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainSecretGraphicsDestroy(virDomainGraphicsDefPtr graphics)
|
|
{
|
|
qemuDomainGraphicsPrivatePtr gfxPriv = QEMU_DOMAIN_GRAPHICS_PRIVATE(graphics);
|
|
|
|
if (!gfxPriv)
|
|
return;
|
|
|
|
VIR_FREE(gfxPriv->tlsAlias);
|
|
qemuDomainSecretInfoFree(&gfxPriv->secinfo);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSecretGraphicsPrepare(virQEMUDriverConfigPtr cfg,
|
|
qemuDomainObjPrivatePtr priv,
|
|
virDomainGraphicsDefPtr graphics)
|
|
{
|
|
virQEMUCapsPtr qemuCaps = priv->qemuCaps;
|
|
qemuDomainGraphicsPrivatePtr gfxPriv = QEMU_DOMAIN_GRAPHICS_PRIVATE(graphics);
|
|
|
|
if (graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_VNC)
|
|
return 0;
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_TLS_CREDS_X509))
|
|
return 0;
|
|
|
|
if (!cfg->vncTLS)
|
|
return 0;
|
|
|
|
gfxPriv->tlsAlias = g_strdup("vnc-tls-creds0");
|
|
|
|
if (cfg->vncTLSx509secretUUID) {
|
|
gfxPriv->secinfo = qemuDomainSecretInfoTLSNew(priv, gfxPriv->tlsAlias,
|
|
cfg->vncTLSx509secretUUID);
|
|
if (!gfxPriv->secinfo)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* qemuDomainSecretDestroy:
|
|
* @vm: Domain object
|
|
*
|
|
* Removes all unnecessary data which was needed to generate 'secret' objects.
|
|
*/
|
|
void
|
|
qemuDomainSecretDestroy(virDomainObjPtr vm)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < vm->def->ndisks; i++)
|
|
qemuDomainSecretDiskDestroy(vm->def->disks[i]);
|
|
|
|
for (i = 0; i < vm->def->nhostdevs; i++)
|
|
qemuDomainSecretHostdevDestroy(vm->def->hostdevs[i]);
|
|
|
|
for (i = 0; i < vm->def->nserials; i++)
|
|
qemuDomainSecretChardevDestroy(vm->def->serials[i]->source);
|
|
|
|
for (i = 0; i < vm->def->nparallels; i++)
|
|
qemuDomainSecretChardevDestroy(vm->def->parallels[i]->source);
|
|
|
|
for (i = 0; i < vm->def->nchannels; i++)
|
|
qemuDomainSecretChardevDestroy(vm->def->channels[i]->source);
|
|
|
|
for (i = 0; i < vm->def->nconsoles; i++)
|
|
qemuDomainSecretChardevDestroy(vm->def->consoles[i]->source);
|
|
|
|
for (i = 0; i < vm->def->nsmartcards; i++) {
|
|
if (vm->def->smartcards[i]->type ==
|
|
VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH)
|
|
qemuDomainSecretChardevDestroy(vm->def->smartcards[i]->data.passthru);
|
|
}
|
|
|
|
for (i = 0; i < vm->def->nrngs; i++) {
|
|
if (vm->def->rngs[i]->backend == VIR_DOMAIN_RNG_BACKEND_EGD)
|
|
qemuDomainSecretChardevDestroy(vm->def->rngs[i]->source.chardev);
|
|
}
|
|
|
|
for (i = 0; i < vm->def->nredirdevs; i++)
|
|
qemuDomainSecretChardevDestroy(vm->def->redirdevs[i]->source);
|
|
|
|
for (i = 0; i < vm->def->ngraphics; i++)
|
|
qemuDomainSecretGraphicsDestroy(vm->def->graphics[i]);
|
|
}
|
|
|
|
|
|
/* qemuDomainSecretPrepare:
|
|
* @driver: Pointer to driver object
|
|
* @vm: Domain object
|
|
*
|
|
* For any objects that may require an auth/secret setup, create a
|
|
* qemuDomainSecretInfo and save it in the appropriate place within
|
|
* the private structures. This will be used by command line build
|
|
* code in order to pass the secret along to qemu in order to provide
|
|
* the necessary authentication data.
|
|
*
|
|
* Returns 0 on success, -1 on failure with error message set
|
|
*/
|
|
int
|
|
qemuDomainSecretPrepare(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
size_t i;
|
|
|
|
/* disk secrets are prepared when preparing disks */
|
|
|
|
for (i = 0; i < vm->def->nhostdevs; i++) {
|
|
if (qemuDomainSecretHostdevPrepare(priv,
|
|
vm->def->hostdevs[i]) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vm->def->nserials; i++) {
|
|
if (qemuDomainSecretChardevPrepare(cfg, priv,
|
|
vm->def->serials[i]->info.alias,
|
|
vm->def->serials[i]->source) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vm->def->nparallels; i++) {
|
|
if (qemuDomainSecretChardevPrepare(cfg, priv,
|
|
vm->def->parallels[i]->info.alias,
|
|
vm->def->parallels[i]->source) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vm->def->nchannels; i++) {
|
|
if (qemuDomainSecretChardevPrepare(cfg, priv,
|
|
vm->def->channels[i]->info.alias,
|
|
vm->def->channels[i]->source) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vm->def->nconsoles; i++) {
|
|
if (qemuDomainSecretChardevPrepare(cfg, priv,
|
|
vm->def->consoles[i]->info.alias,
|
|
vm->def->consoles[i]->source) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vm->def->nsmartcards; i++)
|
|
if (vm->def->smartcards[i]->type ==
|
|
VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH &&
|
|
qemuDomainSecretChardevPrepare(cfg, priv,
|
|
vm->def->smartcards[i]->info.alias,
|
|
vm->def->smartcards[i]->data.passthru) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < vm->def->nrngs; i++) {
|
|
if (vm->def->rngs[i]->backend == VIR_DOMAIN_RNG_BACKEND_EGD &&
|
|
qemuDomainSecretChardevPrepare(cfg, priv,
|
|
vm->def->rngs[i]->info.alias,
|
|
vm->def->rngs[i]->source.chardev) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vm->def->nredirdevs; i++) {
|
|
if (qemuDomainSecretChardevPrepare(cfg, priv,
|
|
vm->def->redirdevs[i]->info.alias,
|
|
vm->def->redirdevs[i]->source) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vm->def->ngraphics; i++) {
|
|
if (qemuDomainSecretGraphicsPrepare(cfg, priv, vm->def->graphics[i]) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* This is the old way of setting up per-domain directories */
|
|
static void
|
|
qemuDomainSetPrivatePathsOld(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
if (!priv->libDir)
|
|
priv->libDir = g_strdup_printf("%s/domain-%s", cfg->libDir, vm->def->name);
|
|
|
|
if (!priv->channelTargetDir)
|
|
priv->channelTargetDir = g_strdup_printf("%s/domain-%s",
|
|
cfg->channelTargetDir, vm->def->name);
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainSetPrivatePaths(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
g_autofree char *domname = virDomainDefGetShortName(vm->def);
|
|
|
|
if (!domname)
|
|
return -1;
|
|
|
|
if (!priv->libDir)
|
|
priv->libDir = g_strdup_printf("%s/domain-%s", cfg->libDir, domname);
|
|
|
|
if (!priv->channelTargetDir)
|
|
priv->channelTargetDir = g_strdup_printf("%s/domain-%s",
|
|
cfg->channelTargetDir, domname);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
dbusVMStateHashFree(void *opaque)
|
|
{
|
|
qemuDBusVMStateFree(opaque);
|
|
}
|
|
|
|
|
|
static void *
|
|
qemuDomainObjPrivateAlloc(void *opaque)
|
|
{
|
|
qemuDomainObjPrivatePtr priv;
|
|
|
|
if (VIR_ALLOC(priv) < 0)
|
|
return NULL;
|
|
|
|
if (qemuDomainObjInitJob(priv) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to init qemu driver mutexes"));
|
|
goto error;
|
|
}
|
|
|
|
if (!(priv->devs = virChrdevAlloc()))
|
|
goto error;
|
|
|
|
if (!(priv->blockjobs = virHashCreate(5, virObjectFreeHashData)))
|
|
goto error;
|
|
|
|
if (!(priv->dbusVMStates = virHashCreate(5, dbusVMStateHashFree)))
|
|
goto error;
|
|
|
|
/* agent commands block by default, user can choose different behavior */
|
|
priv->agentTimeout = VIR_DOMAIN_AGENT_RESPONSE_TIMEOUT_BLOCK;
|
|
priv->migMaxBandwidth = QEMU_DOMAIN_MIG_BANDWIDTH_MAX;
|
|
priv->driver = opaque;
|
|
|
|
return priv;
|
|
|
|
error:
|
|
VIR_FREE(priv);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* qemuDomainObjPrivateDataClear:
|
|
* @priv: domain private data
|
|
*
|
|
* Clears private data entries, which are not necessary or stale if the VM is
|
|
* not running.
|
|
*/
|
|
void
|
|
qemuDomainObjPrivateDataClear(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
virStringListFree(priv->qemuDevices);
|
|
priv->qemuDevices = NULL;
|
|
|
|
virCgroupFree(&priv->cgroup);
|
|
|
|
virPerfFree(priv->perf);
|
|
priv->perf = NULL;
|
|
|
|
VIR_FREE(priv->machineName);
|
|
|
|
virObjectUnref(priv->qemuCaps);
|
|
priv->qemuCaps = NULL;
|
|
|
|
VIR_FREE(priv->pidfile);
|
|
|
|
VIR_FREE(priv->libDir);
|
|
VIR_FREE(priv->channelTargetDir);
|
|
|
|
priv->memPrealloc = false;
|
|
|
|
/* remove automatic pinning data */
|
|
virBitmapFree(priv->autoNodeset);
|
|
priv->autoNodeset = NULL;
|
|
virBitmapFree(priv->autoCpuset);
|
|
priv->autoCpuset = NULL;
|
|
|
|
/* remove address data */
|
|
virDomainPCIAddressSetFree(priv->pciaddrs);
|
|
priv->pciaddrs = NULL;
|
|
virDomainUSBAddressSetFree(priv->usbaddrs);
|
|
priv->usbaddrs = NULL;
|
|
|
|
virCPUDefFree(priv->origCPU);
|
|
priv->origCPU = NULL;
|
|
|
|
/* clear previously used namespaces */
|
|
virBitmapFree(priv->namespaces);
|
|
priv->namespaces = NULL;
|
|
|
|
priv->rememberOwner = false;
|
|
|
|
priv->reconnectBlockjobs = VIR_TRISTATE_BOOL_ABSENT;
|
|
priv->allowReboot = VIR_TRISTATE_BOOL_ABSENT;
|
|
|
|
virBitmapFree(priv->migrationCaps);
|
|
priv->migrationCaps = NULL;
|
|
|
|
virHashRemoveAll(priv->blockjobs);
|
|
virHashRemoveAll(priv->dbusVMStates);
|
|
|
|
virObjectUnref(priv->pflash0);
|
|
priv->pflash0 = NULL;
|
|
virObjectUnref(priv->pflash1);
|
|
priv->pflash1 = NULL;
|
|
|
|
virDomainBackupDefFree(priv->backup);
|
|
priv->backup = NULL;
|
|
|
|
/* reset node name allocator */
|
|
qemuDomainStorageIdReset(priv);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainObjPrivateFree(void *data)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = data;
|
|
|
|
qemuDomainObjPrivateDataClear(priv);
|
|
|
|
virObjectUnref(priv->monConfig);
|
|
qemuDomainObjFreeJob(priv);
|
|
VIR_FREE(priv->lockState);
|
|
VIR_FREE(priv->origname);
|
|
|
|
virChrdevFree(priv->devs);
|
|
|
|
/* This should never be non-NULL if we get here, but just in case... */
|
|
if (priv->mon) {
|
|
VIR_ERROR(_("Unexpected QEMU monitor still active during domain deletion"));
|
|
qemuMonitorClose(priv->mon);
|
|
}
|
|
if (priv->agent) {
|
|
VIR_ERROR(_("Unexpected QEMU agent still active during domain deletion"));
|
|
qemuAgentClose(priv->agent);
|
|
}
|
|
VIR_FREE(priv->cleanupCallbacks);
|
|
|
|
qemuDomainSecretInfoFree(&priv->migSecinfo);
|
|
qemuDomainMasterKeyFree(priv);
|
|
|
|
virHashFree(priv->blockjobs);
|
|
virHashFree(priv->dbusVMStates);
|
|
|
|
VIR_FREE(priv);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuStorageSourcePrivateDataAssignSecinfo(qemuDomainSecretInfoPtr *secinfo,
|
|
char **alias)
|
|
{
|
|
if (!*alias)
|
|
return 0;
|
|
|
|
if (!*secinfo) {
|
|
if (VIR_ALLOC(*secinfo) < 0)
|
|
return -1;
|
|
|
|
(*secinfo)->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES;
|
|
}
|
|
|
|
if ((*secinfo)->type == VIR_DOMAIN_SECRET_INFO_TYPE_AES)
|
|
(*secinfo)->s.aes.alias = g_steal_pointer(&*alias);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt,
|
|
virStorageSourcePtr src)
|
|
{
|
|
qemuDomainStorageSourcePrivatePtr priv;
|
|
g_autofree char *authalias = NULL;
|
|
g_autofree char *encalias = NULL;
|
|
|
|
src->nodestorage = virXPathString("string(./nodenames/nodename[@type='storage']/@name)", ctxt);
|
|
src->nodeformat = virXPathString("string(./nodenames/nodename[@type='format']/@name)", ctxt);
|
|
src->tlsAlias = virXPathString("string(./objects/TLSx509/@alias)", ctxt);
|
|
|
|
if (src->sliceStorage)
|
|
src->sliceStorage->nodename = virXPathString("string(./nodenames/nodename[@type='slice-storage']/@name)", ctxt);
|
|
|
|
if (src->pr)
|
|
src->pr->mgralias = virXPathString("string(./reservations/@mgralias)", ctxt);
|
|
|
|
authalias = virXPathString("string(./objects/secret[@type='auth']/@alias)", ctxt);
|
|
encalias = virXPathString("string(./objects/secret[@type='encryption']/@alias)", ctxt);
|
|
|
|
if (authalias || encalias) {
|
|
if (!src->privateData &&
|
|
!(src->privateData = qemuDomainStorageSourcePrivateNew()))
|
|
return -1;
|
|
|
|
priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
|
|
|
if (qemuStorageSourcePrivateDataAssignSecinfo(&priv->secinfo, &authalias) < 0)
|
|
return -1;
|
|
|
|
if (qemuStorageSourcePrivateDataAssignSecinfo(&priv->encinfo, &encalias) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virStorageSourcePrivateDataParseRelPath(ctxt, src) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuStorageSourcePrivateDataFormatSecinfo(virBufferPtr buf,
|
|
qemuDomainSecretInfoPtr secinfo,
|
|
const char *type)
|
|
{
|
|
if (!secinfo ||
|
|
secinfo->type != VIR_DOMAIN_SECRET_INFO_TYPE_AES ||
|
|
!secinfo->s.aes.alias)
|
|
return;
|
|
|
|
virBufferAsprintf(buf, "<secret type='%s' alias='%s'/>\n",
|
|
type, secinfo->s.aes.alias);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuStorageSourcePrivateDataFormat(virStorageSourcePtr src,
|
|
virBufferPtr buf)
|
|
{
|
|
g_auto(virBuffer) tmp = VIR_BUFFER_INIT_CHILD(buf);
|
|
qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
|
g_auto(virBuffer) nodenamesChildBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
virBufferEscapeString(&nodenamesChildBuf, "<nodename type='storage' name='%s'/>\n", src->nodestorage);
|
|
virBufferEscapeString(&nodenamesChildBuf, "<nodename type='format' name='%s'/>\n", src->nodeformat);
|
|
|
|
if (src->sliceStorage)
|
|
virBufferEscapeString(&nodenamesChildBuf, "<nodename type='slice-storage' name='%s'/>\n",
|
|
src->sliceStorage->nodename);
|
|
|
|
virXMLFormatElement(buf, "nodenames", NULL, &nodenamesChildBuf);
|
|
|
|
if (src->pr)
|
|
virBufferAsprintf(buf, "<reservations mgralias='%s'/>\n", src->pr->mgralias);
|
|
|
|
if (virStorageSourcePrivateDataFormatRelPath(src, buf) < 0)
|
|
return -1;
|
|
|
|
if (srcPriv) {
|
|
qemuStorageSourcePrivateDataFormatSecinfo(&tmp, srcPriv->secinfo, "auth");
|
|
qemuStorageSourcePrivateDataFormatSecinfo(&tmp, srcPriv->encinfo, "encryption");
|
|
}
|
|
|
|
if (src->tlsAlias)
|
|
virBufferAsprintf(&tmp, "<TLSx509 alias='%s'/>\n", src->tlsAlias);
|
|
|
|
virXMLFormatElement(buf, "objects", NULL, &tmp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDiskPrivateParse(xmlXPathContextPtr ctxt,
|
|
virDomainDiskDefPtr disk)
|
|
{
|
|
qemuDomainDiskPrivatePtr priv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
|
|
priv->qomName = virXPathString("string(./qom/@name)", ctxt);
|
|
priv->nodeCopyOnRead = virXPathString("string(./nodenames/nodename[@type='copyOnRead']/@name)", ctxt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDiskPrivateFormat(virDomainDiskDefPtr disk,
|
|
virBufferPtr buf)
|
|
{
|
|
qemuDomainDiskPrivatePtr priv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
|
|
virBufferEscapeString(buf, "<qom name='%s'/>\n", priv->qomName);
|
|
|
|
if (priv->nodeCopyOnRead) {
|
|
virBufferAddLit(buf, "<nodenames>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferEscapeString(buf, "<nodename type='copyOnRead' name='%s'/>\n",
|
|
priv->nodeCopyOnRead);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</nodenames>\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainObjPrivateXMLFormatVcpus(virBufferPtr buf,
|
|
virDomainDefPtr def)
|
|
{
|
|
size_t i;
|
|
size_t maxvcpus = virDomainDefGetVcpusMax(def);
|
|
virDomainVcpuDefPtr vcpu;
|
|
pid_t tid;
|
|
|
|
virBufferAddLit(buf, "<vcpus>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
for (i = 0; i < maxvcpus; i++) {
|
|
vcpu = virDomainDefGetVcpu(def, i);
|
|
tid = QEMU_DOMAIN_VCPU_PRIVATE(vcpu)->tid;
|
|
|
|
if (!vcpu->online || tid == 0)
|
|
continue;
|
|
|
|
virBufferAsprintf(buf, "<vcpu id='%zu' pid='%d'/>\n", i, tid);
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</vcpus>\n");
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatAutomaticPlacement(virBufferPtr buf,
|
|
qemuDomainObjPrivatePtr priv)
|
|
{
|
|
g_autofree char *nodeset = NULL;
|
|
g_autofree char *cpuset = NULL;
|
|
|
|
if (!priv->autoNodeset && !priv->autoCpuset)
|
|
return 0;
|
|
|
|
if (priv->autoNodeset &&
|
|
!((nodeset = virBitmapFormat(priv->autoNodeset))))
|
|
return -1;
|
|
|
|
if (priv->autoCpuset &&
|
|
!((cpuset = virBitmapFormat(priv->autoCpuset))))
|
|
return -1;
|
|
|
|
virBufferAddLit(buf, "<numad");
|
|
virBufferEscapeString(buf, " nodeset='%s'", nodeset);
|
|
virBufferEscapeString(buf, " cpuset='%s'", cpuset);
|
|
virBufferAddLit(buf, "/>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
typedef struct qemuDomainPrivateBlockJobFormatData {
|
|
virDomainXMLOptionPtr xmlopt;
|
|
virBufferPtr buf;
|
|
} qemuDomainPrivateBlockJobFormatData;
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatBlockjobFormatSource(virBufferPtr buf,
|
|
const char *element,
|
|
virStorageSourcePtr src,
|
|
virDomainXMLOptionPtr xmlopt,
|
|
bool chain)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
unsigned int xmlflags = VIR_DOMAIN_DEF_FORMAT_STATUS;
|
|
|
|
virBufferAsprintf(&attrBuf, " type='%s' format='%s'",
|
|
virStorageTypeToString(src->type),
|
|
virStorageFileFormatTypeToString(src->format));
|
|
|
|
if (virDomainDiskSourceFormat(&childBuf, src, "source", 0, true, xmlflags, true, xmlopt) < 0)
|
|
return -1;
|
|
|
|
if (chain &&
|
|
virDomainDiskBackingStoreFormat(&childBuf, src, xmlopt, xmlflags) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, element, &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload,
|
|
const void *name G_GNUC_UNUSED,
|
|
void *opaque)
|
|
{
|
|
struct qemuDomainPrivateBlockJobFormatData *data = opaque;
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(data->buf);
|
|
g_auto(virBuffer) chainsBuf = VIR_BUFFER_INIT_CHILD(&childBuf);
|
|
qemuBlockJobDataPtr job = payload;
|
|
const char *state = qemuBlockjobStateTypeToString(job->state);
|
|
const char *newstate = NULL;
|
|
|
|
if (job->newstate != -1)
|
|
newstate = qemuBlockjobStateTypeToString(job->newstate);
|
|
|
|
virBufferEscapeString(&attrBuf, " name='%s'", job->name);
|
|
virBufferEscapeString(&attrBuf, " type='%s'", qemuBlockjobTypeToString(job->type));
|
|
virBufferEscapeString(&attrBuf, " state='%s'", state);
|
|
virBufferEscapeString(&attrBuf, " newstate='%s'", newstate);
|
|
if (job->brokentype != QEMU_BLOCKJOB_TYPE_NONE)
|
|
virBufferEscapeString(&attrBuf, " brokentype='%s'", qemuBlockjobTypeToString(job->brokentype));
|
|
if (!job->jobflagsmissing)
|
|
virBufferAsprintf(&attrBuf, " jobflags='0x%x'", job->jobflags);
|
|
virBufferEscapeString(&childBuf, "<errmsg>%s</errmsg>", job->errmsg);
|
|
|
|
if (job->disk) {
|
|
virBufferEscapeString(&childBuf, "<disk dst='%s'", job->disk->dst);
|
|
if (job->mirrorChain)
|
|
virBufferAddLit(&childBuf, " mirror='yes'");
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
} else {
|
|
if (job->chain &&
|
|
qemuDomainObjPrivateXMLFormatBlockjobFormatSource(&chainsBuf,
|
|
"disk",
|
|
job->chain,
|
|
data->xmlopt,
|
|
true) < 0)
|
|
return -1;
|
|
|
|
if (job->mirrorChain &&
|
|
qemuDomainObjPrivateXMLFormatBlockjobFormatSource(&chainsBuf,
|
|
"mirror",
|
|
job->mirrorChain,
|
|
data->xmlopt,
|
|
true) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(&childBuf, "chains", NULL, &chainsBuf);
|
|
}
|
|
|
|
switch ((qemuBlockJobType) job->type) {
|
|
case QEMU_BLOCKJOB_TYPE_PULL:
|
|
if (job->data.pull.base)
|
|
virBufferAsprintf(&childBuf, "<base node='%s'/>\n", job->data.pull.base->nodeformat);
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_COMMIT:
|
|
case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
|
|
if (job->data.commit.base)
|
|
virBufferAsprintf(&childBuf, "<base node='%s'/>\n", job->data.commit.base->nodeformat);
|
|
if (job->data.commit.top)
|
|
virBufferAsprintf(&childBuf, "<top node='%s'/>\n", job->data.commit.top->nodeformat);
|
|
if (job->data.commit.topparent)
|
|
virBufferAsprintf(&childBuf, "<topparent node='%s'/>\n", job->data.commit.topparent->nodeformat);
|
|
if (job->data.commit.deleteCommittedImages)
|
|
virBufferAddLit(&childBuf, "<deleteCommittedImages/>\n");
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_CREATE:
|
|
if (job->data.create.storage)
|
|
virBufferAddLit(&childBuf, "<create mode='storage'/>\n");
|
|
|
|
if (job->data.create.src &&
|
|
qemuDomainObjPrivateXMLFormatBlockjobFormatSource(&childBuf,
|
|
"src",
|
|
job->data.create.src,
|
|
data->xmlopt,
|
|
false) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_COPY:
|
|
if (job->data.copy.shallownew)
|
|
virBufferAddLit(&attrBuf, " shallownew='yes'");
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_BACKUP:
|
|
virBufferEscapeString(&childBuf, "<bitmap name='%s'/>\n", job->data.backup.bitmap);
|
|
if (job->data.backup.store) {
|
|
if (qemuDomainObjPrivateXMLFormatBlockjobFormatSource(&childBuf,
|
|
"store",
|
|
job->data.backup.store,
|
|
data->xmlopt,
|
|
false) < 0)
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_BROKEN:
|
|
case QEMU_BLOCKJOB_TYPE_NONE:
|
|
case QEMU_BLOCKJOB_TYPE_INTERNAL:
|
|
case QEMU_BLOCKJOB_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
virXMLFormatElement(data->buf, "blockjob", &attrBuf, &childBuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatBlockjobs(virBufferPtr buf,
|
|
virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
bool bj = qemuDomainHasBlockjob(vm, false);
|
|
struct qemuDomainPrivateBlockJobFormatData iterdata = { priv->driver->xmlopt,
|
|
&childBuf };
|
|
|
|
virBufferAsprintf(&attrBuf, " active='%s'",
|
|
virTristateBoolTypeToString(virTristateBoolFromBool(bj)));
|
|
|
|
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) &&
|
|
virHashForEach(priv->blockjobs,
|
|
qemuDomainObjPrivateXMLFormatBlockjobIterator,
|
|
&iterdata) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, "blockjobs", &attrBuf, &childBuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatBackups(virBufferPtr buf,
|
|
virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP))
|
|
return 0;
|
|
|
|
if (priv->backup &&
|
|
virDomainBackupDefFormat(&childBuf, priv->backup, true) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, "backups", &attrBuf, &childBuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainObjPrivateXMLFormatAllowReboot(virBufferPtr buf,
|
|
virTristateBool allowReboot)
|
|
{
|
|
virBufferAsprintf(buf, "<allowReboot value='%s'/>\n",
|
|
virTristateBoolTypeToString(allowReboot));
|
|
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainObjPrivateXMLFormatPR(virBufferPtr buf,
|
|
qemuDomainObjPrivatePtr priv)
|
|
{
|
|
if (priv->prDaemonRunning)
|
|
virBufferAddLit(buf, "<prDaemon/>\n");
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatNBDMigrationSource(virBufferPtr buf,
|
|
virStorageSourcePtr src,
|
|
virDomainXMLOptionPtr xmlopt)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
virBufferAsprintf(&attrBuf, " type='%s' format='%s'",
|
|
virStorageTypeToString(src->type),
|
|
virStorageFileFormatTypeToString(src->format));
|
|
|
|
if (virDomainDiskSourceFormat(&childBuf, src, "source", 0, false,
|
|
VIR_DOMAIN_DEF_FORMAT_STATUS, true, xmlopt) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, "migrationSource", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatNBDMigration(virBufferPtr buf,
|
|
virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
size_t i;
|
|
virDomainDiskDefPtr disk;
|
|
qemuDomainDiskPrivatePtr diskPriv;
|
|
|
|
for (i = 0; i < vm->def->ndisks; i++) {
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
disk = vm->def->disks[i];
|
|
diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
|
|
virBufferAsprintf(&attrBuf, " dev='%s' migrating='%s'",
|
|
disk->dst, diskPriv->migrating ? "yes" : "no");
|
|
|
|
if (diskPriv->migrSource &&
|
|
qemuDomainObjPrivateXMLFormatNBDMigrationSource(&childBuf,
|
|
diskPriv->migrSource,
|
|
priv->driver->xmlopt) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, "disk", &attrBuf, &childBuf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatJob(virBufferPtr buf,
|
|
virDomainObjPtr vm,
|
|
qemuDomainObjPrivatePtr priv)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
qemuDomainJob job = priv->job.active;
|
|
|
|
if (!qemuDomainTrackJob(job))
|
|
job = QEMU_JOB_NONE;
|
|
|
|
if (job == QEMU_JOB_NONE &&
|
|
priv->job.asyncJob == QEMU_ASYNC_JOB_NONE)
|
|
return 0;
|
|
|
|
virBufferAsprintf(&attrBuf, " type='%s' async='%s'",
|
|
qemuDomainJobTypeToString(job),
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob));
|
|
|
|
if (priv->job.phase) {
|
|
virBufferAsprintf(&attrBuf, " phase='%s'",
|
|
qemuDomainAsyncJobPhaseToString(priv->job.asyncJob,
|
|
priv->job.phase));
|
|
}
|
|
|
|
if (priv->job.asyncJob != QEMU_ASYNC_JOB_NONE)
|
|
virBufferAsprintf(&attrBuf, " flags='0x%lx'", priv->job.apiFlags);
|
|
|
|
if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT &&
|
|
qemuDomainObjPrivateXMLFormatNBDMigration(&childBuf, vm) < 0)
|
|
return -1;
|
|
|
|
if (priv->job.migParams)
|
|
qemuMigrationParamsFormat(&childBuf, priv->job.migParams);
|
|
|
|
virXMLFormatElement(buf, "job", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainHasSlirp(virDomainObjPtr vm)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < vm->def->nnets; i++) {
|
|
virDomainNetDefPtr net = vm->def->nets[i];
|
|
|
|
if (QEMU_DOMAIN_NETWORK_PRIVATE(net)->slirp)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainGetSlirpHelperOk(virDomainObjPtr vm)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < vm->def->nnets; i++) {
|
|
virDomainNetDefPtr net = vm->def->nets[i];
|
|
|
|
/* if there is a builtin slirp, prevent slirp-helper */
|
|
if (net->type == VIR_DOMAIN_NET_TYPE_USER &&
|
|
!QEMU_DOMAIN_NETWORK_PRIVATE(net)->slirp)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormatSlirp(virBufferPtr buf,
|
|
virDomainObjPtr vm)
|
|
{
|
|
size_t i;
|
|
|
|
if (!qemuDomainHasSlirp(vm))
|
|
return 0;
|
|
|
|
virBufferAddLit(buf, "<slirp>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
for (i = 0; i < vm->def->nnets; i++) {
|
|
virDomainNetDefPtr net = vm->def->nets[i];
|
|
qemuSlirpPtr slirp = QEMU_DOMAIN_NETWORK_PRIVATE(net)->slirp;
|
|
size_t j;
|
|
|
|
if (!slirp)
|
|
continue;
|
|
|
|
virBufferAsprintf(buf, "<helper alias='%s' pid='%d'>\n",
|
|
net->info.alias, slirp->pid);
|
|
|
|
virBufferAdjustIndent(buf, 2);
|
|
for (j = 0; j < QEMU_SLIRP_FEATURE_LAST; j++) {
|
|
if (qemuSlirpHasFeature(slirp, j)) {
|
|
virBufferAsprintf(buf, "<feature name='%s'/>\n",
|
|
qemuSlirpFeatureTypeToString(j));
|
|
}
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</helper>\n");
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</slirp>\n");
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLFormat(virBufferPtr buf,
|
|
virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
const char *monitorpath;
|
|
|
|
/* priv->monitor_chr is set only for qemu */
|
|
if (priv->monConfig) {
|
|
switch (priv->monConfig->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
monitorpath = priv->monConfig->data.nix.path;
|
|
break;
|
|
default:
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
monitorpath = priv->monConfig->data.file.path;
|
|
break;
|
|
}
|
|
|
|
virBufferEscapeString(buf, "<monitor path='%s'", monitorpath);
|
|
virBufferAsprintf(buf, " type='%s'/>\n",
|
|
virDomainChrTypeToString(priv->monConfig->type));
|
|
}
|
|
|
|
if (priv->namespaces) {
|
|
ssize_t ns = -1;
|
|
|
|
virBufferAddLit(buf, "<namespaces>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
while ((ns = virBitmapNextSetBit(priv->namespaces, ns)) >= 0)
|
|
virBufferAsprintf(buf, "<%s/>\n", qemuDomainNamespaceTypeToString(ns));
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</namespaces>\n");
|
|
}
|
|
|
|
qemuDomainObjPrivateXMLFormatVcpus(buf, vm->def);
|
|
|
|
if (priv->qemuCaps) {
|
|
size_t i;
|
|
virBufferAddLit(buf, "<qemuCaps>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
for (i = 0; i < QEMU_CAPS_LAST; i++) {
|
|
if (virQEMUCapsGet(priv->qemuCaps, i)) {
|
|
virBufferAsprintf(buf, "<flag name='%s'/>\n",
|
|
virQEMUCapsTypeToString(i));
|
|
}
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</qemuCaps>\n");
|
|
}
|
|
|
|
if (priv->lockState)
|
|
virBufferAsprintf(buf, "<lockstate>%s</lockstate>\n", priv->lockState);
|
|
|
|
if (qemuDomainObjPrivateXMLFormatJob(buf, vm, priv) < 0)
|
|
return -1;
|
|
|
|
if (priv->fakeReboot)
|
|
virBufferAddLit(buf, "<fakereboot/>\n");
|
|
|
|
if (priv->qemuDevices && *priv->qemuDevices) {
|
|
char **tmp = priv->qemuDevices;
|
|
virBufferAddLit(buf, "<devices>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
while (*tmp) {
|
|
virBufferAsprintf(buf, "<device alias='%s'/>\n", *tmp);
|
|
tmp++;
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</devices>\n");
|
|
}
|
|
|
|
if (qemuDomainObjPrivateXMLFormatAutomaticPlacement(buf, priv) < 0)
|
|
return -1;
|
|
|
|
/* Various per-domain paths */
|
|
virBufferEscapeString(buf, "<libDir path='%s'/>\n", priv->libDir);
|
|
virBufferEscapeString(buf, "<channelTargetDir path='%s'/>\n",
|
|
priv->channelTargetDir);
|
|
|
|
virCPUDefFormatBufFull(buf, priv->origCPU, NULL);
|
|
|
|
if (priv->chardevStdioLogd)
|
|
virBufferAddLit(buf, "<chardevStdioLogd/>\n");
|
|
|
|
if (priv->rememberOwner)
|
|
virBufferAddLit(buf, "<rememberOwner/>\n");
|
|
|
|
qemuDomainObjPrivateXMLFormatAllowReboot(buf, priv->allowReboot);
|
|
|
|
qemuDomainObjPrivateXMLFormatPR(buf, priv);
|
|
|
|
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV))
|
|
virBufferAsprintf(buf, "<nodename index='%llu'/>\n", priv->nodenameindex);
|
|
|
|
if (priv->memPrealloc)
|
|
virBufferAddLit(buf, "<memPrealloc/>\n");
|
|
|
|
if (qemuDomainObjPrivateXMLFormatBlockjobs(buf, vm) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainObjPrivateXMLFormatSlirp(buf, vm) < 0)
|
|
return -1;
|
|
|
|
virBufferAsprintf(buf, "<agentTimeout>%i</agentTimeout>\n", priv->agentTimeout);
|
|
|
|
if (qemuDomainObjPrivateXMLFormatBackups(buf, vm) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseVcpu(xmlNodePtr node,
|
|
unsigned int idx,
|
|
virDomainDefPtr def)
|
|
{
|
|
virDomainVcpuDefPtr vcpu;
|
|
g_autofree char *idstr = NULL;
|
|
g_autofree char *pidstr = NULL;
|
|
unsigned int tmp;
|
|
|
|
idstr = virXMLPropString(node, "id");
|
|
|
|
if (idstr &&
|
|
(virStrToLong_uip(idstr, NULL, 10, &idx) < 0)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse vcpu index '%s'"), idstr);
|
|
return -1;
|
|
}
|
|
if (!(vcpu = virDomainDefGetVcpu(def, idx))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("invalid vcpu index '%u'"), idx);
|
|
return -1;
|
|
}
|
|
|
|
if (!(pidstr = virXMLPropString(node, "pid")))
|
|
return -1;
|
|
|
|
if (virStrToLong_uip(pidstr, NULL, 10, &tmp) < 0)
|
|
return -1;
|
|
|
|
QEMU_DOMAIN_VCPU_PRIVATE(vcpu)->tid = tmp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseAutomaticPlacement(xmlXPathContextPtr ctxt,
|
|
qemuDomainObjPrivatePtr priv,
|
|
virQEMUDriverPtr driver)
|
|
{
|
|
g_autoptr(virCapsHostNUMA) caps = NULL;
|
|
g_autofree char *nodeset = NULL;
|
|
g_autofree char *cpuset = NULL;
|
|
int nodesetSize = 0;
|
|
size_t i;
|
|
|
|
nodeset = virXPathString("string(./numad/@nodeset)", ctxt);
|
|
cpuset = virXPathString("string(./numad/@cpuset)", ctxt);
|
|
|
|
if (!nodeset && !cpuset)
|
|
return 0;
|
|
|
|
if (!(caps = virQEMUDriverGetHostNUMACaps(driver)))
|
|
return -1;
|
|
|
|
/* Figure out how big the nodeset bitmap needs to be.
|
|
* This is necessary because NUMA node IDs are not guaranteed to
|
|
* start from 0 or be densely allocated */
|
|
for (i = 0; i < caps->cells->len; i++) {
|
|
virCapsHostNUMACellPtr cell =
|
|
g_ptr_array_index(caps->cells, i);
|
|
nodesetSize = MAX(nodesetSize, cell->num + 1);
|
|
}
|
|
|
|
if (nodeset &&
|
|
virBitmapParse(nodeset, &priv->autoNodeset, nodesetSize) < 0)
|
|
return -1;
|
|
|
|
if (cpuset) {
|
|
if (virBitmapParse(cpuset, &priv->autoCpuset, VIR_DOMAIN_CPUMASK_LEN) < 0)
|
|
return -1;
|
|
} else {
|
|
/* autoNodeset is present in this case, since otherwise we wouldn't
|
|
* reach this code */
|
|
if (!(priv->autoCpuset = virCapabilitiesHostNUMAGetCpus(caps,
|
|
priv->autoNodeset)))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virStorageSourcePtr
|
|
qemuDomainObjPrivateXMLParseBlockjobChain(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOptionPtr xmlopt)
|
|
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt);
|
|
g_autofree char *format = NULL;
|
|
g_autofree char *type = NULL;
|
|
g_autofree char *index = NULL;
|
|
g_autoptr(virStorageSource) src = NULL;
|
|
xmlNodePtr sourceNode;
|
|
unsigned int xmlflags = VIR_DOMAIN_DEF_PARSE_STATUS;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(type = virXMLPropString(ctxt->node, "type")) ||
|
|
!(format = virXMLPropString(ctxt->node, "format")) ||
|
|
!(index = virXPathString("string(./source/@index)", ctxt)) ||
|
|
!(sourceNode = virXPathNode("./source", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing job chain data"));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(src = virDomainStorageSourceParseBase(type, format, index)))
|
|
return NULL;
|
|
|
|
if (virDomainStorageSourceParse(sourceNode, ctxt, src, xmlflags, xmlopt) < 0)
|
|
return NULL;
|
|
|
|
if (virDomainDiskBackingStoreParse(ctxt, src, xmlflags, xmlopt) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&src);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainObjPrivateXMLParseBlockjobNodename(qemuBlockJobDataPtr job,
|
|
const char *xpath,
|
|
virStorageSourcePtr *src,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree char *nodename = NULL;
|
|
|
|
*src = NULL;
|
|
|
|
if (!(nodename = virXPathString(xpath, ctxt)))
|
|
return;
|
|
|
|
if (job->disk &&
|
|
(*src = virStorageSourceFindByNodeName(job->disk->src, nodename, NULL)))
|
|
return;
|
|
|
|
if (job->chain &&
|
|
(*src = virStorageSourceFindByNodeName(job->chain, nodename, NULL)))
|
|
return;
|
|
|
|
if (job->mirrorChain &&
|
|
(*src = virStorageSourceFindByNodeName(job->mirrorChain, nodename, NULL)))
|
|
return;
|
|
|
|
/* the node was in the XML but was not found in the job definitions */
|
|
VIR_DEBUG("marking block job '%s' as invalid: node name '%s' missing",
|
|
job->name, nodename);
|
|
job->invalidData = true;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobDataPtr job,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOptionPtr xmlopt)
|
|
{
|
|
g_autofree char *createmode = NULL;
|
|
g_autofree char *shallownew = NULL;
|
|
xmlNodePtr tmp;
|
|
|
|
switch ((qemuBlockJobType) job->type) {
|
|
case QEMU_BLOCKJOB_TYPE_PULL:
|
|
qemuDomainObjPrivateXMLParseBlockjobNodename(job,
|
|
"string(./base/@node)",
|
|
&job->data.pull.base,
|
|
ctxt);
|
|
/* base is not present if pulling everything */
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_COMMIT:
|
|
qemuDomainObjPrivateXMLParseBlockjobNodename(job,
|
|
"string(./topparent/@node)",
|
|
&job->data.commit.topparent,
|
|
ctxt);
|
|
|
|
if (!job->data.commit.topparent)
|
|
goto broken;
|
|
|
|
G_GNUC_FALLTHROUGH;
|
|
case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
|
|
qemuDomainObjPrivateXMLParseBlockjobNodename(job,
|
|
"string(./top/@node)",
|
|
&job->data.commit.top,
|
|
ctxt);
|
|
qemuDomainObjPrivateXMLParseBlockjobNodename(job,
|
|
"string(./base/@node)",
|
|
&job->data.commit.base,
|
|
ctxt);
|
|
if (virXPathNode("./deleteCommittedImages", ctxt))
|
|
job->data.commit.deleteCommittedImages = true;
|
|
if (!job->data.commit.top ||
|
|
!job->data.commit.base)
|
|
goto broken;
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_CREATE:
|
|
if (!(tmp = virXPathNode("./src", ctxt)) ||
|
|
!(job->data.create.src = qemuDomainObjPrivateXMLParseBlockjobChain(tmp, ctxt, xmlopt)))
|
|
goto broken;
|
|
|
|
if ((createmode = virXPathString("string(./create/@mode)", ctxt))) {
|
|
if (STRNEQ(createmode, "storage"))
|
|
goto broken;
|
|
|
|
job->data.create.storage = true;
|
|
}
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_COPY:
|
|
if ((shallownew = virXPathString("string(./@shallownew)", ctxt))) {
|
|
if (STRNEQ(shallownew, "yes"))
|
|
goto broken;
|
|
|
|
job->data.copy.shallownew = true;
|
|
}
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_BACKUP:
|
|
job->data.backup.bitmap = virXPathString("string(./bitmap/@name)", ctxt);
|
|
|
|
if (!(tmp = virXPathNode("./store", ctxt)) ||
|
|
!(job->data.backup.store = qemuDomainObjPrivateXMLParseBlockjobChain(tmp, ctxt, xmlopt)))
|
|
goto broken;
|
|
break;
|
|
|
|
case QEMU_BLOCKJOB_TYPE_BROKEN:
|
|
case QEMU_BLOCKJOB_TYPE_NONE:
|
|
case QEMU_BLOCKJOB_TYPE_INTERNAL:
|
|
case QEMU_BLOCKJOB_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return;
|
|
|
|
broken:
|
|
VIR_DEBUG("marking block job '%s' as invalid: malformed job data", job->name);
|
|
job->invalidData = true;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseBlockjobData(virDomainObjPtr vm,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOptionPtr xmlopt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt);
|
|
virDomainDiskDefPtr disk = NULL;
|
|
g_autoptr(qemuBlockJobData) job = NULL;
|
|
g_autofree char *name = NULL;
|
|
g_autofree char *typestr = NULL;
|
|
g_autofree char *brokentypestr = NULL;
|
|
int type;
|
|
g_autofree char *statestr = NULL;
|
|
int state = QEMU_BLOCKJOB_STATE_FAILED;
|
|
g_autofree char *diskdst = NULL;
|
|
g_autofree char *newstatestr = NULL;
|
|
g_autofree char *mirror = NULL;
|
|
int newstate = -1;
|
|
bool invalidData = false;
|
|
xmlNodePtr tmp;
|
|
unsigned long jobflags = 0;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(name = virXPathString("string(./@name)", ctxt))) {
|
|
VIR_WARN("malformed block job data for vm '%s'", vm->def->name);
|
|
return 0;
|
|
}
|
|
|
|
/* if the job name is known we need to register such a job so that we can
|
|
* clean it up */
|
|
if (!(typestr = virXPathString("string(./@type)", ctxt)) ||
|
|
(type = qemuBlockjobTypeFromString(typestr)) < 0) {
|
|
type = QEMU_BLOCKJOB_TYPE_BROKEN;
|
|
invalidData = true;
|
|
}
|
|
|
|
if (!(job = qemuBlockJobDataNew(type, name)))
|
|
return -1;
|
|
|
|
if ((brokentypestr = virXPathString("string(./@brokentype)", ctxt)) &&
|
|
(job->brokentype = qemuBlockjobTypeFromString(brokentypestr)) < 0)
|
|
job->brokentype = QEMU_BLOCKJOB_TYPE_NONE;
|
|
|
|
if (!(statestr = virXPathString("string(./@state)", ctxt)) ||
|
|
(state = qemuBlockjobStateTypeFromString(statestr)) < 0)
|
|
invalidData = true;
|
|
|
|
if ((newstatestr = virXPathString("string(./@newstate)", ctxt)) &&
|
|
(newstate = qemuBlockjobStateTypeFromString(newstatestr)) < 0)
|
|
invalidData = true;
|
|
|
|
if ((diskdst = virXPathString("string(./disk/@dst)", ctxt)) &&
|
|
!(disk = virDomainDiskByTarget(vm->def, diskdst)))
|
|
invalidData = true;
|
|
|
|
if ((mirror = virXPathString("string(./disk/@mirror)", ctxt)) &&
|
|
STRNEQ(mirror, "yes"))
|
|
invalidData = true;
|
|
|
|
if (virXPathULongHex("string(./@jobflags)", ctxt, &jobflags) != 0)
|
|
job->jobflagsmissing = true;
|
|
|
|
if (!disk && !invalidData) {
|
|
if ((tmp = virXPathNode("./chains/disk", ctxt)) &&
|
|
!(job->chain = qemuDomainObjPrivateXMLParseBlockjobChain(tmp, ctxt, xmlopt)))
|
|
invalidData = true;
|
|
|
|
if ((tmp = virXPathNode("./chains/mirror", ctxt)) &&
|
|
!(job->mirrorChain = qemuDomainObjPrivateXMLParseBlockjobChain(tmp, ctxt, xmlopt)))
|
|
invalidData = true;
|
|
}
|
|
|
|
if (mirror) {
|
|
if (disk)
|
|
job->mirrorChain = virObjectRef(disk->mirror);
|
|
else
|
|
invalidData = true;
|
|
}
|
|
|
|
job->state = state;
|
|
job->newstate = newstate;
|
|
job->jobflags = jobflags;
|
|
job->errmsg = virXPathString("string(./errmsg)", ctxt);
|
|
job->invalidData = invalidData;
|
|
job->disk = disk;
|
|
|
|
qemuDomainObjPrivateXMLParseBlockjobDataSpecific(job, ctxt, xmlopt);
|
|
|
|
if (qemuBlockJobRegister(job, vm, disk, false) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseBlockjobs(virDomainObjPtr vm,
|
|
qemuDomainObjPrivatePtr priv,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
ssize_t nnodes = 0;
|
|
g_autofree char *active = NULL;
|
|
int tmp;
|
|
size_t i;
|
|
|
|
if ((active = virXPathString("string(./blockjobs/@active)", ctxt)) &&
|
|
(tmp = virTristateBoolTypeFromString(active)) > 0)
|
|
priv->reconnectBlockjobs = tmp;
|
|
|
|
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) {
|
|
if ((nnodes = virXPathNodeSet("./blockjobs/blockjob", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < nnodes; i++) {
|
|
if (qemuDomainObjPrivateXMLParseBlockjobData(vm, nodes[i], ctxt,
|
|
priv->driver->xmlopt) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseBackups(qemuDomainObjPrivatePtr priv,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
ssize_t nnodes = 0;
|
|
|
|
if ((nnodes = virXPathNodeSet("./backups/domainbackup", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (nnodes > 1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("only one backup job is supported"));
|
|
return -1;
|
|
}
|
|
|
|
if (nnodes == 0)
|
|
return 0;
|
|
|
|
if (!(priv->backup = virDomainBackupDefParseNode(ctxt->doc, nodes[0],
|
|
priv->driver->xmlopt,
|
|
VIR_DOMAIN_BACKUP_PARSE_INTERNAL)))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainObjPrivateXMLParseAllowReboot(xmlXPathContextPtr ctxt,
|
|
virTristateBool *allowReboot)
|
|
{
|
|
int val;
|
|
g_autofree char *valStr = NULL;
|
|
|
|
if ((valStr = virXPathString("string(./allowReboot/@value)", ctxt))) {
|
|
if ((val = virTristateBoolTypeFromString(valStr)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("invalid allowReboot value '%s'"), valStr);
|
|
return -1;
|
|
}
|
|
*allowReboot = val;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainObjPrivateXMLParsePR(xmlXPathContextPtr ctxt,
|
|
bool *prDaemonRunning)
|
|
{
|
|
*prDaemonRunning = virXPathBoolean("boolean(./prDaemon)", ctxt) > 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseJobNBDSource(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainDiskDefPtr disk,
|
|
virDomainXMLOptionPtr xmlopt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt);
|
|
qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
g_autofree char *format = NULL;
|
|
g_autofree char *type = NULL;
|
|
g_autoptr(virStorageSource) migrSource = NULL;
|
|
xmlNodePtr sourceNode;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(ctxt->node = virXPathNode("./migrationSource", ctxt)))
|
|
return 0;
|
|
|
|
if (!(type = virXMLPropString(ctxt->node, "type"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing storage source type"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(format = virXMLPropString(ctxt->node, "format"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing storage source format"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(migrSource = virDomainStorageSourceParseBase(type, format, NULL)))
|
|
return -1;
|
|
|
|
/* newer libvirt uses the <source> subelement instead of formatting the
|
|
* source directly into <migrationSource> */
|
|
if ((sourceNode = virXPathNode("./source", ctxt)))
|
|
ctxt->node = sourceNode;
|
|
|
|
if (virDomainStorageSourceParse(ctxt->node, ctxt, migrSource,
|
|
VIR_DOMAIN_DEF_PARSE_STATUS, xmlopt) < 0)
|
|
return -1;
|
|
|
|
diskPriv->migrSource = g_steal_pointer(&migrSource);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseJobNBD(virDomainObjPtr vm,
|
|
qemuDomainObjPrivatePtr priv,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
size_t i;
|
|
int n;
|
|
|
|
if ((n = virXPathNodeSet("./disk[@migrating='yes']", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (n > 0) {
|
|
if (priv->job.asyncJob != QEMU_ASYNC_JOB_MIGRATION_OUT) {
|
|
VIR_WARN("Found disks marked for migration but we were not "
|
|
"migrating");
|
|
n = 0;
|
|
}
|
|
for (i = 0; i < n; i++) {
|
|
virDomainDiskDefPtr disk;
|
|
g_autofree char *dst = NULL;
|
|
|
|
if ((dst = virXMLPropString(nodes[i], "dev")) &&
|
|
(disk = virDomainDiskByTarget(vm->def, dst))) {
|
|
QEMU_DOMAIN_DISK_PRIVATE(disk)->migrating = true;
|
|
|
|
if (qemuDomainObjPrivateXMLParseJobNBDSource(nodes[i], ctxt,
|
|
disk,
|
|
priv->driver->xmlopt) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseJob(virDomainObjPtr vm,
|
|
qemuDomainObjPrivatePtr priv,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt);
|
|
g_autofree char *tmp = NULL;
|
|
|
|
if (!(ctxt->node = virXPathNode("./job[1]", ctxt)))
|
|
return 0;
|
|
|
|
if ((tmp = virXPathString("string(@type)", ctxt))) {
|
|
int type;
|
|
|
|
if ((type = qemuDomainJobTypeFromString(tmp)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown job type %s"), tmp);
|
|
return -1;
|
|
}
|
|
VIR_FREE(tmp);
|
|
priv->job.active = type;
|
|
}
|
|
|
|
if ((tmp = virXPathString("string(@async)", ctxt))) {
|
|
int async;
|
|
|
|
if ((async = qemuDomainAsyncJobTypeFromString(tmp)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown async job type %s"), tmp);
|
|
return -1;
|
|
}
|
|
VIR_FREE(tmp);
|
|
priv->job.asyncJob = async;
|
|
|
|
if ((tmp = virXPathString("string(@phase)", ctxt))) {
|
|
priv->job.phase = qemuDomainAsyncJobPhaseFromString(async, tmp);
|
|
if (priv->job.phase < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown job phase %s"), tmp);
|
|
return -1;
|
|
}
|
|
VIR_FREE(tmp);
|
|
}
|
|
}
|
|
|
|
if (virXPathULongHex("string(@flags)", ctxt, &priv->job.apiFlags) == -2) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid job flags"));
|
|
return -1;
|
|
}
|
|
|
|
if (qemuDomainObjPrivateXMLParseJobNBD(vm, priv, ctxt) < 0)
|
|
return -1;
|
|
|
|
if (qemuMigrationParamsParse(ctxt, &priv->job.migParams) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParseSlirpFeatures(xmlNodePtr featuresNode,
|
|
xmlXPathContextPtr ctxt,
|
|
qemuSlirpPtr slirp)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt);
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
size_t i;
|
|
int n;
|
|
|
|
ctxt->node = featuresNode;
|
|
|
|
if ((n = virXPathNodeSet("./feature", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("failed to parse slirp-helper features"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
g_autofree char *str = virXMLPropString(nodes[i], "name");
|
|
int feature;
|
|
|
|
if (!str)
|
|
continue;
|
|
|
|
feature = qemuSlirpFeatureTypeFromString(str);
|
|
if (feature < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown slirp feature %s"), str);
|
|
return -1;
|
|
}
|
|
|
|
qemuSlirpSetFeature(slirp, feature);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainObjPrivateXMLParse(xmlXPathContextPtr ctxt,
|
|
virDomainObjPtr vm,
|
|
virDomainDefParserConfigPtr config)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virQEMUDriverPtr driver = config->priv;
|
|
char *monitorpath;
|
|
g_autofree char *tmp = NULL;
|
|
int n;
|
|
size_t i;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
xmlNodePtr node = NULL;
|
|
g_autoptr(virQEMUCaps) qemuCaps = NULL;
|
|
|
|
if (!(priv->monConfig = virDomainChrSourceDefNew(NULL)))
|
|
goto error;
|
|
|
|
if (!(monitorpath =
|
|
virXPathString("string(./monitor[1]/@path)", ctxt))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("no monitor path"));
|
|
goto error;
|
|
}
|
|
|
|
tmp = virXPathString("string(./monitor[1]/@type)", ctxt);
|
|
if (tmp)
|
|
priv->monConfig->type = virDomainChrTypeFromString(tmp);
|
|
else
|
|
priv->monConfig->type = VIR_DOMAIN_CHR_TYPE_PTY;
|
|
VIR_FREE(tmp);
|
|
|
|
switch (priv->monConfig->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
priv->monConfig->data.file.path = monitorpath;
|
|
break;
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
priv->monConfig->data.nix.path = monitorpath;
|
|
break;
|
|
default:
|
|
VIR_FREE(monitorpath);
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported monitor type '%s'"),
|
|
virDomainChrTypeToString(priv->monConfig->type));
|
|
goto error;
|
|
}
|
|
|
|
if (virXPathInt("string(./agentTimeout)", ctxt, &priv->agentTimeout) == -2) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to parse agent timeout"));
|
|
goto error;
|
|
}
|
|
|
|
if ((node = virXPathNode("./namespaces", ctxt))) {
|
|
xmlNodePtr next;
|
|
|
|
for (next = node->children; next; next = next->next) {
|
|
int ns = qemuDomainNamespaceTypeFromString((const char *)next->name);
|
|
|
|
if (ns < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("malformed namespace name: %s"),
|
|
next->name);
|
|
goto error;
|
|
}
|
|
|
|
if (qemuDomainEnableNamespace(vm, ns) < 0)
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (priv->namespaces &&
|
|
virBitmapIsAllClear(priv->namespaces)) {
|
|
virBitmapFree(priv->namespaces);
|
|
priv->namespaces = NULL;
|
|
}
|
|
|
|
priv->rememberOwner = virXPathBoolean("count(./rememberOwner) > 0", ctxt);
|
|
|
|
if ((n = virXPathNodeSet("./vcpus/vcpu", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (qemuDomainObjPrivateXMLParseVcpu(nodes[i], i, vm->def) < 0)
|
|
goto error;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./qemuCaps/flag", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("failed to parse qemu capabilities flags"));
|
|
goto error;
|
|
}
|
|
if (n > 0) {
|
|
if (!(qemuCaps = virQEMUCapsNew()))
|
|
goto error;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
g_autofree char *str = virXMLPropString(nodes[i], "name");
|
|
if (str) {
|
|
int flag = virQEMUCapsTypeFromString(str);
|
|
if (flag < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown qemu capabilities flag %s"), str);
|
|
goto error;
|
|
}
|
|
virQEMUCapsSet(qemuCaps, flag);
|
|
}
|
|
}
|
|
|
|
priv->qemuCaps = g_steal_pointer(&qemuCaps);
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
priv->lockState = virXPathString("string(./lockstate)", ctxt);
|
|
|
|
if (qemuDomainObjPrivateXMLParseJob(vm, priv, ctxt) < 0)
|
|
goto error;
|
|
|
|
priv->fakeReboot = virXPathBoolean("boolean(./fakereboot)", ctxt) == 1;
|
|
|
|
if ((n = virXPathNodeSet("./devices/device", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to parse qemu device list"));
|
|
goto error;
|
|
}
|
|
if (n > 0) {
|
|
/* NULL-terminated list */
|
|
if (VIR_ALLOC_N(priv->qemuDevices, n + 1) < 0)
|
|
goto error;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
priv->qemuDevices[i] = virXMLPropString(nodes[i], "alias");
|
|
if (!priv->qemuDevices[i]) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to parse qemu device list"));
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./slirp/helper", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to parse slirp helper list"));
|
|
goto error;
|
|
}
|
|
for (i = 0; i < n; i++) {
|
|
g_autofree char *alias = virXMLPropString(nodes[i], "alias");
|
|
g_autofree char *pid = virXMLPropString(nodes[i], "pid");
|
|
g_autoptr(qemuSlirp) slirp = qemuSlirpNew();
|
|
virDomainDeviceDef dev;
|
|
|
|
if (!alias || !pid || !slirp ||
|
|
virStrToLong_i(pid, NULL, 10, &slirp->pid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to parse slirp helper list"));
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainDefFindDevice(vm->def, alias, &dev, true) < 0 ||
|
|
dev.type != VIR_DOMAIN_DEVICE_NET)
|
|
goto error;
|
|
|
|
if (qemuDomainObjPrivateXMLParseSlirpFeatures(nodes[i], ctxt, slirp) < 0)
|
|
goto error;
|
|
|
|
QEMU_DOMAIN_NETWORK_PRIVATE(dev.data.net)->slirp = g_steal_pointer(&slirp);
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if (qemuDomainObjPrivateXMLParseAutomaticPlacement(ctxt, priv, driver) < 0)
|
|
goto error;
|
|
|
|
if ((tmp = virXPathString("string(./libDir/@path)", ctxt)))
|
|
priv->libDir = tmp;
|
|
if ((tmp = virXPathString("string(./channelTargetDir/@path)", ctxt)))
|
|
priv->channelTargetDir = tmp;
|
|
tmp = NULL;
|
|
|
|
qemuDomainSetPrivatePathsOld(driver, vm);
|
|
|
|
if (virCPUDefParseXML(ctxt, "./cpu", VIR_CPU_TYPE_GUEST, &priv->origCPU) < 0)
|
|
goto error;
|
|
|
|
priv->chardevStdioLogd = virXPathBoolean("boolean(./chardevStdioLogd)",
|
|
ctxt) == 1;
|
|
|
|
qemuDomainObjPrivateXMLParseAllowReboot(ctxt, &priv->allowReboot);
|
|
|
|
qemuDomainObjPrivateXMLParsePR(ctxt, &priv->prDaemonRunning);
|
|
|
|
if (qemuDomainObjPrivateXMLParseBlockjobs(vm, priv, ctxt) < 0)
|
|
goto error;
|
|
|
|
if (qemuDomainObjPrivateXMLParseBackups(priv, ctxt) < 0)
|
|
goto error;
|
|
|
|
qemuDomainStorageIdReset(priv);
|
|
if (virXPathULongLong("string(./nodename/@index)", ctxt,
|
|
&priv->nodenameindex) == -2) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("failed to parse node name index"));
|
|
goto error;
|
|
}
|
|
|
|
priv->memPrealloc = virXPathBoolean("boolean(./memPrealloc)", ctxt) == 1;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
virBitmapFree(priv->namespaces);
|
|
priv->namespaces = NULL;
|
|
virObjectUnref(priv->monConfig);
|
|
priv->monConfig = NULL;
|
|
virStringListFree(priv->qemuDevices);
|
|
priv->qemuDevices = NULL;
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void *
|
|
qemuDomainObjPrivateXMLGetParseOpaque(virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
return priv->qemuCaps;
|
|
}
|
|
|
|
|
|
virDomainXMLPrivateDataCallbacks virQEMUDriverPrivateDataCallbacks = {
|
|
.alloc = qemuDomainObjPrivateAlloc,
|
|
.free = qemuDomainObjPrivateFree,
|
|
.diskNew = qemuDomainDiskPrivateNew,
|
|
.diskParse = qemuDomainDiskPrivateParse,
|
|
.diskFormat = qemuDomainDiskPrivateFormat,
|
|
.vcpuNew = qemuDomainVcpuPrivateNew,
|
|
.chrSourceNew = qemuDomainChrSourcePrivateNew,
|
|
.vsockNew = qemuDomainVsockPrivateNew,
|
|
.graphicsNew = qemuDomainGraphicsPrivateNew,
|
|
.networkNew = qemuDomainNetworkPrivateNew,
|
|
.videoNew = qemuDomainVideoPrivateNew,
|
|
.fsNew = qemuDomainFSPrivateNew,
|
|
.parse = qemuDomainObjPrivateXMLParse,
|
|
.format = qemuDomainObjPrivateXMLFormat,
|
|
.getParseOpaque = qemuDomainObjPrivateXMLGetParseOpaque,
|
|
.storageParse = qemuStorageSourcePrivateDataParse,
|
|
.storageFormat = qemuStorageSourcePrivateDataFormat,
|
|
};
|
|
|
|
|
|
static void
|
|
qemuDomainXmlNsDefFree(qemuDomainXmlNsDefPtr def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virStringListFreeCount(def->args, def->num_args);
|
|
virStringListFreeCount(def->env_name, def->num_env);
|
|
virStringListFreeCount(def->env_value, def->num_env);
|
|
virStringListFreeCount(def->capsadd, def->ncapsadd);
|
|
virStringListFreeCount(def->capsdel, def->ncapsdel);
|
|
|
|
VIR_FREE(def);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainDefNamespaceFree(void *nsdata)
|
|
{
|
|
qemuDomainXmlNsDefPtr cmd = nsdata;
|
|
|
|
qemuDomainXmlNsDefFree(cmd);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefNamespaceParseCommandlineArgs(qemuDomainXmlNsDefPtr nsdef,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
ssize_t nnodes;
|
|
size_t i;
|
|
|
|
if ((nnodes = virXPathNodeSet("./qemu:commandline/qemu:arg", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (nnodes == 0)
|
|
return 0;
|
|
|
|
if (VIR_ALLOC_N(nsdef->args, nnodes) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < nnodes; i++) {
|
|
if (!(nsdef->args[nsdef->num_args++] = virXMLPropString(nodes[i], "value"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No qemu command-line argument specified"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefNamespaceParseCommandlineEnvNameValidate(const char *envname)
|
|
{
|
|
if (!g_ascii_isalpha(envname[0]) && envname[0] != '_') {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Invalid environment name, it must begin with a letter or underscore"));
|
|
return -1;
|
|
}
|
|
|
|
if (strspn(envname, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_") != strlen(envname)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Invalid environment name, it must contain only alphanumerics and underscore"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefNamespaceParseCommandlineEnv(qemuDomainXmlNsDefPtr nsdef,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
ssize_t nnodes;
|
|
size_t i;
|
|
|
|
if ((nnodes = virXPathNodeSet("./qemu:commandline/qemu:env", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (nnodes == 0)
|
|
return 0;
|
|
|
|
if (VIR_ALLOC_N(nsdef->env_name, nnodes) < 0 ||
|
|
VIR_ALLOC_N(nsdef->env_value, nnodes) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < nnodes; i++) {
|
|
if (!(nsdef->env_name[nsdef->num_env] = virXMLPropString(nodes[i], "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No qemu environment name specified"));
|
|
return -1;
|
|
}
|
|
|
|
if (qemuDomainDefNamespaceParseCommandlineEnvNameValidate(nsdef->env_name[nsdef->num_env]) < 0)
|
|
return -1;
|
|
|
|
nsdef->env_value[nsdef->num_env] = virXMLPropString(nodes[i], "value");
|
|
/* a NULL value for command is allowed, since it might be empty */
|
|
nsdef->num_env++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefNamespaceParseCaps(qemuDomainXmlNsDefPtr nsdef,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *nodesadd = NULL;
|
|
ssize_t nnodesadd;
|
|
g_autofree xmlNodePtr *nodesdel = NULL;
|
|
ssize_t nnodesdel;
|
|
size_t i;
|
|
|
|
if ((nnodesadd = virXPathNodeSet("./qemu:capabilities/qemu:add", ctxt, &nodesadd)) < 0 ||
|
|
(nnodesdel = virXPathNodeSet("./qemu:capabilities/qemu:del", ctxt, &nodesdel)) < 0)
|
|
return -1;
|
|
|
|
if (nnodesadd > 0) {
|
|
if (VIR_ALLOC_N(nsdef->capsadd, nnodesadd) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < nnodesadd; i++) {
|
|
if (!(nsdef->capsadd[nsdef->ncapsadd++] = virXMLPropString(nodesadd[i], "capability"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing capability name"));
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nnodesdel > 0) {
|
|
if (VIR_ALLOC_N(nsdef->capsdel, nnodesdel) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < nnodesdel; i++) {
|
|
if (!(nsdef->capsdel[nsdef->ncapsdel++] = virXMLPropString(nodesdel[i], "capability"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing capability name"));
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefNamespaceParse(xmlXPathContextPtr ctxt,
|
|
void **data)
|
|
{
|
|
qemuDomainXmlNsDefPtr nsdata = NULL;
|
|
int ret = -1;
|
|
|
|
if (VIR_ALLOC(nsdata) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefNamespaceParseCommandlineArgs(nsdata, ctxt) < 0 ||
|
|
qemuDomainDefNamespaceParseCommandlineEnv(nsdata, ctxt) < 0 ||
|
|
qemuDomainDefNamespaceParseCaps(nsdata, ctxt) < 0)
|
|
goto cleanup;
|
|
|
|
if (nsdata->num_args > 0 || nsdata->num_env > 0 ||
|
|
nsdata->ncapsadd > 0 || nsdata->ncapsdel > 0)
|
|
*data = g_steal_pointer(&nsdata);
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
qemuDomainDefNamespaceFree(nsdata);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainDefNamespaceFormatXMLCommandline(virBufferPtr buf,
|
|
qemuDomainXmlNsDefPtr cmd)
|
|
{
|
|
size_t i;
|
|
|
|
if (!cmd->num_args && !cmd->num_env)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "<qemu:commandline>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
for (i = 0; i < cmd->num_args; i++)
|
|
virBufferEscapeString(buf, "<qemu:arg value='%s'/>\n",
|
|
cmd->args[i]);
|
|
for (i = 0; i < cmd->num_env; i++) {
|
|
virBufferAsprintf(buf, "<qemu:env name='%s'", cmd->env_name[i]);
|
|
if (cmd->env_value[i])
|
|
virBufferEscapeString(buf, " value='%s'", cmd->env_value[i]);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</qemu:commandline>\n");
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainDefNamespaceFormatXMLCaps(virBufferPtr buf,
|
|
qemuDomainXmlNsDefPtr xmlns)
|
|
{
|
|
size_t i;
|
|
|
|
if (!xmlns->ncapsadd && !xmlns->ncapsdel)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "<qemu:capabilities>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
for (i = 0; i < xmlns->ncapsadd; i++)
|
|
virBufferEscapeString(buf, "<qemu:add capability='%s'/>\n", xmlns->capsadd[i]);
|
|
|
|
for (i = 0; i < xmlns->ncapsdel; i++)
|
|
virBufferEscapeString(buf, "<qemu:del capability='%s'/>\n", xmlns->capsdel[i]);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</qemu:capabilities>\n");
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefNamespaceFormatXML(virBufferPtr buf,
|
|
void *nsdata)
|
|
{
|
|
qemuDomainXmlNsDefPtr cmd = nsdata;
|
|
|
|
qemuDomainDefNamespaceFormatXMLCommandline(buf, cmd);
|
|
qemuDomainDefNamespaceFormatXMLCaps(buf, cmd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
virXMLNamespace virQEMUDriverDomainXMLNamespace = {
|
|
.parse = qemuDomainDefNamespaceParse,
|
|
.free = qemuDomainDefNamespaceFree,
|
|
.format = qemuDomainDefNamespaceFormatXML,
|
|
.prefix = "qemu",
|
|
.uri = "http://libvirt.org/schemas/domain/qemu/1.0",
|
|
};
|
|
|
|
|
|
static int
|
|
qemuDomainDefAddImplicitInputDevice(virDomainDef *def)
|
|
{
|
|
if (ARCH_IS_X86(def->os.arch)) {
|
|
if (virDomainDefMaybeAddInput(def,
|
|
VIR_DOMAIN_INPUT_TYPE_MOUSE,
|
|
VIR_DOMAIN_INPUT_BUS_PS2) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefMaybeAddInput(def,
|
|
VIR_DOMAIN_INPUT_TYPE_KBD,
|
|
VIR_DOMAIN_INPUT_BUS_PS2) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefAddDefaultDevices(virDomainDefPtr def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
bool addDefaultUSB = true;
|
|
int usbModel = -1; /* "default for machinetype" */
|
|
int pciRoot; /* index within def->controllers */
|
|
bool addImplicitSATA = false;
|
|
bool addPCIRoot = false;
|
|
bool addPCIeRoot = false;
|
|
bool addDefaultMemballoon = true;
|
|
bool addDefaultUSBKBD = false;
|
|
bool addDefaultUSBMouse = false;
|
|
bool addPanicDevice = false;
|
|
|
|
/* add implicit input devices */
|
|
if (qemuDomainDefAddImplicitInputDevice(def) < 0)
|
|
return -1;
|
|
|
|
/* Add implicit PCI root controller if the machine has one */
|
|
switch (def->os.arch) {
|
|
case VIR_ARCH_I686:
|
|
case VIR_ARCH_X86_64:
|
|
if (STREQ(def->os.machine, "isapc")) {
|
|
addDefaultUSB = false;
|
|
break;
|
|
}
|
|
if (qemuDomainIsQ35(def)) {
|
|
addPCIeRoot = true;
|
|
addImplicitSATA = true;
|
|
|
|
/* Prefer adding a USB3 controller if supported, fall back
|
|
* to USB2 if there is no USB3 available, and if that's
|
|
* unavailable don't add anything.
|
|
*/
|
|
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_QEMU_XHCI))
|
|
usbModel = VIR_DOMAIN_CONTROLLER_MODEL_USB_QEMU_XHCI;
|
|
else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NEC_USB_XHCI))
|
|
usbModel = VIR_DOMAIN_CONTROLLER_MODEL_USB_NEC_XHCI;
|
|
else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_ICH9_USB_EHCI1))
|
|
usbModel = VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_EHCI1;
|
|
else
|
|
addDefaultUSB = false;
|
|
break;
|
|
}
|
|
if (qemuDomainIsI440FX(def))
|
|
addPCIRoot = true;
|
|
break;
|
|
|
|
case VIR_ARCH_ARMV6L:
|
|
addDefaultUSB = false;
|
|
addDefaultMemballoon = false;
|
|
if (STREQ(def->os.machine, "versatilepb"))
|
|
addPCIRoot = true;
|
|
break;
|
|
|
|
case VIR_ARCH_ARMV7L:
|
|
case VIR_ARCH_AARCH64:
|
|
addDefaultUSB = false;
|
|
addDefaultMemballoon = false;
|
|
if (qemuDomainIsARMVirt(def))
|
|
addPCIeRoot = virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_GPEX);
|
|
break;
|
|
|
|
case VIR_ARCH_PPC64:
|
|
case VIR_ARCH_PPC64LE:
|
|
addPCIRoot = true;
|
|
addDefaultUSBKBD = true;
|
|
addDefaultUSBMouse = true;
|
|
/* For pSeries guests, the firmware provides the same
|
|
* functionality as the pvpanic device, so automatically
|
|
* add the definition if not already present */
|
|
if (qemuDomainIsPSeries(def))
|
|
addPanicDevice = true;
|
|
break;
|
|
|
|
case VIR_ARCH_ALPHA:
|
|
case VIR_ARCH_PPC:
|
|
case VIR_ARCH_PPCEMB:
|
|
case VIR_ARCH_SH4:
|
|
case VIR_ARCH_SH4EB:
|
|
addPCIRoot = true;
|
|
break;
|
|
|
|
case VIR_ARCH_RISCV32:
|
|
case VIR_ARCH_RISCV64:
|
|
addDefaultUSB = false;
|
|
if (qemuDomainIsRISCVVirt(def))
|
|
addPCIeRoot = virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_GPEX);
|
|
break;
|
|
|
|
case VIR_ARCH_S390:
|
|
case VIR_ARCH_S390X:
|
|
addDefaultUSB = false;
|
|
addPanicDevice = true;
|
|
addPCIRoot = virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_ZPCI);
|
|
break;
|
|
|
|
case VIR_ARCH_SPARC:
|
|
case VIR_ARCH_SPARC64:
|
|
addPCIRoot = true;
|
|
break;
|
|
|
|
case VIR_ARCH_ARMV7B:
|
|
case VIR_ARCH_CRIS:
|
|
case VIR_ARCH_ITANIUM:
|
|
case VIR_ARCH_LM32:
|
|
case VIR_ARCH_M68K:
|
|
case VIR_ARCH_MICROBLAZE:
|
|
case VIR_ARCH_MICROBLAZEEL:
|
|
case VIR_ARCH_MIPS:
|
|
case VIR_ARCH_MIPSEL:
|
|
case VIR_ARCH_MIPS64:
|
|
case VIR_ARCH_MIPS64EL:
|
|
case VIR_ARCH_OR32:
|
|
case VIR_ARCH_PARISC:
|
|
case VIR_ARCH_PARISC64:
|
|
case VIR_ARCH_PPCLE:
|
|
case VIR_ARCH_UNICORE32:
|
|
case VIR_ARCH_XTENSA:
|
|
case VIR_ARCH_XTENSAEB:
|
|
case VIR_ARCH_NONE:
|
|
case VIR_ARCH_LAST:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (addDefaultUSB &&
|
|
virDomainControllerFind(def, VIR_DOMAIN_CONTROLLER_TYPE_USB, 0) < 0 &&
|
|
virDomainDefAddUSBController(def, 0, usbModel) < 0)
|
|
return -1;
|
|
|
|
if (addImplicitSATA &&
|
|
virDomainDefMaybeAddController(
|
|
def, VIR_DOMAIN_CONTROLLER_TYPE_SATA, 0, -1) < 0)
|
|
return -1;
|
|
|
|
pciRoot = virDomainControllerFind(def, VIR_DOMAIN_CONTROLLER_TYPE_PCI, 0);
|
|
|
|
/* NB: any machine that sets addPCIRoot to true must also return
|
|
* true from the function qemuDomainSupportsPCI().
|
|
*/
|
|
if (addPCIRoot) {
|
|
if (pciRoot >= 0) {
|
|
if (def->controllers[pciRoot]->model != VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("The PCI controller with index='0' must be "
|
|
"model='pci-root' for this machine type, "
|
|
"but model='%s' was found instead"),
|
|
virDomainControllerModelPCITypeToString(def->controllers[pciRoot]->model));
|
|
return -1;
|
|
}
|
|
} else if (!virDomainDefAddController(def, VIR_DOMAIN_CONTROLLER_TYPE_PCI, 0,
|
|
VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* When a machine has a pcie-root, make sure that there is always
|
|
* a dmi-to-pci-bridge controller added as bus 1, and a pci-bridge
|
|
* as bus 2, so that standard PCI devices can be connected
|
|
*
|
|
* NB: any machine that sets addPCIeRoot to true must also return
|
|
* true from the function qemuDomainSupportsPCI().
|
|
*/
|
|
if (addPCIeRoot) {
|
|
if (pciRoot >= 0) {
|
|
if (def->controllers[pciRoot]->model != VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("The PCI controller with index='0' must be "
|
|
"model='pcie-root' for this machine type, "
|
|
"but model='%s' was found instead"),
|
|
virDomainControllerModelPCITypeToString(def->controllers[pciRoot]->model));
|
|
return -1;
|
|
}
|
|
} else if (!virDomainDefAddController(def, VIR_DOMAIN_CONTROLLER_TYPE_PCI, 0,
|
|
VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (addDefaultMemballoon && !def->memballoon) {
|
|
virDomainMemballoonDefPtr memballoon;
|
|
if (VIR_ALLOC(memballoon) < 0)
|
|
return -1;
|
|
|
|
memballoon->model = VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO;
|
|
def->memballoon = memballoon;
|
|
}
|
|
|
|
if (STRPREFIX(def->os.machine, "s390-virtio") &&
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_S390) && def->memballoon)
|
|
def->memballoon->model = VIR_DOMAIN_MEMBALLOON_MODEL_NONE;
|
|
|
|
if (addDefaultUSBMouse) {
|
|
bool hasUSBTablet = false;
|
|
size_t j;
|
|
|
|
for (j = 0; j < def->ninputs; j++) {
|
|
if (def->inputs[j]->type == VIR_DOMAIN_INPUT_TYPE_TABLET &&
|
|
def->inputs[j]->bus == VIR_DOMAIN_INPUT_BUS_USB) {
|
|
hasUSBTablet = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Historically, we have automatically added USB keyboard and
|
|
* mouse to some guests. While the former device is generally
|
|
* safe to have, adding the latter is undesiderable if a USB
|
|
* tablet is already present in the guest */
|
|
if (hasUSBTablet)
|
|
addDefaultUSBMouse = false;
|
|
}
|
|
|
|
if (addDefaultUSBKBD &&
|
|
def->ngraphics > 0 &&
|
|
virDomainDefMaybeAddInput(def,
|
|
VIR_DOMAIN_INPUT_TYPE_KBD,
|
|
VIR_DOMAIN_INPUT_BUS_USB) < 0)
|
|
return -1;
|
|
|
|
if (addDefaultUSBMouse &&
|
|
def->ngraphics > 0 &&
|
|
virDomainDefMaybeAddInput(def,
|
|
VIR_DOMAIN_INPUT_TYPE_MOUSE,
|
|
VIR_DOMAIN_INPUT_BUS_USB) < 0)
|
|
return -1;
|
|
|
|
if (addPanicDevice) {
|
|
size_t j;
|
|
for (j = 0; j < def->npanics; j++) {
|
|
if (def->panics[j]->model == VIR_DOMAIN_PANIC_MODEL_DEFAULT ||
|
|
(ARCH_IS_PPC64(def->os.arch) &&
|
|
def->panics[j]->model == VIR_DOMAIN_PANIC_MODEL_PSERIES) ||
|
|
(ARCH_IS_S390(def->os.arch) &&
|
|
def->panics[j]->model == VIR_DOMAIN_PANIC_MODEL_S390))
|
|
break;
|
|
}
|
|
|
|
if (j == def->npanics) {
|
|
virDomainPanicDefPtr panic;
|
|
if (VIR_ALLOC(panic) < 0 ||
|
|
VIR_APPEND_ELEMENT_COPY(def->panics,
|
|
def->npanics, panic) < 0) {
|
|
VIR_FREE(panic);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDefEnableDefaultFeatures:
|
|
* @def: domain definition
|
|
* @qemuCaps: QEMU capabilities
|
|
*
|
|
* Make sure that features that should be enabled by default are actually
|
|
* enabled and configure default values related to those features.
|
|
*/
|
|
static void
|
|
qemuDomainDefEnableDefaultFeatures(virDomainDefPtr def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
/* The virt machine type always uses GIC: if the relevant information
|
|
* was not included in the domain XML, we need to choose a suitable
|
|
* GIC version ourselves */
|
|
if ((def->features[VIR_DOMAIN_FEATURE_GIC] == VIR_TRISTATE_SWITCH_ABSENT &&
|
|
qemuDomainIsARMVirt(def)) ||
|
|
(def->features[VIR_DOMAIN_FEATURE_GIC] == VIR_TRISTATE_SWITCH_ON &&
|
|
def->gic_version == VIR_GIC_VERSION_NONE)) {
|
|
virGICVersion version;
|
|
|
|
VIR_DEBUG("Looking for usable GIC version in domain capabilities");
|
|
for (version = VIR_GIC_VERSION_LAST - 1;
|
|
version > VIR_GIC_VERSION_NONE;
|
|
version--) {
|
|
|
|
/* We want to use the highest available GIC version for guests;
|
|
* however, the emulated GICv3 is currently lacking a MSI controller,
|
|
* making it unsuitable for the pure PCIe topology we aim for.
|
|
*
|
|
* For that reason, we skip this step entirely for TCG guests,
|
|
* and rely on the code below to pick the default version, GICv2,
|
|
* which supports all the features we need.
|
|
*
|
|
* See https://bugzilla.redhat.com/show_bug.cgi?id=1414081 */
|
|
if (version == VIR_GIC_VERSION_3 &&
|
|
def->virtType == VIR_DOMAIN_VIRT_QEMU) {
|
|
continue;
|
|
}
|
|
|
|
if (virQEMUCapsSupportsGICVersion(qemuCaps,
|
|
def->virtType,
|
|
version)) {
|
|
VIR_DEBUG("Using GIC version %s",
|
|
virGICVersionTypeToString(version));
|
|
def->gic_version = version;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Use the default GIC version (GICv2) as a last-ditch attempt
|
|
* if no match could be found above */
|
|
if (def->gic_version == VIR_GIC_VERSION_NONE) {
|
|
VIR_DEBUG("Using GIC version 2 (default)");
|
|
def->gic_version = VIR_GIC_VERSION_2;
|
|
}
|
|
|
|
/* Even if we haven't found a usable GIC version in the domain
|
|
* capabilities, we still want to enable this */
|
|
def->features[VIR_DOMAIN_FEATURE_GIC] = VIR_TRISTATE_SWITCH_ON;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
qemuCanonicalizeMachine(virDomainDefPtr def, virQEMUCapsPtr qemuCaps)
|
|
{
|
|
const char *canon;
|
|
|
|
if (!(canon = virQEMUCapsGetCanonicalMachine(qemuCaps, def->virtType,
|
|
def->os.machine)))
|
|
return 0;
|
|
|
|
if (STRNEQ(canon, def->os.machine)) {
|
|
char *tmp;
|
|
tmp = g_strdup(canon);
|
|
VIR_FREE(def->os.machine);
|
|
def->os.machine = tmp;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainRecheckInternalPaths(virDomainDefPtr def,
|
|
virQEMUDriverConfigPtr cfg,
|
|
unsigned int flags)
|
|
{
|
|
size_t i = 0;
|
|
size_t j = 0;
|
|
|
|
for (i = 0; i < def->ngraphics; ++i) {
|
|
virDomainGraphicsDefPtr graphics = def->graphics[i];
|
|
|
|
for (j = 0; j < graphics->nListens; ++j) {
|
|
virDomainGraphicsListenDefPtr glisten = &graphics->listens[j];
|
|
|
|
/* This will happen only if we parse XML from old libvirts where
|
|
* unix socket was available only for VNC graphics. In this
|
|
* particular case we should follow the behavior and if we remove
|
|
* the auto-generated socket based on config option from qemu.conf
|
|
* we need to change the listen type to address. */
|
|
if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC &&
|
|
glisten->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET &&
|
|
glisten->socket &&
|
|
!glisten->autoGenerated &&
|
|
STRPREFIX(glisten->socket, cfg->libDir)) {
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) {
|
|
VIR_FREE(glisten->socket);
|
|
glisten->type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS;
|
|
} else {
|
|
glisten->fromConfig = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefVcpusPostParse(virDomainDefPtr def)
|
|
{
|
|
unsigned int maxvcpus = virDomainDefGetVcpusMax(def);
|
|
virDomainVcpuDefPtr vcpu;
|
|
virDomainVcpuDefPtr prevvcpu;
|
|
size_t i;
|
|
bool has_order = false;
|
|
|
|
/* vcpu 0 needs to be present, first, and non-hotpluggable */
|
|
vcpu = virDomainDefGetVcpu(def, 0);
|
|
if (!vcpu->online) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("vcpu 0 can't be offline"));
|
|
return -1;
|
|
}
|
|
if (vcpu->hotpluggable == VIR_TRISTATE_BOOL_YES) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("vcpu0 can't be hotpluggable"));
|
|
return -1;
|
|
}
|
|
if (vcpu->order != 0 && vcpu->order != 1) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("vcpu0 must be enabled first"));
|
|
return -1;
|
|
}
|
|
|
|
if (vcpu->order != 0)
|
|
has_order = true;
|
|
|
|
prevvcpu = vcpu;
|
|
|
|
/* all online vcpus or non online vcpu need to have order set */
|
|
for (i = 1; i < maxvcpus; i++) {
|
|
vcpu = virDomainDefGetVcpu(def, i);
|
|
|
|
if (vcpu->online &&
|
|
(vcpu->order != 0) != has_order) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("all vcpus must have either set or unset order"));
|
|
return -1;
|
|
}
|
|
|
|
/* few conditions for non-hotpluggable (thus online) vcpus */
|
|
if (vcpu->hotpluggable == VIR_TRISTATE_BOOL_NO) {
|
|
/* they can be ordered only at the beginning */
|
|
if (prevvcpu->hotpluggable == VIR_TRISTATE_BOOL_YES) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("online non-hotpluggable vcpus need to be "
|
|
"ordered prior to hotplugable vcpus"));
|
|
return -1;
|
|
}
|
|
|
|
/* they need to be in order (qemu doesn't support any order yet).
|
|
* Also note that multiple vcpus may share order on some platforms */
|
|
if (prevvcpu->order > vcpu->order) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("online non-hotpluggable vcpus must be ordered "
|
|
"in ascending order"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
prevvcpu = vcpu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefSetDefaultCPU(virDomainDefPtr def,
|
|
virArch hostarch,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
const char *model;
|
|
|
|
if (def->cpu &&
|
|
(def->cpu->mode != VIR_CPU_MODE_CUSTOM ||
|
|
def->cpu->model))
|
|
return 0;
|
|
|
|
if (!virCPUArchIsSupported(def->os.arch))
|
|
return 0;
|
|
|
|
/* Default CPU model info from QEMU is usable for TCG only except for
|
|
* x86, s390, and ppc64. */
|
|
if (!ARCH_IS_X86(def->os.arch) &&
|
|
!ARCH_IS_S390(def->os.arch) &&
|
|
!ARCH_IS_PPC64(def->os.arch) &&
|
|
def->virtType != VIR_DOMAIN_VIRT_QEMU)
|
|
return 0;
|
|
|
|
model = virQEMUCapsGetMachineDefaultCPU(qemuCaps, def->os.machine, def->virtType);
|
|
if (!model) {
|
|
VIR_DEBUG("Unknown default CPU model for domain '%s'", def->name);
|
|
return 0;
|
|
}
|
|
|
|
if (STREQ(model, "host") && def->virtType != VIR_DOMAIN_VIRT_KVM) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("QEMU reports invalid default CPU model \"host\" "
|
|
"for non-kvm domain virt type"));
|
|
return -1;
|
|
}
|
|
|
|
if (!def->cpu)
|
|
def->cpu = virCPUDefNew();
|
|
|
|
def->cpu->type = VIR_CPU_TYPE_GUEST;
|
|
|
|
if (STREQ(model, "host")) {
|
|
if (ARCH_IS_S390(def->os.arch) &&
|
|
virQEMUCapsIsCPUModeSupported(qemuCaps, hostarch, def->virtType,
|
|
VIR_CPU_MODE_HOST_MODEL,
|
|
def->os.machine)) {
|
|
def->cpu->mode = VIR_CPU_MODE_HOST_MODEL;
|
|
} else {
|
|
def->cpu->mode = VIR_CPU_MODE_HOST_PASSTHROUGH;
|
|
}
|
|
|
|
VIR_DEBUG("Setting default CPU mode for domain '%s' to %s",
|
|
def->name, virCPUModeTypeToString(def->cpu->mode));
|
|
} else {
|
|
/* We need to turn off all CPU checks when the domain is started
|
|
* because the default CPU (e.g., qemu64) may not be runnable on any
|
|
* host. QEMU will just disable the unavailable features and we will
|
|
* update the CPU definition accordingly and set check to FULL when
|
|
* starting the domain. */
|
|
def->cpu->check = VIR_CPU_CHECK_NONE;
|
|
def->cpu->mode = VIR_CPU_MODE_CUSTOM;
|
|
def->cpu->match = VIR_CPU_MATCH_EXACT;
|
|
def->cpu->fallback = VIR_CPU_FALLBACK_FORBID;
|
|
def->cpu->model = g_strdup(model);
|
|
|
|
VIR_DEBUG("Setting default CPU model for domain '%s' to %s",
|
|
def->name, model);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefCPUPostParse(virDomainDefPtr def)
|
|
{
|
|
virCPUFeatureDefPtr sveFeature = NULL;
|
|
bool sveVectorLengthsProvided = false;
|
|
size_t i;
|
|
|
|
if (!def->cpu)
|
|
return 0;
|
|
|
|
if (def->cpu->cache) {
|
|
virCPUCacheDefPtr cache = def->cpu->cache;
|
|
|
|
if (!ARCH_IS_X86(def->os.arch)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("CPU cache specification is not supported "
|
|
"for '%s' architecture"),
|
|
virArchToString(def->os.arch));
|
|
return -1;
|
|
}
|
|
|
|
switch (cache->mode) {
|
|
case VIR_CPU_CACHE_MODE_EMULATE:
|
|
if (cache->level != 3) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("CPU cache mode '%s' can only be used with "
|
|
"level='3'"),
|
|
virCPUCacheModeTypeToString(cache->mode));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_CPU_CACHE_MODE_PASSTHROUGH:
|
|
if (def->cpu->mode != VIR_CPU_MODE_HOST_PASSTHROUGH) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("CPU cache mode '%s' can only be used with "
|
|
"'%s' CPUs"),
|
|
virCPUCacheModeTypeToString(cache->mode),
|
|
virCPUModeTypeToString(VIR_CPU_MODE_HOST_PASSTHROUGH));
|
|
return -1;
|
|
}
|
|
|
|
if (cache->level != -1) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported CPU cache level for mode '%s'"),
|
|
virCPUCacheModeTypeToString(cache->mode));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_CPU_CACHE_MODE_DISABLE:
|
|
if (cache->level != -1) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported CPU cache level for mode '%s'"),
|
|
virCPUCacheModeTypeToString(cache->mode));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_CPU_CACHE_MODE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < def->cpu->nfeatures; i++) {
|
|
virCPUFeatureDefPtr feature = &def->cpu->features[i];
|
|
|
|
if (STREQ(feature->name, "sve")) {
|
|
sveFeature = feature;
|
|
} else if (STRPREFIX(feature->name, "sve")) {
|
|
sveVectorLengthsProvided = true;
|
|
}
|
|
}
|
|
|
|
if (sveVectorLengthsProvided) {
|
|
if (sveFeature) {
|
|
if (sveFeature->policy == VIR_CPU_FEATURE_DISABLE ||
|
|
sveFeature->policy == VIR_CPU_FEATURE_FORBID) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("SVE disabled, but SVE vector lengths provided"));
|
|
return -1;
|
|
} else {
|
|
sveFeature->policy = VIR_CPU_FEATURE_REQUIRE;
|
|
}
|
|
} else {
|
|
if (VIR_RESIZE_N(def->cpu->features, def->cpu->nfeatures_max,
|
|
def->cpu->nfeatures, 1) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
def->cpu->features[def->cpu->nfeatures].name = g_strdup("sve");
|
|
def->cpu->features[def->cpu->nfeatures].policy = VIR_CPU_FEATURE_REQUIRE;
|
|
|
|
def->cpu->nfeatures++;
|
|
}
|
|
}
|
|
|
|
/* Nothing to be done if only CPU topology is specified. */
|
|
if (def->cpu->mode == VIR_CPU_MODE_CUSTOM &&
|
|
!def->cpu->model)
|
|
return 0;
|
|
|
|
if (def->cpu->check != VIR_CPU_CHECK_DEFAULT)
|
|
return 0;
|
|
|
|
switch ((virCPUMode) def->cpu->mode) {
|
|
case VIR_CPU_MODE_HOST_PASSTHROUGH:
|
|
def->cpu->check = VIR_CPU_CHECK_NONE;
|
|
break;
|
|
|
|
case VIR_CPU_MODE_HOST_MODEL:
|
|
def->cpu->check = VIR_CPU_CHECK_PARTIAL;
|
|
break;
|
|
|
|
case VIR_CPU_MODE_CUSTOM:
|
|
/* Custom CPUs in TCG mode are not compared to host CPU by default. */
|
|
if (def->virtType == VIR_DOMAIN_VIRT_QEMU)
|
|
def->cpu->check = VIR_CPU_CHECK_NONE;
|
|
else
|
|
def->cpu->check = VIR_CPU_CHECK_PARTIAL;
|
|
break;
|
|
|
|
case VIR_CPU_MODE_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefTsegPostParse(virDomainDefPtr def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (def->features[VIR_DOMAIN_FEATURE_SMM] != VIR_TRISTATE_SWITCH_ON)
|
|
return 0;
|
|
|
|
if (!def->tseg_specified)
|
|
return 0;
|
|
|
|
if (!qemuDomainIsQ35(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("SMM TSEG is only supported with q35 machine type"));
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MCH_EXTENDED_TSEG_MBYTES)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Setting TSEG size is not supported with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
if (def->tseg_size & ((1 << 20) - 1)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("SMM TSEG size must be divisible by 1 MiB"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefPostParseBasic(virDomainDefPtr def,
|
|
void *opaque G_GNUC_UNUSED)
|
|
{
|
|
virQEMUDriverPtr driver = opaque;
|
|
|
|
/* check for emulator and create a default one if needed */
|
|
if (!def->emulator) {
|
|
if (!(def->emulator = virQEMUCapsGetDefaultEmulator(
|
|
driver->hostarch, def->os.arch))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("No emulator found for arch '%s'"),
|
|
virArchToString(def->os.arch));
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefPostParse(virDomainDefPtr def,
|
|
unsigned int parseFlags,
|
|
void *opaque,
|
|
void *parseOpaque)
|
|
{
|
|
virQEMUDriverPtr driver = opaque;
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
virQEMUCapsPtr qemuCaps = parseOpaque;
|
|
|
|
/* Note that qemuCaps may be NULL when this function is called. This
|
|
* function shall not fail in that case. It will be re-run on VM startup
|
|
* with the capabilities populated.
|
|
*/
|
|
if (!qemuCaps)
|
|
return 1;
|
|
|
|
if (def->os.bootloader || def->os.bootloaderArgs) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("bootloader is not supported by QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
if (!def->os.machine) {
|
|
const char *machine = virQEMUCapsGetPreferredMachine(qemuCaps,
|
|
def->virtType);
|
|
def->os.machine = g_strdup(machine);
|
|
}
|
|
|
|
qemuDomainNVRAMPathGenerate(cfg, def);
|
|
|
|
if (qemuDomainDefAddDefaultDevices(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (qemuCanonicalizeMachine(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefSetDefaultCPU(def, driver->hostarch, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
qemuDomainDefEnableDefaultFeatures(def, qemuCaps);
|
|
|
|
if (qemuDomainRecheckInternalPaths(def, cfg, parseFlags) < 0)
|
|
return -1;
|
|
|
|
if (qemuSecurityVerify(driver->securityManager, def) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefVcpusPostParse(def) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefCPUPostParse(def) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefTsegPostParse(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDefGetVcpuHotplugGranularity:
|
|
* @def: domain definition
|
|
*
|
|
* With QEMU 2.7 and newer, vCPUs can only be hotplugged in groups that
|
|
* respect the guest's hotplug granularity; because of that, QEMU will
|
|
* not allow guests to start unless the initial number of vCPUs is a
|
|
* multiple of the hotplug granularity.
|
|
*
|
|
* Returns the vCPU hotplug granularity.
|
|
*/
|
|
static unsigned int
|
|
qemuDomainDefGetVcpuHotplugGranularity(const virDomainDef *def)
|
|
{
|
|
/* If the guest CPU topology has not been configured, assume we
|
|
* can hotplug vCPUs one at a time */
|
|
if (!def->cpu || def->cpu->sockets == 0)
|
|
return 1;
|
|
|
|
/* For pSeries guests, hotplug can only be performed one core
|
|
* at a time, so the vCPU hotplug granularity is the number
|
|
* of threads per core */
|
|
if (qemuDomainIsPSeries(def))
|
|
return def->cpu->threads;
|
|
|
|
/* In all other cases, we can hotplug vCPUs one at a time */
|
|
return 1;
|
|
}
|
|
|
|
|
|
#define QEMU_MAX_VCPUS_WITHOUT_EIM 255
|
|
|
|
|
|
static int
|
|
qemuDomainDefValidatePSeriesFeature(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps,
|
|
int feature)
|
|
{
|
|
const char *str;
|
|
|
|
if (def->features[feature] != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!qemuDomainIsPSeries(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The '%s' feature is not supported for "
|
|
"architecture '%s' or machine type '%s'"),
|
|
virDomainFeatureTypeToString(feature),
|
|
virArchToString(def->os.arch),
|
|
def->os.machine);
|
|
return -1;
|
|
}
|
|
|
|
if (def->features[feature] == VIR_TRISTATE_SWITCH_ABSENT)
|
|
return 0;
|
|
|
|
switch (feature) {
|
|
case VIR_DOMAIN_FEATURE_HPT:
|
|
if (def->features[feature] != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
if (def->hpt_resizing != VIR_DOMAIN_HPT_RESIZING_NONE) {
|
|
if (!virQEMUCapsGet(qemuCaps,
|
|
QEMU_CAPS_MACHINE_PSERIES_RESIZE_HPT)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("HTP resizing is not supported by this "
|
|
"QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
str = virDomainHPTResizingTypeToString(def->hpt_resizing);
|
|
if (!str) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Invalid setting for HPT resizing"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (def->hpt_maxpagesize > 0 &&
|
|
!virQEMUCapsGet(qemuCaps,
|
|
QEMU_CAPS_MACHINE_PSERIES_CAP_HPT_MAX_PAGE_SIZE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Configuring the page size for HPT guests "
|
|
"is not supported by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_HTM:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_PSERIES_CAP_HTM)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("HTM configuration is not supported by this "
|
|
"QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
str = virTristateSwitchTypeToString(def->features[feature]);
|
|
if (!str) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Invalid setting for HTM state"));
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_NESTED_HV:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_PSERIES_CAP_NESTED_HV)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Nested HV configuration is not supported by "
|
|
"this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
str = virTristateSwitchTypeToString(def->features[feature]);
|
|
if (!str) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Invalid setting for nested HV state"));
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_CCF_ASSIST:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_PSERIES_CAP_CCF_ASSIST)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("ccf-assist configuration is not supported by "
|
|
"this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
str = virTristateSwitchTypeToString(def->features[feature]);
|
|
if (!str) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Invalid setting for ccf-assist state"));
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuDomainDefValidateFeatures(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < VIR_DOMAIN_FEATURE_LAST; i++) {
|
|
const char *featureName = virDomainFeatureTypeToString(i);
|
|
|
|
switch ((virDomainFeature) i) {
|
|
case VIR_DOMAIN_FEATURE_IOAPIC:
|
|
if (def->features[i] != VIR_DOMAIN_IOAPIC_NONE) {
|
|
if (!ARCH_IS_X86(def->os.arch)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The '%s' feature is not supported for "
|
|
"architecture '%s' or machine type '%s'"),
|
|
featureName,
|
|
virArchToString(def->os.arch),
|
|
def->os.machine);
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_KERNEL_IRQCHIP)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("I/O APIC tuning is not supported by "
|
|
"this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
switch ((virDomainIOAPIC) def->features[i]) {
|
|
case VIR_DOMAIN_IOAPIC_QEMU:
|
|
if (!virQEMUCapsGet(qemuCaps,
|
|
QEMU_CAPS_MACHINE_KERNEL_IRQCHIP_SPLIT)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("split I/O APIC is not supported by this "
|
|
"QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_IOAPIC_KVM:
|
|
case VIR_DOMAIN_IOAPIC_NONE:
|
|
case VIR_DOMAIN_IOAPIC_LAST:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_HPT:
|
|
case VIR_DOMAIN_FEATURE_HTM:
|
|
case VIR_DOMAIN_FEATURE_NESTED_HV:
|
|
case VIR_DOMAIN_FEATURE_CCF_ASSIST:
|
|
if (qemuDomainDefValidatePSeriesFeature(def, qemuCaps, i) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_GIC:
|
|
if (def->features[i] == VIR_TRISTATE_SWITCH_ON &&
|
|
!qemuDomainIsARMVirt(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The '%s' feature is not supported for "
|
|
"architecture '%s' or machine type '%s'"),
|
|
featureName,
|
|
virArchToString(def->os.arch),
|
|
def->os.machine);
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_SMM:
|
|
if (def->features[i] != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_SMM_OPT)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("smm is not available with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_KVM:
|
|
if (def->kvm_features[VIR_DOMAIN_KVM_DEDICATED] == VIR_TRISTATE_SWITCH_ON &&
|
|
(!def->cpu || def->cpu->mode != VIR_CPU_MODE_HOST_PASSTHROUGH)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("kvm-hint-dedicated=on is only applicable "
|
|
"for cpu host-passthrough"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_VMPORT:
|
|
if (def->features[i] != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!virQEMUCapsSupportsVmport(qemuCaps, def)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("vmport is not available "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_VMCOREINFO:
|
|
if (def->features[i] == VIR_TRISTATE_SWITCH_ON &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VMCOREINFO)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("vmcoreinfo is not available "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_ACPI:
|
|
case VIR_DOMAIN_FEATURE_APIC:
|
|
case VIR_DOMAIN_FEATURE_PAE:
|
|
case VIR_DOMAIN_FEATURE_HAP:
|
|
case VIR_DOMAIN_FEATURE_VIRIDIAN:
|
|
case VIR_DOMAIN_FEATURE_PRIVNET:
|
|
case VIR_DOMAIN_FEATURE_HYPERV:
|
|
case VIR_DOMAIN_FEATURE_PVSPINLOCK:
|
|
case VIR_DOMAIN_FEATURE_CAPABILITIES:
|
|
case VIR_DOMAIN_FEATURE_PMU:
|
|
case VIR_DOMAIN_FEATURE_MSRS:
|
|
case VIR_DOMAIN_FEATURE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefValidateMemory(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
const long system_page_size = virGetSystemPageSizeKB();
|
|
const virDomainMemtune *mem = &def->mem;
|
|
|
|
if (mem->nhugepages == 0)
|
|
return 0;
|
|
|
|
if (mem->allocation == VIR_DOMAIN_MEMORY_ALLOCATION_ONDEMAND) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("hugepages are not allowed with memory "
|
|
"allocation ondemand"));
|
|
return -1;
|
|
}
|
|
|
|
if (mem->source == VIR_DOMAIN_MEMORY_SOURCE_ANONYMOUS) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("hugepages are not allowed with anonymous "
|
|
"memory source"));
|
|
return -1;
|
|
}
|
|
|
|
if (mem->source == VIR_DOMAIN_MEMORY_SOURCE_MEMFD &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_MEMORY_MEMFD_HUGETLB)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("hugepages is not supported with memfd memory source"));
|
|
return -1;
|
|
}
|
|
|
|
/* We can't guarantee any other mem.access
|
|
* if no guest NUMA nodes are defined. */
|
|
if (mem->hugepages[0].size != system_page_size &&
|
|
virDomainNumaGetNodeCount(def->numa) == 0 &&
|
|
mem->access != VIR_DOMAIN_MEMORY_ACCESS_DEFAULT &&
|
|
mem->access != VIR_DOMAIN_MEMORY_ACCESS_PRIVATE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("memory access mode '%s' not supported "
|
|
"without guest numa node"),
|
|
virDomainMemoryAccessTypeToString(mem->access));
|
|
return -1;
|
|
}
|
|
|
|
if (mem->nosharepages && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_MEM_MERGE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("disable shared memory is not available "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefValidateNuma(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
const long system_page_size = virGetSystemPageSizeKB();
|
|
size_t ncells = virDomainNumaGetNodeCount(def->numa);
|
|
size_t i;
|
|
bool hasMemoryCap = virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_MEMORY_RAM) ||
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_MEMORY_FILE) ||
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_MEMORY_MEMFD);
|
|
|
|
if (virDomainNumatuneHasPerNodeBinding(def->numa) && !hasMemoryCap) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Per-node memory binding is not supported "
|
|
"with this QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
if (def->mem.nhugepages &&
|
|
def->mem.hugepages[0].size != system_page_size &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_MEMORY_FILE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("huge pages per NUMA node are not "
|
|
"supported with this QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < ncells; i++) {
|
|
g_autofree char * cpumask = NULL;
|
|
|
|
if (!hasMemoryCap &&
|
|
virDomainNumaGetNodeMemoryAccessMode(def->numa, i)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Shared memory mapping is not supported "
|
|
"with this QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(cpumask = virBitmapFormat(virDomainNumaGetNodeCpumask(def->numa, i))))
|
|
return -1;
|
|
|
|
if (strchr(cpumask, ',') &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_NUMA)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("disjoint NUMA cpu ranges are not supported "
|
|
"with this QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
if (virDomainNumaNodesDistancesAreBeingSet(def->numa) &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_NUMA_DIST)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("setting NUMA distances is not "
|
|
"supported with this qemu"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainValidateCpuCount(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
unsigned int maxCpus = virQEMUCapsGetMachineMaxCpus(qemuCaps, def->virtType,
|
|
def->os.machine);
|
|
|
|
if (virDomainDefGetVcpus(def) == 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Domain requires at least 1 vCPU"));
|
|
return -1;
|
|
}
|
|
|
|
if (maxCpus > 0 && virDomainDefGetVcpusMax(def) > maxCpus) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Maximum CPUs greater than specified machine "
|
|
"type limit %u"), maxCpus);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateNVRAM(virDomainNVRAMDefPtr nvram,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (!nvram)
|
|
return 0;
|
|
|
|
if (qemuDomainIsPSeries(def)) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_NVRAM)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("nvram device is not supported by "
|
|
"this QEMU binary"));
|
|
return -1;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("nvram device is only supported for PPC64"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(nvram->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO &&
|
|
nvram->info.addr.spaprvio.has_reg)) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("nvram address type must be spaprvio"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateHub(virDomainHubDefPtr hub,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (hub->type != VIR_DOMAIN_HUB_TYPE_USB) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("hub type %s not supported"),
|
|
virDomainHubTypeToString(hub->type));
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_USB_HUB)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("usb-hub not supported by QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefValidateClockTimers(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->clock.ntimers; i++) {
|
|
virDomainTimerDefPtr timer = def->clock.timers[i];
|
|
|
|
switch ((virDomainTimerNameType)timer->name) {
|
|
case VIR_DOMAIN_TIMER_NAME_PLATFORM:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported timer type (name) '%s'"),
|
|
virDomainTimerNameTypeToString(timer->name));
|
|
return -1;
|
|
|
|
case VIR_DOMAIN_TIMER_NAME_TSC:
|
|
case VIR_DOMAIN_TIMER_NAME_KVMCLOCK:
|
|
case VIR_DOMAIN_TIMER_NAME_HYPERVCLOCK:
|
|
case VIR_DOMAIN_TIMER_NAME_LAST:
|
|
break;
|
|
|
|
case VIR_DOMAIN_TIMER_NAME_RTC:
|
|
switch (timer->track) {
|
|
case -1: /* unspecified - use hypervisor default */
|
|
case VIR_DOMAIN_TIMER_TRACK_GUEST:
|
|
case VIR_DOMAIN_TIMER_TRACK_WALL:
|
|
break;
|
|
case VIR_DOMAIN_TIMER_TRACK_BOOT:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported rtc timer track '%s'"),
|
|
virDomainTimerTrackTypeToString(timer->track));
|
|
return -1;
|
|
}
|
|
|
|
switch (timer->tickpolicy) {
|
|
case -1:
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_DELAY:
|
|
/* This is the default - missed ticks delivered when
|
|
next scheduled, at normal rate */
|
|
break;
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_CATCHUP:
|
|
/* deliver ticks at a faster rate until caught up */
|
|
break;
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_MERGE:
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_DISCARD:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported rtc timer tickpolicy '%s'"),
|
|
virDomainTimerTickpolicyTypeToString(
|
|
timer->tickpolicy));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_TIMER_NAME_PIT:
|
|
switch (timer->tickpolicy) {
|
|
case -1:
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_DELAY:
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_DISCARD:
|
|
break;
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_CATCHUP:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_KVM_PIT_TICK_POLICY)) {
|
|
/* can't catchup if we don't have kvm-pit */
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported pit tickpolicy '%s'"),
|
|
virDomainTimerTickpolicyTypeToString(
|
|
timer->tickpolicy));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_MERGE:
|
|
/* no way to support this mode for pit in qemu */
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported pit tickpolicy '%s'"),
|
|
virDomainTimerTickpolicyTypeToString(
|
|
timer->tickpolicy));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_TIMER_NAME_HPET:
|
|
/* no hpet timer available. The only possible action
|
|
is to raise an error if present="yes" */
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_NO_HPET) &&
|
|
timer->present == 1) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("hpet timer is not supported"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_TIMER_NAME_ARMVTIMER:
|
|
if (def->virtType != VIR_DOMAIN_VIRT_KVM ||
|
|
!qemuDomainIsARMVirt(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Configuring the '%s' timer is not supported "
|
|
"for virtType=%s arch=%s machine=%s guests"),
|
|
virDomainTimerNameTypeToString(timer->name),
|
|
virDomainVirtTypeToString(def->virtType),
|
|
virArchToString(def->os.arch),
|
|
def->os.machine);
|
|
return -1;
|
|
}
|
|
if (timer->present == 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The '%s' timer can't be disabled"),
|
|
virDomainTimerNameTypeToString(timer->name));
|
|
return -1;
|
|
}
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CPU_KVM_NO_ADJVTIME)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Configuring the '%s' timer is not supported "
|
|
"with this QEMU binary"),
|
|
virDomainTimerNameTypeToString(timer->name));
|
|
return -1;
|
|
}
|
|
|
|
switch (timer->tickpolicy) {
|
|
case -1:
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_DELAY:
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_DISCARD:
|
|
break;
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_CATCHUP:
|
|
case VIR_DOMAIN_TIMER_TICKPOLICY_MERGE:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The '%s' timer does not support tickpolicy '%s'"),
|
|
virDomainTimerNameTypeToString(timer->name),
|
|
virDomainTimerTickpolicyTypeToString(timer->tickpolicy));
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefValidatePM(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
bool q35Dom = qemuDomainIsQ35(def);
|
|
|
|
if (def->pm.s3) {
|
|
bool q35ICH9_S3 = q35Dom &&
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_ICH9_DISABLE_S3);
|
|
|
|
if (!q35ICH9_S3 && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_PIIX_DISABLE_S3)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("setting ACPI S3 not supported"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (def->pm.s4) {
|
|
bool q35ICH9_S4 = q35Dom &&
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_ICH9_DISABLE_S4);
|
|
|
|
if (!q35ICH9_S4 && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_PIIX_DISABLE_S4)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("setting ACPI S4 not supported"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefValidateBoot(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (def->os.bios.rt_set) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_REBOOT_TIMEOUT)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("reboot timeout is not supported "
|
|
"by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (def->os.bm_timeout_set) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPLASH_TIMEOUT)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("splash timeout is not supported "
|
|
"by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuDomainDefValidateConsole(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
size_t i;
|
|
|
|
/* Explicit console devices */
|
|
for (i = 0; i < def->nconsoles; i++) {
|
|
virDomainChrDefPtr console = def->consoles[i];
|
|
|
|
switch (console->targetType) {
|
|
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SCLP:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_SCLPCONSOLE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("sclpconsole is not supported in this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SCLPLM:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_SCLPLMCONSOLE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("sclplmconsole is not supported in this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_VIRTIO:
|
|
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL:
|
|
break;
|
|
|
|
default:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported console target type %s"),
|
|
NULLSTR(virDomainChrConsoleTargetTypeToString(console->targetType)));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuSoundCodecTypeToCaps(int type)
|
|
{
|
|
switch (type) {
|
|
case VIR_DOMAIN_SOUND_CODEC_TYPE_DUPLEX:
|
|
return QEMU_CAPS_HDA_DUPLEX;
|
|
case VIR_DOMAIN_SOUND_CODEC_TYPE_MICRO:
|
|
return QEMU_CAPS_HDA_MICRO;
|
|
case VIR_DOMAIN_SOUND_CODEC_TYPE_OUTPUT:
|
|
return QEMU_CAPS_HDA_OUTPUT;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateSound(virDomainSoundDefPtr sound,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
size_t i;
|
|
|
|
switch ((virDomainSoundModel) sound->model) {
|
|
case VIR_DOMAIN_SOUND_MODEL_USB:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_USB_AUDIO)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("usb-audio controller is not supported "
|
|
"by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_SOUND_MODEL_ICH9:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_ICH9_INTEL_HDA)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("The ich9-intel-hda audio controller "
|
|
"is not supported in this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_SOUND_MODEL_ES1370:
|
|
case VIR_DOMAIN_SOUND_MODEL_AC97:
|
|
case VIR_DOMAIN_SOUND_MODEL_ICH6:
|
|
case VIR_DOMAIN_SOUND_MODEL_SB16:
|
|
case VIR_DOMAIN_SOUND_MODEL_PCSPK:
|
|
break;
|
|
case VIR_DOMAIN_SOUND_MODEL_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("sound card model '%s' is not supported by qemu"),
|
|
virDomainSoundModelTypeToString(sound->model));
|
|
return -1;
|
|
}
|
|
|
|
if (sound->model == VIR_DOMAIN_SOUND_MODEL_ICH6 ||
|
|
sound->model == VIR_DOMAIN_SOUND_MODEL_ICH9) {
|
|
for (i = 0; i < sound->ncodecs; i++) {
|
|
const char *stype;
|
|
int type, flags;
|
|
|
|
type = sound->codecs[i]->type;
|
|
stype = qemuSoundCodecTypeToString(type);
|
|
flags = qemuSoundCodecTypeToCaps(type);
|
|
|
|
if (flags == -1 || !virQEMUCapsGet(qemuCaps, flags)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("%s not supported in this QEMU binary"), stype);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateMemory(virDomainMemoryDefPtr mem,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (mem->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_NVDIMM)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("nvdimm isn't supported by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuDomainDefValidate(const virDomainDef *def,
|
|
void *opaque)
|
|
{
|
|
virQEMUDriverPtr driver = opaque;
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
g_autoptr(virQEMUCaps) qemuCaps = NULL;
|
|
size_t i;
|
|
|
|
if (!(qemuCaps = virQEMUCapsCacheLookup(driver->qemuCapsCache,
|
|
def->emulator)))
|
|
return -1;
|
|
|
|
if (def->os.type != VIR_DOMAIN_OSTYPE_HVM) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Emulator '%s' does not support os type '%s'"),
|
|
def->emulator, virDomainOSTypeToString(def->os.type));
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsIsArchSupported(qemuCaps, def->os.arch)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Emulator '%s' does not support arch '%s'"),
|
|
def->emulator, virArchToString(def->os.arch));
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsIsVirtTypeSupported(qemuCaps, def->virtType)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Emulator '%s' does not support virt type '%s'"),
|
|
def->emulator, virDomainVirtTypeToString(def->virtType));
|
|
return -1;
|
|
}
|
|
|
|
if (qemuCaps &&
|
|
!virQEMUCapsIsMachineSupported(qemuCaps, def->virtType, def->os.machine)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Emulator '%s' does not support machine type '%s'"),
|
|
def->emulator, def->os.machine);
|
|
return -1;
|
|
}
|
|
|
|
if (def->mem.min_guarantee) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Parameter 'min_guarantee' not supported by QEMU."));
|
|
return -1;
|
|
}
|
|
|
|
/* On x86, UEFI requires ACPI */
|
|
if ((def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_EFI ||
|
|
virDomainDefHasOldStyleUEFI(def)) &&
|
|
ARCH_IS_X86(def->os.arch) &&
|
|
def->features[VIR_DOMAIN_FEATURE_ACPI] != VIR_TRISTATE_SWITCH_ON) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("UEFI requires ACPI on this architecture"));
|
|
return -1;
|
|
}
|
|
|
|
/* On aarch64, ACPI requires UEFI */
|
|
if (def->features[VIR_DOMAIN_FEATURE_ACPI] == VIR_TRISTATE_SWITCH_ON &&
|
|
def->os.arch == VIR_ARCH_AARCH64 &&
|
|
(def->os.firmware != VIR_DOMAIN_OS_DEF_FIRMWARE_EFI &&
|
|
!virDomainDefHasOldStyleUEFI(def))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("ACPI requires UEFI on this architecture"));
|
|
return -1;
|
|
}
|
|
|
|
if (def->os.loader &&
|
|
def->os.loader->secure == VIR_TRISTATE_BOOL_YES) {
|
|
/* These are the QEMU implementation limitations. But we
|
|
* have to live with them for now. */
|
|
|
|
if (!qemuDomainIsQ35(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Secure boot is supported with q35 machine types only"));
|
|
return -1;
|
|
}
|
|
|
|
/* Now, technically it is possible to have secure boot on
|
|
* 32bits too, but that would require some -cpu xxx magic
|
|
* too. Not worth it unless we are explicitly asked. */
|
|
if (def->os.arch != VIR_ARCH_X86_64) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Secure boot is supported for x86_64 architecture only"));
|
|
return -1;
|
|
}
|
|
|
|
/* SMM will be enabled by qemuFirmwareFillDomain() if needed. */
|
|
if (def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_NONE &&
|
|
def->features[VIR_DOMAIN_FEATURE_SMM] != VIR_TRISTATE_SWITCH_ON) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Secure boot requires SMM feature enabled"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (def->genidRequested &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VMGENID)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("this QEMU does not support the 'genid' capability"));
|
|
return -1;
|
|
}
|
|
|
|
/* Serial graphics adapter */
|
|
if (def->os.bios.useserial == VIR_TRISTATE_BOOL_YES) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SGA)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("qemu does not support SGA"));
|
|
return -1;
|
|
}
|
|
if (!def->nserials) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("need at least one serial port to use SGA"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (qemuDomainDefValidateClockTimers(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefValidatePM(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefValidateBoot(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
/* QEMU 2.7 (detected via the availability of query-hotpluggable-cpus)
|
|
* enforces stricter rules than previous versions when it comes to guest
|
|
* CPU topology. Verify known constraints are respected */
|
|
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_QUERY_HOTPLUGGABLE_CPUS)) {
|
|
unsigned int topologycpus;
|
|
unsigned int granularity;
|
|
unsigned int numacpus;
|
|
|
|
/* Starting from QEMU 2.5, max vCPU count and overall vCPU topology
|
|
* must agree. We only actually enforce this with QEMU 2.7+, due
|
|
* to the capability check above */
|
|
if (virDomainDefGetVcpusTopology(def, &topologycpus) == 0) {
|
|
if (topologycpus != virDomainDefGetVcpusMax(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("CPU topology doesn't match maximum vcpu count"));
|
|
return -1;
|
|
}
|
|
|
|
numacpus = virDomainNumaGetCPUCountTotal(def->numa);
|
|
if ((numacpus != 0) && (topologycpus != numacpus)) {
|
|
VIR_WARN("CPU topology doesn't match numa CPU count; "
|
|
"partial NUMA mapping is obsoleted and will "
|
|
"be removed in future");
|
|
}
|
|
}
|
|
|
|
/* vCPU hotplug granularity must be respected */
|
|
granularity = qemuDomainDefGetVcpuHotplugGranularity(def);
|
|
if ((virDomainDefGetVcpus(def) % granularity) != 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("vCPUs count must be a multiple of the vCPU "
|
|
"hotplug granularity (%u)"),
|
|
granularity);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (qemuDomainValidateCpuCount(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (ARCH_IS_X86(def->os.arch) &&
|
|
virDomainDefGetVcpusMax(def) > QEMU_MAX_VCPUS_WITHOUT_EIM) {
|
|
if (!qemuDomainIsQ35(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("more than %d vCPUs are only supported on "
|
|
"q35-based machine types"),
|
|
QEMU_MAX_VCPUS_WITHOUT_EIM);
|
|
return -1;
|
|
}
|
|
if (!def->iommu || def->iommu->eim != VIR_TRISTATE_SWITCH_ON) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("more than %d vCPUs require extended interrupt "
|
|
"mode enabled on the iommu device"),
|
|
QEMU_MAX_VCPUS_WITHOUT_EIM);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (def->nresctrls &&
|
|
def->virtType != VIR_DOMAIN_VIRT_KVM) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("cachetune is only supported for KVM domains"));
|
|
return -1;
|
|
}
|
|
|
|
if (qemuDomainDefValidateFeatures(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefValidateMemory(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefValidateNuma(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainDefValidateConsole(def, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (cfg->vncTLS && cfg->vncTLSx509secretUUID &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_TLS_CREDS_X509)) {
|
|
for (i = 0; i < def->ngraphics; i++) {
|
|
if (def->graphics[i]->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("encrypted VNC TLS keys are not supported with "
|
|
"this QEMU binary"));
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainNetSupportsCoalesce(virDomainNetType type)
|
|
{
|
|
switch (type) {
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
return true;
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainChrSourceReconnectDefValidate(const virDomainChrSourceReconnectDef *def)
|
|
{
|
|
if (def->enabled == VIR_TRISTATE_BOOL_YES &&
|
|
def->timeout == 0) {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("chardev reconnect source timeout cannot be '0'"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainChrSourceDefValidate(const virDomainChrSourceDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
switch ((virDomainChrType)def->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_TCP:
|
|
if (qemuDomainChrSourceReconnectDefValidate(&def->data.tcp.reconnect) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
if (qemuDomainChrSourceReconnectDefValidate(&def->data.nix.reconnect) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
if (def->data.file.append != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV_FILE_APPEND)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("append not supported in this QEMU binary"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_NULL:
|
|
case VIR_DOMAIN_CHR_TYPE_VC:
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
case VIR_DOMAIN_CHR_TYPE_STDIO:
|
|
case VIR_DOMAIN_CHR_TYPE_UDP:
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
case VIR_DOMAIN_CHR_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (def->logfile) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV_LOGFILE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("logfile not supported in this QEMU binary"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainChrSerialTargetTypeToAddressType(int targetType)
|
|
{
|
|
switch ((virDomainChrSerialTargetType)targetType) {
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA:
|
|
return VIR_DOMAIN_DEVICE_ADDRESS_TYPE_ISA;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB:
|
|
return VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_PCI:
|
|
return VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SPAPR_VIO:
|
|
return VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SYSTEM:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SCLP:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_LAST:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE:
|
|
break;
|
|
}
|
|
|
|
return VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainChrSerialTargetModelToTargetType(int targetModel)
|
|
{
|
|
switch ((virDomainChrSerialTargetModel) targetModel) {
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_ISA_SERIAL:
|
|
return VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_USB_SERIAL:
|
|
return VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_PCI_SERIAL:
|
|
return VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_PCI;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SPAPR_VTY:
|
|
return VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SPAPR_VIO;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_PL011:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_16550A:
|
|
return VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SYSTEM;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SCLPCONSOLE:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SCLPLMCONSOLE:
|
|
return VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SCLP;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_NONE:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_LAST:
|
|
break;
|
|
}
|
|
|
|
return VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainChrTargetDefValidate(const virDomainChrDef *chr)
|
|
{
|
|
int expected;
|
|
|
|
switch ((virDomainChrDeviceType)chr->deviceType) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
|
|
|
|
/* Validate target type */
|
|
switch ((virDomainChrSerialTargetType)chr->targetType) {
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_PCI:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SPAPR_VIO:
|
|
|
|
expected = qemuDomainChrSerialTargetTypeToAddressType(chr->targetType);
|
|
|
|
if (chr->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
chr->info.type != expected) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target type '%s' requires address type '%s'"),
|
|
virDomainChrSerialTargetTypeToString(chr->targetType),
|
|
virDomainDeviceAddressTypeToString(expected));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SYSTEM:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SCLP:
|
|
if (chr->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target type '%s' cannot have an "
|
|
"associated address"),
|
|
virDomainChrSerialTargetTypeToString(chr->targetType));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
/* Validate target model */
|
|
switch ((virDomainChrSerialTargetModel) chr->targetModel) {
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_ISA_SERIAL:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_USB_SERIAL:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_PCI_SERIAL:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SPAPR_VTY:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_PL011:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SCLPCONSOLE:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SCLPLMCONSOLE:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_16550A:
|
|
|
|
expected = qemuDomainChrSerialTargetModelToTargetType(chr->targetModel);
|
|
|
|
if (chr->targetType != expected) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target model '%s' requires target type '%s'"),
|
|
virDomainChrSerialTargetModelTypeToString(chr->targetModel),
|
|
virDomainChrSerialTargetTypeToString(expected));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_NONE:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_LAST:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
|
|
/* Nothing to do */
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainChrDefValidate(const virDomainChrDef *dev,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (qemuDomainChrSourceDefValidate(dev->source, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainChrTargetDefValidate(dev) < 0)
|
|
return -1;
|
|
|
|
if (dev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL &&
|
|
(ARCH_IS_S390(def->os.arch) || qemuDomainIsPSeries(def))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("parallel ports are not supported"));
|
|
return -1;
|
|
}
|
|
|
|
if (dev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL) {
|
|
bool isCompatible = true;
|
|
|
|
if (dev->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SYSTEM) {
|
|
if (dev->targetModel == VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_PL011 &&
|
|
!qemuDomainIsARMVirt(def)) {
|
|
isCompatible = false;
|
|
}
|
|
if (dev->targetModel == VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_16550A &&
|
|
!qemuDomainIsRISCVVirt(def)) {
|
|
isCompatible = false;
|
|
}
|
|
}
|
|
|
|
if (!qemuDomainIsPSeries(def) &&
|
|
(dev->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SPAPR_VIO ||
|
|
dev->targetModel == VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SPAPR_VTY)) {
|
|
isCompatible = false;
|
|
}
|
|
|
|
if (!ARCH_IS_S390(def->os.arch) &&
|
|
(dev->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SCLP ||
|
|
dev->targetModel == VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SCLPCONSOLE ||
|
|
dev->targetModel == VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SCLPLMCONSOLE)) {
|
|
isCompatible = false;
|
|
}
|
|
|
|
if (!isCompatible) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Serial device with target type '%s' and "
|
|
"target model '%s' not compatible with guest "
|
|
"architecture or machine type"),
|
|
virDomainChrSerialTargetTypeToString(dev->targetType),
|
|
virDomainChrSerialTargetModelTypeToString(dev->targetModel));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSmartcardDefValidate(const virDomainSmartcardDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CCID_EMULATED)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("this QEMU binary lacks smartcard host "
|
|
"mode support"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CCID_EMULATED)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("this QEMU binary lacks smartcard host "
|
|
"mode support"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CCID_PASSTHRU)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("this QEMU binary lacks smartcard "
|
|
"passthrough mode support"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
virReportEnumRangeError(virDomainSmartcardType, def->type);
|
|
return -1;
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH &&
|
|
qemuDomainChrSourceDefValidate(def->data.passthru, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainRNGDefValidate(const virDomainRNGDef *def,
|
|
virQEMUCapsPtr qemuCaps G_GNUC_UNUSED)
|
|
{
|
|
if (def->backend == VIR_DOMAIN_RNG_BACKEND_EGD &&
|
|
qemuDomainChrSourceDefValidate(def->source.chardev, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainRedirdevDefValidate(const virDomainRedirdevDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (qemuDomainChrSourceDefValidate(def->source, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainWatchdogDefValidate(const virDomainWatchdogDef *dev,
|
|
const virDomainDef *def)
|
|
{
|
|
switch ((virDomainWatchdogModel) dev->model) {
|
|
case VIR_DOMAIN_WATCHDOG_MODEL_I6300ESB:
|
|
if (dev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
dev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("%s model of watchdog can go only on PCI bus"),
|
|
virDomainWatchdogModelTypeToString(dev->model));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_WATCHDOG_MODEL_IB700:
|
|
if (dev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
dev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_ISA) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("%s model of watchdog can go only on ISA bus"),
|
|
virDomainWatchdogModelTypeToString(dev->model));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_WATCHDOG_MODEL_DIAG288:
|
|
if (dev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("%s model of watchdog is virtual and cannot go on any bus."),
|
|
virDomainWatchdogModelTypeToString(dev->model));
|
|
return -1;
|
|
}
|
|
if (!(ARCH_IS_S390(def->os.arch))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("%s model of watchdog is allowed for s390 and s390x only"),
|
|
virDomainWatchdogModelTypeToString(dev->model));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_WATCHDOG_MODEL_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainValidateActualNetDef(const virDomainNetDef *net,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
/*
|
|
* Validations that can only be properly checked at runtime (after
|
|
* an <interface type='network'> has been resolved to its actual
|
|
* type.
|
|
*
|
|
* (In its current form this function can still be called before
|
|
* the actual type has been resolved (e.g. at domain definition
|
|
* time), but only if the validations would SUCCEED for
|
|
* type='network'.)
|
|
*/
|
|
char macstr[VIR_MAC_STRING_BUFLEN];
|
|
virDomainNetType actualType = virDomainNetGetActualType(net);
|
|
|
|
virMacAddrFormat(&net->mac, macstr);
|
|
|
|
/* hypervisor-agnostic validation */
|
|
if (virDomainActualNetDefValidate(net) < 0)
|
|
return -1;
|
|
|
|
/* QEMU-specific validation */
|
|
|
|
/* Only tap/macvtap devices support multiqueue. */
|
|
if (net->driver.virtio.queues > 0) {
|
|
|
|
if (!(actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_DIRECT ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_ETHERNET ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_VHOSTUSER)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("interface %s - multiqueue is not supported for network interfaces of type %s"),
|
|
macstr, virDomainNetTypeToString(actualType));
|
|
return -1;
|
|
}
|
|
|
|
if (net->driver.virtio.queues > 1 &&
|
|
actualType == VIR_DOMAIN_NET_TYPE_VHOSTUSER &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VHOSTUSER_MULTIQUEUE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("interface %s - multiqueue is not supported for network interfaces of type vhost-user with this QEMU binary"),
|
|
macstr);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Only standard tap devices support nwfilter rules, and even then only
|
|
* when *not* connected to an OVS bridge or midonet (indicated by having
|
|
* a <virtualport> element in the config)
|
|
*/
|
|
if (net->filter) {
|
|
const virNetDevVPortProfile *vport = virDomainNetGetActualVirtPortProfile(net);
|
|
|
|
if (!(actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_ETHERNET)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("interface %s - filterref is not supported for network interfaces of type %s"),
|
|
macstr, virDomainNetTypeToString(actualType));
|
|
return -1;
|
|
}
|
|
if (vport && vport->virtPortType != VIR_NETDEV_VPORT_PROFILE_NONE) {
|
|
/* currently none of the defined virtualport types support iptables */
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("interface %s - filterref is not supported for network interfaces with virtualport type %s"),
|
|
macstr, virNetDevVPortTypeToString(vport->virtPortType));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (net->backend.tap &&
|
|
!(actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_ETHERNET)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("interface %s - custom tap device path is not supported for network interfaces of type %s"),
|
|
macstr, virDomainNetTypeToString(actualType));
|
|
return -1;
|
|
}
|
|
|
|
if (net->teaming.type == VIR_DOMAIN_NET_TEAMING_TYPE_TRANSIENT &&
|
|
actualType != VIR_DOMAIN_NET_TYPE_HOSTDEV) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("interface %s - teaming transient device must be type='hostdev', not '%s'"),
|
|
macstr, virDomainNetTypeToString(actualType));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateNetwork(const virDomainNetDef *net,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
bool hasIPv4 = false;
|
|
bool hasIPv6 = false;
|
|
size_t i;
|
|
|
|
if (net->type == VIR_DOMAIN_NET_TYPE_USER) {
|
|
if (net->guestIP.nroutes) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Invalid attempt to set network interface "
|
|
"guest-side IP route, not supported by QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < net->guestIP.nips; i++) {
|
|
const virNetDevIPAddr *ip = net->guestIP.ips[i];
|
|
|
|
if (VIR_SOCKET_ADDR_VALID(&net->guestIP.ips[i]->peer)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Invalid attempt to set peer IP for guest"));
|
|
return -1;
|
|
}
|
|
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET)) {
|
|
if (hasIPv4) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Only one IPv4 address per "
|
|
"interface is allowed"));
|
|
return -1;
|
|
}
|
|
hasIPv4 = true;
|
|
|
|
if (ip->prefix > 0 &&
|
|
(ip->prefix < 4 || ip->prefix > 27)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid prefix, must be in range of 4-27"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET6)) {
|
|
if (hasIPv6) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Only one IPv6 address per "
|
|
"interface is allowed"));
|
|
return -1;
|
|
}
|
|
hasIPv6 = true;
|
|
|
|
if (ip->prefix > 120) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("prefix too long"));
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
} else if (net->guestIP.nroutes || net->guestIP.nips) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Invalid attempt to set network interface "
|
|
"guest-side IP route and/or address info, "
|
|
"not supported by QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainNetIsVirtioModel(net)) {
|
|
if (net->driver.virtio.rx_queue_size & (net->driver.virtio.rx_queue_size - 1)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("rx_queue_size has to be a power of two"));
|
|
return -1;
|
|
}
|
|
if (net->driver.virtio.tx_queue_size & (net->driver.virtio.tx_queue_size - 1)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("tx_queue_size has to be a power of two"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (net->mtu &&
|
|
!qemuDomainNetSupportsMTU(net->type)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("setting MTU on interface type %s is not supported yet"),
|
|
virDomainNetTypeToString(net->type));
|
|
return -1;
|
|
}
|
|
|
|
if (net->teaming.type != VIR_DOMAIN_NET_TEAMING_TYPE_NONE &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_NET_FAILOVER)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtio-net failover (teaming) is not supported with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
if (net->teaming.type == VIR_DOMAIN_NET_TEAMING_TYPE_PERSISTENT
|
|
&& !virDomainNetIsVirtioModel(net)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("virtio-net teaming persistent interface must be <model type='virtio'/>, not '%s'"),
|
|
virDomainNetGetModelString(net));
|
|
return -1;
|
|
}
|
|
if (net->teaming.type == VIR_DOMAIN_NET_TEAMING_TYPE_TRANSIENT &&
|
|
net->type != VIR_DOMAIN_NET_TYPE_HOSTDEV &&
|
|
net->type != VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("virtio-net teaming transient interface must be type='hostdev', not '%s'"),
|
|
virDomainNetTypeToString(net->type));
|
|
return -1;
|
|
}
|
|
|
|
if (net->coalesce && !qemuDomainNetSupportsCoalesce(net->type)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("coalesce settings on interface type %s are not supported"),
|
|
virDomainNetTypeToString(net->type));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainMdevDefVFIOPCIValidate(const virDomainHostdevDef *hostdev,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
const virDomainHostdevSubsysMediatedDev *dev;
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VFIO_PCI)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("VFIO PCI device assignment is not "
|
|
"supported by this version of QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
/* VFIO-PCI does not support boot */
|
|
if (hostdev->info->bootIndex) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("booting from assigned devices is not "
|
|
"supported by mediated devices of "
|
|
"model vfio-pci"));
|
|
return -1;
|
|
}
|
|
|
|
dev = &hostdev->source.subsys.u.mdev;
|
|
if (dev->display == VIR_TRISTATE_SWITCH_ABSENT)
|
|
return 0;
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VFIO_PCI_DISPLAY)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("display property of device vfio-pci is "
|
|
"not supported by this version of QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
if (dev->model != VIR_MDEV_MODEL_TYPE_VFIO_PCI) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("<hostdev> attribute 'display' is only supported"
|
|
" with model='vfio-pci'"));
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (dev->display == VIR_TRISTATE_SWITCH_ON) {
|
|
if (def->ngraphics == 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("graphics device is needed for attribute value "
|
|
"'display=on' in <hostdev>"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainMdevDefVFIOAPValidate(const virDomainHostdevDef *hostdev,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
size_t i;
|
|
bool vfioap_found = false;
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VFIO_AP)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("VFIO AP device assignment is not "
|
|
"supported by this version of QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
/* VFIO-AP does not support boot */
|
|
if (hostdev->info->bootIndex) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("booting from assigned devices is not "
|
|
"supported by mediated devices of "
|
|
"model vfio-ap"));
|
|
return -1;
|
|
}
|
|
|
|
/* VFIO-AP is restricted to a single mediated device only */
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
virDomainHostdevDefPtr hdev = def->hostdevs[i];
|
|
|
|
if (virHostdevIsMdevDevice(hdev) &&
|
|
hdev->source.subsys.u.mdev.model == VIR_MDEV_MODEL_TYPE_VFIO_AP) {
|
|
if (vfioap_found) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Only one hostdev of model vfio-ap is "
|
|
"supported"));
|
|
return -1;
|
|
}
|
|
vfioap_found = true;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainMdevDefValidate(const virDomainHostdevDef *hostdev,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
const virDomainHostdevSubsysMediatedDev *mdevsrc;
|
|
|
|
mdevsrc = &hostdev->source.subsys.u.mdev;
|
|
switch ((virMediatedDeviceModelType) mdevsrc->model) {
|
|
case VIR_MDEV_MODEL_TYPE_VFIO_PCI:
|
|
return qemuDomainMdevDefVFIOPCIValidate(hostdev, def, qemuCaps);
|
|
case VIR_MDEV_MODEL_TYPE_VFIO_AP:
|
|
return qemuDomainMdevDefVFIOAPValidate(hostdev, def, qemuCaps);
|
|
case VIR_MDEV_MODEL_TYPE_VFIO_CCW:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VFIO_CCW)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("VFIO CCW device assignment is not "
|
|
"supported by this version of QEMU"));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_MDEV_MODEL_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virMediatedDeviceModelType,
|
|
mdevsrc->model);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateHostdev(const virDomainHostdevDef *hostdev,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
int backend;
|
|
|
|
/* forbid capabilities mode hostdev in this kind of hypervisor */
|
|
if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("hostdev mode 'capabilities' is not "
|
|
"supported in %s"),
|
|
virDomainVirtTypeToString(def->virtType));
|
|
return -1;
|
|
}
|
|
|
|
if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) {
|
|
switch ((virDomainHostdevSubsysType) hostdev->source.subsys.type) {
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI:
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
|
|
backend = hostdev->source.subsys.u.pci.backend;
|
|
|
|
if (backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VFIO_PCI)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("VFIO PCI device assignment is not "
|
|
"supported by this version of qemu"));
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST:
|
|
if (hostdev->info->bootIndex) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("booting from assigned devices is not "
|
|
"supported by vhost SCSI devices"));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV:
|
|
return qemuDomainMdevDefValidate(hostdev, def, qemuCaps);
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainHostdevSubsysType,
|
|
hostdev->source.subsys.type);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateVideo(const virDomainVideoDef *video,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
/* there's no properties to validate for NONE video devices */
|
|
if (video->type == VIR_DOMAIN_VIDEO_TYPE_NONE)
|
|
return 0;
|
|
|
|
if (!video->primary &&
|
|
video->type != VIR_DOMAIN_VIDEO_TYPE_QXL &&
|
|
video->type != VIR_DOMAIN_VIDEO_TYPE_VIRTIO) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("video type '%s' is only valid as primary "
|
|
"video device"),
|
|
virDomainVideoTypeToString(video->type));
|
|
return -1;
|
|
}
|
|
|
|
if (video->accel && video->accel->accel2d == VIR_TRISTATE_SWITCH_ON) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("qemu does not support the accel2d setting"));
|
|
return -1;
|
|
}
|
|
|
|
if (video->type == VIR_DOMAIN_VIDEO_TYPE_QXL) {
|
|
if (video->vram > (UINT_MAX / 1024)) {
|
|
virReportError(VIR_ERR_OVERFLOW,
|
|
_("value for 'vram' must be less than '%u'"),
|
|
UINT_MAX / 1024);
|
|
return -1;
|
|
}
|
|
if (video->ram > (UINT_MAX / 1024)) {
|
|
virReportError(VIR_ERR_OVERFLOW,
|
|
_("value for 'ram' must be less than '%u'"),
|
|
UINT_MAX / 1024);
|
|
return -1;
|
|
}
|
|
if (video->vgamem) {
|
|
if (video->vgamem < 1024) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("value for 'vgamem' must be at least 1 MiB "
|
|
"(1024 KiB)"));
|
|
return -1;
|
|
}
|
|
|
|
if (video->vgamem != VIR_ROUND_UP_POWER_OF_TWO(video->vgamem)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("value for 'vgamem' must be power of two"));
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (video->type != VIR_DOMAIN_VIDEO_TYPE_VGA &&
|
|
video->type != VIR_DOMAIN_VIDEO_TYPE_QXL &&
|
|
video->type != VIR_DOMAIN_VIDEO_TYPE_VIRTIO &&
|
|
video->type != VIR_DOMAIN_VIDEO_TYPE_BOCHS) {
|
|
if (video->res) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("model resolution is not supported"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (video->type == VIR_DOMAIN_VIDEO_TYPE_VGA ||
|
|
video->type == VIR_DOMAIN_VIDEO_TYPE_VMVGA) {
|
|
if (video->vram && video->vram < 1024) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("value for 'vram' must be at least "
|
|
"1 MiB (1024 KiB)"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (video->backend == VIR_DOMAIN_VIDEO_BACKEND_TYPE_VHOSTUSER) {
|
|
if (video->type == VIR_DOMAIN_VIDEO_TYPE_VIRTIO &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VHOST_USER_GPU)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("this QEMU does not support 'vhost-user' video device"));
|
|
return -1;
|
|
}
|
|
} else if (video->accel) {
|
|
if (video->accel->accel3d == VIR_TRISTATE_SWITCH_ON &&
|
|
(video->type != VIR_DOMAIN_VIDEO_TYPE_VIRTIO ||
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_GPU_VIRGL))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("%s 3d acceleration is not supported"),
|
|
virDomainVideoTypeToString(video->type));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainValidateStorageSource(virStorageSourcePtr src,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
int actualType = virStorageSourceGetActualType(src);
|
|
|
|
if (src->format == VIR_STORAGE_FILE_COW) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("'cow' storage format is not supported"));
|
|
return -1;
|
|
}
|
|
|
|
if (src->format == VIR_STORAGE_FILE_DIR) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("'directory' storage format is not directly supported by QEMU, "
|
|
"use 'dir' disk type instead"));
|
|
return -1;
|
|
}
|
|
|
|
if (src->format == VIR_STORAGE_FILE_ISO) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("storage format 'iso' is not directly supported by QEMU, "
|
|
"use 'raw' instead"));
|
|
return -1;
|
|
}
|
|
|
|
if ((src->format == VIR_STORAGE_FILE_QCOW ||
|
|
src->format == VIR_STORAGE_FILE_QCOW2) &&
|
|
src->encryption &&
|
|
(src->encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT ||
|
|
src->encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_QCOW)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("old qcow/qcow2 encryption is not supported"));
|
|
return -1;
|
|
}
|
|
|
|
if (src->format == VIR_STORAGE_FILE_QCOW2 &&
|
|
src->encryption &&
|
|
src->encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_QCOW2_LUKS)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("LUKS encrypted QCOW2 images are not supported by this QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
if (src->format == VIR_STORAGE_FILE_FAT &&
|
|
actualType != VIR_STORAGE_TYPE_VOLUME &&
|
|
actualType != VIR_STORAGE_TYPE_DIR) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("storage format 'fat' is supported only with 'dir' "
|
|
"storage type"));
|
|
return -1;
|
|
}
|
|
|
|
if (actualType == VIR_STORAGE_TYPE_DIR) {
|
|
if (src->format > 0 &&
|
|
src->format != VIR_STORAGE_FILE_FAT) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("storage type 'dir' requires use of storage format 'fat'"));
|
|
return -1;
|
|
}
|
|
|
|
if (!src->readonly) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtual FAT storage can't be accessed in read-write mode"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (src->pr &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_PR_MANAGER_HELPER)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("reservations not supported with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
/* Use QEMU_CAPS_ISCSI_PASSWORD_SECRET as witness that iscsi 'initiator-name'
|
|
* option is available, it was introduced at the same time. */
|
|
if (src->initiator.iqn &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_ISCSI_PASSWORD_SECRET)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("iSCSI initiator IQN not supported with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
if (src->sliceStorage) {
|
|
/* In pre-blockdev era we can't configure the slice so we can allow them
|
|
* only for detected backing store entries as they are populated
|
|
* from a place that qemu would be able to read */
|
|
if (!src->detected &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("storage slice is not supported by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainDeviceDefValidateDisk(const virDomainDiskDef *disk,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
const char *driverName = virDomainDiskGetDriver(disk);
|
|
virStorageSourcePtr n;
|
|
int idx;
|
|
int partition;
|
|
|
|
if (disk->src->shared && !disk->src->readonly &&
|
|
!qemuBlockStorageSourceSupportsConcurrentAccess(disk->src)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("shared access for disk '%s' requires use of "
|
|
"supported storage format"), disk->dst);
|
|
return -1;
|
|
}
|
|
|
|
if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON) {
|
|
if (disk->src->readonly) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("copy_on_read is not compatible with read-only disk '%s'"),
|
|
disk->dst);
|
|
return -1;
|
|
}
|
|
|
|
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM ||
|
|
disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("copy_on_read is not supported with removable disk '%s'"),
|
|
disk->dst);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (disk->geometry.cylinders > 0 &&
|
|
disk->geometry.heads > 0 &&
|
|
disk->geometry.sectors > 0) {
|
|
if (disk->bus == VIR_DOMAIN_DISK_BUS_USB ||
|
|
disk->bus == VIR_DOMAIN_DISK_BUS_SD) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("CHS geometry can not be set for '%s' bus"),
|
|
virDomainDiskBusTypeToString(disk->bus));
|
|
return -1;
|
|
}
|
|
|
|
if (disk->geometry.trans != VIR_DOMAIN_DISK_TRANS_DEFAULT &&
|
|
disk->bus != VIR_DOMAIN_DISK_BUS_IDE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("CHS translation mode can only be set for 'ide' bus not '%s'"),
|
|
virDomainDiskBusTypeToString(disk->bus));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (disk->serial && disk->bus == VIR_DOMAIN_DISK_BUS_SD) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Serial property not supported for drive bus '%s'"),
|
|
virDomainDiskBusTypeToString(disk->bus));
|
|
return -1;
|
|
}
|
|
|
|
if (driverName && STRNEQ(driverName, "qemu")) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported driver name '%s' for disk '%s'"),
|
|
driverName, disk->dst);
|
|
return -1;
|
|
}
|
|
|
|
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM &&
|
|
disk->bus == VIR_DOMAIN_DISK_BUS_VIRTIO) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("disk type 'virtio' of '%s' does not support ejectable media"),
|
|
disk->dst);
|
|
return -1;
|
|
}
|
|
|
|
if (virDiskNameParse(disk->dst, &idx, &partition) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("invalid disk target '%s'"), disk->dst);
|
|
return -1;
|
|
}
|
|
|
|
if (partition != 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("invalid disk target '%s', partitions can't appear in disk targets"),
|
|
disk->dst);
|
|
return -1;
|
|
}
|
|
|
|
for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
if (qemuDomainValidateStorageSource(n, qemuCaps) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateControllerAttributes(const virDomainControllerDef *controller)
|
|
{
|
|
if (!(controller->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI &&
|
|
(controller->model == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI ||
|
|
controller->model == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_TRANSITIONAL ||
|
|
controller->model == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_NON_TRANSITIONAL))) {
|
|
if (controller->queues) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("'queues' is only supported by virtio-scsi controller"));
|
|
return -1;
|
|
}
|
|
if (controller->cmd_per_lun) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("'cmd_per_lun' is only supported by virtio-scsi controller"));
|
|
return -1;
|
|
}
|
|
if (controller->max_sectors) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("'max_sectors' is only supported by virtio-scsi controller"));
|
|
return -1;
|
|
}
|
|
if (controller->ioeventfd) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("'ioeventfd' is only supported by virtio-scsi controller"));
|
|
return -1;
|
|
}
|
|
if (controller->iothread) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("'iothread' is only supported for virtio-scsi controller"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* @qemuCaps: QEMU capabilities
|
|
* @model: SCSI model to check
|
|
*
|
|
* Using the @qemuCaps, let's ensure the provided @model can be supported
|
|
*
|
|
* Returns true if acceptable, false otherwise with error message set.
|
|
*/
|
|
static bool
|
|
qemuDomainCheckSCSIControllerModel(virQEMUCapsPtr qemuCaps,
|
|
int model)
|
|
{
|
|
switch ((virDomainControllerModelSCSI) model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_LSI)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("This QEMU doesn't support "
|
|
"the LSI 53C895A SCSI controller"));
|
|
return false;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_TRANSITIONAL:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_NON_TRANSITIONAL:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_SCSI)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("This QEMU doesn't support "
|
|
"virtio scsi controller"));
|
|
return false;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_IBMVSCSI:
|
|
/*TODO: need checking work here if necessary */
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSISAS1068:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_MPTSAS1068)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("This QEMU doesn't support "
|
|
"the LSI SAS1068 (MPT Fusion) controller"));
|
|
return false;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSISAS1078:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_MEGASAS)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("This QEMU doesn't support "
|
|
"the LSI SAS1078 (MegaRAID) controller"));
|
|
return false;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_AUTO:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_BUSLOGIC:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VMPVSCSI:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported controller model: %s"),
|
|
virDomainControllerModelSCSITypeToString(model));
|
|
return false;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unexpected SCSI controller model %d"),
|
|
model);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateControllerIDE(const virDomainControllerDef *controller,
|
|
const virDomainDef *def)
|
|
{
|
|
/* first IDE controller is implicit on various machines */
|
|
if (controller->idx == 0 && qemuDomainHasBuiltinIDE(def))
|
|
return 0;
|
|
|
|
/* Since we currently only support the integrated IDE
|
|
* controller on various boards, if we ever get to here, it's
|
|
* because some other machinetype had an IDE controller
|
|
* specified, or one with a single IDE controller had multiple
|
|
* IDE controllers specified.
|
|
*/
|
|
if (qemuDomainHasBuiltinIDE(def))
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Only a single IDE controller is supported "
|
|
"for this machine type"));
|
|
else
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("IDE controllers are unsupported for "
|
|
"this QEMU binary or machine type"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* qemuDomainCheckSCSIControllerIOThreads:
|
|
* @controller: Pointer to controller def
|
|
* @def: Pointer to domain def
|
|
*
|
|
* If this controller definition has iothreads set, let's make sure the
|
|
* configuration is right before adding to the command line
|
|
*
|
|
* Returns true if either supported or there are no iothreads for controller;
|
|
* otherwise, returns false if configuration is not quite right.
|
|
*/
|
|
static bool
|
|
qemuDomainCheckSCSIControllerIOThreads(const virDomainControllerDef *controller,
|
|
const virDomainDef *def)
|
|
{
|
|
if (!controller->iothread)
|
|
return true;
|
|
|
|
if (controller->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
controller->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI &&
|
|
controller->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtio-scsi IOThreads only available for virtio "
|
|
"pci and virtio ccw controllers"));
|
|
return false;
|
|
}
|
|
|
|
/* Can we find the controller iothread in the iothreadid list? */
|
|
if (!virDomainIOThreadIDFind(def, controller->iothread)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("controller iothread '%u' not defined in iothreadid"),
|
|
controller->iothread);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateControllerSCSI(const virDomainControllerDef *controller,
|
|
const virDomainDef *def)
|
|
{
|
|
switch ((virDomainControllerModelSCSI) controller->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_TRANSITIONAL:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_NON_TRANSITIONAL:
|
|
if (!qemuDomainCheckSCSIControllerIOThreads(controller, def))
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_AUTO:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_BUSLOGIC:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSISAS1068:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VMPVSCSI:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_IBMVSCSI:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSISAS1078:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainControllerPCIModelNameToQEMUCaps:
|
|
* @modelName: model name
|
|
*
|
|
* Maps model names for PCI controllers (virDomainControllerPCIModelName)
|
|
* to the QEMU capabilities required to use them (virQEMUCapsFlags).
|
|
*
|
|
* Returns: the QEMU capability itself (>0) on success; 0 if no QEMU
|
|
* capability is needed; <0 on error.
|
|
*/
|
|
static int
|
|
virDomainControllerPCIModelNameToQEMUCaps(int modelName)
|
|
{
|
|
switch ((virDomainControllerPCIModelName) modelName) {
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PCI_BRIDGE:
|
|
return QEMU_CAPS_DEVICE_PCI_BRIDGE;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_I82801B11_BRIDGE:
|
|
return QEMU_CAPS_DEVICE_DMI_TO_PCI_BRIDGE;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_IOH3420:
|
|
return QEMU_CAPS_DEVICE_IOH3420;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_X3130_UPSTREAM:
|
|
return QEMU_CAPS_DEVICE_X3130_UPSTREAM;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_XIO3130_DOWNSTREAM:
|
|
return QEMU_CAPS_DEVICE_XIO3130_DOWNSTREAM;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PXB:
|
|
return QEMU_CAPS_DEVICE_PXB;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PXB_PCIE:
|
|
return QEMU_CAPS_DEVICE_PXB_PCIE;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PCIE_ROOT_PORT:
|
|
return QEMU_CAPS_DEVICE_PCIE_ROOT_PORT;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE:
|
|
return QEMU_CAPS_DEVICE_SPAPR_PCI_HOST_BRIDGE;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PCIE_PCI_BRIDGE:
|
|
return QEMU_CAPS_DEVICE_PCIE_PCI_BRIDGE;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE:
|
|
return 0;
|
|
case VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_LAST:
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
#define virReportControllerMissingOption(cont, model, modelName, option) \
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, \
|
|
_("Required option '%s' is not set for PCI controller " \
|
|
"with index '%d', model '%s' and modelName '%s'"), \
|
|
(option), (cont->idx), (model), (modelName));
|
|
#define virReportControllerInvalidOption(cont, model, modelName, option) \
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \
|
|
_("Option '%s' is not valid for PCI controller " \
|
|
"with index '%d', model '%s' and modelName '%s'"), \
|
|
(option), (cont->idx), (model), (modelName));
|
|
#define virReportControllerInvalidValue(cont, model, modelName, option) \
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \
|
|
_("Option '%s' has invalid value for PCI controller " \
|
|
"with index '%d', model '%s' and modelName '%s'"), \
|
|
(option), (cont->idx), (model), (modelName));
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateControllerPCI(const virDomainControllerDef *cont,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
|
|
{
|
|
const virDomainPCIControllerOpts *pciopts = &cont->opts.pciopts;
|
|
const char *model = virDomainControllerModelPCITypeToString(cont->model);
|
|
const char *modelName = virDomainControllerPCIModelNameTypeToString(pciopts->modelName);
|
|
int cap = virDomainControllerPCIModelNameToQEMUCaps(pciopts->modelName);
|
|
|
|
if (!model) {
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
if (!modelName) {
|
|
virReportEnumRangeError(virDomainControllerPCIModelName, pciopts->modelName);
|
|
return -1;
|
|
}
|
|
|
|
/* modelName */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
/* modelName should have been set automatically */
|
|
if (pciopts->modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE) {
|
|
virReportControllerMissingOption(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
/* modelName must be set for pSeries guests, but it's an error
|
|
* for it to be set for any other guest */
|
|
if (qemuDomainIsPSeries(def)) {
|
|
if (pciopts->modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE) {
|
|
virReportControllerMissingOption(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
|
|
/* modelName (cont'd) */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE &&
|
|
pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PCI_BRIDGE) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_I82801B11_BRIDGE) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_IOH3420 &&
|
|
pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PCIE_ROOT_PORT) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_X3130_UPSTREAM) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_XIO3130_DOWNSTREAM) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PXB) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PXB_PCIE) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
if (pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_PCIE_PCI_BRIDGE) {
|
|
virReportControllerInvalidValue(cont, model, modelName, "modelName");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
|
|
/* index */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
if (cont->idx == 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Index for '%s' controllers must be > 0"),
|
|
model);
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
/* pSeries guests can have multiple PHBs, so it's expected that
|
|
* the index will not be zero for some of them */
|
|
if (cont->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT &&
|
|
pciopts->modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE) {
|
|
break;
|
|
}
|
|
|
|
/* For all other pci-root and pcie-root controllers, though,
|
|
* the index must be zero */
|
|
if (cont->idx != 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Index for '%s' controllers must be 0"),
|
|
model);
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
|
|
/* targetIndex */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
/* PHBs for pSeries guests must have been assigned a targetIndex */
|
|
if (pciopts->targetIndex == -1 &&
|
|
pciopts->modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE) {
|
|
virReportControllerMissingOption(cont, model, modelName, "targetIndex");
|
|
return -1;
|
|
}
|
|
|
|
/* targetIndex only applies to PHBs, so for any other pci-root
|
|
* controller it being present is an error */
|
|
if (pciopts->targetIndex != -1 &&
|
|
pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "targetIndex");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
if (pciopts->targetIndex != -1) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "targetIndex");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
|
|
/* pcihole64 */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
if (pciopts->pcihole64 || pciopts->pcihole64size != 0) {
|
|
if (!qemuDomainIsI440FX(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Setting the 64-bit PCI hole size is not "
|
|
"supported for machine '%s'"), def->os.machine);
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_I440FX_PCI_HOLE64_SIZE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("64-bit PCI hole size setting is not supported "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
if (pciopts->pcihole64 || pciopts->pcihole64size != 0) {
|
|
if (!qemuDomainIsQ35(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Setting the 64-bit PCI hole size is not "
|
|
"supported for machine '%s'"), def->os.machine);
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_Q35_PCI_HOLE64_SIZE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("64-bit PCI hole size setting is not supported "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
if (pciopts->pcihole64 ||
|
|
pciopts->pcihole64size != 0) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "pcihole64");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
|
|
/* busNr */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
if (pciopts->busNr == -1) {
|
|
virReportControllerMissingOption(cont, model, modelName, "busNr");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
if (pciopts->busNr != -1) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "busNr");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
|
|
/* numaNode */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
/* numaNode can be used for these controllers, but it's not set
|
|
* automatically so it can be missing */
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
/* Only PHBs support numaNode */
|
|
if (pciopts->numaNode != -1 &&
|
|
pciopts->modelName != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "numaNode");
|
|
return -1;
|
|
}
|
|
|
|
/* However, the default PHB doesn't support numaNode */
|
|
if (pciopts->numaNode != -1 &&
|
|
pciopts->modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE &&
|
|
pciopts->targetIndex == 0) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "numaNode");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
if (pciopts->numaNode != -1) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "numaNode");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
|
|
/* chassisNr */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
if (pciopts->chassisNr == -1) {
|
|
virReportControllerMissingOption(cont, model, modelName, "chassisNr");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
if (pciopts->chassisNr != -1) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "chassisNr");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
return -1;
|
|
}
|
|
|
|
/* chassis and port */
|
|
switch ((virDomainControllerModelPCI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
if (pciopts->chassis == -1) {
|
|
virReportControllerMissingOption(cont, model, modelName, "chassis");
|
|
return -1;
|
|
}
|
|
if (pciopts->port == -1) {
|
|
virReportControllerMissingOption(cont, model, modelName, "port");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
if (pciopts->chassis != -1) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "chassis");
|
|
return -1;
|
|
}
|
|
if (pciopts->port != -1) {
|
|
virReportControllerInvalidOption(cont, model, modelName, "port");
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainControllerModelPCI, cont->model);
|
|
}
|
|
|
|
/* QEMU device availability */
|
|
if (cap < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown QEMU device for '%s' controller"),
|
|
modelName);
|
|
return -1;
|
|
}
|
|
if (cap > 0 && !virQEMUCapsGet(qemuCaps, cap)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The '%s' device is not supported by this QEMU binary"),
|
|
modelName);
|
|
return -1;
|
|
}
|
|
|
|
/* PHBs didn't support numaNode from the very beginning, so an extra
|
|
* capability check is required */
|
|
if (cont->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT &&
|
|
pciopts->modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE &&
|
|
pciopts->numaNode != -1 &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPAPR_PCI_HOST_BRIDGE_NUMA_NODE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Option '%s' is not supported by '%s' device with this QEMU binary"),
|
|
"numaNode", modelName);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#undef virReportControllerInvalidValue
|
|
#undef virReportControllerInvalidOption
|
|
#undef virReportControllerMissingOption
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateControllerSATA(const virDomainControllerDef *controller,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
/* first SATA controller on Q35 machines is implicit */
|
|
if (controller->idx == 0 && qemuDomainIsQ35(def))
|
|
return 0;
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_ICH9_AHCI)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("SATA is not supported with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateController(const virDomainControllerDef *controller,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!qemuDomainCheckCCWS390AddressSupport(def, &controller->info, qemuCaps,
|
|
"controller"))
|
|
return -1;
|
|
|
|
if (controller->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI &&
|
|
!qemuDomainCheckSCSIControllerModel(qemuCaps, controller->model))
|
|
return -1;
|
|
|
|
if (qemuDomainDeviceDefValidateControllerAttributes(controller) < 0)
|
|
return -1;
|
|
|
|
switch ((virDomainControllerType)controller->type) {
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_IDE:
|
|
ret = qemuDomainDeviceDefValidateControllerIDE(controller, def);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_SCSI:
|
|
ret = qemuDomainDeviceDefValidateControllerSCSI(controller, def);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_PCI:
|
|
ret = qemuDomainDeviceDefValidateControllerPCI(controller, def,
|
|
qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_SATA:
|
|
ret = qemuDomainDeviceDefValidateControllerSATA(controller, def,
|
|
qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_FDC:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_CCID:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_USB:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateVsock(const virDomainVsockDef *vsock,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VHOST_VSOCK)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("vsock device is not supported "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
if (!qemuDomainCheckCCWS390AddressSupport(def, &vsock->info, qemuCaps,
|
|
"vsock"))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateTPM(virDomainTPMDef *tpm,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
virQEMUCapsFlags flag;
|
|
|
|
/* TPM 1.2 and 2 are not compatible, so we choose a specific version here */
|
|
if (tpm->version == VIR_DOMAIN_TPM_VERSION_DEFAULT)
|
|
tpm->version = VIR_DOMAIN_TPM_VERSION_1_2;
|
|
|
|
switch (tpm->version) {
|
|
case VIR_DOMAIN_TPM_VERSION_1_2:
|
|
/* TPM 1.2 + CRB do not work */
|
|
if (tpm->type == VIR_DOMAIN_TPM_TYPE_EMULATOR &&
|
|
tpm->model == VIR_DOMAIN_TPM_MODEL_CRB) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported interface %s for TPM 1.2"),
|
|
virDomainTPMModelTypeToString(tpm->model));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_TPM_VERSION_2_0:
|
|
case VIR_DOMAIN_TPM_VERSION_DEFAULT:
|
|
case VIR_DOMAIN_TPM_VERSION_LAST:
|
|
break;
|
|
}
|
|
|
|
switch (tpm->type) {
|
|
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_TPM_PASSTHROUGH))
|
|
goto no_support;
|
|
break;
|
|
|
|
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_TPM_EMULATOR))
|
|
goto no_support;
|
|
|
|
break;
|
|
case VIR_DOMAIN_TPM_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
switch (tpm->model) {
|
|
case VIR_DOMAIN_TPM_MODEL_TIS:
|
|
flag = QEMU_CAPS_DEVICE_TPM_TIS;
|
|
break;
|
|
case VIR_DOMAIN_TPM_MODEL_CRB:
|
|
flag = QEMU_CAPS_DEVICE_TPM_CRB;
|
|
break;
|
|
case VIR_DOMAIN_TPM_MODEL_SPAPR:
|
|
flag = QEMU_CAPS_DEVICE_TPM_SPAPR;
|
|
break;
|
|
case VIR_DOMAIN_TPM_MODEL_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainTPMModel, tpm->model);
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, flag)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The QEMU executable %s does not support TPM "
|
|
"model %s"),
|
|
def->emulator,
|
|
virDomainTPMModelTypeToString(tpm->model));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
no_support:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("The QEMU executable %s does not support TPM "
|
|
"backend type %s"),
|
|
def->emulator,
|
|
virDomainTPMBackendTypeToString(tpm->type));
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateSPICEGraphics(const virDomainGraphicsDef *graphics,
|
|
virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
virDomainGraphicsListenDefPtr glisten = NULL;
|
|
int tlsPort = graphics->data.spice.tlsPort;
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPICE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("spice graphics are not supported with this QEMU"));
|
|
return -1;
|
|
}
|
|
|
|
glisten = virDomainGraphicsGetListen((virDomainGraphicsDefPtr)graphics, 0);
|
|
if (!glisten) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing listen element"));
|
|
return -1;
|
|
}
|
|
|
|
switch (glisten->type) {
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPICE_UNIX)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("unix socket for spice graphics are not supported "
|
|
"with this QEMU"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS:
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK:
|
|
if (tlsPort > 0 && !cfg->spiceTLS) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("spice TLS port set in XML configuration, "
|
|
"but TLS is disabled in qemu.conf"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE:
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (graphics->data.spice.filetransfer == VIR_TRISTATE_BOOL_NO &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPICE_FILE_XFER_DISABLE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("This QEMU can't disable file transfers through spice"));
|
|
return -1;
|
|
}
|
|
|
|
if (graphics->data.spice.gl == VIR_TRISTATE_BOOL_YES) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPICE_GL)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("This QEMU doesn't support spice OpenGL"));
|
|
return -1;
|
|
}
|
|
|
|
if (graphics->data.spice.rendernode &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPICE_RENDERNODE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("This QEMU doesn't support spice OpenGL rendernode"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateGraphics(const virDomainGraphicsDef *graphics,
|
|
const virDomainDef *def,
|
|
virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
bool have_egl_headless = false;
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ngraphics; i++) {
|
|
if (def->graphics[i]->type == VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS) {
|
|
have_egl_headless = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Only VNC and SPICE can be paired with egl-headless, the other types
|
|
* either don't make sense to pair with egl-headless or aren't even
|
|
* supported by QEMU.
|
|
*/
|
|
if (have_egl_headless) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_EGL_HEADLESS)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("egl-headless display is not supported with this "
|
|
"QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
if (graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS &&
|
|
graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_VNC &&
|
|
graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_SPICE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("graphics type 'egl-headless' is only supported "
|
|
"with one of: 'vnc', 'spice' graphics types"));
|
|
return -1;
|
|
}
|
|
|
|
/* '-spice gl=on' and '-display egl-headless' are mutually
|
|
* exclusive
|
|
*/
|
|
if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE &&
|
|
graphics->data.spice.gl == VIR_TRISTATE_BOOL_YES) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("multiple OpenGL displays are not supported "
|
|
"by QEMU"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
switch (graphics->type) {
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
|
|
if (graphics->data.sdl.gl != VIR_TRISTATE_BOOL_ABSENT) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SDL_GL)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("OpenGL for SDL is not supported with this QEMU "
|
|
"binary"));
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNC)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("vnc graphics are not supported with this QEMU"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
|
|
if (qemuDomainDeviceDefValidateSPICEGraphics(graphics, driver,
|
|
qemuCaps) < 0)
|
|
return -1;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
|
|
if (graphics->data.egl_headless.rendernode &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_EGL_HEADLESS_RENDERNODE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("This QEMU doesn't support OpenGL rendernode "
|
|
"with egl-headless graphics type"));
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported graphics type '%s'"),
|
|
virDomainGraphicsTypeToString(graphics->type));
|
|
return -1;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateInput(const virDomainInputDef *input,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
const char *baseName;
|
|
int cap;
|
|
int ccwCap;
|
|
|
|
if (input->bus == VIR_DOMAIN_INPUT_BUS_PS2 && !ARCH_IS_X86(def->os.arch) &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_I8042)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("%s is not supported by this QEMU binary"),
|
|
virDomainInputBusTypeToString(input->bus));
|
|
return -1;
|
|
}
|
|
|
|
if (input->bus != VIR_DOMAIN_INPUT_BUS_VIRTIO)
|
|
return 0;
|
|
|
|
/* Only type=passthrough supports model=virtio-(non-)transitional */
|
|
switch ((virDomainInputModel)input->model) {
|
|
case VIR_DOMAIN_INPUT_MODEL_VIRTIO_TRANSITIONAL:
|
|
case VIR_DOMAIN_INPUT_MODEL_VIRTIO_NON_TRANSITIONAL:
|
|
switch ((virDomainInputType)input->type) {
|
|
case VIR_DOMAIN_INPUT_TYPE_MOUSE:
|
|
case VIR_DOMAIN_INPUT_TYPE_TABLET:
|
|
case VIR_DOMAIN_INPUT_TYPE_KBD:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("virtio (non-)transitional models are not "
|
|
"supported for input type=%s"),
|
|
virDomainInputTypeToString(input->type));
|
|
return -1;
|
|
case VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH:
|
|
break;
|
|
case VIR_DOMAIN_INPUT_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainInputType,
|
|
input->type);
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_INPUT_MODEL_VIRTIO:
|
|
case VIR_DOMAIN_INPUT_MODEL_DEFAULT:
|
|
break;
|
|
case VIR_DOMAIN_INPUT_MODEL_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainInputModel,
|
|
input->model);
|
|
return -1;
|
|
}
|
|
|
|
switch ((virDomainInputType)input->type) {
|
|
case VIR_DOMAIN_INPUT_TYPE_MOUSE:
|
|
baseName = "virtio-mouse";
|
|
cap = QEMU_CAPS_VIRTIO_MOUSE;
|
|
ccwCap = QEMU_CAPS_DEVICE_VIRTIO_MOUSE_CCW;
|
|
break;
|
|
case VIR_DOMAIN_INPUT_TYPE_TABLET:
|
|
baseName = "virtio-tablet";
|
|
cap = QEMU_CAPS_VIRTIO_TABLET;
|
|
ccwCap = QEMU_CAPS_DEVICE_VIRTIO_TABLET_CCW;
|
|
break;
|
|
case VIR_DOMAIN_INPUT_TYPE_KBD:
|
|
baseName = "virtio-keyboard";
|
|
cap = QEMU_CAPS_VIRTIO_KEYBOARD;
|
|
ccwCap = QEMU_CAPS_DEVICE_VIRTIO_KEYBOARD_CCW;
|
|
break;
|
|
case VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH:
|
|
baseName = "virtio-input-host";
|
|
cap = QEMU_CAPS_VIRTIO_INPUT_HOST;
|
|
ccwCap = QEMU_CAPS_LAST;
|
|
break;
|
|
case VIR_DOMAIN_INPUT_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainInputType,
|
|
input->type);
|
|
return -1;
|
|
}
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, cap) ||
|
|
(input->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW &&
|
|
!virQEMUCapsGet(qemuCaps, ccwCap))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("%s is not supported by this QEMU binary"),
|
|
baseName);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateMemballoon(const virDomainMemballoonDef *memballoon,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (!memballoon ||
|
|
memballoon->model == VIR_DOMAIN_MEMBALLOON_MODEL_NONE) {
|
|
return 0;
|
|
}
|
|
|
|
if (memballoon->model != VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO &&
|
|
memballoon->model != VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO_TRANSITIONAL &&
|
|
memballoon->model != VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO_NON_TRANSITIONAL) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Memory balloon device type '%s' is not supported by this version of qemu"),
|
|
virDomainMemballoonModelTypeToString(memballoon->model));
|
|
return -1;
|
|
}
|
|
|
|
if (memballoon->autodeflate != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_BALLOON_AUTODEFLATE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("deflate-on-oom is not supported by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateIOMMU(const virDomainIOMMUDef *iommu,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
switch (iommu->model) {
|
|
case VIR_DOMAIN_IOMMU_MODEL_INTEL:
|
|
if (!qemuDomainIsQ35(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("IOMMU device: '%s' is only supported with "
|
|
"Q35 machines"),
|
|
virDomainIOMMUModelTypeToString(iommu->model));
|
|
return -1;
|
|
}
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_INTEL_IOMMU) &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_IOMMU)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("IOMMU device: '%s' is not supported with "
|
|
"this QEMU binary"),
|
|
virDomainIOMMUModelTypeToString(iommu->model));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_IOMMU_MODEL_SMMUV3:
|
|
if (!qemuDomainIsARMVirt(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("IOMMU device: '%s' is only supported with "
|
|
"ARM Virt machines"),
|
|
virDomainIOMMUModelTypeToString(iommu->model));
|
|
return -1;
|
|
}
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_VIRT_IOMMU)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("IOMMU device: '%s' is not supported with "
|
|
"this QEMU binary"),
|
|
virDomainIOMMUModelTypeToString(iommu->model));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_IOMMU_MODEL_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainIOMMUModel, iommu->model);
|
|
return -1;
|
|
}
|
|
|
|
/* These capability checks ensure we're not trying to use features
|
|
* of Intel IOMMU that the QEMU binary does not support, but they
|
|
* also make sure we report an error when trying to use features
|
|
* that are not implemented by SMMUv3 */
|
|
|
|
if (iommu->intremap != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_INTEL_IOMMU_INTREMAP)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("iommu: interrupt remapping is not supported "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
if (iommu->caching_mode != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_INTEL_IOMMU_CACHING_MODE)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("iommu: caching mode is not supported "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
if (iommu->eim != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_INTEL_IOMMU_EIM)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("iommu: eim is not supported "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
if (iommu->iotlb != VIR_TRISTATE_SWITCH_ABSENT &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_INTEL_IOMMU_DEVICE_IOTLB)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("iommu: device IOTLB is not supported "
|
|
"with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuDomainDefValidateVirtioFSSharedMemory(const virDomainDef *def)
|
|
{
|
|
size_t numa_nodes = virDomainNumaGetNodeCount(def->numa);
|
|
size_t i;
|
|
|
|
if (numa_nodes == 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs requires one or more NUMA nodes"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < numa_nodes; i++) {
|
|
virDomainMemoryAccess node_access =
|
|
virDomainNumaGetNodeMemoryAccessMode(def->numa, i);
|
|
|
|
switch (node_access) {
|
|
case VIR_DOMAIN_MEMORY_ACCESS_DEFAULT:
|
|
if (def->mem.access != VIR_DOMAIN_MEMORY_ACCESS_SHARED) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs requires shared memory"));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_MEMORY_ACCESS_SHARED:
|
|
break;
|
|
case VIR_DOMAIN_MEMORY_ACCESS_PRIVATE:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs requires shared memory"));
|
|
return -1;
|
|
|
|
case VIR_DOMAIN_MEMORY_ACCESS_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainMemoryAccess, node_access);
|
|
return -1;
|
|
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateFS(virDomainFSDefPtr fs,
|
|
const virDomainDef *def,
|
|
virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (fs->type != VIR_DOMAIN_FS_TYPE_MOUNT) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("only supports mount filesystem type"));
|
|
return -1;
|
|
}
|
|
|
|
switch ((virDomainFSDriverType) fs->fsdriver) {
|
|
case VIR_DOMAIN_FS_DRIVER_TYPE_DEFAULT:
|
|
case VIR_DOMAIN_FS_DRIVER_TYPE_PATH:
|
|
break;
|
|
|
|
case VIR_DOMAIN_FS_DRIVER_TYPE_HANDLE:
|
|
if (fs->accessmode != VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("only supports passthrough accessmode"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FS_DRIVER_TYPE_LOOP:
|
|
case VIR_DOMAIN_FS_DRIVER_TYPE_NBD:
|
|
case VIR_DOMAIN_FS_DRIVER_TYPE_PLOOP:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Filesystem driver type not supported"));
|
|
return -1;
|
|
|
|
case VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS:
|
|
if (!virQEMUDriverIsPrivileged(driver)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs is not yet supported in session mode"));
|
|
return -1;
|
|
}
|
|
if (fs->accessmode != VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs only supports passthrough accessmode"));
|
|
return -1;
|
|
}
|
|
if (fs->wrpolicy != VIR_DOMAIN_FS_WRPOLICY_DEFAULT) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs does not support wrpolicy"));
|
|
return -1;
|
|
}
|
|
if (fs->model != VIR_DOMAIN_FS_MODEL_DEFAULT) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs does not support model"));
|
|
return -1;
|
|
}
|
|
if (fs->format != VIR_STORAGE_FILE_NONE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs does not support format"));
|
|
return -1;
|
|
}
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VHOST_USER_FS)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtiofs is not supported with this QEMU binary"));
|
|
return -1;
|
|
}
|
|
if (qemuDomainDefValidateVirtioFSSharedMemory(def) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_FS_DRIVER_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainFSDriverType, fs->fsdriver);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateZPCIAddress(virDomainDeviceInfoPtr info,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (!virZPCIDeviceAddressIsEmpty(&info->addr.pci.zpci) &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_ZPCI)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s",
|
|
_("This QEMU binary doesn't support zPCI"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidateAddress(const virDomainDeviceDef *dev,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
virDomainDeviceInfoPtr info;
|
|
|
|
if (!(info = virDomainDeviceGetInfo((virDomainDeviceDef *)dev)))
|
|
return 0;
|
|
|
|
switch ((virDomainDeviceAddressType) info->type) {
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI:
|
|
return qemuDomainDeviceDefValidateZPCIAddress(info, qemuCaps);
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE:
|
|
/* Address validation might happen before we have had a chance to
|
|
* automatically assign addresses to devices for which the user
|
|
* didn't specify one themselves */
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO: {
|
|
virDomainDeviceSpaprVioAddressPtr addr = &(info->addr.spaprvio);
|
|
|
|
if (addr->has_reg && addr->reg > 0xffffffff) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("spapr-vio reg='0x%llx' exceeds maximum "
|
|
"possible value (0xffffffff)"),
|
|
addr->reg);
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_ISA:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_UNASSIGNED:
|
|
/* No validation for these address types yet */
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainDeviceAddressType, info->type);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefValidate(const virDomainDeviceDef *dev,
|
|
const virDomainDef *def,
|
|
void *opaque)
|
|
{
|
|
int ret = 0;
|
|
virQEMUDriverPtr driver = opaque;
|
|
g_autoptr(virQEMUCaps) qemuCaps = NULL;
|
|
g_autoptr(virDomainCaps) domCaps = NULL;
|
|
|
|
if (!(qemuCaps = virQEMUCapsCacheLookup(driver->qemuCapsCache,
|
|
def->emulator)))
|
|
return -1;
|
|
|
|
if (!(domCaps = virQEMUDriverGetDomainCapabilities(driver, qemuCaps,
|
|
def->os.machine,
|
|
def->os.arch,
|
|
def->virtType)))
|
|
return -1;
|
|
|
|
if ((ret = qemuDomainDeviceDefValidateAddress(dev, qemuCaps)) < 0)
|
|
return ret;
|
|
|
|
if ((ret = virDomainCapsDeviceDefValidate(domCaps, dev, def)) < 0)
|
|
return ret;
|
|
|
|
switch ((virDomainDeviceType)dev->type) {
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
ret = qemuDomainDeviceDefValidateNetwork(dev->data.net, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
ret = qemuDomainChrDefValidate(dev->data.chr, def, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
ret = qemuDomainSmartcardDefValidate(dev->data.smartcard, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
ret = qemuDomainRNGDefValidate(dev->data.rng, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
ret = qemuDomainRedirdevDefValidate(dev->data.redirdev, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
ret = qemuDomainWatchdogDefValidate(dev->data.watchdog, def);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
ret = qemuDomainDeviceDefValidateHostdev(dev->data.hostdev, def,
|
|
qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
ret = qemuDomainDeviceDefValidateVideo(dev->data.video, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
ret = qemuDomainDeviceDefValidateDisk(dev->data.disk, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
ret = qemuDomainDeviceDefValidateController(dev->data.controller, def,
|
|
qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
ret = qemuDomainDeviceDefValidateVsock(dev->data.vsock, def, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_TPM:
|
|
ret = qemuDomainDeviceDefValidateTPM(dev->data.tpm, def, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
ret = qemuDomainDeviceDefValidateGraphics(dev->data.graphics, def,
|
|
driver, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
ret = qemuDomainDeviceDefValidateInput(dev->data.input, def, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_MEMBALLOON:
|
|
ret = qemuDomainDeviceDefValidateMemballoon(dev->data.memballoon, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
ret = qemuDomainDeviceDefValidateIOMMU(dev->data.iommu, def, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
ret = qemuDomainDeviceDefValidateFS(dev->data.fs, def, driver, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_NVRAM:
|
|
ret = qemuDomainDeviceDefValidateNVRAM(dev->data.nvram, def, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
ret = qemuDomainDeviceDefValidateHub(dev->data.hub, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
ret = qemuDomainDeviceDefValidateSound(dev->data.sound, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
ret = qemuDomainDeviceDefValidateMemory(dev->data.memory, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
case VIR_DOMAIN_DEVICE_SHMEM:
|
|
case VIR_DOMAIN_DEVICE_PANIC:
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
case VIR_DOMAIN_DEVICE_LAST:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDefaultNetModel:
|
|
* @def: domain definition
|
|
* @qemuCaps: qemu capabilities
|
|
*
|
|
* Returns the default network model for a given domain. Note that if @qemuCaps
|
|
* is NULL this function may return NULL if the default model depends on the
|
|
* capabilities.
|
|
*/
|
|
static int
|
|
qemuDomainDefaultNetModel(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (ARCH_IS_S390(def->os.arch))
|
|
return VIR_DOMAIN_NET_MODEL_VIRTIO;
|
|
|
|
if (def->os.arch == VIR_ARCH_ARMV6L ||
|
|
def->os.arch == VIR_ARCH_ARMV7L ||
|
|
def->os.arch == VIR_ARCH_AARCH64) {
|
|
if (STREQ(def->os.machine, "versatilepb"))
|
|
return VIR_DOMAIN_NET_MODEL_SMC91C111;
|
|
|
|
if (qemuDomainIsARMVirt(def))
|
|
return VIR_DOMAIN_NET_MODEL_VIRTIO;
|
|
|
|
/* Incomplete. vexpress (and a few others) use this, but not all
|
|
* arm boards */
|
|
return VIR_DOMAIN_NET_MODEL_LAN9118;
|
|
}
|
|
|
|
/* virtio is a sensible default for RISC-V virt guests */
|
|
if (qemuDomainIsRISCVVirt(def))
|
|
return VIR_DOMAIN_NET_MODEL_VIRTIO;
|
|
|
|
/* In all other cases the model depends on the capabilities. If they were
|
|
* not provided don't report any default. */
|
|
if (!qemuCaps)
|
|
return VIR_DOMAIN_NET_MODEL_UNKNOWN;
|
|
|
|
/* Try several network devices in turn; each of these devices is
|
|
* less likely be supported out-of-the-box by the guest operating
|
|
* system than the previous one */
|
|
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_RTL8139))
|
|
return VIR_DOMAIN_NET_MODEL_RTL8139;
|
|
else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_E1000))
|
|
return VIR_DOMAIN_NET_MODEL_E1000;
|
|
else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VIRTIO_NET))
|
|
return VIR_DOMAIN_NET_MODEL_VIRTIO;
|
|
|
|
/* We've had no luck detecting support for any network device,
|
|
* but we have to return something: might as well be rtl8139 */
|
|
return VIR_DOMAIN_NET_MODEL_RTL8139;
|
|
}
|
|
|
|
|
|
/*
|
|
* Clear auto generated unix socket paths:
|
|
*
|
|
* libvirt 1.2.18 and older:
|
|
* {cfg->channelTargetDir}/{dom-name}.{target-name}
|
|
*
|
|
* libvirt 1.2.19 - 1.3.2:
|
|
* {cfg->channelTargetDir}/domain-{dom-name}/{target-name}
|
|
*
|
|
* libvirt 1.3.3 and newer:
|
|
* {cfg->channelTargetDir}/domain-{dom-id}-{short-dom-name}/{target-name}
|
|
*
|
|
* The unix socket path was stored in config XML until libvirt 1.3.0.
|
|
* If someone specifies the same path as we generate, they shouldn't do it.
|
|
*
|
|
* This function clears the path for migration as well, so we need to clear
|
|
* the path even if we are not storing it in the XML.
|
|
*/
|
|
static void
|
|
qemuDomainChrDefDropDefaultPath(virDomainChrDefPtr chr,
|
|
virQEMUDriverPtr driver)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = NULL;
|
|
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
|
g_autofree char *regexp = NULL;
|
|
|
|
if (chr->deviceType != VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL ||
|
|
chr->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO ||
|
|
chr->source->type != VIR_DOMAIN_CHR_TYPE_UNIX ||
|
|
!chr->source->data.nix.path) {
|
|
return;
|
|
}
|
|
|
|
cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
virBufferEscapeRegex(&buf, "^%s", cfg->channelTargetDir);
|
|
virBufferAddLit(&buf, "/([^/]+\\.)|(domain-[^/]+/)");
|
|
virBufferEscapeRegex(&buf, "%s$", chr->target.name);
|
|
|
|
regexp = virBufferContentAndReset(&buf);
|
|
|
|
if (virStringMatch(chr->source->data.nix.path, regexp))
|
|
VIR_FREE(chr->source->data.nix.path);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainShmemDefPostParse(virDomainShmemDefPtr shm)
|
|
{
|
|
/* This was the default since the introduction of this device. */
|
|
if (shm->model != VIR_DOMAIN_SHMEM_MODEL_IVSHMEM_DOORBELL && !shm->size)
|
|
shm->size = 4 << 20;
|
|
|
|
/* Nothing more to check/change for IVSHMEM */
|
|
if (shm->model == VIR_DOMAIN_SHMEM_MODEL_IVSHMEM)
|
|
return 0;
|
|
|
|
if (!shm->server.enabled) {
|
|
if (shm->model == VIR_DOMAIN_SHMEM_MODEL_IVSHMEM_DOORBELL) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("shmem model '%s' is supported "
|
|
"only with server option enabled"),
|
|
virDomainShmemModelTypeToString(shm->model));
|
|
return -1;
|
|
}
|
|
|
|
if (shm->msi.enabled) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("shmem model '%s' doesn't support "
|
|
"msi"),
|
|
virDomainShmemModelTypeToString(shm->model));
|
|
}
|
|
} else {
|
|
if (shm->model == VIR_DOMAIN_SHMEM_MODEL_IVSHMEM_PLAIN) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("shmem model '%s' is supported "
|
|
"only with server option disabled"),
|
|
virDomainShmemModelTypeToString(shm->model));
|
|
return -1;
|
|
}
|
|
|
|
if (shm->size) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("shmem model '%s' does not support size setting"),
|
|
virDomainShmemModelTypeToString(shm->model));
|
|
return -1;
|
|
}
|
|
shm->msi.enabled = true;
|
|
if (!shm->msi.ioeventfd)
|
|
shm->msi.ioeventfd = VIR_TRISTATE_SWITCH_ON;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define QEMU_USB_XHCI_MAXPORTS 15
|
|
|
|
|
|
static int
|
|
qemuDomainControllerDefPostParse(virDomainControllerDefPtr cont,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps,
|
|
unsigned int parseFlags)
|
|
{
|
|
switch ((virDomainControllerType)cont->type) {
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_SCSI:
|
|
/* Set the default SCSI controller model if not already set */
|
|
if (qemuDomainSetSCSIControllerModel(def, cont, qemuCaps) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_USB:
|
|
if (cont->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_DEFAULT && qemuCaps) {
|
|
/* Pick a suitable default model for the USB controller if none
|
|
* has been selected by the user and we have the qemuCaps for
|
|
* figuring out which contollers are supported.
|
|
*
|
|
* We rely on device availability instead of setting the model
|
|
* unconditionally because, for some machine types, there's a
|
|
* chance we will get away with using the legacy USB controller
|
|
* when the relevant device is not available.
|
|
*
|
|
* See qemuBuildControllerDevCommandLine() */
|
|
|
|
/* Default USB controller is piix3-uhci if available. */
|
|
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_PIIX3_USB_UHCI))
|
|
cont->model = VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX3_UHCI;
|
|
|
|
if (ARCH_IS_S390(def->os.arch)) {
|
|
if (cont->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
|
|
/* set the default USB model to none for s390 unless an
|
|
* address is found */
|
|
cont->model = VIR_DOMAIN_CONTROLLER_MODEL_USB_NONE;
|
|
}
|
|
} else if (ARCH_IS_PPC64(def->os.arch)) {
|
|
/* To not break migration we need to set default USB controller
|
|
* for ppc64 to pci-ohci if we cannot change ABI of the VM.
|
|
* The nec-usb-xhci or qemu-xhci controller is used as default
|
|
* only for newly defined domains or devices. */
|
|
if ((parseFlags & VIR_DOMAIN_DEF_PARSE_ABI_UPDATE) &&
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_QEMU_XHCI)) {
|
|
cont->model = VIR_DOMAIN_CONTROLLER_MODEL_USB_QEMU_XHCI;
|
|
} else if ((parseFlags & VIR_DOMAIN_DEF_PARSE_ABI_UPDATE) &&
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_NEC_USB_XHCI)) {
|
|
cont->model = VIR_DOMAIN_CONTROLLER_MODEL_USB_NEC_XHCI;
|
|
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_PCI_OHCI)) {
|
|
cont->model = VIR_DOMAIN_CONTROLLER_MODEL_USB_PCI_OHCI;
|
|
} else {
|
|
/* Explicitly fallback to legacy USB controller for PPC64. */
|
|
cont->model = -1;
|
|
}
|
|
} else if (def->os.arch == VIR_ARCH_AARCH64) {
|
|
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_QEMU_XHCI))
|
|
cont->model = VIR_DOMAIN_CONTROLLER_MODEL_USB_QEMU_XHCI;
|
|
else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NEC_USB_XHCI))
|
|
cont->model = VIR_DOMAIN_CONTROLLER_MODEL_USB_NEC_XHCI;
|
|
}
|
|
}
|
|
/* forbid usb model 'qusb1' and 'qusb2' in this kind of hyperviosr */
|
|
if (cont->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_QUSB1 ||
|
|
cont->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_QUSB2) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("USB controller model type 'qusb1' or 'qusb2' "
|
|
"is not supported in %s"),
|
|
virDomainVirtTypeToString(def->virtType));
|
|
return -1;
|
|
}
|
|
if ((cont->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_NEC_XHCI ||
|
|
cont->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_QEMU_XHCI) &&
|
|
cont->opts.usbopts.ports > QEMU_USB_XHCI_MAXPORTS) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("'%s' controller only supports up to '%u' ports"),
|
|
virDomainControllerModelUSBTypeToString(cont->model),
|
|
QEMU_USB_XHCI_MAXPORTS);
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_PCI:
|
|
|
|
/* pSeries guests can have multiple pci-root controllers,
|
|
* but other machine types only support a single one */
|
|
if (!qemuDomainIsPSeries(def) &&
|
|
(cont->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT ||
|
|
cont->model == VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT) &&
|
|
cont->idx != 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("pci-root and pcie-root controllers "
|
|
"should have index 0"));
|
|
return -1;
|
|
}
|
|
|
|
if (cont->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS &&
|
|
!qemuDomainIsI440FX(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("pci-expander-bus controllers are only supported "
|
|
"on 440fx-based machinetypes"));
|
|
return -1;
|
|
}
|
|
if (cont->model == VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS &&
|
|
!qemuDomainIsQ35(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("pcie-expander-bus controllers are only supported "
|
|
"on q35-based machinetypes"));
|
|
return -1;
|
|
}
|
|
|
|
/* if a PCI expander bus or pci-root on Pseries has a NUMA node
|
|
* set, make sure that NUMA node is configured in the guest
|
|
* <cpu><numa> array. NUMA cell id's in this array are numbered
|
|
* from 0 .. size-1.
|
|
*/
|
|
if (cont->opts.pciopts.numaNode >= 0 &&
|
|
cont->opts.pciopts.numaNode >=
|
|
(int)virDomainNumaGetNodeCount(def->numa)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("%s with index %d is "
|
|
"configured for a NUMA node (%d) "
|
|
"not present in the domain's "
|
|
"<cpu><numa> array (%zu)"),
|
|
virDomainControllerModelPCITypeToString(cont->model),
|
|
cont->idx, cont->opts.pciopts.numaNode,
|
|
virDomainNumaGetNodeCount(def->numa));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_SATA:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_CCID:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_IDE:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_FDC:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuDomainChrDefPostParse(virDomainChrDefPtr chr,
|
|
const virDomainDef *def,
|
|
virQEMUDriverPtr driver,
|
|
unsigned int parseFlags)
|
|
{
|
|
/* Historically, isa-serial and the default matched, so in order to
|
|
* maintain backwards compatibility we map them here. The actual default
|
|
* will be picked below based on the architecture and machine type. */
|
|
if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL &&
|
|
chr->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA) {
|
|
chr->targetType = VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE;
|
|
}
|
|
|
|
/* Set the default serial type */
|
|
if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL &&
|
|
chr->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE) {
|
|
if (ARCH_IS_X86(def->os.arch)) {
|
|
chr->targetType = VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA;
|
|
} else if (qemuDomainIsPSeries(def)) {
|
|
chr->targetType = VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SPAPR_VIO;
|
|
} else if (qemuDomainIsARMVirt(def) || qemuDomainIsRISCVVirt(def)) {
|
|
chr->targetType = VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SYSTEM;
|
|
} else if (ARCH_IS_S390(def->os.arch)) {
|
|
chr->targetType = VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SCLP;
|
|
}
|
|
}
|
|
|
|
/* Set the default target model */
|
|
if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL &&
|
|
chr->targetModel == VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_NONE) {
|
|
switch ((virDomainChrSerialTargetType)chr->targetType) {
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA:
|
|
chr->targetModel = VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_ISA_SERIAL;
|
|
break;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB:
|
|
chr->targetModel = VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_USB_SERIAL;
|
|
break;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_PCI:
|
|
chr->targetModel = VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_PCI_SERIAL;
|
|
break;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SPAPR_VIO:
|
|
chr->targetModel = VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SPAPR_VTY;
|
|
break;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SYSTEM:
|
|
if (qemuDomainIsARMVirt(def)) {
|
|
chr->targetModel = VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_PL011;
|
|
} else if (qemuDomainIsRISCVVirt(def)) {
|
|
chr->targetModel = VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_16550A;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SCLP:
|
|
chr->targetModel = VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_SCLPCONSOLE;
|
|
break;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_LAST:
|
|
/* Nothing to do */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* clear auto generated unix socket path for inactive definitions */
|
|
if (parseFlags & VIR_DOMAIN_DEF_PARSE_INACTIVE) {
|
|
qemuDomainChrDefDropDefaultPath(chr, driver);
|
|
|
|
/* For UNIX chardev if no path is provided we generate one.
|
|
* This also implies that the mode is 'bind'. */
|
|
if (chr->source &&
|
|
chr->source->type == VIR_DOMAIN_CHR_TYPE_UNIX &&
|
|
!chr->source->data.nix.path) {
|
|
chr->source->data.nix.listen = true;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDeviceDiskDefPostParseRestoreSecAlias:
|
|
*
|
|
* Re-generate aliases for objects related to the storage source if they
|
|
* were not stored in the status XML by an older libvirt.
|
|
*
|
|
* Note that qemuCaps should be always present for a status XML.
|
|
*/
|
|
static int
|
|
qemuDomainDeviceDiskDefPostParseRestoreSecAlias(virDomainDiskDefPtr disk,
|
|
virQEMUCapsPtr qemuCaps,
|
|
unsigned int parseFlags)
|
|
{
|
|
qemuDomainStorageSourcePrivatePtr priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src);
|
|
bool restoreAuthSecret = false;
|
|
bool restoreEncSecret = false;
|
|
g_autofree char *authalias = NULL;
|
|
g_autofree char *encalias = NULL;
|
|
|
|
if (!(parseFlags & VIR_DOMAIN_DEF_PARSE_STATUS) ||
|
|
!qemuCaps ||
|
|
virStorageSourceIsEmpty(disk->src) ||
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_SECRET))
|
|
return 0;
|
|
|
|
/* network storage authentication secret */
|
|
if (disk->src->auth &&
|
|
(!priv || !priv->secinfo)) {
|
|
|
|
/* only RBD and iSCSI (with capability) were supporting authentication
|
|
* using secret object at the time we did not format the alias into the
|
|
* status XML */
|
|
if (virStorageSourceGetActualType(disk->src) == VIR_STORAGE_TYPE_NETWORK &&
|
|
(disk->src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD ||
|
|
(disk->src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI &&
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_ISCSI_PASSWORD_SECRET))))
|
|
restoreAuthSecret = true;
|
|
}
|
|
|
|
/* disk encryption secret */
|
|
if (disk->src->encryption &&
|
|
disk->src->encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS &&
|
|
(!priv || !priv->encinfo))
|
|
restoreEncSecret = true;
|
|
|
|
if (!restoreAuthSecret && !restoreEncSecret)
|
|
return 0;
|
|
|
|
if (!priv) {
|
|
if (!(disk->src->privateData = qemuDomainStorageSourcePrivateNew()))
|
|
return -1;
|
|
|
|
priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src);
|
|
}
|
|
|
|
if (restoreAuthSecret) {
|
|
if (!(authalias = qemuDomainGetSecretAESAlias(disk->info.alias, false)))
|
|
return -1;
|
|
|
|
if (qemuStorageSourcePrivateDataAssignSecinfo(&priv->secinfo, &authalias) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (restoreEncSecret) {
|
|
if (!(encalias = qemuDomainGetSecretAESAlias(disk->info.alias, true)))
|
|
return -1;
|
|
|
|
if (qemuStorageSourcePrivateDataAssignSecinfo(&priv->encinfo, &encalias) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDiskDefPostParse(virDomainDiskDefPtr disk,
|
|
virQEMUCapsPtr qemuCaps,
|
|
unsigned int parseFlags)
|
|
{
|
|
/* set default disk types and drivers */
|
|
if (!virDomainDiskGetDriver(disk) &&
|
|
virDomainDiskSetDriver(disk, "qemu") < 0)
|
|
return -1;
|
|
|
|
/* default disk format for drives */
|
|
if (virDomainDiskGetFormat(disk) == VIR_STORAGE_FILE_NONE &&
|
|
virDomainDiskGetType(disk) != VIR_STORAGE_TYPE_VOLUME)
|
|
virDomainDiskSetFormat(disk, VIR_STORAGE_FILE_RAW);
|
|
|
|
/* default disk format for mirrored drive */
|
|
if (disk->mirror &&
|
|
disk->mirror->format == VIR_STORAGE_FILE_NONE)
|
|
disk->mirror->format = VIR_STORAGE_FILE_RAW;
|
|
|
|
if (qemuDomainDeviceDiskDefPostParseRestoreSecAlias(disk, qemuCaps,
|
|
parseFlags) < 0)
|
|
return -1;
|
|
|
|
/* regenerate TLS alias for old status XMLs */
|
|
if (parseFlags & VIR_DOMAIN_DEF_PARSE_STATUS &&
|
|
disk->src->haveTLS == VIR_TRISTATE_BOOL_YES &&
|
|
!disk->src->tlsAlias &&
|
|
!(disk->src->tlsAlias = qemuAliasTLSObjFromSrcAlias(disk->info.alias)))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceNetDefPostParse(virDomainNetDefPtr net,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (net->type != VIR_DOMAIN_NET_TYPE_HOSTDEV &&
|
|
!virDomainNetGetModelString(net))
|
|
net->model = qemuDomainDefaultNetModel(def, qemuCaps);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefaultVideoDevice(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (ARCH_IS_PPC64(def->os.arch))
|
|
return VIR_DOMAIN_VIDEO_TYPE_VGA;
|
|
if (qemuDomainIsARMVirt(def) ||
|
|
qemuDomainIsRISCVVirt(def) ||
|
|
ARCH_IS_S390(def->os.arch)) {
|
|
return VIR_DOMAIN_VIDEO_TYPE_VIRTIO;
|
|
}
|
|
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_CIRRUS_VGA))
|
|
return VIR_DOMAIN_VIDEO_TYPE_CIRRUS;
|
|
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VGA))
|
|
return VIR_DOMAIN_VIDEO_TYPE_VGA;
|
|
return VIR_DOMAIN_VIDEO_TYPE_DEFAULT;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceVideoDefPostParse(virDomainVideoDefPtr video,
|
|
const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (video->type == VIR_DOMAIN_VIDEO_TYPE_DEFAULT)
|
|
video->type = qemuDomainDefaultVideoDevice(def, qemuCaps);
|
|
|
|
if (video->type == VIR_DOMAIN_VIDEO_TYPE_QXL &&
|
|
!video->vgamem) {
|
|
video->vgamem = QEMU_QXL_VGAMEM_DEFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDevicePanicDefPostParse(virDomainPanicDefPtr panic,
|
|
const virDomainDef *def)
|
|
{
|
|
if (panic->model == VIR_DOMAIN_PANIC_MODEL_DEFAULT) {
|
|
if (qemuDomainIsPSeries(def))
|
|
panic->model = VIR_DOMAIN_PANIC_MODEL_PSERIES;
|
|
else if (ARCH_IS_S390(def->os.arch))
|
|
panic->model = VIR_DOMAIN_PANIC_MODEL_S390;
|
|
else
|
|
panic->model = VIR_DOMAIN_PANIC_MODEL_ISA;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainVsockDefPostParse(virDomainVsockDefPtr vsock)
|
|
{
|
|
if (vsock->model == VIR_DOMAIN_VSOCK_MODEL_DEFAULT)
|
|
vsock->model = VIR_DOMAIN_VSOCK_MODEL_VIRTIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainHostdevDefMdevPostParse(virDomainHostdevSubsysMediatedDevPtr mdevsrc,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
/* QEMU 2.12 added support for vfio-pci display type, we default to
|
|
* 'display=off' to stay safe from future changes */
|
|
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VFIO_PCI_DISPLAY) &&
|
|
mdevsrc->model == VIR_MDEV_MODEL_TYPE_VFIO_PCI &&
|
|
mdevsrc->display == VIR_TRISTATE_SWITCH_ABSENT)
|
|
mdevsrc->display = VIR_TRISTATE_SWITCH_OFF;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainHostdevDefPostParse(virDomainHostdevDefPtr hostdev,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
virDomainHostdevSubsysPtr subsys = &hostdev->source.subsys;
|
|
|
|
if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
|
|
hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV &&
|
|
qemuDomainHostdevDefMdevPostParse(&subsys->u.mdev, qemuCaps) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainTPMDefPostParse(virDomainTPMDefPtr tpm,
|
|
virArch arch)
|
|
{
|
|
if (tpm->model == VIR_DOMAIN_TPM_MODEL_DEFAULT) {
|
|
if (ARCH_IS_PPC64(arch))
|
|
tpm->model = VIR_DOMAIN_TPM_MODEL_SPAPR;
|
|
else
|
|
tpm->model = VIR_DOMAIN_TPM_MODEL_TIS;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDeviceDefPostParse(virDomainDeviceDefPtr dev,
|
|
const virDomainDef *def,
|
|
unsigned int parseFlags,
|
|
void *opaque,
|
|
void *parseOpaque)
|
|
{
|
|
virQEMUDriverPtr driver = opaque;
|
|
/* Note that qemuCaps may be NULL when this function is called. This
|
|
* function shall not fail in that case. It will be re-run on VM startup
|
|
* with the capabilities populated. */
|
|
virQEMUCapsPtr qemuCaps = parseOpaque;
|
|
int ret = -1;
|
|
|
|
switch ((virDomainDeviceType) dev->type) {
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
ret = qemuDomainDeviceNetDefPostParse(dev->data.net, def, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
ret = qemuDomainDeviceDiskDefPostParse(dev->data.disk, qemuCaps,
|
|
parseFlags);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
ret = qemuDomainDeviceVideoDefPostParse(dev->data.video, def, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_PANIC:
|
|
ret = qemuDomainDevicePanicDefPostParse(dev->data.panic, def);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
ret = qemuDomainControllerDefPostParse(dev->data.controller, def,
|
|
qemuCaps, parseFlags);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_SHMEM:
|
|
ret = qemuDomainShmemDefPostParse(dev->data.shmem);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
ret = qemuDomainChrDefPostParse(dev->data.chr, def, driver, parseFlags);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
ret = qemuDomainVsockDefPostParse(dev->data.vsock);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
ret = qemuDomainHostdevDefPostParse(dev->data.hostdev, qemuCaps);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_TPM:
|
|
ret = qemuDomainTPMDefPostParse(dev->data.tpm, def->os.arch);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
case VIR_DOMAIN_DEVICE_MEMBALLOON:
|
|
case VIR_DOMAIN_DEVICE_NVRAM:
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
ret = 0;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("unexpected VIR_DOMAIN_DEVICE_NONE"));
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainDeviceType, dev->type);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefAssignAddresses(virDomainDef *def,
|
|
unsigned int parseFlags G_GNUC_UNUSED,
|
|
void *opaque,
|
|
void *parseOpaque)
|
|
{
|
|
virQEMUDriverPtr driver = opaque;
|
|
/* Note that qemuCaps may be NULL when this function is called. This
|
|
* function shall not fail in that case. It will be re-run on VM startup
|
|
* with the capabilities populated. */
|
|
virQEMUCapsPtr qemuCaps = parseOpaque;
|
|
bool newDomain = parseFlags & VIR_DOMAIN_DEF_PARSE_ABI_UPDATE;
|
|
|
|
/* Skip address assignment if @qemuCaps is not present. In such case devices
|
|
* which are automatically added may be missing. Additionally @qemuCaps should
|
|
* only be missing when reloading configs, thus addresses were already
|
|
* assigned. */
|
|
if (!qemuCaps)
|
|
return 1;
|
|
|
|
return qemuDomainAssignAddresses(def, qemuCaps, driver, NULL, newDomain);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainPostParseDataAlloc(const virDomainDef *def,
|
|
unsigned int parseFlags G_GNUC_UNUSED,
|
|
void *opaque,
|
|
void **parseOpaque)
|
|
{
|
|
virQEMUDriverPtr driver = opaque;
|
|
|
|
if (!(*parseOpaque = virQEMUCapsCacheLookup(driver->qemuCapsCache,
|
|
def->emulator)))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainPostParseDataFree(void *parseOpaque)
|
|
{
|
|
virQEMUCapsPtr qemuCaps = parseOpaque;
|
|
|
|
virObjectUnref(qemuCaps);
|
|
}
|
|
|
|
|
|
virDomainDefParserConfig virQEMUDriverDomainDefParserConfig = {
|
|
.domainPostParseBasicCallback = qemuDomainDefPostParseBasic,
|
|
.domainPostParseDataAlloc = qemuDomainPostParseDataAlloc,
|
|
.domainPostParseDataFree = qemuDomainPostParseDataFree,
|
|
.devicesPostParseCallback = qemuDomainDeviceDefPostParse,
|
|
.domainPostParseCallback = qemuDomainDefPostParse,
|
|
.assignAddressesCallback = qemuDomainDefAssignAddresses,
|
|
.domainValidateCallback = qemuDomainDefValidate,
|
|
.deviceValidateCallback = qemuDomainDeviceDefValidate,
|
|
|
|
.features = VIR_DOMAIN_DEF_FEATURE_MEMORY_HOTPLUG |
|
|
VIR_DOMAIN_DEF_FEATURE_OFFLINE_VCPUPIN |
|
|
VIR_DOMAIN_DEF_FEATURE_INDIVIDUAL_VCPUS |
|
|
VIR_DOMAIN_DEF_FEATURE_USER_ALIAS |
|
|
VIR_DOMAIN_DEF_FEATURE_FW_AUTOSELECT |
|
|
VIR_DOMAIN_DEF_FEATURE_NET_MODEL_STRING,
|
|
};
|
|
|
|
|
|
static void
|
|
qemuDomainObjSaveStatus(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
if (virDomainObjIsActive(obj)) {
|
|
if (virDomainObjSave(obj, driver->xmlopt, cfg->stateDir) < 0)
|
|
VIR_WARN("Failed to save status on vm %s", obj->def->name);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainSaveStatus(virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjSaveStatus(QEMU_DOMAIN_PRIVATE(obj)->driver, obj);
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainSaveConfig(virDomainObjPtr obj)
|
|
{
|
|
virQEMUDriverPtr driver = QEMU_DOMAIN_PRIVATE(obj)->driver;
|
|
g_autoptr(virQEMUDriverConfig) cfg = NULL;
|
|
virDomainDefPtr def = NULL;
|
|
|
|
if (virDomainObjIsActive(obj))
|
|
def = obj->newDef;
|
|
else
|
|
def = obj->def;
|
|
|
|
if (!def)
|
|
return;
|
|
|
|
cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
if (virDomainDefSave(def, driver->xmlopt, cfg->configDir) < 0)
|
|
VIR_WARN("Failed to save config of vm %s", obj->def->name);
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainObjSetJobPhase(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
int phase)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
unsigned long long me = virThreadSelfID();
|
|
|
|
if (!priv->job.asyncJob)
|
|
return;
|
|
|
|
VIR_DEBUG("Setting '%s' phase to '%s'",
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
qemuDomainAsyncJobPhaseToString(priv->job.asyncJob, phase));
|
|
|
|
if (priv->job.asyncOwner && me != priv->job.asyncOwner) {
|
|
VIR_WARN("'%s' async job is owned by thread %llu",
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
priv->job.asyncOwner);
|
|
}
|
|
|
|
priv->job.phase = phase;
|
|
priv->job.asyncOwner = me;
|
|
qemuDomainObjSaveStatus(driver, obj);
|
|
}
|
|
|
|
void
|
|
qemuDomainObjSetAsyncJobMask(virDomainObjPtr obj,
|
|
unsigned long long allowedJobs)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
|
|
if (!priv->job.asyncJob)
|
|
return;
|
|
|
|
priv->job.mask = allowedJobs | JOB_MASK(QEMU_JOB_DESTROY);
|
|
}
|
|
|
|
void
|
|
qemuDomainObjDiscardAsyncJob(virQEMUDriverPtr driver, virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
|
|
if (priv->job.active == QEMU_JOB_ASYNC_NESTED)
|
|
qemuDomainObjResetJob(priv);
|
|
qemuDomainObjResetAsyncJob(priv);
|
|
qemuDomainObjSaveStatus(driver, obj);
|
|
}
|
|
|
|
void
|
|
qemuDomainObjReleaseAsyncJob(virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
|
|
VIR_DEBUG("Releasing ownership of '%s' async job",
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob));
|
|
|
|
if (priv->job.asyncOwner != virThreadSelfID()) {
|
|
VIR_WARN("'%s' async job is owned by thread %llu",
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
priv->job.asyncOwner);
|
|
}
|
|
priv->job.asyncOwner = 0;
|
|
}
|
|
|
|
static bool
|
|
qemuDomainNestedJobAllowed(qemuDomainObjPrivatePtr priv, qemuDomainJob job)
|
|
{
|
|
return !priv->job.asyncJob ||
|
|
job == QEMU_JOB_NONE ||
|
|
(priv->job.mask & JOB_MASK(job)) != 0;
|
|
}
|
|
|
|
bool
|
|
qemuDomainJobAllowed(qemuDomainObjPrivatePtr priv, qemuDomainJob job)
|
|
{
|
|
return !priv->job.active && qemuDomainNestedJobAllowed(priv, job);
|
|
}
|
|
|
|
static bool
|
|
qemuDomainObjCanSetJob(qemuDomainObjPrivatePtr priv,
|
|
qemuDomainJob job,
|
|
qemuDomainAgentJob agentJob)
|
|
{
|
|
return ((job == QEMU_JOB_NONE ||
|
|
priv->job.active == QEMU_JOB_NONE) &&
|
|
(agentJob == QEMU_AGENT_JOB_NONE ||
|
|
priv->job.agentActive == QEMU_AGENT_JOB_NONE));
|
|
}
|
|
|
|
/* Give up waiting for mutex after 30 seconds */
|
|
#define QEMU_JOB_WAIT_TIME (1000ull * 30)
|
|
|
|
/**
|
|
* qemuDomainObjBeginJobInternal:
|
|
* @driver: qemu driver
|
|
* @obj: domain object
|
|
* @job: qemuDomainJob to start
|
|
* @asyncJob: qemuDomainAsyncJob to start
|
|
* @nowait: don't wait trying to acquire @job
|
|
*
|
|
* Acquires job for a domain object which must be locked before
|
|
* calling. If there's already a job running waits up to
|
|
* QEMU_JOB_WAIT_TIME after which the functions fails reporting
|
|
* an error unless @nowait is set.
|
|
*
|
|
* If @nowait is true this function tries to acquire job and if
|
|
* it fails, then it returns immediately without waiting. No
|
|
* error is reported in this case.
|
|
*
|
|
* Returns: 0 on success,
|
|
* -2 if unable to start job because of timeout or
|
|
* maxQueuedJobs limit,
|
|
* -1 otherwise.
|
|
*/
|
|
static int ATTRIBUTE_NONNULL(1)
|
|
qemuDomainObjBeginJobInternal(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainJob job,
|
|
qemuDomainAgentJob agentJob,
|
|
qemuDomainAsyncJob asyncJob,
|
|
bool nowait)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
unsigned long long now;
|
|
unsigned long long then;
|
|
bool nested = job == QEMU_JOB_ASYNC_NESTED;
|
|
bool async = job == QEMU_JOB_ASYNC;
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
const char *blocker = NULL;
|
|
const char *agentBlocker = NULL;
|
|
int ret = -1;
|
|
unsigned long long duration = 0;
|
|
unsigned long long agentDuration = 0;
|
|
unsigned long long asyncDuration = 0;
|
|
|
|
VIR_DEBUG("Starting job: job=%s agentJob=%s asyncJob=%s "
|
|
"(vm=%p name=%s, current job=%s agentJob=%s async=%s)",
|
|
qemuDomainJobTypeToString(job),
|
|
qemuDomainAgentJobTypeToString(agentJob),
|
|
qemuDomainAsyncJobTypeToString(asyncJob),
|
|
obj, obj->def->name,
|
|
qemuDomainJobTypeToString(priv->job.active),
|
|
qemuDomainAgentJobTypeToString(priv->job.agentActive),
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob));
|
|
|
|
if (virTimeMillisNow(&now) < 0)
|
|
return -1;
|
|
|
|
priv->jobs_queued++;
|
|
then = now + QEMU_JOB_WAIT_TIME;
|
|
|
|
retry:
|
|
if ((!async && job != QEMU_JOB_DESTROY) &&
|
|
cfg->maxQueuedJobs &&
|
|
priv->jobs_queued > cfg->maxQueuedJobs) {
|
|
goto error;
|
|
}
|
|
|
|
while (!nested && !qemuDomainNestedJobAllowed(priv, job)) {
|
|
if (nowait)
|
|
goto cleanup;
|
|
|
|
VIR_DEBUG("Waiting for async job (vm=%p name=%s)", obj, obj->def->name);
|
|
if (virCondWaitUntil(&priv->job.asyncCond, &obj->parent.lock, then) < 0)
|
|
goto error;
|
|
}
|
|
|
|
while (!qemuDomainObjCanSetJob(priv, job, agentJob)) {
|
|
if (nowait)
|
|
goto cleanup;
|
|
|
|
VIR_DEBUG("Waiting for job (vm=%p name=%s)", obj, obj->def->name);
|
|
if (virCondWaitUntil(&priv->job.cond, &obj->parent.lock, then) < 0)
|
|
goto error;
|
|
}
|
|
|
|
/* No job is active but a new async job could have been started while obj
|
|
* was unlocked, so we need to recheck it. */
|
|
if (!nested && !qemuDomainNestedJobAllowed(priv, job))
|
|
goto retry;
|
|
|
|
ignore_value(virTimeMillisNow(&now));
|
|
|
|
if (job) {
|
|
qemuDomainObjResetJob(priv);
|
|
|
|
if (job != QEMU_JOB_ASYNC) {
|
|
VIR_DEBUG("Started job: %s (async=%s vm=%p name=%s)",
|
|
qemuDomainJobTypeToString(job),
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
obj, obj->def->name);
|
|
priv->job.active = job;
|
|
priv->job.owner = virThreadSelfID();
|
|
priv->job.ownerAPI = virThreadJobGet();
|
|
priv->job.started = now;
|
|
} else {
|
|
VIR_DEBUG("Started async job: %s (vm=%p name=%s)",
|
|
qemuDomainAsyncJobTypeToString(asyncJob),
|
|
obj, obj->def->name);
|
|
qemuDomainObjResetAsyncJob(priv);
|
|
if (VIR_ALLOC(priv->job.current) < 0)
|
|
goto cleanup;
|
|
priv->job.current->status = QEMU_DOMAIN_JOB_STATUS_ACTIVE;
|
|
priv->job.asyncJob = asyncJob;
|
|
priv->job.asyncOwner = virThreadSelfID();
|
|
priv->job.asyncOwnerAPI = virThreadJobGet();
|
|
priv->job.asyncStarted = now;
|
|
priv->job.current->started = now;
|
|
}
|
|
}
|
|
|
|
if (agentJob) {
|
|
qemuDomainObjResetAgentJob(priv);
|
|
|
|
VIR_DEBUG("Started agent job: %s (vm=%p name=%s job=%s async=%s)",
|
|
qemuDomainAgentJobTypeToString(agentJob),
|
|
obj, obj->def->name,
|
|
qemuDomainJobTypeToString(priv->job.active),
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob));
|
|
priv->job.agentActive = agentJob;
|
|
priv->job.agentOwner = virThreadSelfID();
|
|
priv->job.agentOwnerAPI = virThreadJobGet();
|
|
priv->job.agentStarted = now;
|
|
}
|
|
|
|
if (qemuDomainTrackJob(job))
|
|
qemuDomainObjSaveStatus(driver, obj);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
ignore_value(virTimeMillisNow(&now));
|
|
if (priv->job.active && priv->job.started)
|
|
duration = now - priv->job.started;
|
|
if (priv->job.agentActive && priv->job.agentStarted)
|
|
agentDuration = now - priv->job.agentStarted;
|
|
if (priv->job.asyncJob && priv->job.asyncStarted)
|
|
asyncDuration = now - priv->job.asyncStarted;
|
|
|
|
VIR_WARN("Cannot start job (%s, %s, %s) for domain %s; "
|
|
"current job is (%s, %s, %s) "
|
|
"owned by (%llu %s, %llu %s, %llu %s (flags=0x%lx)) "
|
|
"for (%llus, %llus, %llus)",
|
|
qemuDomainJobTypeToString(job),
|
|
qemuDomainAgentJobTypeToString(agentJob),
|
|
qemuDomainAsyncJobTypeToString(asyncJob),
|
|
obj->def->name,
|
|
qemuDomainJobTypeToString(priv->job.active),
|
|
qemuDomainAgentJobTypeToString(priv->job.agentActive),
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
priv->job.owner, NULLSTR(priv->job.ownerAPI),
|
|
priv->job.agentOwner, NULLSTR(priv->job.agentOwnerAPI),
|
|
priv->job.asyncOwner, NULLSTR(priv->job.asyncOwnerAPI),
|
|
priv->job.apiFlags,
|
|
duration / 1000, agentDuration / 1000, asyncDuration / 1000);
|
|
|
|
if (job) {
|
|
if (nested || qemuDomainNestedJobAllowed(priv, job))
|
|
blocker = priv->job.ownerAPI;
|
|
else
|
|
blocker = priv->job.asyncOwnerAPI;
|
|
}
|
|
|
|
if (agentJob)
|
|
agentBlocker = priv->job.agentOwnerAPI;
|
|
|
|
if (errno == ETIMEDOUT) {
|
|
if (blocker && agentBlocker) {
|
|
virReportError(VIR_ERR_OPERATION_TIMEOUT,
|
|
_("cannot acquire state change "
|
|
"lock (held by monitor=%s agent=%s)"),
|
|
blocker, agentBlocker);
|
|
} else if (blocker) {
|
|
virReportError(VIR_ERR_OPERATION_TIMEOUT,
|
|
_("cannot acquire state change "
|
|
"lock (held by monitor=%s)"),
|
|
blocker);
|
|
} else if (agentBlocker) {
|
|
virReportError(VIR_ERR_OPERATION_TIMEOUT,
|
|
_("cannot acquire state change "
|
|
"lock (held by agent=%s)"),
|
|
agentBlocker);
|
|
} else {
|
|
virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s",
|
|
_("cannot acquire state change lock"));
|
|
}
|
|
ret = -2;
|
|
} else if (cfg->maxQueuedJobs &&
|
|
priv->jobs_queued > cfg->maxQueuedJobs) {
|
|
if (blocker && agentBlocker) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("cannot acquire state change "
|
|
"lock (held by monitor=%s agent=%s) "
|
|
"due to max_queued limit"),
|
|
blocker, agentBlocker);
|
|
} else if (blocker) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("cannot acquire state change "
|
|
"lock (held by monitor=%s) "
|
|
"due to max_queued limit"),
|
|
blocker);
|
|
} else if (agentBlocker) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("cannot acquire state change "
|
|
"lock (held by agent=%s) "
|
|
"due to max_queued limit"),
|
|
agentBlocker);
|
|
} else {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("cannot acquire state change lock "
|
|
"due to max_queued limit"));
|
|
}
|
|
ret = -2;
|
|
} else {
|
|
virReportSystemError(errno, "%s", _("cannot acquire job mutex"));
|
|
}
|
|
|
|
cleanup:
|
|
priv->jobs_queued--;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* obj must be locked before calling
|
|
*
|
|
* This must be called by anything that will change the VM state
|
|
* in any way, or anything that will use the QEMU monitor.
|
|
*
|
|
* Successful calls must be followed by EndJob eventually
|
|
*/
|
|
int qemuDomainObjBeginJob(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainJob job)
|
|
{
|
|
if (qemuDomainObjBeginJobInternal(driver, obj, job,
|
|
QEMU_AGENT_JOB_NONE,
|
|
QEMU_ASYNC_JOB_NONE, false) < 0)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* qemuDomainObjBeginAgentJob:
|
|
*
|
|
* Grabs agent type of job. Use if caller talks to guest agent only.
|
|
*
|
|
* To end job call qemuDomainObjEndAgentJob.
|
|
*/
|
|
int
|
|
qemuDomainObjBeginAgentJob(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainAgentJob agentJob)
|
|
{
|
|
return qemuDomainObjBeginJobInternal(driver, obj, QEMU_JOB_NONE,
|
|
agentJob,
|
|
QEMU_ASYNC_JOB_NONE, false);
|
|
}
|
|
|
|
int qemuDomainObjBeginAsyncJob(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainAsyncJob asyncJob,
|
|
virDomainJobOperation operation,
|
|
unsigned long apiFlags)
|
|
{
|
|
qemuDomainObjPrivatePtr priv;
|
|
|
|
if (qemuDomainObjBeginJobInternal(driver, obj, QEMU_JOB_ASYNC,
|
|
QEMU_AGENT_JOB_NONE,
|
|
asyncJob, false) < 0)
|
|
return -1;
|
|
|
|
priv = obj->privateData;
|
|
priv->job.current->operation = operation;
|
|
priv->job.apiFlags = apiFlags;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuDomainObjBeginNestedJob(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainAsyncJob asyncJob)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
|
|
if (asyncJob != priv->job.asyncJob) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected async job %d type expected %d"),
|
|
asyncJob, priv->job.asyncJob);
|
|
return -1;
|
|
}
|
|
|
|
if (priv->job.asyncOwner != virThreadSelfID()) {
|
|
VIR_WARN("This thread doesn't seem to be the async job owner: %llu",
|
|
priv->job.asyncOwner);
|
|
}
|
|
|
|
return qemuDomainObjBeginJobInternal(driver, obj,
|
|
QEMU_JOB_ASYNC_NESTED,
|
|
QEMU_AGENT_JOB_NONE,
|
|
QEMU_ASYNC_JOB_NONE,
|
|
false);
|
|
}
|
|
|
|
/**
|
|
* qemuDomainObjBeginJobNowait:
|
|
*
|
|
* @driver: qemu driver
|
|
* @obj: domain object
|
|
* @job: qemuDomainJob to start
|
|
*
|
|
* Acquires job for a domain object which must be locked before
|
|
* calling. If there's already a job running it returns
|
|
* immediately without any error reported.
|
|
*
|
|
* Returns: see qemuDomainObjBeginJobInternal
|
|
*/
|
|
int
|
|
qemuDomainObjBeginJobNowait(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainJob job)
|
|
{
|
|
return qemuDomainObjBeginJobInternal(driver, obj, job,
|
|
QEMU_AGENT_JOB_NONE,
|
|
QEMU_ASYNC_JOB_NONE, true);
|
|
}
|
|
|
|
/*
|
|
* obj must be locked and have a reference before calling
|
|
*
|
|
* To be called after completing the work associated with the
|
|
* earlier qemuDomainBeginJob() call
|
|
*/
|
|
void
|
|
qemuDomainObjEndJob(virQEMUDriverPtr driver, virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
qemuDomainJob job = priv->job.active;
|
|
|
|
priv->jobs_queued--;
|
|
|
|
VIR_DEBUG("Stopping job: %s (async=%s vm=%p name=%s)",
|
|
qemuDomainJobTypeToString(job),
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
obj, obj->def->name);
|
|
|
|
qemuDomainObjResetJob(priv);
|
|
if (qemuDomainTrackJob(job))
|
|
qemuDomainObjSaveStatus(driver, obj);
|
|
/* We indeed need to wake up ALL threads waiting because
|
|
* grabbing a job requires checking more variables. */
|
|
virCondBroadcast(&priv->job.cond);
|
|
}
|
|
|
|
void
|
|
qemuDomainObjEndAgentJob(virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
qemuDomainAgentJob agentJob = priv->job.agentActive;
|
|
|
|
priv->jobs_queued--;
|
|
|
|
VIR_DEBUG("Stopping agent job: %s (async=%s vm=%p name=%s)",
|
|
qemuDomainAgentJobTypeToString(agentJob),
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
obj, obj->def->name);
|
|
|
|
qemuDomainObjResetAgentJob(priv);
|
|
/* We indeed need to wake up ALL threads waiting because
|
|
* grabbing a job requires checking more variables. */
|
|
virCondBroadcast(&priv->job.cond);
|
|
}
|
|
|
|
void
|
|
qemuDomainObjEndAsyncJob(virQEMUDriverPtr driver, virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
|
|
priv->jobs_queued--;
|
|
|
|
VIR_DEBUG("Stopping async job: %s (vm=%p name=%s)",
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
obj, obj->def->name);
|
|
|
|
qemuDomainObjResetAsyncJob(priv);
|
|
qemuDomainObjSaveStatus(driver, obj);
|
|
virCondBroadcast(&priv->job.asyncCond);
|
|
}
|
|
|
|
void
|
|
qemuDomainObjAbortAsyncJob(virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
|
|
VIR_DEBUG("Requesting abort of async job: %s (vm=%p name=%s)",
|
|
qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
|
|
obj, obj->def->name);
|
|
|
|
priv->job.abortJob = true;
|
|
virDomainObjBroadcast(obj);
|
|
}
|
|
|
|
/*
|
|
* obj must be locked before calling
|
|
*
|
|
* To be called immediately before any QEMU monitor API call
|
|
* Must have already called qemuDomainObjBeginJob() and checked
|
|
* that the VM is still active; may not be used for nested async
|
|
* jobs.
|
|
*
|
|
* To be followed with qemuDomainObjExitMonitor() once complete
|
|
*/
|
|
static int
|
|
qemuDomainObjEnterMonitorInternal(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainAsyncJob asyncJob)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
|
|
if (asyncJob != QEMU_ASYNC_JOB_NONE) {
|
|
int ret;
|
|
if ((ret = qemuDomainObjBeginNestedJob(driver, obj, asyncJob)) < 0)
|
|
return ret;
|
|
if (!virDomainObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("domain is no longer running"));
|
|
qemuDomainObjEndJob(driver, obj);
|
|
return -1;
|
|
}
|
|
} else if (priv->job.asyncOwner == virThreadSelfID()) {
|
|
VIR_WARN("This thread seems to be the async job owner; entering"
|
|
" monitor without asking for a nested job is dangerous");
|
|
} else if (priv->job.owner != virThreadSelfID()) {
|
|
VIR_WARN("Entering a monitor without owning a job. "
|
|
"Job %s owner %s (%llu)",
|
|
qemuDomainJobTypeToString(priv->job.active),
|
|
priv->job.ownerAPI, priv->job.owner);
|
|
}
|
|
|
|
VIR_DEBUG("Entering monitor (mon=%p vm=%p name=%s)",
|
|
priv->mon, obj, obj->def->name);
|
|
virObjectLock(priv->mon);
|
|
virObjectRef(priv->mon);
|
|
ignore_value(virTimeMillisNow(&priv->monStart));
|
|
virObjectUnlock(obj);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ATTRIBUTE_NONNULL(1)
|
|
qemuDomainObjExitMonitorInternal(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
bool hasRefs;
|
|
|
|
hasRefs = virObjectUnref(priv->mon);
|
|
|
|
if (hasRefs)
|
|
virObjectUnlock(priv->mon);
|
|
|
|
virObjectLock(obj);
|
|
VIR_DEBUG("Exited monitor (mon=%p vm=%p name=%s)",
|
|
priv->mon, obj, obj->def->name);
|
|
|
|
priv->monStart = 0;
|
|
if (!hasRefs)
|
|
priv->mon = NULL;
|
|
|
|
if (priv->job.active == QEMU_JOB_ASYNC_NESTED)
|
|
qemuDomainObjEndJob(driver, obj);
|
|
}
|
|
|
|
void qemuDomainObjEnterMonitor(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj)
|
|
{
|
|
ignore_value(qemuDomainObjEnterMonitorInternal(driver, obj,
|
|
QEMU_ASYNC_JOB_NONE));
|
|
}
|
|
|
|
/* obj must NOT be locked before calling
|
|
*
|
|
* Should be paired with an earlier qemuDomainObjEnterMonitor() call
|
|
*
|
|
* Returns -1 if the domain is no longer alive after exiting the monitor.
|
|
* In that case, the caller should be careful when using obj's data,
|
|
* e.g. the live definition in vm->def has been freed by qemuProcessStop
|
|
* and replaced by the persistent definition, so pointers stolen
|
|
* from the live definition could no longer be valid.
|
|
*/
|
|
int qemuDomainObjExitMonitor(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjExitMonitorInternal(driver, obj);
|
|
if (!virDomainObjIsActive(obj)) {
|
|
if (virGetLastErrorCode() == VIR_ERR_OK)
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("domain is no longer running"));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* obj must be locked before calling
|
|
*
|
|
* To be called immediately before any QEMU monitor API call.
|
|
* Must have already either called qemuDomainObjBeginJob()
|
|
* and checked that the VM is still active, with asyncJob of
|
|
* QEMU_ASYNC_JOB_NONE; or already called qemuDomainObjBeginAsyncJob,
|
|
* with the same asyncJob.
|
|
*
|
|
* Returns 0 if job was started, in which case this must be followed with
|
|
* qemuDomainObjExitMonitor(); -2 if waiting for the nested job times out;
|
|
* or -1 if the job could not be started (probably because the vm exited
|
|
* in the meantime).
|
|
*/
|
|
int
|
|
qemuDomainObjEnterMonitorAsync(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainAsyncJob asyncJob)
|
|
{
|
|
return qemuDomainObjEnterMonitorInternal(driver, obj, asyncJob);
|
|
}
|
|
|
|
|
|
/*
|
|
* obj must be locked before calling
|
|
*
|
|
* To be called immediately before any QEMU agent API call.
|
|
* Must have already called qemuDomainObjBeginAgentJob() and
|
|
* checked that the VM is still active.
|
|
*
|
|
* To be followed with qemuDomainObjExitAgent() once complete
|
|
*/
|
|
qemuAgentPtr
|
|
qemuDomainObjEnterAgent(virDomainObjPtr obj)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
qemuAgentPtr agent = priv->agent;
|
|
|
|
VIR_DEBUG("Entering agent (agent=%p vm=%p name=%s)",
|
|
priv->agent, obj, obj->def->name);
|
|
|
|
virObjectLock(agent);
|
|
virObjectRef(agent);
|
|
virObjectUnlock(obj);
|
|
|
|
return agent;
|
|
}
|
|
|
|
|
|
/* obj must NOT be locked before calling
|
|
*
|
|
* Should be paired with an earlier qemuDomainObjEnterAgent() call
|
|
*/
|
|
void
|
|
qemuDomainObjExitAgent(virDomainObjPtr obj, qemuAgentPtr agent)
|
|
{
|
|
virObjectUnlock(agent);
|
|
virObjectUnref(agent);
|
|
virObjectLock(obj);
|
|
|
|
VIR_DEBUG("Exited agent (agent=%p vm=%p name=%s)",
|
|
agent, obj, obj->def->name);
|
|
}
|
|
|
|
void qemuDomainObjEnterRemote(virDomainObjPtr obj)
|
|
{
|
|
VIR_DEBUG("Entering remote (vm=%p name=%s)",
|
|
obj, obj->def->name);
|
|
virObjectUnlock(obj);
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainObjExitRemote(virDomainObjPtr obj,
|
|
bool checkActive)
|
|
{
|
|
virObjectLock(obj);
|
|
VIR_DEBUG("Exited remote (vm=%p name=%s)",
|
|
obj, obj->def->name);
|
|
|
|
if (checkActive && !virDomainObjIsActive(obj)) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("domain '%s' is not running"),
|
|
obj->def->name);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virDomainDefPtr
|
|
qemuDomainDefFromXML(virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps,
|
|
const char *xml)
|
|
{
|
|
virDomainDefPtr def;
|
|
|
|
def = virDomainDefParseString(xml, driver->xmlopt, qemuCaps,
|
|
VIR_DOMAIN_DEF_PARSE_INACTIVE |
|
|
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE);
|
|
|
|
return def;
|
|
}
|
|
|
|
|
|
virDomainDefPtr
|
|
qemuDomainDefCopy(virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps,
|
|
virDomainDefPtr src,
|
|
unsigned int flags)
|
|
{
|
|
virDomainDefPtr ret = NULL;
|
|
g_autofree char *xml = NULL;
|
|
|
|
if (!(xml = qemuDomainDefFormatXML(driver, qemuCaps, src, flags)))
|
|
return NULL;
|
|
|
|
ret = qemuDomainDefFromXML(driver, qemuCaps, xml);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainMakeCPUMigratable(virCPUDefPtr cpu)
|
|
{
|
|
if (cpu->mode == VIR_CPU_MODE_CUSTOM &&
|
|
STREQ_NULLABLE(cpu->model, "Icelake-Server")) {
|
|
/* Originally Icelake-Server CPU model contained pconfig CPU feature.
|
|
* It was never actually enabled and thus it was removed. To enable
|
|
* migration to QEMU 3.1.0 (with both new and old libvirt), we
|
|
* explicitly disable pconfig in migration XML (otherwise old libvirt
|
|
* would think it was implicitly enabled on the source). New libvirt
|
|
* will drop it from the XML before starting the domain on new QEMU.
|
|
*/
|
|
if (virCPUDefUpdateFeature(cpu, "pconfig", VIR_CPU_FEATURE_DISABLE) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefFormatBufInternal(virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps,
|
|
virDomainDefPtr def,
|
|
virCPUDefPtr origCPU,
|
|
unsigned int flags,
|
|
virBuffer *buf)
|
|
{
|
|
int ret = -1;
|
|
virDomainDefPtr copy = NULL;
|
|
|
|
virCheckFlags(VIR_DOMAIN_XML_COMMON_FLAGS | VIR_DOMAIN_XML_UPDATE_CPU, -1);
|
|
|
|
if (!(flags & (VIR_DOMAIN_XML_UPDATE_CPU | VIR_DOMAIN_XML_MIGRATABLE)))
|
|
goto format;
|
|
|
|
if (!(copy = virDomainDefCopy(def, driver->xmlopt, qemuCaps,
|
|
flags & VIR_DOMAIN_XML_MIGRATABLE)))
|
|
goto cleanup;
|
|
|
|
def = copy;
|
|
|
|
/* Update guest CPU requirements according to host CPU */
|
|
if ((flags & VIR_DOMAIN_XML_UPDATE_CPU) &&
|
|
def->cpu &&
|
|
(def->cpu->mode != VIR_CPU_MODE_CUSTOM ||
|
|
def->cpu->model)) {
|
|
g_autoptr(virQEMUCaps) qCaps = NULL;
|
|
|
|
if (qemuCaps) {
|
|
qCaps = virObjectRef(qemuCaps);
|
|
} else {
|
|
if (!(qCaps = virQEMUCapsCacheLookupCopy(driver->qemuCapsCache,
|
|
def->virtType,
|
|
def->emulator,
|
|
def->os.machine)))
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virCPUUpdate(def->os.arch, def->cpu,
|
|
virQEMUCapsGetHostModel(qCaps, def->virtType,
|
|
VIR_QEMU_CAPS_HOST_CPU_MIGRATABLE)) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((flags & VIR_DOMAIN_XML_MIGRATABLE)) {
|
|
size_t i;
|
|
int toremove = 0;
|
|
virDomainControllerDefPtr usb = NULL, pci = NULL;
|
|
|
|
/* If only the default USB controller is present, we can remove it
|
|
* and make the XML compatible with older versions of libvirt which
|
|
* didn't support USB controllers in the XML but always added the
|
|
* default one to qemu anyway.
|
|
*/
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_USB) {
|
|
if (usb) {
|
|
usb = NULL;
|
|
break;
|
|
}
|
|
usb = def->controllers[i];
|
|
}
|
|
}
|
|
|
|
/* In order to maintain compatibility with version of libvirt that
|
|
* didn't support <controller type='usb'/> (<= 0.9.4), we need to
|
|
* drop the default USB controller, ie. a USB controller at index
|
|
* zero with no model or with the default piix3-ohci model.
|
|
*
|
|
* However, we only need to do so for x86 i440fx machine types,
|
|
* because other architectures and machine types were introduced
|
|
* when libvirt already supported <controller type='usb'/>.
|
|
*/
|
|
if (qemuDomainIsI440FX(def) &&
|
|
usb && usb->idx == 0 &&
|
|
(usb->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_DEFAULT ||
|
|
usb->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX3_UHCI) &&
|
|
!virDomainDeviceAliasIsUserAlias(usb->info.alias)) {
|
|
VIR_DEBUG("Removing default USB controller from domain '%s'"
|
|
" for migration compatibility", def->name);
|
|
toremove++;
|
|
} else {
|
|
usb = NULL;
|
|
}
|
|
|
|
/* Remove the default PCI controller if there is only one present
|
|
* and its model is pci-root */
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
|
|
if (pci) {
|
|
pci = NULL;
|
|
break;
|
|
}
|
|
pci = def->controllers[i];
|
|
}
|
|
}
|
|
|
|
if (pci && pci->idx == 0 &&
|
|
pci->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT &&
|
|
!virDomainDeviceAliasIsUserAlias(pci->info.alias) &&
|
|
!pci->opts.pciopts.pcihole64) {
|
|
VIR_DEBUG("Removing default pci-root from domain '%s'"
|
|
" for migration compatibility", def->name);
|
|
toremove++;
|
|
} else {
|
|
pci = NULL;
|
|
}
|
|
|
|
if (toremove) {
|
|
virDomainControllerDefPtr *controllers = def->controllers;
|
|
int ncontrollers = def->ncontrollers;
|
|
|
|
if (VIR_ALLOC_N(def->controllers, ncontrollers - toremove) < 0) {
|
|
def->controllers = controllers;
|
|
goto cleanup;
|
|
}
|
|
|
|
def->ncontrollers = 0;
|
|
for (i = 0; i < ncontrollers; i++) {
|
|
if (controllers[i] != usb && controllers[i] != pci)
|
|
def->controllers[def->ncontrollers++] = controllers[i];
|
|
}
|
|
|
|
VIR_FREE(controllers);
|
|
virDomainControllerDefFree(pci);
|
|
virDomainControllerDefFree(usb);
|
|
}
|
|
|
|
/* Remove the panic device for selected models if present */
|
|
for (i = 0; i < def->npanics; i++) {
|
|
if (def->panics[i]->model == VIR_DOMAIN_PANIC_MODEL_S390 ||
|
|
def->panics[i]->model == VIR_DOMAIN_PANIC_MODEL_PSERIES) {
|
|
VIR_DELETE_ELEMENT(def->panics, i, def->npanics);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < def->nchannels; i++)
|
|
qemuDomainChrDefDropDefaultPath(def->channels[i], driver);
|
|
|
|
for (i = 0; i < def->nserials; i++) {
|
|
virDomainChrDefPtr serial = def->serials[i];
|
|
|
|
/* Historically, the native console type for some machine types
|
|
* was not set at all, which means it defaulted to ISA even
|
|
* though that was not even remotely accurate. To ensure migration
|
|
* towards older libvirt versions works for such guests, we switch
|
|
* it back to the default here */
|
|
if (flags & VIR_DOMAIN_XML_MIGRATABLE) {
|
|
switch ((virDomainChrSerialTargetType)serial->targetType) {
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SPAPR_VIO:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SYSTEM:
|
|
serial->targetType = VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE;
|
|
serial->targetModel = VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_NONE;
|
|
break;
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_PCI:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SCLP:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_LAST:
|
|
/* Nothing to do */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Replace the CPU definition updated according to QEMU with the one
|
|
* used for starting the domain. The updated def will be sent
|
|
* separately for backward compatibility.
|
|
*/
|
|
if (origCPU) {
|
|
virCPUDefFree(def->cpu);
|
|
if (!(def->cpu = virCPUDefCopy(origCPU)))
|
|
goto cleanup;
|
|
}
|
|
|
|
if (def->cpu && qemuDomainMakeCPUMigratable(def->cpu) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
format:
|
|
ret = virDomainDefFormatInternal(def, driver->xmlopt, buf,
|
|
virDomainDefFormatConvertXMLFlags(flags));
|
|
|
|
cleanup:
|
|
virDomainDefFree(copy);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainDefFormatBuf(virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps,
|
|
virDomainDefPtr def,
|
|
unsigned int flags,
|
|
virBufferPtr buf)
|
|
{
|
|
return qemuDomainDefFormatBufInternal(driver, qemuCaps, def, NULL, flags, buf);
|
|
}
|
|
|
|
|
|
static char *
|
|
qemuDomainDefFormatXMLInternal(virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps,
|
|
virDomainDefPtr def,
|
|
virCPUDefPtr origCPU,
|
|
unsigned int flags)
|
|
{
|
|
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (qemuDomainDefFormatBufInternal(driver, qemuCaps, def, origCPU, flags, &buf) < 0)
|
|
return NULL;
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
}
|
|
|
|
|
|
char *
|
|
qemuDomainDefFormatXML(virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps,
|
|
virDomainDefPtr def,
|
|
unsigned int flags)
|
|
{
|
|
return qemuDomainDefFormatXMLInternal(driver, qemuCaps, def, NULL, flags);
|
|
}
|
|
|
|
|
|
char *qemuDomainFormatXML(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
unsigned int flags)
|
|
{
|
|
virDomainDefPtr def;
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virCPUDefPtr origCPU = NULL;
|
|
|
|
if ((flags & VIR_DOMAIN_XML_INACTIVE) && vm->newDef) {
|
|
def = vm->newDef;
|
|
} else {
|
|
def = vm->def;
|
|
origCPU = priv->origCPU;
|
|
}
|
|
|
|
return qemuDomainDefFormatXMLInternal(driver, priv->qemuCaps, def, origCPU, flags);
|
|
}
|
|
|
|
char *
|
|
qemuDomainDefFormatLive(virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps,
|
|
virDomainDefPtr def,
|
|
virCPUDefPtr origCPU,
|
|
bool inactive,
|
|
bool compatible)
|
|
{
|
|
unsigned int flags = QEMU_DOMAIN_FORMAT_LIVE_FLAGS;
|
|
|
|
if (inactive)
|
|
flags |= VIR_DOMAIN_XML_INACTIVE;
|
|
if (compatible)
|
|
flags |= VIR_DOMAIN_XML_MIGRATABLE;
|
|
|
|
return qemuDomainDefFormatXMLInternal(driver, qemuCaps, def, origCPU, flags);
|
|
}
|
|
|
|
|
|
void qemuDomainObjTaint(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
virDomainTaintFlags taint,
|
|
qemuDomainLogContextPtr logCtxt)
|
|
{
|
|
virErrorPtr orig_err = NULL;
|
|
g_autofree char *timestamp = NULL;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
int rc;
|
|
|
|
if (!virDomainObjTaint(obj, taint))
|
|
return;
|
|
|
|
virUUIDFormat(obj->def->uuid, uuidstr);
|
|
|
|
VIR_WARN("Domain id=%d name='%s' uuid=%s is tainted: %s",
|
|
obj->def->id,
|
|
obj->def->name,
|
|
uuidstr,
|
|
virDomainTaintTypeToString(taint));
|
|
|
|
/* We don't care about errors logging taint info, so
|
|
* preserve original error, and clear any error that
|
|
* is raised */
|
|
virErrorPreserveLast(&orig_err);
|
|
|
|
if (!(timestamp = virTimeStringNow()))
|
|
goto cleanup;
|
|
|
|
if (logCtxt) {
|
|
rc = qemuDomainLogContextWrite(logCtxt,
|
|
"%s: Domain id=%d is tainted: %s\n",
|
|
timestamp,
|
|
obj->def->id,
|
|
virDomainTaintTypeToString(taint));
|
|
} else {
|
|
rc = qemuDomainLogAppendMessage(driver, obj,
|
|
"%s: Domain id=%d is tainted: %s\n",
|
|
timestamp,
|
|
obj->def->id,
|
|
virDomainTaintTypeToString(taint));
|
|
}
|
|
|
|
if (rc < 0)
|
|
virResetLastError();
|
|
|
|
cleanup:
|
|
virErrorRestore(&orig_err);
|
|
}
|
|
|
|
|
|
void qemuDomainObjCheckTaint(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
qemuDomainLogContextPtr logCtxt)
|
|
{
|
|
size_t i;
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
qemuDomainObjPrivatePtr priv = obj->privateData;
|
|
bool custom_hypervisor_feat = false;
|
|
|
|
if (virQEMUDriverIsPrivileged(driver) &&
|
|
(cfg->user == 0 ||
|
|
cfg->group == 0))
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HIGH_PRIVILEGES, logCtxt);
|
|
|
|
if (priv->hookRun)
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HOOK, logCtxt);
|
|
|
|
if (obj->def->namespaceData) {
|
|
qemuDomainXmlNsDefPtr qemuxmlns = obj->def->namespaceData;
|
|
if (qemuxmlns->num_args || qemuxmlns->num_env)
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_CUSTOM_ARGV, logCtxt);
|
|
if (qemuxmlns->ncapsadd > 0 || qemuxmlns->ncapsdel > 0)
|
|
custom_hypervisor_feat = true;
|
|
}
|
|
|
|
if (custom_hypervisor_feat ||
|
|
(cfg->capabilityfilters && *cfg->capabilityfilters)) {
|
|
qemuDomainObjTaint(driver, obj,
|
|
VIR_DOMAIN_TAINT_CUSTOM_HYPERVISOR_FEATURE, logCtxt);
|
|
}
|
|
|
|
if (obj->def->cpu && obj->def->cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH)
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HOST_CPU, logCtxt);
|
|
|
|
for (i = 0; i < obj->def->ndisks; i++)
|
|
qemuDomainObjCheckDiskTaint(driver, obj, obj->def->disks[i], logCtxt);
|
|
|
|
for (i = 0; i < obj->def->nhostdevs; i++)
|
|
qemuDomainObjCheckHostdevTaint(driver, obj, obj->def->hostdevs[i],
|
|
logCtxt);
|
|
|
|
for (i = 0; i < obj->def->nnets; i++)
|
|
qemuDomainObjCheckNetTaint(driver, obj, obj->def->nets[i], logCtxt);
|
|
|
|
if (obj->def->os.dtb)
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_CUSTOM_DTB, logCtxt);
|
|
}
|
|
|
|
|
|
void qemuDomainObjCheckDiskTaint(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
virDomainDiskDefPtr disk,
|
|
qemuDomainLogContextPtr logCtxt)
|
|
{
|
|
if (disk->rawio == VIR_TRISTATE_BOOL_YES)
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HIGH_PRIVILEGES,
|
|
logCtxt);
|
|
|
|
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM &&
|
|
virStorageSourceGetActualType(disk->src) == VIR_STORAGE_TYPE_BLOCK &&
|
|
disk->src->path && virFileIsCDROM(disk->src->path) == 1)
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_CDROM_PASSTHROUGH,
|
|
logCtxt);
|
|
}
|
|
|
|
|
|
void qemuDomainObjCheckHostdevTaint(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
virDomainHostdevDefPtr hostdev,
|
|
qemuDomainLogContextPtr logCtxt)
|
|
{
|
|
if (!virHostdevIsSCSIDevice(hostdev))
|
|
return;
|
|
|
|
if (hostdev->source.subsys.u.scsi.rawio == VIR_TRISTATE_BOOL_YES)
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_HIGH_PRIVILEGES, logCtxt);
|
|
}
|
|
|
|
|
|
void qemuDomainObjCheckNetTaint(virQEMUDriverPtr driver,
|
|
virDomainObjPtr obj,
|
|
virDomainNetDefPtr net,
|
|
qemuDomainLogContextPtr logCtxt)
|
|
{
|
|
/* script is only useful for NET_TYPE_ETHERNET (qemu) and
|
|
* NET_TYPE_BRIDGE (xen), but could be (incorrectly) specified for
|
|
* any interface type. In any case, it's adding user sauce into
|
|
* the soup, so it should taint the domain.
|
|
*/
|
|
if (net->script != NULL)
|
|
qemuDomainObjTaint(driver, obj, VIR_DOMAIN_TAINT_SHELL_SCRIPTS, logCtxt);
|
|
}
|
|
|
|
|
|
qemuDomainLogContextPtr qemuDomainLogContextNew(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
qemuDomainLogContextMode mode)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
qemuDomainLogContextPtr ctxt = NULL;
|
|
|
|
if (qemuDomainInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(ctxt = virObjectNew(qemuDomainLogContextClass)))
|
|
return NULL;
|
|
|
|
VIR_DEBUG("Context new %p stdioLogD=%d", ctxt, cfg->stdioLogD);
|
|
ctxt->writefd = -1;
|
|
ctxt->readfd = -1;
|
|
|
|
ctxt->path = g_strdup_printf("%s/%s.log", cfg->logDir, vm->def->name);
|
|
|
|
if (cfg->stdioLogD) {
|
|
ctxt->manager = virLogManagerNew(virQEMUDriverIsPrivileged(driver));
|
|
if (!ctxt->manager)
|
|
goto error;
|
|
|
|
ctxt->writefd = virLogManagerDomainOpenLogFile(ctxt->manager,
|
|
"qemu",
|
|
vm->def->uuid,
|
|
vm->def->name,
|
|
ctxt->path,
|
|
0,
|
|
&ctxt->inode,
|
|
&ctxt->pos);
|
|
if (ctxt->writefd < 0)
|
|
goto error;
|
|
} else {
|
|
if ((ctxt->writefd = open(ctxt->path, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) < 0) {
|
|
virReportSystemError(errno, _("failed to create logfile %s"),
|
|
ctxt->path);
|
|
goto error;
|
|
}
|
|
if (virSetCloseExec(ctxt->writefd) < 0) {
|
|
virReportSystemError(errno, _("failed to set close-on-exec flag on %s"),
|
|
ctxt->path);
|
|
goto error;
|
|
}
|
|
|
|
/* For unprivileged startup we must truncate the file since
|
|
* we can't rely on logrotate. We don't use O_TRUNC since
|
|
* it is better for SELinux policy if we truncate afterwards */
|
|
if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START &&
|
|
!virQEMUDriverIsPrivileged(driver) &&
|
|
ftruncate(ctxt->writefd, 0) < 0) {
|
|
virReportSystemError(errno, _("failed to truncate %s"),
|
|
ctxt->path);
|
|
goto error;
|
|
}
|
|
|
|
if (mode == QEMU_DOMAIN_LOG_CONTEXT_MODE_START) {
|
|
if ((ctxt->readfd = open(ctxt->path, O_RDONLY, S_IRUSR | S_IWUSR)) < 0) {
|
|
virReportSystemError(errno, _("failed to open logfile %s"),
|
|
ctxt->path);
|
|
goto error;
|
|
}
|
|
if (virSetCloseExec(ctxt->readfd) < 0) {
|
|
virReportSystemError(errno, _("failed to set close-on-exec flag on %s"),
|
|
ctxt->path);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if ((ctxt->pos = lseek(ctxt->writefd, 0, SEEK_END)) < 0) {
|
|
virReportSystemError(errno, _("failed to seek in log file %s"),
|
|
ctxt->path);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return ctxt;
|
|
|
|
error:
|
|
virObjectUnref(ctxt);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int qemuDomainLogContextWrite(qemuDomainLogContextPtr ctxt,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
g_autofree char *message = NULL;
|
|
int ret = -1;
|
|
|
|
va_start(argptr, fmt);
|
|
|
|
message = g_strdup_vprintf(fmt, argptr);
|
|
if (!ctxt->manager &&
|
|
lseek(ctxt->writefd, 0, SEEK_END) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to seek to end of domain logfile"));
|
|
goto cleanup;
|
|
}
|
|
if (safewrite(ctxt->writefd, message, strlen(message)) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to write to domain logfile"));
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
va_end(argptr);
|
|
return ret;
|
|
}
|
|
|
|
|
|
ssize_t qemuDomainLogContextRead(qemuDomainLogContextPtr ctxt,
|
|
char **msg)
|
|
{
|
|
VIR_DEBUG("Context read %p manager=%p inode=%llu pos=%llu",
|
|
ctxt, ctxt->manager,
|
|
(unsigned long long)ctxt->inode,
|
|
(unsigned long long)ctxt->pos);
|
|
char *buf;
|
|
size_t buflen;
|
|
if (ctxt->manager) {
|
|
buf = virLogManagerDomainReadLogFile(ctxt->manager,
|
|
ctxt->path,
|
|
ctxt->inode,
|
|
ctxt->pos,
|
|
1024 * 128,
|
|
0);
|
|
if (!buf)
|
|
return -1;
|
|
buflen = strlen(buf);
|
|
} else {
|
|
ssize_t got;
|
|
|
|
buflen = 1024 * 128;
|
|
|
|
/* Best effort jump to start of messages */
|
|
ignore_value(lseek(ctxt->readfd, ctxt->pos, SEEK_SET));
|
|
|
|
if (VIR_ALLOC_N(buf, buflen) < 0)
|
|
return -1;
|
|
|
|
got = saferead(ctxt->readfd, buf, buflen - 1);
|
|
if (got < 0) {
|
|
VIR_FREE(buf);
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to read from log file"));
|
|
return -1;
|
|
}
|
|
|
|
buf[got] = '\0';
|
|
|
|
ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1));
|
|
buflen = got;
|
|
}
|
|
|
|
*msg = buf;
|
|
|
|
return buflen;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainLogAppendMessage:
|
|
*
|
|
* This is a best-effort attempt to add a log message to the qemu log file
|
|
* either by using virtlogd or the legacy approach */
|
|
int
|
|
qemuDomainLogAppendMessage(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
const char *fmt,
|
|
...)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
virLogManagerPtr manager = NULL;
|
|
va_list ap;
|
|
g_autofree char *path = NULL;
|
|
int writefd = -1;
|
|
g_autofree char *message = NULL;
|
|
int ret = -1;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
message = g_strdup_vprintf(fmt, ap);
|
|
|
|
VIR_DEBUG("Append log message (vm='%s' message='%s) stdioLogD=%d",
|
|
vm->def->name, message, cfg->stdioLogD);
|
|
|
|
path = g_strdup_printf("%s/%s.log", cfg->logDir, vm->def->name);
|
|
|
|
if (cfg->stdioLogD) {
|
|
if (!(manager = virLogManagerNew(virQEMUDriverIsPrivileged(driver))))
|
|
goto cleanup;
|
|
|
|
if (virLogManagerDomainAppendMessage(manager, "qemu", vm->def->uuid,
|
|
vm->def->name, path, message, 0) < 0)
|
|
goto cleanup;
|
|
} else {
|
|
if ((writefd = open(path, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) < 0) {
|
|
virReportSystemError(errno, _("failed to create logfile %s"),
|
|
path);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (safewrite(writefd, message, strlen(message)) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
va_end(ap);
|
|
VIR_FORCE_CLOSE(writefd);
|
|
virLogManagerFree(manager);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int qemuDomainLogContextGetWriteFD(qemuDomainLogContextPtr ctxt)
|
|
{
|
|
return ctxt->writefd;
|
|
}
|
|
|
|
|
|
void qemuDomainLogContextMarkPosition(qemuDomainLogContextPtr ctxt)
|
|
{
|
|
if (ctxt->manager)
|
|
virLogManagerDomainGetLogFilePosition(ctxt->manager,
|
|
ctxt->path,
|
|
0,
|
|
&ctxt->inode,
|
|
&ctxt->pos);
|
|
else
|
|
ctxt->pos = lseek(ctxt->writefd, 0, SEEK_END);
|
|
}
|
|
|
|
|
|
virLogManagerPtr qemuDomainLogContextGetManager(qemuDomainLogContextPtr ctxt)
|
|
{
|
|
return ctxt->manager;
|
|
}
|
|
|
|
|
|
/* Locate an appropriate 'qemu-img' binary. */
|
|
const char *
|
|
qemuFindQemuImgBinary(virQEMUDriverPtr driver)
|
|
{
|
|
if (!driver->qemuImgBinary)
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("unable to find qemu-img"));
|
|
|
|
return driver->qemuImgBinary;
|
|
}
|
|
|
|
int
|
|
qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm,
|
|
virDomainMomentObjPtr snapshot,
|
|
virDomainXMLOptionPtr xmlopt,
|
|
const char *snapshotDir)
|
|
{
|
|
g_autofree char *newxml = NULL;
|
|
g_autofree char *snapDir = NULL;
|
|
g_autofree char *snapFile = NULL;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
unsigned int flags = VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE |
|
|
VIR_DOMAIN_SNAPSHOT_FORMAT_INTERNAL;
|
|
virDomainSnapshotDefPtr def = virDomainSnapshotObjGetDef(snapshot);
|
|
|
|
if (virDomainSnapshotGetCurrent(vm->snapshots) == snapshot)
|
|
flags |= VIR_DOMAIN_SNAPSHOT_FORMAT_CURRENT;
|
|
virUUIDFormat(vm->def->uuid, uuidstr);
|
|
newxml = virDomainSnapshotDefFormat(uuidstr, def, xmlopt, flags);
|
|
if (newxml == NULL)
|
|
return -1;
|
|
|
|
snapDir = g_strdup_printf("%s/%s", snapshotDir, vm->def->name);
|
|
if (virFileMakePath(snapDir) < 0) {
|
|
virReportSystemError(errno, _("cannot create snapshot directory '%s'"),
|
|
snapDir);
|
|
return -1;
|
|
}
|
|
|
|
snapFile = g_strdup_printf("%s/%s.xml", snapDir, def->parent.name);
|
|
|
|
return virXMLSaveFile(snapFile, NULL, "snapshot-edit", newxml);
|
|
}
|
|
|
|
|
|
/* The domain is expected to be locked and inactive. Return -1 on normal
|
|
* failure, 1 if we skipped a disk due to try_all. */
|
|
static int
|
|
qemuDomainSnapshotForEachQcow2Raw(virQEMUDriverPtr driver,
|
|
virDomainDefPtr def,
|
|
const char *name,
|
|
const char *op,
|
|
bool try_all,
|
|
int ndisks)
|
|
{
|
|
const char *qemuimgarg[] = { NULL, "snapshot", NULL, NULL, NULL, NULL };
|
|
size_t i;
|
|
bool skipped = false;
|
|
|
|
qemuimgarg[0] = qemuFindQemuImgBinary(driver);
|
|
if (qemuimgarg[0] == NULL) {
|
|
/* qemuFindQemuImgBinary set the error */
|
|
return -1;
|
|
}
|
|
|
|
qemuimgarg[2] = op;
|
|
qemuimgarg[3] = name;
|
|
|
|
for (i = 0; i < ndisks; i++) {
|
|
/* FIXME: we also need to handle LVM here */
|
|
if (def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) {
|
|
int format = virDomainDiskGetFormat(def->disks[i]);
|
|
|
|
if (format > 0 && format != VIR_STORAGE_FILE_QCOW2) {
|
|
if (try_all) {
|
|
/* Continue on even in the face of error, since other
|
|
* disks in this VM may have the same snapshot name.
|
|
*/
|
|
VIR_WARN("skipping snapshot action on %s",
|
|
def->disks[i]->dst);
|
|
skipped = true;
|
|
continue;
|
|
} else if (STREQ(op, "-c") && i) {
|
|
/* We must roll back partial creation by deleting
|
|
* all earlier snapshots. */
|
|
qemuDomainSnapshotForEachQcow2Raw(driver, def, name,
|
|
"-d", false, i);
|
|
}
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("Disk device '%s' does not support"
|
|
" snapshotting"),
|
|
def->disks[i]->dst);
|
|
return -1;
|
|
}
|
|
|
|
qemuimgarg[4] = virDomainDiskGetSource(def->disks[i]);
|
|
|
|
if (virRun(qemuimgarg, NULL) < 0) {
|
|
if (try_all) {
|
|
VIR_WARN("skipping snapshot action on %s",
|
|
def->disks[i]->dst);
|
|
skipped = true;
|
|
continue;
|
|
} else if (STREQ(op, "-c") && i) {
|
|
/* We must roll back partial creation by deleting
|
|
* all earlier snapshots. */
|
|
qemuDomainSnapshotForEachQcow2Raw(driver, def, name,
|
|
"-d", false, i);
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return skipped ? 1 : 0;
|
|
}
|
|
|
|
/* The domain is expected to be locked and inactive. Return -1 on normal
|
|
* failure, 1 if we skipped a disk due to try_all. */
|
|
int
|
|
qemuDomainSnapshotForEachQcow2(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virDomainMomentObjPtr snap,
|
|
const char *op,
|
|
bool try_all)
|
|
{
|
|
/* Prefer action on the disks in use at the time the snapshot was
|
|
* created; but fall back to current definition if dealing with a
|
|
* snapshot created prior to libvirt 0.9.5. */
|
|
virDomainDefPtr def = snap->def->dom;
|
|
|
|
if (!def)
|
|
def = vm->def;
|
|
return qemuDomainSnapshotForEachQcow2Raw(driver, def, snap->def->name,
|
|
op, try_all, def->ndisks);
|
|
}
|
|
|
|
/* Discard one snapshot (or its metadata), without reparenting any children. */
|
|
int
|
|
qemuDomainSnapshotDiscard(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virDomainMomentObjPtr snap,
|
|
bool update_parent,
|
|
bool metadata_only)
|
|
{
|
|
g_autofree char *snapFile = NULL;
|
|
qemuDomainObjPrivatePtr priv;
|
|
virDomainMomentObjPtr parentsnap = NULL;
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
if (!metadata_only) {
|
|
if (!virDomainObjIsActive(vm)) {
|
|
/* Ignore any skipped disks */
|
|
if (qemuDomainSnapshotForEachQcow2(driver, vm, snap, "-d",
|
|
true) < 0)
|
|
return -1;
|
|
} else {
|
|
priv = vm->privateData;
|
|
qemuDomainObjEnterMonitor(driver, vm);
|
|
/* we continue on even in the face of error */
|
|
qemuMonitorDeleteSnapshot(priv->mon, snap->def->name);
|
|
ignore_value(qemuDomainObjExitMonitor(driver, vm));
|
|
}
|
|
}
|
|
|
|
snapFile = g_strdup_printf("%s/%s/%s.xml", cfg->snapshotDir, vm->def->name,
|
|
snap->def->name);
|
|
|
|
if (snap == virDomainSnapshotGetCurrent(vm->snapshots)) {
|
|
virDomainSnapshotSetCurrent(vm->snapshots, NULL);
|
|
if (update_parent && snap->def->parent_name) {
|
|
parentsnap = virDomainSnapshotFindByName(vm->snapshots,
|
|
snap->def->parent_name);
|
|
if (!parentsnap) {
|
|
VIR_WARN("missing parent snapshot matching name '%s'",
|
|
snap->def->parent_name);
|
|
} else {
|
|
virDomainSnapshotSetCurrent(vm->snapshots, parentsnap);
|
|
if (qemuDomainSnapshotWriteMetadata(vm, parentsnap,
|
|
driver->xmlopt,
|
|
cfg->snapshotDir) < 0) {
|
|
VIR_WARN("failed to set parent snapshot '%s' as current",
|
|
snap->def->parent_name);
|
|
virDomainSnapshotSetCurrent(vm->snapshots, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (unlink(snapFile) < 0)
|
|
VIR_WARN("Failed to unlink %s", snapFile);
|
|
if (update_parent)
|
|
virDomainMomentDropParent(snap);
|
|
virDomainSnapshotObjListRemove(vm->snapshots, snap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Hash iterator callback to discard multiple snapshots. */
|
|
int qemuDomainMomentDiscardAll(void *payload,
|
|
const void *name G_GNUC_UNUSED,
|
|
void *data)
|
|
{
|
|
virDomainMomentObjPtr moment = payload;
|
|
virQEMUMomentRemovePtr curr = data;
|
|
int err;
|
|
|
|
if (!curr->found && curr->current == moment)
|
|
curr->found = true;
|
|
err = curr->momentDiscard(curr->driver, curr->vm, moment, false,
|
|
curr->metadata_only);
|
|
if (err && !curr->err)
|
|
curr->err = err;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuDomainSnapshotDiscardAllMetadata(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
virQEMUMomentRemove rem = {
|
|
.driver = driver,
|
|
.vm = vm,
|
|
.metadata_only = true,
|
|
.momentDiscard = qemuDomainSnapshotDiscard,
|
|
};
|
|
|
|
virDomainSnapshotForEach(vm->snapshots, qemuDomainMomentDiscardAll, &rem);
|
|
virDomainSnapshotObjListRemoveAll(vm->snapshots);
|
|
|
|
return rem.err;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainRemoveInactiveCommon(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
g_autofree char *snapDir = NULL;
|
|
g_autofree char *chkDir = NULL;
|
|
|
|
/* Remove any snapshot metadata prior to removing the domain */
|
|
if (qemuDomainSnapshotDiscardAllMetadata(driver, vm) < 0) {
|
|
VIR_WARN("unable to remove all snapshots for domain %s",
|
|
vm->def->name);
|
|
} else {
|
|
snapDir = g_strdup_printf("%s/%s", cfg->snapshotDir, vm->def->name);
|
|
|
|
if (rmdir(snapDir) < 0 && errno != ENOENT)
|
|
VIR_WARN("unable to remove snapshot directory %s", snapDir);
|
|
}
|
|
/* Remove any checkpoint metadata prior to removing the domain */
|
|
if (qemuCheckpointDiscardAllMetadata(driver, vm) < 0) {
|
|
VIR_WARN("unable to remove all checkpoints for domain %s",
|
|
vm->def->name);
|
|
} else {
|
|
chkDir = g_strdup_printf("%s/%s", cfg->checkpointDir,
|
|
vm->def->name);
|
|
if (rmdir(chkDir) < 0 && errno != ENOENT)
|
|
VIR_WARN("unable to remove checkpoint directory %s", chkDir);
|
|
}
|
|
qemuExtDevicesCleanupHost(driver, vm->def);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainRemoveInactive:
|
|
*
|
|
* The caller must hold a lock to the vm.
|
|
*/
|
|
void
|
|
qemuDomainRemoveInactive(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
if (vm->persistent) {
|
|
/* Short-circuit, we don't want to remove a persistent domain */
|
|
return;
|
|
}
|
|
|
|
qemuDomainRemoveInactiveCommon(driver, vm);
|
|
|
|
virDomainObjListRemove(driver->domains, vm);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainRemoveInactiveLocked:
|
|
*
|
|
* The caller must hold a lock to the vm and must hold the
|
|
* lock on driver->domains in order to call the remove obj
|
|
* from locked list method.
|
|
*/
|
|
static void
|
|
qemuDomainRemoveInactiveLocked(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
if (vm->persistent) {
|
|
/* Short-circuit, we don't want to remove a persistent domain */
|
|
return;
|
|
}
|
|
|
|
qemuDomainRemoveInactiveCommon(driver, vm);
|
|
|
|
virDomainObjListRemoveLocked(driver->domains, vm);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainRemoveInactiveJob:
|
|
*
|
|
* Just like qemuDomainRemoveInactive but it tries to grab a
|
|
* QEMU_JOB_MODIFY first. Even though it doesn't succeed in
|
|
* grabbing the job the control carries with
|
|
* qemuDomainRemoveInactive call.
|
|
*/
|
|
void
|
|
qemuDomainRemoveInactiveJob(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
bool haveJob;
|
|
|
|
haveJob = qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) >= 0;
|
|
|
|
qemuDomainRemoveInactive(driver, vm);
|
|
|
|
if (haveJob)
|
|
qemuDomainObjEndJob(driver, vm);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainRemoveInactiveJobLocked:
|
|
*
|
|
* Similar to qemuDomainRemoveInactiveJob, except that the caller must
|
|
* also hold the lock @driver->domains
|
|
*/
|
|
void
|
|
qemuDomainRemoveInactiveJobLocked(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
bool haveJob;
|
|
|
|
haveJob = qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) >= 0;
|
|
|
|
qemuDomainRemoveInactiveLocked(driver, vm);
|
|
|
|
if (haveJob)
|
|
qemuDomainObjEndJob(driver, vm);
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainSetFakeReboot(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
bool value)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
if (priv->fakeReboot == value)
|
|
return;
|
|
|
|
priv->fakeReboot = value;
|
|
|
|
if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0)
|
|
VIR_WARN("Failed to save status on vm %s", vm->def->name);
|
|
}
|
|
|
|
static void
|
|
qemuDomainCheckRemoveOptionalDisk(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
size_t diskIndex)
|
|
{
|
|
char uuid[VIR_UUID_STRING_BUFLEN];
|
|
virObjectEventPtr event = NULL;
|
|
virDomainDiskDefPtr disk = vm->def->disks[diskIndex];
|
|
const char *src = virDomainDiskGetSource(disk);
|
|
|
|
virUUIDFormat(vm->def->uuid, uuid);
|
|
|
|
VIR_DEBUG("Dropping disk '%s' on domain '%s' (UUID '%s') "
|
|
"due to inaccessible source '%s'",
|
|
disk->dst, vm->def->name, uuid, src);
|
|
|
|
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM ||
|
|
disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) {
|
|
|
|
event = virDomainEventDiskChangeNewFromObj(vm, src, NULL,
|
|
disk->info.alias,
|
|
VIR_DOMAIN_EVENT_DISK_CHANGE_MISSING_ON_START);
|
|
virDomainDiskEmptySource(disk);
|
|
/* keeping the old startup policy would be invalid for new images */
|
|
disk->startupPolicy = VIR_DOMAIN_STARTUP_POLICY_DEFAULT;
|
|
} else {
|
|
event = virDomainEventDiskChangeNewFromObj(vm, src, NULL,
|
|
disk->info.alias,
|
|
VIR_DOMAIN_EVENT_DISK_DROP_MISSING_ON_START);
|
|
virDomainDiskRemove(vm->def, diskIndex);
|
|
virDomainDiskDefFree(disk);
|
|
}
|
|
|
|
virObjectEventStateQueue(driver->domainEventState, event);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainCheckDiskStartupPolicy:
|
|
* @driver: qemu driver object
|
|
* @vm: domain object
|
|
* @disk: index of disk to check
|
|
* @cold_boot: true if a new VM is being started
|
|
*
|
|
* This function should be called when the source storage for a disk device is
|
|
* missing. The function checks whether the startup policy for the disk allows
|
|
* removal of the source (or disk) according to the state of the VM.
|
|
*
|
|
* The function returns 0 if the source or disk was dropped and -1 if the state
|
|
* of the VM does not allow this. This function does not report errors, but
|
|
* clears any reported error if 0 is returned.
|
|
*/
|
|
int
|
|
qemuDomainCheckDiskStartupPolicy(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
size_t diskIndex,
|
|
bool cold_boot)
|
|
{
|
|
int startupPolicy = vm->def->disks[diskIndex]->startupPolicy;
|
|
int device = vm->def->disks[diskIndex]->device;
|
|
|
|
switch ((virDomainStartupPolicy) startupPolicy) {
|
|
case VIR_DOMAIN_STARTUP_POLICY_OPTIONAL:
|
|
/* Once started with an optional disk, qemu saves its section
|
|
* in the migration stream, so later, when restoring from it
|
|
* we must make sure the sections match. */
|
|
if (!cold_boot &&
|
|
device != VIR_DOMAIN_DISK_DEVICE_FLOPPY &&
|
|
device != VIR_DOMAIN_DISK_DEVICE_CDROM)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_STARTUP_POLICY_DEFAULT:
|
|
case VIR_DOMAIN_STARTUP_POLICY_MANDATORY:
|
|
return -1;
|
|
|
|
case VIR_DOMAIN_STARTUP_POLICY_REQUISITE:
|
|
if (cold_boot)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_STARTUP_POLICY_LAST:
|
|
/* this should never happen */
|
|
break;
|
|
}
|
|
|
|
qemuDomainCheckRemoveOptionalDisk(driver, vm, diskIndex);
|
|
virResetLastError();
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* The vm must be locked when any of the following cleanup functions is
|
|
* called.
|
|
*/
|
|
int
|
|
qemuDomainCleanupAdd(virDomainObjPtr vm,
|
|
qemuDomainCleanupCallback cb)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
size_t i;
|
|
|
|
VIR_DEBUG("vm=%s, cb=%p", vm->def->name, cb);
|
|
|
|
for (i = 0; i < priv->ncleanupCallbacks; i++) {
|
|
if (priv->cleanupCallbacks[i] == cb)
|
|
return 0;
|
|
}
|
|
|
|
if (VIR_RESIZE_N(priv->cleanupCallbacks,
|
|
priv->ncleanupCallbacks_max,
|
|
priv->ncleanupCallbacks, 1) < 0)
|
|
return -1;
|
|
|
|
priv->cleanupCallbacks[priv->ncleanupCallbacks++] = cb;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
qemuDomainCleanupRemove(virDomainObjPtr vm,
|
|
qemuDomainCleanupCallback cb)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
size_t i;
|
|
|
|
VIR_DEBUG("vm=%s, cb=%p", vm->def->name, cb);
|
|
|
|
for (i = 0; i < priv->ncleanupCallbacks; i++) {
|
|
if (priv->cleanupCallbacks[i] == cb)
|
|
VIR_DELETE_ELEMENT_INPLACE(priv->cleanupCallbacks,
|
|
i, priv->ncleanupCallbacks);
|
|
}
|
|
|
|
VIR_SHRINK_N(priv->cleanupCallbacks,
|
|
priv->ncleanupCallbacks_max,
|
|
priv->ncleanupCallbacks_max - priv->ncleanupCallbacks);
|
|
}
|
|
|
|
void
|
|
qemuDomainCleanupRun(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
size_t i;
|
|
|
|
VIR_DEBUG("driver=%p, vm=%s", driver, vm->def->name);
|
|
|
|
/* run cleanup callbacks in reverse order */
|
|
for (i = 0; i < priv->ncleanupCallbacks; i++) {
|
|
if (priv->cleanupCallbacks[priv->ncleanupCallbacks - (i + 1)])
|
|
priv->cleanupCallbacks[i](driver, vm);
|
|
}
|
|
|
|
VIR_FREE(priv->cleanupCallbacks);
|
|
priv->ncleanupCallbacks = 0;
|
|
priv->ncleanupCallbacks_max = 0;
|
|
}
|
|
|
|
void
|
|
qemuDomainGetImageIds(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
virStorageSourcePtr src,
|
|
virStorageSourcePtr parentSrc,
|
|
uid_t *uid, gid_t *gid)
|
|
{
|
|
virSecurityLabelDefPtr vmlabel;
|
|
virSecurityDeviceLabelDefPtr disklabel;
|
|
|
|
if (uid)
|
|
*uid = -1;
|
|
if (gid)
|
|
*gid = -1;
|
|
|
|
if (cfg) {
|
|
if (uid)
|
|
*uid = cfg->user;
|
|
|
|
if (gid)
|
|
*gid = cfg->group;
|
|
}
|
|
|
|
if (vm && (vmlabel = virDomainDefGetSecurityLabelDef(vm->def, "dac")) &&
|
|
vmlabel->label)
|
|
virParseOwnershipIds(vmlabel->label, uid, gid);
|
|
|
|
if (parentSrc &&
|
|
(disklabel = virStorageSourceGetSecurityLabelDef(parentSrc, "dac")) &&
|
|
disklabel->label)
|
|
virParseOwnershipIds(disklabel->label, uid, gid);
|
|
|
|
if ((disklabel = virStorageSourceGetSecurityLabelDef(src, "dac")) &&
|
|
disklabel->label)
|
|
virParseOwnershipIds(disklabel->label, uid, gid);
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainStorageFileInit(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virStorageSourcePtr src,
|
|
virStorageSourcePtr parent)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
uid_t uid;
|
|
gid_t gid;
|
|
|
|
qemuDomainGetImageIds(cfg, vm, src, parent, &uid, &gid);
|
|
|
|
if (virStorageFileInitAs(src, uid, gid) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
char *
|
|
qemuDomainStorageAlias(const char *device, int depth)
|
|
{
|
|
char *alias;
|
|
|
|
device = qemuAliasDiskDriveSkipPrefix(device);
|
|
|
|
if (!depth)
|
|
alias = g_strdup(device);
|
|
else
|
|
alias = g_strdup_printf("%s.%d", device, depth);
|
|
return alias;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainStorageSourceValidateDepth:
|
|
* @src: storage source chain to validate
|
|
* @add: offsets the calculated number of images
|
|
* @diskdst: optional disk target to use in error message
|
|
*
|
|
* The XML parser limits the maximum element nesting to 256 layers. As libvirt
|
|
* reports the chain into the status and in some cases the config XML we must
|
|
* validate that any user-provided chains will not exceed the XML nesting limit
|
|
* when formatted to the XML.
|
|
*
|
|
* This function validates that the storage source chain starting @src is at
|
|
* most 200 layers deep. @add modifies the calculated value to offset the number
|
|
* to allow checking cases when new layers are going to be added to the chain.
|
|
*
|
|
* Returns 0 on success and -1 if the chain is too deep. Error is reported.
|
|
*/
|
|
int
|
|
qemuDomainStorageSourceValidateDepth(virStorageSourcePtr src,
|
|
int add,
|
|
const char *diskdst)
|
|
{
|
|
virStorageSourcePtr n;
|
|
size_t nlayers = 0;
|
|
|
|
for (n = src; virStorageSourceIsBacking(n); n = n->backingStore)
|
|
nlayers++;
|
|
|
|
nlayers += add;
|
|
|
|
if (nlayers > 200) {
|
|
if (diskdst)
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("backing chains more than 200 layers deep are not "
|
|
"supported for disk '%s'"), diskdst);
|
|
else
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
_("backing chains more than 200 layers deep are not "
|
|
"supported"));
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainPrepareStorageSourceConfig:
|
|
* @src: storage source to configure
|
|
* @cfg: qemu driver config object
|
|
* @qemuCaps: capabilities of qemu
|
|
*
|
|
* Set properties of @src based on the qemu driver config @cfg.
|
|
*
|
|
*/
|
|
static void
|
|
qemuDomainPrepareStorageSourceConfig(virStorageSourcePtr src,
|
|
virQEMUDriverConfigPtr cfg,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (!cfg)
|
|
return;
|
|
|
|
if (src->type == VIR_STORAGE_TYPE_NETWORK &&
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER &&
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_GLUSTER_DEBUG_LEVEL)) {
|
|
src->debug = true;
|
|
src->debugLevel = cfg->glusterDebugLevel;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDetermineDiskChain:
|
|
* @driver: qemu driver object
|
|
* @vm: domain object
|
|
* @disk: disk definition
|
|
* @disksrc: source to determine the chain for, may be NULL
|
|
* @report_broken: report broken chain verbosely
|
|
*
|
|
* Prepares and initializes the backing chain of disk @disk. In cases where
|
|
* a new source is to be associated with @disk the @disksrc parameter can be
|
|
* used to override the source. If @report_broken is true missing images
|
|
* in the backing chain are reported.
|
|
*/
|
|
int
|
|
qemuDomainDetermineDiskChain(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virDomainDiskDefPtr disk,
|
|
virStorageSourcePtr disksrc,
|
|
bool report_broken)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
virStorageSourcePtr src; /* iterator for the backing chain declared in XML */
|
|
virStorageSourcePtr n; /* iterator for the backing chain detected from disk */
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
uid_t uid;
|
|
gid_t gid;
|
|
|
|
if (!disksrc)
|
|
disksrc = disk->src;
|
|
|
|
if (virStorageSourceIsEmpty(disksrc))
|
|
return 0;
|
|
|
|
/* There is no need to check the backing chain for disks without backing
|
|
* support */
|
|
if (virStorageSourceIsLocalStorage(disksrc) &&
|
|
disksrc->format > VIR_STORAGE_FILE_NONE &&
|
|
disksrc->format < VIR_STORAGE_FILE_BACKING) {
|
|
|
|
if (!virFileExists(disksrc->path)) {
|
|
if (report_broken)
|
|
virStorageFileReportBrokenChain(errno, disksrc, disksrc);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* terminate the chain for such images as the code below would do */
|
|
if (!disksrc->backingStore &&
|
|
!(disksrc->backingStore = virStorageSourceNew()))
|
|
return -1;
|
|
|
|
/* host cdrom requires special treatment in qemu, so we need to check
|
|
* whether a block device is a cdrom */
|
|
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM &&
|
|
disksrc->format == VIR_STORAGE_FILE_RAW &&
|
|
virStorageSourceIsBlockLocal(disksrc) &&
|
|
virFileIsCDROM(disksrc->path) == 1)
|
|
disksrc->hostcdrom = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
src = disksrc;
|
|
/* skip to the end of the chain if there is any */
|
|
while (virStorageSourceHasBacking(src)) {
|
|
if (report_broken) {
|
|
int rv = virStorageFileSupportsAccess(src);
|
|
|
|
if (rv < 0)
|
|
return -1;
|
|
|
|
if (rv > 0) {
|
|
if (qemuDomainStorageFileInit(driver, vm, src, disksrc) < 0)
|
|
return -1;
|
|
|
|
if (virStorageFileAccess(src, F_OK) < 0) {
|
|
virStorageFileReportBrokenChain(errno, src, disksrc);
|
|
virStorageFileDeinit(src);
|
|
return -1;
|
|
}
|
|
|
|
virStorageFileDeinit(src);
|
|
}
|
|
}
|
|
src = src->backingStore;
|
|
}
|
|
|
|
/* We skipped to the end of the chain. Skip detection if there's the
|
|
* terminator. (An allocated but empty backingStore) */
|
|
if (src->backingStore) {
|
|
if (qemuDomainStorageSourceValidateDepth(disksrc, 0, disk->dst) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
qemuDomainGetImageIds(cfg, vm, src, disksrc, &uid, &gid);
|
|
|
|
if (virStorageFileGetMetadata(src, uid, gid, report_broken) < 0)
|
|
return -1;
|
|
|
|
for (n = src->backingStore; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
/* convert detected ISO format to 'raw' as qemu would not understand it */
|
|
if (n->format == VIR_STORAGE_FILE_ISO)
|
|
n->format = VIR_STORAGE_FILE_RAW;
|
|
|
|
if (qemuDomainValidateStorageSource(n, priv->qemuCaps) < 0)
|
|
return -1;
|
|
|
|
qemuDomainPrepareStorageSourceConfig(n, cfg, priv->qemuCaps);
|
|
qemuDomainPrepareDiskSourceData(disk, n);
|
|
|
|
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) &&
|
|
qemuDomainPrepareStorageSourceBlockdev(disk, n, priv, cfg) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (qemuDomainStorageSourceValidateDepth(disksrc, 0, disk->dst) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDiskGetTopNodename:
|
|
*
|
|
* @disk: disk definition object
|
|
*
|
|
* Returns the pointer to the node-name of the topmost layer used by @disk as
|
|
* backend. Currently returns the nodename of the copy-on-read filter if enabled
|
|
* or the nodename of the top image's format driver. Empty disks return NULL.
|
|
* This must be used only when VIR_QEMU_CAPS_BLOCKDEV is enabled.
|
|
*/
|
|
const char *
|
|
qemuDomainDiskGetTopNodename(virDomainDiskDefPtr disk)
|
|
{
|
|
qemuDomainDiskPrivatePtr priv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
|
|
if (virStorageSourceIsEmpty(disk->src))
|
|
return NULL;
|
|
|
|
if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON)
|
|
return priv->nodeCopyOnRead;
|
|
|
|
return disk->src->nodeformat;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDiskGetBackendAlias:
|
|
* @disk: disk definition
|
|
* @qemuCaps: emulator capabilities
|
|
* @backendAlias: filled with the alias of the disk storage backend
|
|
*
|
|
* Returns the correct alias for the disk backend. This may be the alias of
|
|
* -drive for legacy setup or the correct node name for -blockdev setups.
|
|
*
|
|
* @backendAlias may be NULL on success if the backend does not exist
|
|
* (disk is empty). Caller is responsible for freeing @backendAlias.
|
|
*
|
|
* Returns 0 on success, -1 on error with libvirt error reported.
|
|
*/
|
|
int
|
|
qemuDomainDiskGetBackendAlias(virDomainDiskDefPtr disk,
|
|
virQEMUCapsPtr qemuCaps,
|
|
char **backendAlias)
|
|
{
|
|
*backendAlias = NULL;
|
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV)) {
|
|
if (!(*backendAlias = qemuAliasDiskDriveFromDisk(disk)))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
*backendAlias = g_strdup(qemuDomainDiskGetTopNodename(disk));
|
|
return 0;
|
|
}
|
|
|
|
|
|
typedef enum {
|
|
/* revoke access to the image instead of allowing it */
|
|
QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_REVOKE = 1 << 0,
|
|
/* operate on full backing chain rather than single image */
|
|
QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN = 1 << 1,
|
|
/* force permissions to read-only/read-write when allowing */
|
|
/* currently does not properly work with QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN */
|
|
QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_FORCE_READ_ONLY = 1 << 2,
|
|
QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_FORCE_READ_WRITE = 1 << 3,
|
|
/* don't revoke permissions when modification has failed */
|
|
QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_SKIP_REVOKE = 1 << 4,
|
|
/* VM already has access to the source and we are just modifying it */
|
|
QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_MODIFY_ACCESS = 1 << 5,
|
|
} qemuDomainStorageSourceAccessFlags;
|
|
|
|
|
|
static int
|
|
qemuDomainStorageSourceAccessModifyNVMe(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virStorageSourcePtr src,
|
|
bool revoke)
|
|
{
|
|
bool revoke_maxmemlock = false;
|
|
bool revoke_hostdev = false;
|
|
int ret = -1;
|
|
|
|
if (!virStorageSourceChainHasNVMe(src))
|
|
return 0;
|
|
|
|
VIR_DEBUG("Modifying access for a NVMe disk src=%p revoke=%d",
|
|
src, revoke);
|
|
|
|
if (revoke) {
|
|
revoke_maxmemlock = true;
|
|
revoke_hostdev = true;
|
|
ret = 0;
|
|
goto revoke;
|
|
}
|
|
|
|
if (qemuDomainAdjustMaxMemLock(vm, true) < 0)
|
|
goto revoke;
|
|
|
|
revoke_maxmemlock = true;
|
|
|
|
if (qemuHostdevPrepareOneNVMeDisk(driver, vm->def->name, src) < 0)
|
|
goto revoke;
|
|
|
|
revoke_hostdev = true;
|
|
|
|
return 0;
|
|
|
|
revoke:
|
|
if (revoke_maxmemlock) {
|
|
if (qemuDomainAdjustMaxMemLock(vm, false) < 0)
|
|
VIR_WARN("Unable to change max memlock limit");
|
|
}
|
|
|
|
if (revoke_hostdev)
|
|
qemuHostdevReAttachOneNVMeDisk(driver, vm->def->name, src);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainStorageSourceAccessModify:
|
|
* @driver: qemu driver struct
|
|
* @vm: domain object
|
|
* @src: Source to prepare
|
|
* @flags: bitwise or of qemuDomainStorageSourceAccessFlags
|
|
*
|
|
* Setup the locks, cgroups and security permissions on a disk source and its
|
|
* backing chain.
|
|
*
|
|
* Returns 0 on success and -1 on error. Reports libvirt error.
|
|
*/
|
|
static int
|
|
qemuDomainStorageSourceAccessModify(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virStorageSourcePtr src,
|
|
qemuDomainStorageSourceAccessFlags flags)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
const char *srcstr = NULLSTR(src->path);
|
|
int ret = -1;
|
|
virErrorPtr orig_err = NULL;
|
|
bool chain = flags & QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN;
|
|
bool force_ro = flags & QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_FORCE_READ_ONLY;
|
|
bool force_rw = flags & QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_FORCE_READ_WRITE;
|
|
bool revoke = flags & QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_REVOKE;
|
|
int rc;
|
|
bool was_readonly = src->readonly;
|
|
bool revoke_cgroup = false;
|
|
bool revoke_label = false;
|
|
bool revoke_namespace = false;
|
|
bool revoke_nvme = false;
|
|
bool revoke_lockspace = false;
|
|
|
|
VIR_DEBUG("src='%s' readonly=%d force_ro=%d force_rw=%d revoke=%d chain=%d",
|
|
NULLSTR(src->path), src->readonly, force_ro, force_rw, revoke, chain);
|
|
|
|
if (force_ro)
|
|
src->readonly = true;
|
|
|
|
if (force_rw)
|
|
src->readonly = false;
|
|
|
|
/* just tear down the disk access */
|
|
if (revoke) {
|
|
virErrorPreserveLast(&orig_err);
|
|
revoke_cgroup = true;
|
|
revoke_label = true;
|
|
revoke_namespace = true;
|
|
revoke_nvme = true;
|
|
revoke_lockspace = true;
|
|
ret = 0;
|
|
goto revoke;
|
|
}
|
|
|
|
if (virDomainLockImageAttach(driver->lockManager, cfg->uri, vm, src) < 0)
|
|
goto revoke;
|
|
|
|
revoke_lockspace = true;
|
|
|
|
if (!(flags & QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_MODIFY_ACCESS)) {
|
|
if (qemuDomainStorageSourceAccessModifyNVMe(driver, vm, src, false) < 0)
|
|
goto revoke;
|
|
|
|
revoke_nvme = true;
|
|
|
|
if (qemuDomainNamespaceSetupDisk(vm, src) < 0)
|
|
goto revoke;
|
|
|
|
revoke_namespace = true;
|
|
}
|
|
|
|
if (qemuSecuritySetImageLabel(driver, vm, src, chain) < 0)
|
|
goto revoke;
|
|
|
|
revoke_label = true;
|
|
|
|
if (chain)
|
|
rc = qemuSetupImageChainCgroup(vm, src);
|
|
else
|
|
rc = qemuSetupImageCgroup(vm, src);
|
|
|
|
if (rc < 0)
|
|
goto revoke;
|
|
|
|
revoke_cgroup = true;
|
|
|
|
ret = 0;
|
|
goto cleanup;
|
|
|
|
revoke:
|
|
if (flags & QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_SKIP_REVOKE)
|
|
goto cleanup;
|
|
|
|
if (revoke_cgroup) {
|
|
if (chain)
|
|
rc = qemuTeardownImageChainCgroup(vm, src);
|
|
else
|
|
rc = qemuTeardownImageCgroup(vm, src);
|
|
|
|
if (rc < 0)
|
|
VIR_WARN("Unable to tear down cgroup access on %s", srcstr);
|
|
}
|
|
|
|
if (revoke_label) {
|
|
if (qemuSecurityRestoreImageLabel(driver, vm, src, chain) < 0)
|
|
VIR_WARN("Unable to restore security label on %s", srcstr);
|
|
}
|
|
|
|
if (revoke_namespace) {
|
|
if (qemuDomainNamespaceTeardownDisk(vm, src) < 0)
|
|
VIR_WARN("Unable to remove /dev entry for %s", srcstr);
|
|
}
|
|
|
|
if (revoke_nvme)
|
|
qemuDomainStorageSourceAccessModifyNVMe(driver, vm, src, true);
|
|
|
|
if (revoke_lockspace) {
|
|
if (virDomainLockImageDetach(driver->lockManager, vm, src) < 0)
|
|
VIR_WARN("Unable to release lock on %s", srcstr);
|
|
}
|
|
|
|
cleanup:
|
|
src->readonly = was_readonly;
|
|
virErrorRestore(&orig_err);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainStorageSourceChainAccessAllow(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virStorageSourcePtr src)
|
|
{
|
|
qemuDomainStorageSourceAccessFlags flags = QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN;
|
|
|
|
return qemuDomainStorageSourceAccessModify(driver, vm, src, flags);
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainStorageSourceChainAccessRevoke(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virStorageSourcePtr src)
|
|
{
|
|
qemuDomainStorageSourceAccessFlags flags = QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_REVOKE |
|
|
QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN;
|
|
|
|
return qemuDomainStorageSourceAccessModify(driver, vm, src, flags);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainStorageSourceAccessRevoke:
|
|
*
|
|
* Revoke access to a single backing chain element. This restores the labels,
|
|
* removes cgroup ACLs for devices and removes locks.
|
|
*/
|
|
void
|
|
qemuDomainStorageSourceAccessRevoke(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virStorageSourcePtr elem)
|
|
{
|
|
qemuDomainStorageSourceAccessFlags flags = QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_REVOKE;
|
|
|
|
ignore_value(qemuDomainStorageSourceAccessModify(driver, vm, elem, flags));
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainStorageSourceAccessAllow:
|
|
* @driver: qemu driver data
|
|
* @vm: domain object
|
|
* @elem: source structure to set access for
|
|
* @readonly: setup read-only access if true
|
|
* @newSource: @elem describes a storage source which @vm can't access yet
|
|
*
|
|
* Allow a VM access to a single element of a disk backing chain; this helper
|
|
* ensures that the lock manager, cgroup device controller, and security manager
|
|
* labelling are all aware of each new file before it is added to a chain.
|
|
*
|
|
* When modifying permissions of @elem which @vm can already access (is in the
|
|
* backing chain) @newSource needs to be set to false.
|
|
*/
|
|
int
|
|
qemuDomainStorageSourceAccessAllow(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virStorageSourcePtr elem,
|
|
bool readonly,
|
|
bool newSource)
|
|
{
|
|
qemuDomainStorageSourceAccessFlags flags = QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_SKIP_REVOKE;
|
|
|
|
if (readonly)
|
|
flags |= QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_FORCE_READ_ONLY;
|
|
else
|
|
flags |= QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_FORCE_READ_WRITE;
|
|
|
|
if (!newSource)
|
|
flags |= QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_MODIFY_ACCESS;
|
|
|
|
return qemuDomainStorageSourceAccessModify(driver, vm, elem, flags);
|
|
}
|
|
|
|
|
|
/*
|
|
* Makes sure the @disk differs from @orig_disk only by the source
|
|
* path and nothing else. Fields that are being checked and the
|
|
* information whether they are nullable (may not be specified) or is
|
|
* taken from the virDomainDiskDefFormat() code.
|
|
*/
|
|
bool
|
|
qemuDomainDiskChangeSupported(virDomainDiskDefPtr disk,
|
|
virDomainDiskDefPtr orig_disk)
|
|
{
|
|
#define CHECK_EQ(field, field_name, nullable) \
|
|
do { \
|
|
if (nullable && !disk->field) \
|
|
break; \
|
|
if (disk->field != orig_disk->field) { \
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, \
|
|
_("cannot modify field '%s' of the disk"), \
|
|
field_name); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define CHECK_STREQ_NULLABLE(field, field_name) \
|
|
do { \
|
|
if (!disk->field) \
|
|
break; \
|
|
if (STRNEQ_NULLABLE(disk->field, orig_disk->field)) { \
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, \
|
|
_("cannot modify field '%s' of the disk"), \
|
|
field_name); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
CHECK_EQ(device, "device", false);
|
|
CHECK_EQ(bus, "bus", false);
|
|
if (STRNEQ(disk->dst, orig_disk->dst)) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("cannot modify field '%s' of the disk"),
|
|
"target");
|
|
return false;
|
|
}
|
|
CHECK_EQ(tray_status, "tray", true);
|
|
CHECK_EQ(removable, "removable", true);
|
|
|
|
if (disk->geometry.cylinders &&
|
|
disk->geometry.heads &&
|
|
disk->geometry.sectors) {
|
|
CHECK_EQ(geometry.cylinders, "geometry cylinders", false);
|
|
CHECK_EQ(geometry.heads, "geometry heads", false);
|
|
CHECK_EQ(geometry.sectors, "geometry sectors", false);
|
|
CHECK_EQ(geometry.trans, "BIOS-translation-modus", true);
|
|
}
|
|
|
|
CHECK_EQ(blockio.logical_block_size,
|
|
"blockio logical_block_size", false);
|
|
CHECK_EQ(blockio.physical_block_size,
|
|
"blockio physical_block_size", false);
|
|
|
|
CHECK_EQ(blkdeviotune.total_bytes_sec,
|
|
"blkdeviotune total_bytes_sec",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.read_bytes_sec,
|
|
"blkdeviotune read_bytes_sec",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.write_bytes_sec,
|
|
"blkdeviotune write_bytes_sec",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.total_iops_sec,
|
|
"blkdeviotune total_iops_sec",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.read_iops_sec,
|
|
"blkdeviotune read_iops_sec",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.write_iops_sec,
|
|
"blkdeviotune write_iops_sec",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.total_bytes_sec_max,
|
|
"blkdeviotune total_bytes_sec_max",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.read_bytes_sec_max,
|
|
"blkdeviotune read_bytes_sec_max",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.write_bytes_sec_max,
|
|
"blkdeviotune write_bytes_sec_max",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.total_iops_sec_max,
|
|
"blkdeviotune total_iops_sec_max",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.read_iops_sec_max,
|
|
"blkdeviotune read_iops_sec_max",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.write_iops_sec_max,
|
|
"blkdeviotune write_iops_sec_max",
|
|
true);
|
|
CHECK_EQ(blkdeviotune.size_iops_sec,
|
|
"blkdeviotune size_iops_sec",
|
|
true);
|
|
CHECK_STREQ_NULLABLE(blkdeviotune.group_name,
|
|
"blkdeviotune group name");
|
|
|
|
CHECK_STREQ_NULLABLE(serial,
|
|
"serial");
|
|
CHECK_STREQ_NULLABLE(wwn,
|
|
"wwn");
|
|
CHECK_STREQ_NULLABLE(vendor,
|
|
"vendor");
|
|
CHECK_STREQ_NULLABLE(product,
|
|
"product");
|
|
|
|
CHECK_EQ(cachemode, "cache", true);
|
|
CHECK_EQ(error_policy, "error_policy", true);
|
|
CHECK_EQ(rerror_policy, "rerror_policy", true);
|
|
CHECK_EQ(iomode, "io", true);
|
|
CHECK_EQ(ioeventfd, "ioeventfd", true);
|
|
CHECK_EQ(event_idx, "event_idx", true);
|
|
CHECK_EQ(copy_on_read, "copy_on_read", true);
|
|
/* "snapshot" is a libvirt internal field and thus can be changed */
|
|
/* startupPolicy is allowed to be updated. Therefore not checked here. */
|
|
CHECK_EQ(transient, "transient", true);
|
|
|
|
/* Note: For some address types the address auto generation for
|
|
* @disk has still not happened at this point (e.g. driver
|
|
* specific addresses) therefore we can't catch these possible
|
|
* address modifications here. */
|
|
if (disk->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
!virDomainDeviceInfoAddressIsEqual(&disk->info, &orig_disk->info)) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("cannot modify field '%s' of the disk"),
|
|
"address");
|
|
return false;
|
|
}
|
|
|
|
/* device alias is checked already in virDomainDefCompatibleDevice */
|
|
|
|
CHECK_EQ(info.bootIndex, "boot order", true);
|
|
CHECK_EQ(rawio, "rawio", true);
|
|
CHECK_EQ(sgio, "sgio", true);
|
|
CHECK_EQ(discard, "discard", true);
|
|
CHECK_EQ(iothread, "iothread", true);
|
|
|
|
CHECK_STREQ_NULLABLE(domain_name,
|
|
"backenddomain");
|
|
|
|
/* checks for fields stored in disk->src */
|
|
/* unfortunately 'readonly' and 'shared' can't be converted to tristate
|
|
* values thus we need to ignore the check if the new value is 'false' */
|
|
CHECK_EQ(src->readonly, "readonly", true);
|
|
CHECK_EQ(src->shared, "shared", true);
|
|
|
|
if (!virStoragePRDefIsEqual(disk->src->pr,
|
|
orig_disk->src->pr)) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("cannot modify field '%s' of the disk"),
|
|
"reservations");
|
|
return false;
|
|
}
|
|
|
|
#undef CHECK_EQ
|
|
#undef CHECK_STREQ_NULLABLE
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainDiskBlockJobIsActive(virDomainDiskDefPtr disk)
|
|
{
|
|
qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
|
|
if (disk->mirror) {
|
|
virReportError(VIR_ERR_BLOCK_COPY_ACTIVE,
|
|
_("disk '%s' already in active block job"),
|
|
disk->dst);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (diskPriv->blockjob &&
|
|
qemuBlockJobIsRunning(diskPriv->blockjob)) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("disk '%s' already in active block job"),
|
|
disk->dst);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainHasBlockjob:
|
|
* @vm: domain object
|
|
* @copy_only: Reject only block copy job
|
|
*
|
|
* Return true if @vm has at least one disk involved in a current block
|
|
* copy/commit/pull job. If @copy_only is true this returns true only if the
|
|
* disk is involved in a block copy.
|
|
* */
|
|
bool
|
|
qemuDomainHasBlockjob(virDomainObjPtr vm,
|
|
bool copy_only)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < vm->def->ndisks; i++) {
|
|
virDomainDiskDefPtr disk = vm->def->disks[i];
|
|
qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
|
|
if (!copy_only && diskPriv->blockjob &&
|
|
qemuBlockJobIsRunning(diskPriv->blockjob))
|
|
return true;
|
|
|
|
if (disk->mirror && disk->mirrorJob == VIR_DOMAIN_BLOCK_JOB_TYPE_COPY)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainUpdateDeviceList(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
int asyncJob)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
char **aliases;
|
|
int rc;
|
|
|
|
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
|
|
return -1;
|
|
rc = qemuMonitorGetDeviceAliases(priv->mon, &aliases);
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0)
|
|
return -1;
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
virStringListFree(priv->qemuDevices);
|
|
priv->qemuDevices = aliases;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainUpdateMemoryDeviceInfo(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
int asyncJob)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virHashTablePtr meminfo = NULL;
|
|
int rc;
|
|
size_t i;
|
|
|
|
if (vm->def->nmems == 0)
|
|
return 0;
|
|
|
|
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
|
|
return -1;
|
|
|
|
rc = qemuMonitorGetMemoryDeviceInfo(priv->mon, &meminfo);
|
|
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0) {
|
|
virHashFree(meminfo);
|
|
return -1;
|
|
}
|
|
|
|
/* if qemu doesn't support the info request, just carry on */
|
|
if (rc == -2)
|
|
return 0;
|
|
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < vm->def->nmems; i++) {
|
|
virDomainMemoryDefPtr mem = vm->def->mems[i];
|
|
qemuMonitorMemoryDeviceInfoPtr dimm;
|
|
|
|
if (!mem->info.alias)
|
|
continue;
|
|
|
|
if (!(dimm = virHashLookup(meminfo, mem->info.alias)))
|
|
continue;
|
|
|
|
mem->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM;
|
|
mem->info.addr.dimm.slot = dimm->slot;
|
|
mem->info.addr.dimm.base = dimm->address;
|
|
}
|
|
|
|
virHashFree(meminfo);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainABIStabilityCheck(const virDomainDef *src,
|
|
const virDomainDef *dst)
|
|
{
|
|
size_t i;
|
|
|
|
if (src->mem.source != dst->mem.source) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target memoryBacking source '%s' doesn't "
|
|
"match source memoryBacking source'%s'"),
|
|
virDomainMemorySourceTypeToString(dst->mem.source),
|
|
virDomainMemorySourceTypeToString(src->mem.source));
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < src->nmems; i++) {
|
|
const char *srcAlias = src->mems[i]->info.alias;
|
|
const char *dstAlias = dst->mems[i]->info.alias;
|
|
|
|
if (STRNEQ_NULLABLE(srcAlias, dstAlias)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target memory device alias '%s' doesn't "
|
|
"match source alias '%s'"),
|
|
NULLSTR(srcAlias), NULLSTR(dstAlias));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
virDomainABIStability virQEMUDriverDomainABIStability = {
|
|
.domain = qemuDomainABIStabilityCheck,
|
|
};
|
|
|
|
|
|
static bool
|
|
qemuDomainMigratableDefCheckABIStability(virQEMUDriverPtr driver,
|
|
virDomainDefPtr src,
|
|
virDomainDefPtr migratableSrc,
|
|
virDomainDefPtr dst,
|
|
virDomainDefPtr migratableDst)
|
|
{
|
|
if (!virDomainDefCheckABIStabilityFlags(migratableSrc,
|
|
migratableDst,
|
|
driver->xmlopt,
|
|
VIR_DOMAIN_DEF_ABI_CHECK_SKIP_VOLATILE))
|
|
return false;
|
|
|
|
/* Force update any skipped values from the volatile flag */
|
|
dst->mem.cur_balloon = src->mem.cur_balloon;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
#define COPY_FLAGS (VIR_DOMAIN_XML_SECURE | \
|
|
VIR_DOMAIN_XML_MIGRATABLE)
|
|
|
|
bool
|
|
qemuDomainDefCheckABIStability(virQEMUDriverPtr driver,
|
|
virQEMUCapsPtr qemuCaps,
|
|
virDomainDefPtr src,
|
|
virDomainDefPtr dst)
|
|
{
|
|
virDomainDefPtr migratableDefSrc = NULL;
|
|
virDomainDefPtr migratableDefDst = NULL;
|
|
bool ret = false;
|
|
|
|
if (!(migratableDefSrc = qemuDomainDefCopy(driver, qemuCaps, src, COPY_FLAGS)) ||
|
|
!(migratableDefDst = qemuDomainDefCopy(driver, qemuCaps, dst, COPY_FLAGS)))
|
|
goto cleanup;
|
|
|
|
ret = qemuDomainMigratableDefCheckABIStability(driver,
|
|
src, migratableDefSrc,
|
|
dst, migratableDefDst);
|
|
|
|
cleanup:
|
|
virDomainDefFree(migratableDefSrc);
|
|
virDomainDefFree(migratableDefDst);
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainCheckABIStability(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
virDomainDefPtr dst)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virDomainDefPtr migratableSrc = NULL;
|
|
virDomainDefPtr migratableDst = NULL;
|
|
g_autofree char *xml = NULL;
|
|
bool ret = false;
|
|
|
|
if (!(xml = qemuDomainFormatXML(driver, vm, COPY_FLAGS)) ||
|
|
!(migratableSrc = qemuDomainDefFromXML(driver, priv->qemuCaps, xml)) ||
|
|
!(migratableDst = qemuDomainDefCopy(driver, priv->qemuCaps, dst, COPY_FLAGS)))
|
|
goto cleanup;
|
|
|
|
ret = qemuDomainMigratableDefCheckABIStability(driver,
|
|
vm->def, migratableSrc,
|
|
dst, migratableDst);
|
|
|
|
cleanup:
|
|
virDomainDefFree(migratableSrc);
|
|
virDomainDefFree(migratableDst);
|
|
return ret;
|
|
}
|
|
|
|
#undef COPY_FLAGS
|
|
|
|
|
|
bool
|
|
qemuDomainAgentAvailable(virDomainObjPtr vm,
|
|
bool reportError)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_RUNNING) {
|
|
if (reportError) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("domain is not running"));
|
|
}
|
|
return false;
|
|
}
|
|
if (priv->agentError) {
|
|
if (reportError) {
|
|
virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
|
|
_("QEMU guest agent is not "
|
|
"available due to an error"));
|
|
}
|
|
return false;
|
|
}
|
|
if (!priv->agent) {
|
|
if (qemuFindAgentConfig(vm->def)) {
|
|
if (reportError) {
|
|
virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
|
|
_("QEMU guest agent is not connected"));
|
|
}
|
|
return false;
|
|
} else {
|
|
if (reportError) {
|
|
virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
|
|
_("QEMU guest agent is not configured"));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static unsigned long long
|
|
qemuDomainGetMemorySizeAlignment(virDomainDefPtr def)
|
|
{
|
|
/* PPC requires the memory sizes to be rounded to 256MiB increments, so
|
|
* round them to the size always. */
|
|
if (ARCH_IS_PPC64(def->os.arch))
|
|
return 256 * 1024;
|
|
|
|
/* Align memory size. QEMU requires rounding to next 4KiB block.
|
|
* We'll take the "traditional" path and round it to 1MiB */
|
|
|
|
return 1024;
|
|
}
|
|
|
|
|
|
static unsigned long long
|
|
qemuDomainGetMemoryModuleSizeAlignment(const virDomainDef *def,
|
|
const virDomainMemoryDef *mem G_GNUC_UNUSED)
|
|
{
|
|
/* PPC requires the memory sizes to be rounded to 256MiB increments, so
|
|
* round them to the size always. */
|
|
if (ARCH_IS_PPC64(def->os.arch))
|
|
return 256 * 1024;
|
|
|
|
/* dimm memory modules require 2MiB alignment rather than the 1MiB we are
|
|
* using elsewhere. */
|
|
return 2048;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainAlignMemorySizes(virDomainDefPtr def)
|
|
{
|
|
unsigned long long maxmemkb = virMemoryMaxValue(false) >> 10;
|
|
unsigned long long maxmemcapped = virMemoryMaxValue(true) >> 10;
|
|
unsigned long long initialmem = 0;
|
|
unsigned long long hotplugmem = 0;
|
|
unsigned long long mem;
|
|
unsigned long long align = qemuDomainGetMemorySizeAlignment(def);
|
|
size_t ncells = virDomainNumaGetNodeCount(def->numa);
|
|
size_t i;
|
|
|
|
/* align NUMA cell sizes if relevant */
|
|
for (i = 0; i < ncells; i++) {
|
|
mem = VIR_ROUND_UP(virDomainNumaGetNodeMemorySize(def->numa, i), align);
|
|
initialmem += mem;
|
|
|
|
if (mem > maxmemkb) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("memory size of NUMA node '%zu' overflowed after "
|
|
"alignment"), i);
|
|
return -1;
|
|
}
|
|
virDomainNumaSetNodeMemorySize(def->numa, i, mem);
|
|
}
|
|
|
|
/* align initial memory size, if NUMA is present calculate it as total of
|
|
* individual aligned NUMA node sizes */
|
|
if (initialmem == 0)
|
|
initialmem = VIR_ROUND_UP(virDomainDefGetMemoryInitial(def), align);
|
|
|
|
if (initialmem > maxmemcapped) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("initial memory size overflowed after alignment"));
|
|
return -1;
|
|
}
|
|
|
|
def->mem.max_memory = VIR_ROUND_UP(def->mem.max_memory, align);
|
|
if (def->mem.max_memory > maxmemkb) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("maximum memory size overflowed after alignment"));
|
|
return -1;
|
|
}
|
|
|
|
/* Align memory module sizes */
|
|
for (i = 0; i < def->nmems; i++) {
|
|
align = qemuDomainGetMemoryModuleSizeAlignment(def, def->mems[i]);
|
|
def->mems[i]->size = VIR_ROUND_UP(def->mems[i]->size, align);
|
|
hotplugmem += def->mems[i]->size;
|
|
|
|
if (def->mems[i]->size > maxmemkb) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("size of memory module '%zu' overflowed after "
|
|
"alignment"), i);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
virDomainDefSetMemoryTotal(def, initialmem + hotplugmem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainMemoryDeviceAlignSize:
|
|
* @mem: memory device definition object
|
|
*
|
|
* Aligns the size of the memory module as qemu enforces it. The size is updated
|
|
* inplace. Default rounding is now to 1 MiB (qemu requires rouding to page,
|
|
* size so this should be safe).
|
|
*/
|
|
void
|
|
qemuDomainMemoryDeviceAlignSize(virDomainDefPtr def,
|
|
virDomainMemoryDefPtr mem)
|
|
{
|
|
mem->size = VIR_ROUND_UP(mem->size, qemuDomainGetMemorySizeAlignment(def));
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainGetMonitor:
|
|
* @vm: domain object
|
|
*
|
|
* Returns the monitor pointer corresponding to the domain object @vm.
|
|
*/
|
|
qemuMonitorPtr
|
|
qemuDomainGetMonitor(virDomainObjPtr vm)
|
|
{
|
|
return ((qemuDomainObjPrivatePtr) vm->privateData)->mon;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuFindAgentConfig:
|
|
* @def: domain definition
|
|
*
|
|
* Returns the pointer to the channel definition that is used to access the
|
|
* guest agent if the agent is configured or NULL otherwise.
|
|
*/
|
|
virDomainChrDefPtr
|
|
qemuFindAgentConfig(virDomainDefPtr def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nchannels; i++) {
|
|
virDomainChrDefPtr channel = def->channels[i];
|
|
|
|
if (channel->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO)
|
|
continue;
|
|
|
|
if (STREQ_NULLABLE(channel->target.name, "org.qemu.guest_agent.0"))
|
|
return channel;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainMachineIsQ35(const char *machine,
|
|
const virArch arch)
|
|
{
|
|
if (!ARCH_IS_X86(arch))
|
|
return false;
|
|
|
|
if (STREQ(machine, "q35") ||
|
|
STRPREFIX(machine, "pc-q35-")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainMachineIsI440FX(const char *machine,
|
|
const virArch arch)
|
|
{
|
|
if (!ARCH_IS_X86(arch))
|
|
return false;
|
|
|
|
if (STREQ(machine, "pc") ||
|
|
STRPREFIX(machine, "pc-0.") ||
|
|
STRPREFIX(machine, "pc-1.") ||
|
|
STRPREFIX(machine, "pc-i440fx-") ||
|
|
STRPREFIX(machine, "rhel")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainMachineIsS390CCW(const char *machine,
|
|
const virArch arch)
|
|
{
|
|
if (!ARCH_IS_S390(arch))
|
|
return false;
|
|
|
|
if (STRPREFIX(machine, "s390-ccw"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* You should normally avoid this function and use
|
|
* qemuDomainIsARMVirt() instead. */
|
|
bool
|
|
qemuDomainMachineIsARMVirt(const char *machine,
|
|
const virArch arch)
|
|
{
|
|
if (arch != VIR_ARCH_ARMV6L &&
|
|
arch != VIR_ARCH_ARMV7L &&
|
|
arch != VIR_ARCH_AARCH64) {
|
|
return false;
|
|
}
|
|
|
|
if (STREQ(machine, "virt") ||
|
|
STRPREFIX(machine, "virt-")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainMachineIsRISCVVirt(const char *machine,
|
|
const virArch arch)
|
|
{
|
|
if (!ARCH_IS_RISCV(arch))
|
|
return false;
|
|
|
|
if (STREQ(machine, "virt") ||
|
|
STRPREFIX(machine, "virt-")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* You should normally avoid this function and use
|
|
* qemuDomainIsPSeries() instead. */
|
|
bool
|
|
qemuDomainMachineIsPSeries(const char *machine,
|
|
const virArch arch)
|
|
{
|
|
if (!ARCH_IS_PPC64(arch))
|
|
return false;
|
|
|
|
if (STREQ(machine, "pseries") ||
|
|
STRPREFIX(machine, "pseries-")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* You should normally avoid this function and use
|
|
* qemuDomainHasBuiltinIDE() instead. */
|
|
bool
|
|
qemuDomainMachineHasBuiltinIDE(const char *machine,
|
|
const virArch arch)
|
|
{
|
|
return qemuDomainMachineIsI440FX(machine, arch) ||
|
|
STREQ(machine, "malta") ||
|
|
STREQ(machine, "sun4u") ||
|
|
STREQ(machine, "g3beige");
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuDomainMachineNeedsFDC(const char *machine,
|
|
const virArch arch)
|
|
{
|
|
const char *p = STRSKIP(machine, "pc-q35-");
|
|
|
|
if (!ARCH_IS_X86(arch))
|
|
return false;
|
|
|
|
if (!p)
|
|
return false;
|
|
|
|
if (STRPREFIX(p, "1.") ||
|
|
STREQ(p, "2.0") ||
|
|
STREQ(p, "2.1") ||
|
|
STREQ(p, "2.2") ||
|
|
STREQ(p, "2.3")) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainIsQ35(const virDomainDef *def)
|
|
{
|
|
return qemuDomainMachineIsQ35(def->os.machine, def->os.arch);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainIsI440FX(const virDomainDef *def)
|
|
{
|
|
return qemuDomainMachineIsI440FX(def->os.machine, def->os.arch);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainIsS390CCW(const virDomainDef *def)
|
|
{
|
|
return qemuDomainMachineIsS390CCW(def->os.machine, def->os.arch);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainIsARMVirt(const virDomainDef *def)
|
|
{
|
|
return qemuDomainMachineIsARMVirt(def->os.machine, def->os.arch);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainIsRISCVVirt(const virDomainDef *def)
|
|
{
|
|
return qemuDomainMachineIsRISCVVirt(def->os.machine, def->os.arch);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainIsPSeries(const virDomainDef *def)
|
|
{
|
|
return qemuDomainMachineIsPSeries(def->os.machine, def->os.arch);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainHasPCIRoot(const virDomainDef *def)
|
|
{
|
|
int root = virDomainControllerFind(def, VIR_DOMAIN_CONTROLLER_TYPE_PCI, 0);
|
|
|
|
if (root < 0)
|
|
return false;
|
|
|
|
if (def->controllers[root]->model != VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainHasPCIeRoot(const virDomainDef *def)
|
|
{
|
|
int root = virDomainControllerFind(def, VIR_DOMAIN_CONTROLLER_TYPE_PCI, 0);
|
|
|
|
if (root < 0)
|
|
return false;
|
|
|
|
if (def->controllers[root]->model != VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainHasBuiltinIDE(const virDomainDef *def)
|
|
{
|
|
return qemuDomainMachineHasBuiltinIDE(def->os.machine, def->os.arch);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainNeedsFDC(const virDomainDef *def)
|
|
{
|
|
return qemuDomainMachineNeedsFDC(def->os.machine, def->os.arch);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainSupportsPCI(virDomainDefPtr def,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (def->os.arch != VIR_ARCH_ARMV6L &&
|
|
def->os.arch != VIR_ARCH_ARMV7L &&
|
|
def->os.arch != VIR_ARCH_AARCH64 &&
|
|
!ARCH_IS_RISCV(def->os.arch)) {
|
|
return true;
|
|
}
|
|
|
|
if (STREQ(def->os.machine, "versatilepb"))
|
|
return true;
|
|
|
|
if ((qemuDomainIsARMVirt(def) ||
|
|
qemuDomainIsRISCVVirt(def)) &&
|
|
virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_GPEX)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuCheckMemoryDimmConflict(const virDomainDef *def,
|
|
const virDomainMemoryDef *mem)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nmems; i++) {
|
|
virDomainMemoryDefPtr tmp = def->mems[i];
|
|
|
|
if (tmp == mem ||
|
|
tmp->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM)
|
|
continue;
|
|
|
|
if (mem->info.addr.dimm.slot == tmp->info.addr.dimm.slot) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("memory device slot '%u' is already being "
|
|
"used by another memory device"),
|
|
mem->info.addr.dimm.slot);
|
|
return true;
|
|
}
|
|
|
|
if (mem->info.addr.dimm.base != 0 &&
|
|
mem->info.addr.dimm.base == tmp->info.addr.dimm.base) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("memory device base '0x%llx' is already being "
|
|
"used by another memory device"),
|
|
mem->info.addr.dimm.base);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
static int
|
|
qemuDomainDefValidateMemoryHotplugDevice(const virDomainMemoryDef *mem,
|
|
const virDomainDef *def)
|
|
{
|
|
switch ((virDomainMemoryModel) mem->model) {
|
|
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
|
|
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
|
|
if (mem->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM &&
|
|
mem->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("only 'dimm' addresses are supported for the "
|
|
"pc-dimm device"));
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainNumaGetNodeCount(def->numa) != 0) {
|
|
if (mem->targetNode == -1) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("target NUMA node needs to be specified for "
|
|
"memory device"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (mem->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM) {
|
|
if (mem->info.addr.dimm.slot >= def->mem.memory_slots) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("memory device slot '%u' exceeds slots "
|
|
"count '%u'"),
|
|
mem->info.addr.dimm.slot, def->mem.memory_slots);
|
|
return -1;
|
|
}
|
|
|
|
|
|
if (qemuCheckMemoryDimmConflict(def, mem))
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NONE:
|
|
case VIR_DOMAIN_MEMORY_MODEL_LAST:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDefValidateMemoryHotplug:
|
|
* @def: domain definition
|
|
* @qemuCaps: qemu capabilities object
|
|
* @mem: definition of memory device that is to be added to @def with hotplug,
|
|
* NULL in case of regular VM startup
|
|
*
|
|
* Validates that the domain definition and memory modules have valid
|
|
* configuration and are possibly able to accept @mem via hotplug if it's
|
|
* non-NULL.
|
|
*
|
|
* Returns 0 on success; -1 and a libvirt error on error.
|
|
*/
|
|
int
|
|
qemuDomainDefValidateMemoryHotplug(const virDomainDef *def,
|
|
virQEMUCapsPtr qemuCaps,
|
|
const virDomainMemoryDef *mem)
|
|
{
|
|
unsigned int nmems = def->nmems;
|
|
unsigned long long hotplugSpace;
|
|
unsigned long long hotplugMemory = 0;
|
|
bool needPCDimmCap = false;
|
|
bool needNvdimmCap = false;
|
|
size_t i;
|
|
|
|
hotplugSpace = def->mem.max_memory - virDomainDefGetMemoryInitial(def);
|
|
|
|
if (mem) {
|
|
nmems++;
|
|
hotplugMemory = mem->size;
|
|
|
|
if (qemuDomainDefValidateMemoryHotplugDevice(mem, def) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!virDomainDefHasMemoryHotplug(def)) {
|
|
if (nmems) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("cannot use/hotplug a memory device when domain "
|
|
"'maxMemory' is not defined"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (!ARCH_IS_PPC64(def->os.arch)) {
|
|
/* due to guest support, qemu would silently enable NUMA with one node
|
|
* once the memory hotplug backend is enabled. To avoid possible
|
|
* confusion we will enforce user originated numa configuration along
|
|
* with memory hotplug. */
|
|
if (virDomainNumaGetNodeCount(def->numa) == 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("At least one numa node has to be configured when "
|
|
"enabling memory hotplug"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (nmems > def->mem.memory_slots) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("memory device count '%u' exceeds slots count '%u'"),
|
|
nmems, def->mem.memory_slots);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < def->nmems; i++) {
|
|
hotplugMemory += def->mems[i]->size;
|
|
|
|
switch ((virDomainMemoryModel) def->mems[i]->model) {
|
|
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
|
|
needPCDimmCap = true;
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
|
|
needNvdimmCap = true;
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NONE:
|
|
case VIR_DOMAIN_MEMORY_MODEL_LAST:
|
|
break;
|
|
}
|
|
|
|
/* already existing devices don't need to be checked on hotplug */
|
|
if (!mem &&
|
|
qemuDomainDefValidateMemoryHotplugDevice(def->mems[i], def) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (needPCDimmCap &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_PC_DIMM)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("memory hotplug isn't supported by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
if (needNvdimmCap &&
|
|
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_NVDIMM)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("nvdimm isn't supported by this QEMU binary"));
|
|
return -1;
|
|
}
|
|
|
|
if (hotplugMemory > hotplugSpace) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("memory device total size exceeds hotplug space"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainUpdateCurrentMemorySize:
|
|
*
|
|
* In case when the balloon is not present for the domain, the function
|
|
* recalculates the maximum size to reflect possible changes.
|
|
*/
|
|
void
|
|
qemuDomainUpdateCurrentMemorySize(virDomainObjPtr vm)
|
|
{
|
|
/* inactive domain doesn't need size update */
|
|
if (!virDomainObjIsActive(vm))
|
|
return;
|
|
|
|
/* if no balloning is available, the current size equals to the current
|
|
* full memory size */
|
|
if (!virDomainDefHasMemballoon(vm->def))
|
|
vm->def->mem.cur_balloon = virDomainDefGetMemoryTotal(vm->def);
|
|
}
|
|
|
|
|
|
/**
|
|
* ppc64VFIODeviceIsNV2Bridge:
|
|
* @device: string with the PCI device address
|
|
*
|
|
* This function receives a string that represents a PCI device,
|
|
* such as '0004:04:00.0', and tells if the device is a NVLink2
|
|
* bridge.
|
|
*/
|
|
static bool
|
|
ppc64VFIODeviceIsNV2Bridge(const char *device)
|
|
{
|
|
const char *nvlink2Files[] = {"ibm,gpu", "ibm,nvlink",
|
|
"ibm,nvlink-speed", "memory-region"};
|
|
size_t i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(nvlink2Files); i++) {
|
|
g_autofree char *file = NULL;
|
|
|
|
file = g_strdup_printf("/sys/bus/pci/devices/%s/of_node/%s",
|
|
device, nvlink2Files[i]);
|
|
|
|
if (!virFileExists(file))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* getPPC64MemLockLimitBytes:
|
|
* @def: domain definition
|
|
* @forceVFIO: force VFIO usage
|
|
*
|
|
* A PPC64 helper that calculates the memory locking limit in order for
|
|
* the guest to operate properly.
|
|
*/
|
|
static unsigned long long
|
|
getPPC64MemLockLimitBytes(virDomainDefPtr def,
|
|
bool forceVFIO)
|
|
{
|
|
unsigned long long memKB = 0;
|
|
unsigned long long baseLimit = 0;
|
|
unsigned long long memory = 0;
|
|
unsigned long long maxMemory = 0;
|
|
unsigned long long passthroughLimit = 0;
|
|
size_t i, nPCIHostBridges = 0;
|
|
virPCIDeviceAddressPtr pciAddr;
|
|
bool usesVFIO = false;
|
|
bool nvlink2Capable = false;
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
virDomainControllerDefPtr cont = def->controllers[i];
|
|
|
|
if (!virDomainControllerIsPSeriesPHB(cont))
|
|
continue;
|
|
|
|
nPCIHostBridges++;
|
|
}
|
|
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
virDomainHostdevDefPtr dev = def->hostdevs[i];
|
|
|
|
if (virHostdevIsVFIODevice(dev)) {
|
|
usesVFIO = true;
|
|
|
|
pciAddr = &dev->source.subsys.u.pci.addr;
|
|
if (virPCIDeviceAddressIsValid(pciAddr, false)) {
|
|
g_autofree char *pciAddrStr = NULL;
|
|
|
|
pciAddrStr = virPCIDeviceAddressAsString(pciAddr);
|
|
if (ppc64VFIODeviceIsNV2Bridge(pciAddrStr)) {
|
|
nvlink2Capable = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (virDomainDefHasNVMeDisk(def))
|
|
usesVFIO = true;
|
|
|
|
memory = virDomainDefGetMemoryTotal(def);
|
|
|
|
if (def->mem.max_memory)
|
|
maxMemory = def->mem.max_memory;
|
|
else
|
|
maxMemory = memory;
|
|
|
|
/* baseLimit := maxMemory / 128 (a)
|
|
* + 4 MiB * #PHBs + 8 MiB (b)
|
|
*
|
|
* (a) is the hash table
|
|
*
|
|
* (b) is accounting for the 32-bit DMA window - it could be either the
|
|
* KVM accelerated TCE tables for emulated devices, or the VFIO
|
|
* userspace view. The 4 MiB per-PHB (including the default one) covers
|
|
* a 2GiB DMA window: default is 1GiB, but it's possible it'll be
|
|
* increased to help performance. The 8 MiB extra should be plenty for
|
|
* the TCE table index for any reasonable number of PHBs and several
|
|
* spapr-vlan or spapr-vscsi devices (512kB + a tiny bit each) */
|
|
baseLimit = maxMemory / 128 +
|
|
4096 * nPCIHostBridges +
|
|
8192;
|
|
|
|
/* NVLink2 support in QEMU is a special case of the passthrough
|
|
* mechanics explained in the usesVFIO case below. The GPU RAM
|
|
* is placed with a gap after maxMemory. The current QEMU
|
|
* implementation puts the NVIDIA RAM above the PCI MMIO, which
|
|
* starts at 32TiB and is the MMIO reserved for the guest main RAM.
|
|
*
|
|
* This window ends at 64TiB, and this is where the GPUs are being
|
|
* placed. The next available window size is at 128TiB, and
|
|
* 64TiB..128TiB will fit all possible NVIDIA GPUs.
|
|
*
|
|
* The same assumption as the most common case applies here:
|
|
* the guest will request a 64-bit DMA window, per PHB, that is
|
|
* big enough to map all its RAM, which is now at 128TiB due
|
|
* to the GPUs.
|
|
*
|
|
* Note that the NVIDIA RAM window must be accounted for the TCE
|
|
* table size, but *not* for the main RAM (maxMemory). This gives
|
|
* us the following passthroughLimit for the NVLink2 case:
|
|
*
|
|
* passthroughLimit = maxMemory +
|
|
* 128TiB/512KiB * #PHBs + 8 MiB */
|
|
if (nvlink2Capable) {
|
|
passthroughLimit = maxMemory +
|
|
128 * (1ULL<<30) / 512 * nPCIHostBridges +
|
|
8192;
|
|
} else if (usesVFIO || forceVFIO) {
|
|
/* For regular (non-NVLink2 present) VFIO passthrough, the value
|
|
* of passthroughLimit is:
|
|
*
|
|
* passthroughLimit := max( 2 GiB * #PHBs, (c)
|
|
* memory (d)
|
|
* + memory * 1/512 * #PHBs + 8 MiB ) (e)
|
|
*
|
|
* (c) is the pre-DDW VFIO DMA window accounting. We're allowing 2
|
|
* GiB rather than 1 GiB
|
|
*
|
|
* (d) is the with-DDW (and memory pre-registration and related
|
|
* features) DMA window accounting - assuming that we only account
|
|
* RAM once, even if mapped to multiple PHBs
|
|
*
|
|
* (e) is the with-DDW userspace view and overhead for the 64-bit
|
|
* DMA window. This is based a bit on expected guest behaviour, but
|
|
* there really isn't a way to completely avoid that. We assume the
|
|
* guest requests a 64-bit DMA window (per PHB) just big enough to
|
|
* map all its RAM. 4 kiB page size gives the 1/512; it will be
|
|
* less with 64 kiB pages, less still if the guest is mapped with
|
|
* hugepages (unlike the default 32-bit DMA window, DDW windows
|
|
* can use large IOMMU pages). 8 MiB is for second and further level
|
|
* overheads, like (b) */
|
|
passthroughLimit = MAX(2 * 1024 * 1024 * nPCIHostBridges,
|
|
memory +
|
|
memory / 512 * nPCIHostBridges + 8192);
|
|
}
|
|
|
|
memKB = baseLimit + passthroughLimit;
|
|
|
|
return memKB << 10;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainGetMemLockLimitBytes:
|
|
* @def: domain definition
|
|
* @forceVFIO: force VFIO calculation
|
|
*
|
|
* Calculate the memory locking limit that needs to be set in order for
|
|
* the guest to operate properly. The limit depends on a number of factors,
|
|
* including certain configuration options and less immediately apparent ones
|
|
* such as the guest architecture or the use of certain devices.
|
|
* The @forceVFIO argument can be used to tell this function will use VFIO even
|
|
* though @def doesn't indicates so right now.
|
|
*
|
|
* Returns: the memory locking limit, or 0 if setting the limit is not needed
|
|
*/
|
|
unsigned long long
|
|
qemuDomainGetMemLockLimitBytes(virDomainDefPtr def,
|
|
bool forceVFIO)
|
|
{
|
|
unsigned long long memKB = 0;
|
|
bool usesVFIO = false;
|
|
size_t i;
|
|
|
|
/* prefer the hard limit */
|
|
if (virMemoryLimitIsSet(def->mem.hard_limit)) {
|
|
memKB = def->mem.hard_limit;
|
|
return memKB << 10;
|
|
}
|
|
|
|
/* If the guest wants its memory to be locked, we need to raise the memory
|
|
* locking limit so that the OS will not refuse allocation requests;
|
|
* however, there is no reliable way for us to figure out how much memory
|
|
* the QEMU process will allocate for its own use, so our only way out is
|
|
* to remove the limit altogether. Use with extreme care */
|
|
if (def->mem.locked)
|
|
return VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
|
|
if (ARCH_IS_PPC64(def->os.arch) && def->virtType == VIR_DOMAIN_VIRT_KVM)
|
|
return getPPC64MemLockLimitBytes(def, forceVFIO);
|
|
|
|
/* For device passthrough using VFIO the guest memory and MMIO memory
|
|
* regions need to be locked persistent in order to allow DMA.
|
|
*
|
|
* Currently the below limit is based on assumptions about the x86 platform.
|
|
*
|
|
* The chosen value of 1GiB below originates from x86 systems where it was
|
|
* used as space reserved for the MMIO region for the whole system.
|
|
*
|
|
* On x86_64 systems the MMIO regions of the IOMMU mapped devices don't
|
|
* count towards the locked memory limit since the memory is owned by the
|
|
* device. Emulated devices though do count, but the regions are usually
|
|
* small. Although it's not guaranteed that the limit will be enough for all
|
|
* configurations it didn't pose a problem for now.
|
|
*
|
|
* http://www.redhat.com/archives/libvir-list/2015-November/msg00329.html
|
|
*
|
|
* Note that this may not be valid for all platforms.
|
|
*/
|
|
if (!forceVFIO) {
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
if (virHostdevIsVFIODevice(def->hostdevs[i]) ||
|
|
virHostdevIsMdevDevice(def->hostdevs[i])) {
|
|
usesVFIO = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (virDomainDefHasNVMeDisk(def))
|
|
usesVFIO = true;
|
|
}
|
|
|
|
if (usesVFIO || forceVFIO)
|
|
memKB = virDomainDefGetMemoryTotal(def) + 1024 * 1024;
|
|
|
|
return memKB << 10;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainAdjustMaxMemLock:
|
|
* @vm: domain
|
|
* @forceVFIO: apply VFIO requirements even if vm's def doesn't require it
|
|
*
|
|
* Adjust the memory locking limit for the QEMU process associated to @vm, in
|
|
* order to comply with VFIO or architecture requirements. If @forceVFIO is
|
|
* true then the limit is changed even if nothing in @vm's definition indicates
|
|
* so.
|
|
*
|
|
* The limit will not be changed unless doing so is needed; the first time
|
|
* the limit is changed, the original (default) limit is stored in @vm and
|
|
* that value will be restored if qemuDomainAdjustMaxMemLock() is called once
|
|
* memory locking is no longer required.
|
|
*
|
|
* Returns: 0 on success, <0 on failure
|
|
*/
|
|
int
|
|
qemuDomainAdjustMaxMemLock(virDomainObjPtr vm,
|
|
bool forceVFIO)
|
|
{
|
|
unsigned long long bytes = 0;
|
|
|
|
bytes = qemuDomainGetMemLockLimitBytes(vm->def, forceVFIO);
|
|
|
|
if (bytes) {
|
|
/* If this is the first time adjusting the limit, save the current
|
|
* value so that we can restore it once memory locking is no longer
|
|
* required. Failing to obtain the current limit is not a critical
|
|
* failure, it just means we'll be unable to lower it later */
|
|
if (!vm->original_memlock) {
|
|
if (virProcessGetMaxMemLock(vm->pid, &(vm->original_memlock)) < 0)
|
|
vm->original_memlock = 0;
|
|
}
|
|
} else {
|
|
/* Once memory locking is no longer required, we can restore the
|
|
* original, usually very low, limit */
|
|
bytes = vm->original_memlock;
|
|
vm->original_memlock = 0;
|
|
}
|
|
|
|
/* Trying to set the memory locking limit to zero is a no-op */
|
|
if (virProcessSetMaxMemLock(vm->pid, bytes) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainAdjustMaxMemLockHostdev:
|
|
* @vm: domain
|
|
* @hostdev: device
|
|
*
|
|
* Temporarily add the hostdev to the domain definition. This is needed
|
|
* because qemuDomainAdjustMaxMemLock() requires the hostdev to be already
|
|
* part of the domain definition, but other functions like
|
|
* qemuAssignDeviceHostdevAlias() expect it *not* to be there.
|
|
* A better way to handle this would be nice
|
|
*
|
|
* Returns: 0 on success, <0 on failure
|
|
*/
|
|
int
|
|
qemuDomainAdjustMaxMemLockHostdev(virDomainObjPtr vm,
|
|
virDomainHostdevDefPtr hostdev)
|
|
{
|
|
int ret = 0;
|
|
|
|
vm->def->hostdevs[vm->def->nhostdevs++] = hostdev;
|
|
if (qemuDomainAdjustMaxMemLock(vm, false) < 0)
|
|
ret = -1;
|
|
|
|
vm->def->hostdevs[--(vm->def->nhostdevs)] = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainHasVcpuPids:
|
|
* @vm: Domain object
|
|
*
|
|
* Returns true if we were able to successfully detect vCPU pids for the VM.
|
|
*/
|
|
bool
|
|
qemuDomainHasVcpuPids(virDomainObjPtr vm)
|
|
{
|
|
size_t i;
|
|
size_t maxvcpus = virDomainDefGetVcpusMax(vm->def);
|
|
virDomainVcpuDefPtr vcpu;
|
|
|
|
for (i = 0; i < maxvcpus; i++) {
|
|
vcpu = virDomainDefGetVcpu(vm->def, i);
|
|
|
|
if (QEMU_DOMAIN_VCPU_PRIVATE(vcpu)->tid > 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainGetVcpuPid:
|
|
* @vm: domain object
|
|
* @vcpu: cpu id
|
|
*
|
|
* Returns the vCPU pid. If @vcpu is offline or out of range 0 is returned.
|
|
*/
|
|
pid_t
|
|
qemuDomainGetVcpuPid(virDomainObjPtr vm,
|
|
unsigned int vcpuid)
|
|
{
|
|
virDomainVcpuDefPtr vcpu = virDomainDefGetVcpu(vm->def, vcpuid);
|
|
return QEMU_DOMAIN_VCPU_PRIVATE(vcpu)->tid;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainValidateVcpuInfo:
|
|
*
|
|
* Validates vcpu thread information. If vcpu thread IDs are reported by qemu,
|
|
* this function validates that online vcpus have thread info present and
|
|
* offline vcpus don't.
|
|
*
|
|
* Returns 0 on success -1 on error.
|
|
*/
|
|
int
|
|
qemuDomainValidateVcpuInfo(virDomainObjPtr vm)
|
|
{
|
|
size_t maxvcpus = virDomainDefGetVcpusMax(vm->def);
|
|
virDomainVcpuDefPtr vcpu;
|
|
qemuDomainVcpuPrivatePtr vcpupriv;
|
|
size_t i;
|
|
|
|
if (!qemuDomainHasVcpuPids(vm))
|
|
return 0;
|
|
|
|
for (i = 0; i < maxvcpus; i++) {
|
|
vcpu = virDomainDefGetVcpu(vm->def, i);
|
|
vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu);
|
|
|
|
if (vcpu->online && vcpupriv->tid == 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("qemu didn't report thread id for vcpu '%zu'"), i);
|
|
return -1;
|
|
}
|
|
|
|
if (!vcpu->online && vcpupriv->tid != 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("qemu reported thread id for inactive vcpu '%zu'"),
|
|
i);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainSupportsNewVcpuHotplug(virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
return virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_QUERY_HOTPLUGGABLE_CPUS);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainRefreshVcpuInfo:
|
|
* @driver: qemu driver data
|
|
* @vm: domain object
|
|
* @asyncJob: current asynchronous job type
|
|
* @state: refresh vcpu state
|
|
*
|
|
* Updates vCPU information private data of @vm. Due to historical reasons this
|
|
* function returns success even if some data were not reported by qemu.
|
|
*
|
|
* If @state is true, the vcpu state is refreshed as reported by the monitor.
|
|
*
|
|
* Returns 0 on success and -1 on fatal error.
|
|
*/
|
|
int
|
|
qemuDomainRefreshVcpuInfo(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
int asyncJob,
|
|
bool state)
|
|
{
|
|
virDomainVcpuDefPtr vcpu;
|
|
qemuDomainVcpuPrivatePtr vcpupriv;
|
|
qemuMonitorCPUInfoPtr info = NULL;
|
|
size_t maxvcpus = virDomainDefGetVcpusMax(vm->def);
|
|
size_t i, j;
|
|
bool hotplug;
|
|
bool fast;
|
|
bool validTIDs = true;
|
|
int rc;
|
|
int ret = -1;
|
|
|
|
hotplug = qemuDomainSupportsNewVcpuHotplug(vm);
|
|
fast = virQEMUCapsGet(QEMU_DOMAIN_PRIVATE(vm)->qemuCaps,
|
|
QEMU_CAPS_QUERY_CPUS_FAST);
|
|
|
|
VIR_DEBUG("Maxvcpus %zu hotplug %d fast query %d", maxvcpus, hotplug, fast);
|
|
|
|
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
|
|
return -1;
|
|
|
|
rc = qemuMonitorGetCPUInfo(qemuDomainGetMonitor(vm), &info, maxvcpus,
|
|
hotplug, fast);
|
|
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0)
|
|
goto cleanup;
|
|
|
|
if (rc < 0)
|
|
goto cleanup;
|
|
|
|
/*
|
|
* The query-cpus[-fast] commands return information
|
|
* about the vCPUs, including the OS level PID that
|
|
* is executing the vCPU.
|
|
*
|
|
* For KVM there is always a 1-1 mapping between
|
|
* vCPUs and host OS PIDs.
|
|
*
|
|
* For TCG things are a little more complicated.
|
|
*
|
|
* - In some cases the vCPUs will all have the same
|
|
* PID as the main emulator thread.
|
|
* - In some cases the first vCPU will have a distinct
|
|
* PID, but other vCPUs will share the emulator thread
|
|
*
|
|
* For MTTCG, things work the same as KVM, with each
|
|
* vCPU getting its own PID.
|
|
*
|
|
* We use the Host OS PIDs for doing vCPU pinning
|
|
* and reporting. The TCG data reporting will result
|
|
* in bad behaviour such as pinning the wrong PID.
|
|
* We must thus detect and discard bogus PID info
|
|
* from TCG, while still honouring the modern MTTCG
|
|
* impl which we can support.
|
|
*/
|
|
for (i = 0; i < maxvcpus && validTIDs; i++) {
|
|
if (info[i].tid == vm->pid) {
|
|
VIR_DEBUG("vCPU[%zu] PID %llu duplicates process",
|
|
i, (unsigned long long)info[i].tid);
|
|
validTIDs = false;
|
|
}
|
|
|
|
for (j = 0; j < i; j++) {
|
|
if (info[i].tid != 0 && info[i].tid == info[j].tid) {
|
|
VIR_DEBUG("vCPU[%zu] PID %llu duplicates vCPU[%zu]",
|
|
i, (unsigned long long)info[i].tid, j);
|
|
validTIDs = false;
|
|
}
|
|
}
|
|
|
|
if (validTIDs)
|
|
VIR_DEBUG("vCPU[%zu] PID %llu is valid",
|
|
i, (unsigned long long)info[i].tid);
|
|
}
|
|
|
|
VIR_DEBUG("Extracting vCPU information validTIDs=%d", validTIDs);
|
|
for (i = 0; i < maxvcpus; i++) {
|
|
vcpu = virDomainDefGetVcpu(vm->def, i);
|
|
vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu);
|
|
|
|
if (validTIDs)
|
|
vcpupriv->tid = info[i].tid;
|
|
|
|
vcpupriv->socket_id = info[i].socket_id;
|
|
vcpupriv->core_id = info[i].core_id;
|
|
vcpupriv->thread_id = info[i].thread_id;
|
|
vcpupriv->node_id = info[i].node_id;
|
|
vcpupriv->vcpus = info[i].vcpus;
|
|
VIR_FREE(vcpupriv->type);
|
|
vcpupriv->type = g_steal_pointer(&info[i].type);
|
|
VIR_FREE(vcpupriv->alias);
|
|
vcpupriv->alias = g_steal_pointer(&info[i].alias);
|
|
virJSONValueFree(vcpupriv->props);
|
|
vcpupriv->props = g_steal_pointer(&info[i].props);
|
|
vcpupriv->enable_id = info[i].id;
|
|
vcpupriv->qemu_id = info[i].qemu_id;
|
|
|
|
if (hotplug && state) {
|
|
vcpu->online = info[i].online;
|
|
if (info[i].hotpluggable)
|
|
vcpu->hotpluggable = VIR_TRISTATE_BOOL_YES;
|
|
else
|
|
vcpu->hotpluggable = VIR_TRISTATE_BOOL_NO;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
qemuMonitorCPUInfoFree(info, maxvcpus);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* qemuDomainGetVcpuHalted:
|
|
* @vm: domain object
|
|
* @vcpu: cpu id
|
|
*
|
|
* Returns the vCPU halted state.
|
|
*/
|
|
bool
|
|
qemuDomainGetVcpuHalted(virDomainObjPtr vm,
|
|
unsigned int vcpuid)
|
|
{
|
|
virDomainVcpuDefPtr vcpu = virDomainDefGetVcpu(vm->def, vcpuid);
|
|
return QEMU_DOMAIN_VCPU_PRIVATE(vcpu)->halted;
|
|
}
|
|
|
|
/**
|
|
* qemuDomainRefreshVcpuHalted:
|
|
* @driver: qemu driver data
|
|
* @vm: domain object
|
|
* @asyncJob: current asynchronous job type
|
|
*
|
|
* Updates vCPU halted state in the private data of @vm.
|
|
*
|
|
* Returns 0 on success and -1 on error
|
|
*/
|
|
int
|
|
qemuDomainRefreshVcpuHalted(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
int asyncJob)
|
|
{
|
|
virDomainVcpuDefPtr vcpu;
|
|
qemuDomainVcpuPrivatePtr vcpupriv;
|
|
size_t maxvcpus = virDomainDefGetVcpusMax(vm->def);
|
|
virBitmapPtr haltedmap = NULL;
|
|
size_t i;
|
|
int ret = -1;
|
|
bool fast;
|
|
|
|
/* Not supported currently for TCG, see qemuDomainRefreshVcpuInfo */
|
|
if (vm->def->virtType == VIR_DOMAIN_VIRT_QEMU)
|
|
return 0;
|
|
|
|
/* The halted state is interresting only on s390(x). On other platforms
|
|
* the data would be stale at the time when it would be used.
|
|
* Calling qemuMonitorGetCpuHalted() can adversely affect the running
|
|
* VM's performance unless QEMU supports query-cpus-fast.
|
|
*/
|
|
if (!ARCH_IS_S390(vm->def->os.arch) ||
|
|
!virQEMUCapsGet(QEMU_DOMAIN_PRIVATE(vm)->qemuCaps,
|
|
QEMU_CAPS_QUERY_CPUS_FAST))
|
|
return 0;
|
|
|
|
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
|
|
return -1;
|
|
|
|
fast = virQEMUCapsGet(QEMU_DOMAIN_PRIVATE(vm)->qemuCaps,
|
|
QEMU_CAPS_QUERY_CPUS_FAST);
|
|
haltedmap = qemuMonitorGetCpuHalted(qemuDomainGetMonitor(vm), maxvcpus,
|
|
fast);
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0 || !haltedmap)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < maxvcpus; i++) {
|
|
vcpu = virDomainDefGetVcpu(vm->def, i);
|
|
vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu);
|
|
vcpupriv->halted = virTristateBoolFromBool(virBitmapIsBitSet(haltedmap,
|
|
vcpupriv->qemu_id));
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virBitmapFree(haltedmap);
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
qemuDomainSupportsNicdev(virDomainDefPtr def,
|
|
virDomainNetDefPtr net)
|
|
{
|
|
/* non-virtio ARM nics require legacy -net nic */
|
|
if (((def->os.arch == VIR_ARCH_ARMV6L) ||
|
|
(def->os.arch == VIR_ARCH_ARMV7L) ||
|
|
(def->os.arch == VIR_ARCH_AARCH64)) &&
|
|
net->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO &&
|
|
net->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
qemuDomainNetSupportsMTU(virDomainNetType type)
|
|
{
|
|
switch (type) {
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
return true;
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
virDomainDiskDefPtr
|
|
qemuDomainDiskByName(virDomainDefPtr def,
|
|
const char *name)
|
|
{
|
|
virDomainDiskDefPtr ret;
|
|
|
|
if (!(ret = virDomainDiskByName(def, name, true))) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("disk '%s' not found in domain"), name);
|
|
return NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDefValidateDiskLunSource:
|
|
* @src: disk source struct
|
|
*
|
|
* Validate whether the disk source is valid for disk device='lun'.
|
|
*
|
|
* Returns 0 if the configuration is valid -1 and a libvirt error if the soure
|
|
* is invalid.
|
|
*/
|
|
int
|
|
qemuDomainDefValidateDiskLunSource(const virStorageSource *src)
|
|
{
|
|
if (virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_NETWORK) {
|
|
if (src->protocol != VIR_STORAGE_NET_PROTOCOL_ISCSI) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("disk device='lun' is not supported "
|
|
"for protocol='%s'"),
|
|
virStorageNetProtocolTypeToString(src->protocol));
|
|
return -1;
|
|
}
|
|
} else if (!virStorageSourceIsBlockLocal(src)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("disk device='lun' is only valid for block "
|
|
"type disk source"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainPrepareChannel(virDomainChrDefPtr channel,
|
|
const char *domainChannelTargetDir)
|
|
{
|
|
if (channel->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO ||
|
|
channel->source->type != VIR_DOMAIN_CHR_TYPE_UNIX ||
|
|
channel->source->data.nix.path)
|
|
return 0;
|
|
|
|
if (channel->target.name) {
|
|
channel->source->data.nix.path = g_strdup_printf("%s/%s",
|
|
domainChannelTargetDir,
|
|
channel->target.name);
|
|
} else {
|
|
/* Generate a unique name */
|
|
channel->source->data.nix.path = g_strdup_printf("%s/vioser-%02d-%02d-%02d.sock",
|
|
domainChannelTargetDir,
|
|
channel->info.addr.vioserial.controller,
|
|
channel->info.addr.vioserial.bus,
|
|
channel->info.addr.vioserial.port);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* qemuDomainPrepareChardevSourceTLS:
|
|
* @source: pointer to host interface data for char devices
|
|
* @cfg: driver configuration
|
|
*
|
|
* Updates host interface TLS encryption setting based on qemu.conf
|
|
* for char devices. This will be presented as "tls='yes|no'" in
|
|
* live XML of a guest.
|
|
*/
|
|
void
|
|
qemuDomainPrepareChardevSourceTLS(virDomainChrSourceDefPtr source,
|
|
virQEMUDriverConfigPtr cfg)
|
|
{
|
|
if (source->type == VIR_DOMAIN_CHR_TYPE_TCP) {
|
|
if (source->data.tcp.haveTLS == VIR_TRISTATE_BOOL_ABSENT) {
|
|
if (cfg->chardevTLS)
|
|
source->data.tcp.haveTLS = VIR_TRISTATE_BOOL_YES;
|
|
else
|
|
source->data.tcp.haveTLS = VIR_TRISTATE_BOOL_NO;
|
|
source->data.tcp.tlsFromConfig = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* qemuDomainPrepareChardevSource:
|
|
* @def: live domain definition
|
|
* @cfg: driver configuration
|
|
*
|
|
* Iterate through all devices that use virDomainChrSourceDefPtr as host
|
|
* interface part.
|
|
*/
|
|
void
|
|
qemuDomainPrepareChardevSource(virDomainDefPtr def,
|
|
virQEMUDriverConfigPtr cfg)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nserials; i++)
|
|
qemuDomainPrepareChardevSourceTLS(def->serials[i]->source, cfg);
|
|
|
|
for (i = 0; i < def->nparallels; i++)
|
|
qemuDomainPrepareChardevSourceTLS(def->parallels[i]->source, cfg);
|
|
|
|
for (i = 0; i < def->nchannels; i++)
|
|
qemuDomainPrepareChardevSourceTLS(def->channels[i]->source, cfg);
|
|
|
|
for (i = 0; i < def->nconsoles; i++)
|
|
qemuDomainPrepareChardevSourceTLS(def->consoles[i]->source, cfg);
|
|
|
|
for (i = 0; i < def->nrngs; i++)
|
|
if (def->rngs[i]->backend == VIR_DOMAIN_RNG_BACKEND_EGD)
|
|
qemuDomainPrepareChardevSourceTLS(def->rngs[i]->source.chardev, cfg);
|
|
|
|
for (i = 0; i < def->nsmartcards; i++)
|
|
if (def->smartcards[i]->type == VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH)
|
|
qemuDomainPrepareChardevSourceTLS(def->smartcards[i]->data.passthru,
|
|
cfg);
|
|
|
|
for (i = 0; i < def->nredirdevs; i++)
|
|
qemuDomainPrepareChardevSourceTLS(def->redirdevs[i]->source, cfg);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuProcessPrepareStorageSourceTLSVxhs(virStorageSourcePtr src,
|
|
virQEMUDriverConfigPtr cfg)
|
|
{
|
|
/* VxHS uses only client certificates and thus has no need for
|
|
* the server-key.pem nor a secret that could be used to decrypt
|
|
* the it, so no need to add a secinfo for a secret UUID. */
|
|
if (src->haveTLS == VIR_TRISTATE_BOOL_ABSENT) {
|
|
if (cfg->vxhsTLS)
|
|
src->haveTLS = VIR_TRISTATE_BOOL_YES;
|
|
else
|
|
src->haveTLS = VIR_TRISTATE_BOOL_NO;
|
|
src->tlsFromConfig = true;
|
|
}
|
|
|
|
if (src->haveTLS == VIR_TRISTATE_BOOL_YES)
|
|
src->tlsCertdir = g_strdup(cfg->vxhsTLSx509certdir);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuProcessPrepareStorageSourceTLSNBD(virStorageSourcePtr src,
|
|
virQEMUDriverConfigPtr cfg,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (src->haveTLS == VIR_TRISTATE_BOOL_ABSENT) {
|
|
if (cfg->nbdTLS)
|
|
src->haveTLS = VIR_TRISTATE_BOOL_YES;
|
|
else
|
|
src->haveTLS = VIR_TRISTATE_BOOL_NO;
|
|
src->tlsFromConfig = true;
|
|
}
|
|
|
|
if (src->haveTLS == VIR_TRISTATE_BOOL_YES) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_NBD_TLS)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("this qemu does not support TLS transport for NBD"));
|
|
return -1;
|
|
}
|
|
|
|
src->tlsCertdir = g_strdup(cfg->nbdTLSx509certdir);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* qemuProcessPrepareStorageSourceTLS:
|
|
* @source: source for a disk
|
|
* @cfg: driver configuration
|
|
* @parentAlias: alias of the parent device
|
|
*
|
|
* Updates host interface TLS encryption setting based on qemu.conf
|
|
* for disk devices. This will be presented as "tls='yes|no'" in
|
|
* live XML of a guest.
|
|
*
|
|
* Returns 0 on success, -1 on bad config/failure
|
|
*/
|
|
static int
|
|
qemuDomainPrepareStorageSourceTLS(virStorageSourcePtr src,
|
|
virQEMUDriverConfigPtr cfg,
|
|
const char *parentAlias,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (virStorageSourceGetActualType(src) != VIR_STORAGE_TYPE_NETWORK)
|
|
return 0;
|
|
|
|
switch ((virStorageNetProtocol) src->protocol) {
|
|
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
|
if (qemuProcessPrepareStorageSourceTLSVxhs(src, cfg) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
|
if (qemuProcessPrepareStorageSourceTLSNBD(src, cfg, qemuCaps) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
|
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
|
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
|
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
|
case VIR_STORAGE_NET_PROTOCOL_HTTP:
|
|
case VIR_STORAGE_NET_PROTOCOL_HTTPS:
|
|
case VIR_STORAGE_NET_PROTOCOL_FTP:
|
|
case VIR_STORAGE_NET_PROTOCOL_FTPS:
|
|
case VIR_STORAGE_NET_PROTOCOL_TFTP:
|
|
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
|
if (src->haveTLS == VIR_TRISTATE_BOOL_YES) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("TLS transport is not supported for disk protocol '%s'"),
|
|
virStorageNetProtocolTypeToString(src->protocol));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
|
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
|
default:
|
|
virReportEnumRangeError(virStorageNetProtocol, src->protocol);
|
|
return -1;
|
|
}
|
|
|
|
if (src->haveTLS == VIR_TRISTATE_BOOL_YES &&
|
|
!(src->tlsAlias = qemuAliasTLSObjFromSrcAlias(parentAlias)))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainPrepareShmemChardev(virDomainShmemDefPtr shmem)
|
|
{
|
|
if (!shmem->server.enabled ||
|
|
shmem->server.chr.data.nix.path)
|
|
return;
|
|
|
|
shmem->server.chr.data.nix.path = g_strdup_printf("/var/lib/libvirt/shmem-%s-sock",
|
|
shmem->name);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainVcpuHotplugIsInOrder:
|
|
* @def: domain definition
|
|
*
|
|
* Returns true if online vcpus were added in order (clustered behind vcpu0
|
|
* with increasing order).
|
|
*/
|
|
bool
|
|
qemuDomainVcpuHotplugIsInOrder(virDomainDefPtr def)
|
|
{
|
|
size_t maxvcpus = virDomainDefGetVcpusMax(def);
|
|
virDomainVcpuDefPtr vcpu;
|
|
unsigned int prevorder = 0;
|
|
size_t seenonlinevcpus = 0;
|
|
size_t i;
|
|
|
|
for (i = 0; i < maxvcpus; i++) {
|
|
vcpu = virDomainDefGetVcpu(def, i);
|
|
|
|
if (!vcpu->online)
|
|
break;
|
|
|
|
if (vcpu->order < prevorder)
|
|
break;
|
|
|
|
if (vcpu->order > prevorder)
|
|
prevorder = vcpu->order;
|
|
|
|
seenonlinevcpus++;
|
|
}
|
|
|
|
return seenonlinevcpus == virDomainDefGetVcpus(def);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainVcpuPersistOrder:
|
|
* @def: domain definition
|
|
*
|
|
* Saves the order of vcpus detected from qemu to the domain definition.
|
|
* The private data note the order only for the entry describing the
|
|
* hotpluggable entity. This function copies the order into the definition part
|
|
* of all sub entities.
|
|
*/
|
|
void
|
|
qemuDomainVcpuPersistOrder(virDomainDefPtr def)
|
|
{
|
|
size_t maxvcpus = virDomainDefGetVcpusMax(def);
|
|
virDomainVcpuDefPtr vcpu;
|
|
qemuDomainVcpuPrivatePtr vcpupriv;
|
|
unsigned int prevorder = 0;
|
|
size_t i;
|
|
|
|
for (i = 0; i < maxvcpus; i++) {
|
|
vcpu = virDomainDefGetVcpu(def, i);
|
|
vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu);
|
|
|
|
if (!vcpu->online) {
|
|
vcpu->order = 0;
|
|
} else {
|
|
if (vcpupriv->enable_id != 0)
|
|
prevorder = vcpupriv->enable_id;
|
|
|
|
vcpu->order = prevorder;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainCheckMonitor(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
qemuDomainAsyncJob asyncJob)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
int ret;
|
|
|
|
if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
|
|
return -1;
|
|
|
|
ret = qemuMonitorCheck(priv->mon);
|
|
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0)
|
|
return -1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainSupportsVideoVga(virDomainVideoDefPtr video,
|
|
virQEMUCapsPtr qemuCaps)
|
|
{
|
|
if (video->type == VIR_DOMAIN_VIDEO_TYPE_VIRTIO) {
|
|
if (video->backend == VIR_DOMAIN_VIDEO_BACKEND_TYPE_VHOSTUSER) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VHOST_USER_VGA))
|
|
return false;
|
|
} else if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VIRTIO_VGA)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainNeedsVFIO(const virDomainDef *def)
|
|
{
|
|
return virDomainDefHasVFIOHostdev(def) ||
|
|
virDomainDefHasMdevHostdev(def) ||
|
|
virDomainDefHasNVMeDisk(def);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainGetHostdevPath:
|
|
* @dev: host device definition
|
|
* @path: resulting path to @dev
|
|
* @perms: Optional pointer to VIR_CGROUP_DEVICE_* perms
|
|
*
|
|
* For given device @dev fetch its host path and store it at
|
|
* @path. Optionally, caller can get @perms on the path (e.g.
|
|
* rw/ro). When called on a missing device, the function will return success
|
|
* and store NULL at @path.
|
|
*
|
|
* The caller is responsible for freeing the @path when no longer
|
|
* needed.
|
|
*
|
|
* Returns 0 on success, -1 otherwise.
|
|
*/
|
|
int
|
|
qemuDomainGetHostdevPath(virDomainHostdevDefPtr dev,
|
|
char **path,
|
|
int *perms)
|
|
{
|
|
virDomainHostdevSubsysUSBPtr usbsrc = &dev->source.subsys.u.usb;
|
|
virDomainHostdevSubsysPCIPtr pcisrc = &dev->source.subsys.u.pci;
|
|
virDomainHostdevSubsysSCSIPtr scsisrc = &dev->source.subsys.u.scsi;
|
|
virDomainHostdevSubsysSCSIVHostPtr hostsrc = &dev->source.subsys.u.scsi_host;
|
|
virDomainHostdevSubsysMediatedDevPtr mdevsrc = &dev->source.subsys.u.mdev;
|
|
g_autoptr(virUSBDevice) usb = NULL;
|
|
g_autoptr(virSCSIDevice) scsi = NULL;
|
|
g_autoptr(virSCSIVHostDevice) host = NULL;
|
|
g_autofree char *tmpPath = NULL;
|
|
int perm = 0;
|
|
|
|
switch ((virDomainHostdevMode) dev->mode) {
|
|
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
|
|
switch ((virDomainHostdevSubsysType)dev->source.subsys.type) {
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
|
|
if (pcisrc->backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) {
|
|
if (!(tmpPath = virPCIDeviceAddressGetIOMMUGroupDev(&pcisrc->addr)))
|
|
return -1;
|
|
|
|
perm = VIR_CGROUP_DEVICE_RW;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
|
|
if (dev->missing)
|
|
break;
|
|
usb = virUSBDeviceNew(usbsrc->bus,
|
|
usbsrc->device,
|
|
NULL);
|
|
if (!usb)
|
|
return -1;
|
|
|
|
tmpPath = g_strdup(virUSBDeviceGetPath(usb));
|
|
perm = VIR_CGROUP_DEVICE_RW;
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI:
|
|
if (scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI) {
|
|
virDomainHostdevSubsysSCSIiSCSIPtr iscsisrc = &scsisrc->u.iscsi;
|
|
VIR_DEBUG("Not updating /dev for hostdev iSCSI path '%s'", iscsisrc->src->path);
|
|
} else {
|
|
virDomainHostdevSubsysSCSIHostPtr scsihostsrc = &scsisrc->u.host;
|
|
scsi = virSCSIDeviceNew(NULL,
|
|
scsihostsrc->adapter,
|
|
scsihostsrc->bus,
|
|
scsihostsrc->target,
|
|
scsihostsrc->unit,
|
|
dev->readonly,
|
|
dev->shareable);
|
|
|
|
if (!scsi)
|
|
return -1;
|
|
|
|
tmpPath = g_strdup(virSCSIDeviceGetPath(scsi));
|
|
perm = virSCSIDeviceGetReadonly(scsi) ?
|
|
VIR_CGROUP_DEVICE_READ : VIR_CGROUP_DEVICE_RW;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST: {
|
|
if (hostsrc->protocol ==
|
|
VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_HOST_PROTOCOL_TYPE_VHOST) {
|
|
if (!(host = virSCSIVHostDeviceNew(hostsrc->wwpn)))
|
|
return -1;
|
|
|
|
tmpPath = g_strdup(virSCSIVHostDeviceGetPath(host));
|
|
perm = VIR_CGROUP_DEVICE_RW;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV:
|
|
if (!(tmpPath = virMediatedDeviceGetIOMMUGroupDev(mdevsrc->uuidstr)))
|
|
return -1;
|
|
|
|
perm = VIR_CGROUP_DEVICE_RW;
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
|
|
case VIR_DOMAIN_HOSTDEV_MODE_LAST:
|
|
/* nada */
|
|
break;
|
|
}
|
|
|
|
*path = g_steal_pointer(&tmpPath);
|
|
if (perms)
|
|
*perms = perm;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainGetPreservedMountPath:
|
|
* @cfg: driver configuration data
|
|
* @vm: domain object
|
|
* @mountpoint: mount point path to convert
|
|
*
|
|
* For given @mountpoint return new path where the mount point
|
|
* should be moved temporarily whilst building the namespace.
|
|
*
|
|
* Returns: allocated string on success which the caller must free,
|
|
* NULL on failure.
|
|
*/
|
|
static char *
|
|
qemuDomainGetPreservedMountPath(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
const char *mountpoint)
|
|
{
|
|
char *path = NULL;
|
|
char *tmp;
|
|
const char *suffix = mountpoint + strlen(QEMU_DEVPREFIX);
|
|
g_autofree char *domname = virDomainDefGetShortName(vm->def);
|
|
size_t off;
|
|
|
|
if (!domname)
|
|
return NULL;
|
|
|
|
if (STREQ(mountpoint, "/dev"))
|
|
suffix = "dev";
|
|
|
|
path = g_strdup_printf("%s/%s.%s", cfg->stateDir, domname, suffix);
|
|
|
|
/* Now consider that @mountpoint is "/dev/blah/blah2".
|
|
* @suffix then points to "blah/blah2". However, caller
|
|
* expects all the @paths to be the same depth. The
|
|
* caller doesn't always do `mkdir -p` but sometimes bare
|
|
* `touch`. Therefore fix all the suffixes. */
|
|
off = strlen(path) - strlen(suffix);
|
|
|
|
tmp = path + off;
|
|
while (*tmp) {
|
|
if (*tmp == '/')
|
|
*tmp = '.';
|
|
tmp++;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainGetPreservedMounts:
|
|
*
|
|
* Process list of mounted filesystems and:
|
|
* a) save all FSs mounted under /dev to @devPath
|
|
* b) generate backup path for all the entries in a)
|
|
*
|
|
* Any of the return pointers can be NULL.
|
|
*
|
|
* Returns 0 on success, -1 otherwise (with error reported)
|
|
*/
|
|
static int
|
|
qemuDomainGetPreservedMounts(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
char ***devPath,
|
|
char ***devSavePath,
|
|
size_t *ndevPath)
|
|
{
|
|
char **paths = NULL, **mounts = NULL;
|
|
size_t i, j, nmounts;
|
|
|
|
if (virFileGetMountSubtree(QEMU_PROC_MOUNTS, "/dev",
|
|
&mounts, &nmounts) < 0)
|
|
goto error;
|
|
|
|
if (!nmounts) {
|
|
if (ndevPath)
|
|
*ndevPath = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* There can be nested mount points. For instance
|
|
* /dev/shm/blah can be a mount point and /dev/shm too. It
|
|
* doesn't make much sense to return the former path because
|
|
* caller preserves the latter (and with that the former
|
|
* too). Therefore prune nested mount points.
|
|
* NB mounts[0] is "/dev". Should we start the outer loop
|
|
* from the beginning of the array all we'd be left with is
|
|
* just the first element. Think about it.
|
|
*/
|
|
for (i = 1; i < nmounts; i++) {
|
|
j = i + 1;
|
|
while (j < nmounts) {
|
|
char *c = STRSKIP(mounts[j], mounts[i]);
|
|
|
|
if (c && (*c == '/' || *c == '\0')) {
|
|
VIR_DEBUG("Dropping path %s because of %s", mounts[j], mounts[i]);
|
|
VIR_DELETE_ELEMENT(mounts, j, nmounts);
|
|
} else {
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (VIR_ALLOC_N(paths, nmounts) < 0)
|
|
goto error;
|
|
|
|
for (i = 0; i < nmounts; i++) {
|
|
if (!(paths[i] = qemuDomainGetPreservedMountPath(cfg, vm, mounts[i])))
|
|
goto error;
|
|
}
|
|
|
|
if (devPath)
|
|
*devPath = mounts;
|
|
else
|
|
virStringListFreeCount(mounts, nmounts);
|
|
|
|
if (devSavePath)
|
|
*devSavePath = paths;
|
|
else
|
|
virStringListFreeCount(paths, nmounts);
|
|
|
|
if (ndevPath)
|
|
*ndevPath = nmounts;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
virStringListFreeCount(mounts, nmounts);
|
|
virStringListFreeCount(paths, nmounts);
|
|
return -1;
|
|
}
|
|
|
|
|
|
struct qemuDomainCreateDeviceData {
|
|
const char *path; /* Path to temp new /dev location */
|
|
char * const *devMountsPath;
|
|
size_t ndevMountsPath;
|
|
};
|
|
|
|
|
|
static int
|
|
qemuDomainCreateDeviceRecursive(const char *device,
|
|
const struct qemuDomainCreateDeviceData *data,
|
|
bool allow_noent,
|
|
unsigned int ttl)
|
|
{
|
|
g_autofree char *devicePath = NULL;
|
|
g_autofree char *target = NULL;
|
|
GStatBuf sb;
|
|
int ret = -1;
|
|
bool isLink = false;
|
|
bool isDev = false;
|
|
bool isReg = false;
|
|
bool isDir = false;
|
|
bool create = false;
|
|
#ifdef WITH_SELINUX
|
|
char *tcon = NULL;
|
|
#endif
|
|
|
|
if (!ttl) {
|
|
virReportSystemError(ELOOP,
|
|
_("Too many levels of symbolic links: %s"),
|
|
device);
|
|
return ret;
|
|
}
|
|
|
|
if (g_lstat(device, &sb) < 0) {
|
|
if (errno == ENOENT && allow_noent) {
|
|
/* Ignore non-existent device. */
|
|
return 0;
|
|
}
|
|
virReportSystemError(errno, _("Unable to stat %s"), device);
|
|
return ret;
|
|
}
|
|
|
|
isLink = S_ISLNK(sb.st_mode);
|
|
isDev = S_ISCHR(sb.st_mode) || S_ISBLK(sb.st_mode);
|
|
isReg = S_ISREG(sb.st_mode) || S_ISFIFO(sb.st_mode) || S_ISSOCK(sb.st_mode);
|
|
isDir = S_ISDIR(sb.st_mode);
|
|
|
|
/* Here, @device might be whatever path in the system. We
|
|
* should create the path in the namespace iff it's "/dev"
|
|
* prefixed. However, if it is a symlink, we need to traverse
|
|
* it too (it might point to something in "/dev"). Just
|
|
* consider:
|
|
*
|
|
* /var/sym1 -> /var/sym2 -> /dev/sda (because users can)
|
|
*
|
|
* This means, "/var/sym1" is not created (it's shared with
|
|
* the parent namespace), nor "/var/sym2", but "/dev/sda".
|
|
*
|
|
* TODO Remove all `.' and `..' from the @device path.
|
|
* Otherwise we might get fooled with `/dev/../var/my_image'.
|
|
* For now, lets hope callers play nice.
|
|
*/
|
|
if (STRPREFIX(device, QEMU_DEVPREFIX)) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < data->ndevMountsPath; i++) {
|
|
if (STREQ(data->devMountsPath[i], "/dev"))
|
|
continue;
|
|
if (STRPREFIX(device, data->devMountsPath[i]))
|
|
break;
|
|
}
|
|
|
|
if (i == data->ndevMountsPath) {
|
|
/* Okay, @device is in /dev but not in any mount point under /dev.
|
|
* Create it. */
|
|
devicePath = g_strdup_printf("%s/%s", data->path,
|
|
device + strlen(QEMU_DEVPREFIX));
|
|
|
|
if (virFileMakeParentPath(devicePath) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to create %s"),
|
|
devicePath);
|
|
goto cleanup;
|
|
}
|
|
VIR_DEBUG("Creating dev %s", device);
|
|
create = true;
|
|
} else {
|
|
VIR_DEBUG("Skipping dev %s because of %s mount point",
|
|
device, data->devMountsPath[i]);
|
|
}
|
|
}
|
|
|
|
if (isLink) {
|
|
g_autoptr(GError) gerr = NULL;
|
|
|
|
/* We are dealing with a symlink. Create a dangling symlink and descend
|
|
* down one level which hopefully creates the symlink's target. */
|
|
if (!(target = g_file_read_link(device, &gerr))) {
|
|
virReportError(VIR_ERR_SYSTEM_ERROR,
|
|
_("failed to resolve symlink %s: %s"), device, gerr->message);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (create &&
|
|
symlink(target, devicePath) < 0) {
|
|
if (errno == EEXIST) {
|
|
ret = 0;
|
|
} else {
|
|
virReportSystemError(errno,
|
|
_("unable to create symlink %s"),
|
|
devicePath);
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Tricky part. If the target starts with a slash then we need to take
|
|
* it as it is. Otherwise we need to replace the last component in the
|
|
* original path with the link target:
|
|
* /dev/rtc -> rtc0 (want /dev/rtc0)
|
|
* /dev/disk/by-id/ata-SanDisk_SDSSDXPS480G_161101402485 -> ../../sda
|
|
* (want /dev/disk/by-id/../../sda)
|
|
* /dev/stdout -> /proc/self/fd/1 (no change needed)
|
|
*/
|
|
if (!g_path_is_absolute(target)) {
|
|
g_autofree char *devTmp = g_strdup(device);
|
|
char *c = NULL, *tmp = NULL;
|
|
|
|
if ((c = strrchr(devTmp, '/')))
|
|
*(c + 1) = '\0';
|
|
|
|
tmp = g_strdup_printf("%s%s", devTmp, target);
|
|
VIR_FREE(target);
|
|
target = g_steal_pointer(&tmp);
|
|
}
|
|
|
|
if (qemuDomainCreateDeviceRecursive(target, data,
|
|
allow_noent, ttl - 1) < 0)
|
|
goto cleanup;
|
|
} else if (isDev) {
|
|
if (create) {
|
|
unlink(devicePath);
|
|
if (mknod(devicePath, sb.st_mode, sb.st_rdev) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Failed to make device %s"),
|
|
devicePath);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
} else if (isReg) {
|
|
if (create &&
|
|
virFileTouch(devicePath, sb.st_mode) < 0)
|
|
goto cleanup;
|
|
/* Just create the file here so that code below sets
|
|
* proper owner and mode. Bind mount only after that. */
|
|
} else if (isDir) {
|
|
if (create &&
|
|
virFileMakePathWithMode(devicePath, sb.st_mode) < 0)
|
|
goto cleanup;
|
|
} else {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("unsupported device type %s 0%o"),
|
|
device, sb.st_mode);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!create) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (lchown(devicePath, sb.st_uid, sb.st_gid) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Failed to chown device %s"),
|
|
devicePath);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Symlinks don't have mode */
|
|
if (!isLink &&
|
|
chmod(devicePath, sb.st_mode) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Failed to set permissions for device %s"),
|
|
devicePath);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Symlinks don't have ACLs. */
|
|
if (!isLink &&
|
|
virFileCopyACLs(device, devicePath) < 0 &&
|
|
errno != ENOTSUP) {
|
|
virReportSystemError(errno,
|
|
_("Failed to copy ACLs on device %s"),
|
|
devicePath);
|
|
goto cleanup;
|
|
}
|
|
|
|
#ifdef WITH_SELINUX
|
|
if (lgetfilecon_raw(device, &tcon) < 0 &&
|
|
(errno != ENOTSUP && errno != ENODATA)) {
|
|
virReportSystemError(errno,
|
|
_("Unable to get SELinux label from %s"),
|
|
device);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (tcon &&
|
|
lsetfilecon_raw(devicePath, (const char *)tcon) < 0) {
|
|
VIR_WARNINGS_NO_WLOGICALOP_EQUAL_EXPR
|
|
if (errno != EOPNOTSUPP && errno != ENOTSUP) {
|
|
VIR_WARNINGS_RESET
|
|
virReportSystemError(errno,
|
|
_("Unable to set SELinux label on %s"),
|
|
devicePath);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Finish mount process started earlier. */
|
|
if ((isReg || isDir) &&
|
|
virFileBindMountDevice(device, devicePath) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
#ifdef WITH_SELINUX
|
|
freecon(tcon);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainCreateDevice(const char *device,
|
|
const struct qemuDomainCreateDeviceData *data,
|
|
bool allow_noent)
|
|
{
|
|
long symloop_max = sysconf(_SC_SYMLOOP_MAX);
|
|
|
|
return qemuDomainCreateDeviceRecursive(device, data,
|
|
allow_noent, symloop_max);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainPopulateDevices(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm G_GNUC_UNUSED,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
const char *const *devices = (const char *const *) cfg->cgroupDeviceACL;
|
|
size_t i;
|
|
|
|
if (!devices)
|
|
devices = defaultDeviceACL;
|
|
|
|
for (i = 0; devices[i]; i++) {
|
|
if (qemuDomainCreateDevice(devices[i], data, true) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupDev(virQEMUDriverConfigPtr cfg,
|
|
virSecurityManagerPtr mgr,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
g_autofree char *mount_options = NULL;
|
|
g_autofree char *opts = NULL;
|
|
|
|
VIR_DEBUG("Setting up /dev/ for domain %s", vm->def->name);
|
|
|
|
mount_options = qemuSecurityGetMountOptions(mgr, vm->def);
|
|
|
|
if (!mount_options)
|
|
mount_options = g_strdup("");
|
|
|
|
/*
|
|
* tmpfs is limited to 64kb, since we only have device nodes in there
|
|
* and don't want to DOS the entire OS RAM usage
|
|
*/
|
|
opts = g_strdup_printf("mode=755,size=65536%s", mount_options);
|
|
|
|
if (virFileSetupDev(data->path, opts) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainPopulateDevices(cfg, vm, data) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupDisk(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainDiskDefPtr disk,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
virStorageSourcePtr next;
|
|
bool hasNVMe = false;
|
|
|
|
for (next = disk->src; virStorageSourceIsBacking(next); next = next->backingStore) {
|
|
if (next->type == VIR_STORAGE_TYPE_NVME) {
|
|
g_autofree char *nvmePath = NULL;
|
|
|
|
hasNVMe = true;
|
|
|
|
if (!(nvmePath = virPCIDeviceAddressGetIOMMUGroupDev(&next->nvme->pciAddr)))
|
|
return -1;
|
|
|
|
if (qemuDomainCreateDevice(nvmePath, data, false) < 0)
|
|
return -1;
|
|
} else {
|
|
if (!next->path || !virStorageSourceIsLocalStorage(next)) {
|
|
/* Not creating device. Just continue. */
|
|
continue;
|
|
}
|
|
|
|
if (qemuDomainCreateDevice(next->path, data, false) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* qemu-pr-helper might require access to /dev/mapper/control. */
|
|
if (disk->src->pr &&
|
|
qemuDomainCreateDevice(QEMU_DEVICE_MAPPER_CONTROL_PATH, data, true) < 0)
|
|
return -1;
|
|
|
|
if (hasNVMe &&
|
|
qemuDomainCreateDevice(QEMU_DEV_VFIO, data, false) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupAllDisks(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
size_t i;
|
|
VIR_DEBUG("Setting up disks");
|
|
|
|
for (i = 0; i < vm->def->ndisks; i++) {
|
|
if (qemuDomainSetupDisk(cfg,
|
|
vm->def->disks[i],
|
|
data) < 0)
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("Setup all disks");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupHostdev(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainHostdevDefPtr dev,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
|
|
if (qemuDomainGetHostdevPath(dev, &path, NULL) < 0)
|
|
return -1;
|
|
|
|
if (path && qemuDomainCreateDevice(path, data, false) < 0)
|
|
return -1;
|
|
|
|
if (qemuHostdevNeedsVFIO(dev) &&
|
|
qemuDomainCreateDevice(QEMU_DEV_VFIO, data, false) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupAllHostdevs(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
size_t i;
|
|
|
|
VIR_DEBUG("Setting up hostdevs");
|
|
for (i = 0; i < vm->def->nhostdevs; i++) {
|
|
if (qemuDomainSetupHostdev(cfg,
|
|
vm->def->hostdevs[i],
|
|
data) < 0)
|
|
return -1;
|
|
}
|
|
VIR_DEBUG("Setup all hostdevs");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupMemory(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainMemoryDefPtr mem,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
if (mem->model != VIR_DOMAIN_MEMORY_MODEL_NVDIMM)
|
|
return 0;
|
|
|
|
return qemuDomainCreateDevice(mem->nvdimmPath, data, false);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupAllMemories(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
size_t i;
|
|
|
|
VIR_DEBUG("Setting up memories");
|
|
for (i = 0; i < vm->def->nmems; i++) {
|
|
if (qemuDomainSetupMemory(cfg,
|
|
vm->def->mems[i],
|
|
data) < 0)
|
|
return -1;
|
|
}
|
|
VIR_DEBUG("Setup all memories");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupChardev(virDomainDefPtr def G_GNUC_UNUSED,
|
|
virDomainChrDefPtr dev,
|
|
void *opaque)
|
|
{
|
|
const struct qemuDomainCreateDeviceData *data = opaque;
|
|
const char *path = NULL;
|
|
|
|
if (!(path = virDomainChrSourceDefGetPath(dev->source)))
|
|
return 0;
|
|
|
|
/* Socket created by qemu. It doesn't exist upfront. */
|
|
if (dev->source->type == VIR_DOMAIN_CHR_TYPE_UNIX &&
|
|
dev->source->data.nix.listen)
|
|
return 0;
|
|
|
|
return qemuDomainCreateDevice(path, data, true);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupAllChardevs(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
VIR_DEBUG("Setting up chardevs");
|
|
|
|
if (virDomainChrDefForeach(vm->def,
|
|
true,
|
|
qemuDomainSetupChardev,
|
|
(void *)data) < 0)
|
|
return -1;
|
|
|
|
VIR_DEBUG("Setup all chardevs");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupTPM(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
virDomainTPMDefPtr dev = vm->def->tpm;
|
|
|
|
if (!dev)
|
|
return 0;
|
|
|
|
VIR_DEBUG("Setting up TPM");
|
|
|
|
switch (dev->type) {
|
|
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
|
|
if (qemuDomainCreateDevice(dev->data.passthrough.source.data.file.path,
|
|
data, false) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
|
|
case VIR_DOMAIN_TPM_TYPE_LAST:
|
|
/* nada */
|
|
break;
|
|
}
|
|
|
|
VIR_DEBUG("Setup TPM");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupGraphics(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainGraphicsDefPtr gfx,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
const char *rendernode = virDomainGraphicsGetRenderNode(gfx);
|
|
|
|
if (!rendernode)
|
|
return 0;
|
|
|
|
return qemuDomainCreateDevice(rendernode, data, false);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupAllGraphics(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
size_t i;
|
|
|
|
VIR_DEBUG("Setting up graphics");
|
|
for (i = 0; i < vm->def->ngraphics; i++) {
|
|
if (qemuDomainSetupGraphics(cfg,
|
|
vm->def->graphics[i],
|
|
data) < 0)
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("Setup all graphics");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupInput(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainInputDefPtr input,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
const char *path = virDomainInputDefGetPath(input);
|
|
|
|
if (path && qemuDomainCreateDevice(path, data, false) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupAllInputs(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
size_t i;
|
|
|
|
VIR_DEBUG("Setting up inputs");
|
|
for (i = 0; i < vm->def->ninputs; i++) {
|
|
if (qemuDomainSetupInput(cfg,
|
|
vm->def->inputs[i],
|
|
data) < 0)
|
|
return -1;
|
|
}
|
|
VIR_DEBUG("Setup all inputs");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupRNG(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainRNGDefPtr rng,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
switch ((virDomainRNGBackend) rng->backend) {
|
|
case VIR_DOMAIN_RNG_BACKEND_RANDOM:
|
|
if (qemuDomainCreateDevice(rng->source.file, data, false) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_EGD:
|
|
case VIR_DOMAIN_RNG_BACKEND_BUILTIN:
|
|
case VIR_DOMAIN_RNG_BACKEND_LAST:
|
|
/* nada */
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupAllRNGs(virQEMUDriverConfigPtr cfg,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
size_t i;
|
|
|
|
VIR_DEBUG("Setting up RNGs");
|
|
for (i = 0; i < vm->def->nrngs; i++) {
|
|
if (qemuDomainSetupRNG(cfg,
|
|
vm->def->rngs[i],
|
|
data) < 0)
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("Setup all RNGs");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupLoader(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
virDomainLoaderDefPtr loader = vm->def->os.loader;
|
|
|
|
VIR_DEBUG("Setting up loader");
|
|
|
|
if (loader) {
|
|
switch ((virDomainLoader) loader->type) {
|
|
case VIR_DOMAIN_LOADER_TYPE_ROM:
|
|
if (qemuDomainCreateDevice(loader->path, data, false) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_LOADER_TYPE_PFLASH:
|
|
if (qemuDomainCreateDevice(loader->path, data, false) < 0)
|
|
return -1;
|
|
|
|
if (loader->nvram &&
|
|
qemuDomainCreateDevice(loader->nvram, data, false) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_LOADER_TYPE_NONE:
|
|
case VIR_DOMAIN_LOADER_TYPE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
VIR_DEBUG("Setup loader");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSetupLaunchSecurity(virQEMUDriverConfigPtr cfg G_GNUC_UNUSED,
|
|
virDomainObjPtr vm,
|
|
const struct qemuDomainCreateDeviceData *data)
|
|
{
|
|
virDomainSEVDefPtr sev = vm->def->sev;
|
|
|
|
if (!sev || sev->sectype != VIR_DOMAIN_LAUNCH_SECURITY_SEV)
|
|
return 0;
|
|
|
|
VIR_DEBUG("Setting up launch security");
|
|
|
|
if (qemuDomainCreateDevice(QEMU_DEV_SEV, data, false) < 0)
|
|
return -1;
|
|
|
|
VIR_DEBUG("Set up launch security");
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainBuildNamespace(virQEMUDriverConfigPtr cfg,
|
|
virSecurityManagerPtr mgr,
|
|
virDomainObjPtr vm)
|
|
{
|
|
struct qemuDomainCreateDeviceData data;
|
|
char *devPath = NULL;
|
|
char **devMountsPath = NULL, **devMountsSavePath = NULL;
|
|
size_t ndevMountsPath = 0, i;
|
|
int ret = -1;
|
|
|
|
if (!qemuDomainNamespaceEnabled(vm, QEMU_DOMAIN_NS_MOUNT)) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (qemuDomainGetPreservedMounts(cfg, vm,
|
|
&devMountsPath, &devMountsSavePath,
|
|
&ndevMountsPath) < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < ndevMountsPath; i++) {
|
|
if (STREQ(devMountsPath[i], "/dev")) {
|
|
devPath = devMountsSavePath[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!devPath) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Unable to find any /dev mount"));
|
|
goto cleanup;
|
|
}
|
|
|
|
data.path = devPath;
|
|
data.devMountsPath = devMountsPath;
|
|
data.ndevMountsPath = ndevMountsPath;
|
|
|
|
if (virProcessSetupPrivateMountNS() < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupDev(cfg, mgr, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupAllDisks(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupAllHostdevs(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupAllMemories(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupAllChardevs(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupTPM(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupAllGraphics(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupAllInputs(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupAllRNGs(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupLoader(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuDomainSetupLaunchSecurity(cfg, vm, &data) < 0)
|
|
goto cleanup;
|
|
|
|
/* Save some mount points because we want to share them with the host */
|
|
for (i = 0; i < ndevMountsPath; i++) {
|
|
struct stat sb;
|
|
|
|
if (devMountsSavePath[i] == devPath)
|
|
continue;
|
|
|
|
if (stat(devMountsPath[i], &sb) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to stat: %s"),
|
|
devMountsPath[i]);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* At this point, devMountsPath is either:
|
|
* a file (regular or special), or
|
|
* a directory. */
|
|
if ((S_ISDIR(sb.st_mode) && virFileMakePath(devMountsSavePath[i]) < 0) ||
|
|
(!S_ISDIR(sb.st_mode) && virFileTouch(devMountsSavePath[i], sb.st_mode) < 0)) {
|
|
virReportSystemError(errno,
|
|
_("Failed to create %s"),
|
|
devMountsSavePath[i]);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virFileMoveMount(devMountsPath[i], devMountsSavePath[i]) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virFileMoveMount(devPath, "/dev") < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < ndevMountsPath; i++) {
|
|
struct stat sb;
|
|
|
|
if (devMountsSavePath[i] == devPath)
|
|
continue;
|
|
|
|
if (stat(devMountsSavePath[i], &sb) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to stat: %s"),
|
|
devMountsSavePath[i]);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (S_ISDIR(sb.st_mode)) {
|
|
if (virFileMakePath(devMountsPath[i]) < 0) {
|
|
virReportSystemError(errno, _("Cannot create %s"),
|
|
devMountsPath[i]);
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
if (virFileMakeParentPath(devMountsPath[i]) < 0 ||
|
|
virFileTouch(devMountsPath[i], sb.st_mode) < 0) {
|
|
virReportSystemError(errno, _("Cannot create %s"),
|
|
devMountsPath[i]);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (virFileMoveMount(devMountsSavePath[i], devMountsPath[i]) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
for (i = 0; i < ndevMountsPath; i++) {
|
|
/* The path can be either a regular file or a dir. */
|
|
if (virFileIsDir(devMountsSavePath[i]))
|
|
rmdir(devMountsSavePath[i]);
|
|
else
|
|
unlink(devMountsSavePath[i]);
|
|
}
|
|
virStringListFreeCount(devMountsPath, ndevMountsPath);
|
|
virStringListFreeCount(devMountsSavePath, ndevMountsPath);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainCreateNamespace(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
if (virBitmapIsBitSet(cfg->namespaces, QEMU_DOMAIN_NS_MOUNT) &&
|
|
qemuDomainEnableNamespace(vm, QEMU_DOMAIN_NS_MOUNT) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainDestroyNamespace(virQEMUDriverPtr driver G_GNUC_UNUSED,
|
|
virDomainObjPtr vm)
|
|
{
|
|
if (qemuDomainNamespaceEnabled(vm, QEMU_DOMAIN_NS_MOUNT))
|
|
qemuDomainDisableNamespace(vm, QEMU_DOMAIN_NS_MOUNT);
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainNamespaceAvailable(qemuDomainNamespace ns G_GNUC_UNUSED)
|
|
{
|
|
#if !defined(__linux__)
|
|
/* Namespaces are Linux specific. */
|
|
return false;
|
|
|
|
#else /* defined(__linux__) */
|
|
|
|
switch (ns) {
|
|
case QEMU_DOMAIN_NS_MOUNT:
|
|
# if !defined(HAVE_SYS_ACL_H) || !defined(WITH_SELINUX)
|
|
/* We can't create the exact copy of paths if either of
|
|
* these is not available. */
|
|
return false;
|
|
# else
|
|
if (virProcessNamespaceAvailable(VIR_PROCESS_NAMESPACE_MNT) < 0)
|
|
return false;
|
|
# endif
|
|
break;
|
|
case QEMU_DOMAIN_NS_LAST:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
#endif /* defined(__linux__) */
|
|
}
|
|
|
|
|
|
struct qemuDomainAttachDeviceMknodData {
|
|
virQEMUDriverPtr driver;
|
|
virDomainObjPtr vm;
|
|
const char *file;
|
|
const char *target;
|
|
GStatBuf sb;
|
|
void *acl;
|
|
#ifdef WITH_SELINUX
|
|
char *tcon;
|
|
#endif
|
|
};
|
|
|
|
|
|
/* Our way of creating devices is highly linux specific */
|
|
#if defined(__linux__)
|
|
static int
|
|
qemuDomainAttachDeviceMknodHelper(pid_t pid G_GNUC_UNUSED,
|
|
void *opaque)
|
|
{
|
|
struct qemuDomainAttachDeviceMknodData *data = opaque;
|
|
int ret = -1;
|
|
bool delDevice = false;
|
|
bool isLink = S_ISLNK(data->sb.st_mode);
|
|
bool isDev = S_ISCHR(data->sb.st_mode) || S_ISBLK(data->sb.st_mode);
|
|
bool isReg = S_ISREG(data->sb.st_mode) || S_ISFIFO(data->sb.st_mode) || S_ISSOCK(data->sb.st_mode);
|
|
bool isDir = S_ISDIR(data->sb.st_mode);
|
|
|
|
qemuSecurityPostFork(data->driver->securityManager);
|
|
|
|
if (virFileMakeParentPath(data->file) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to create %s"), data->file);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (isLink) {
|
|
VIR_DEBUG("Creating symlink %s -> %s", data->file, data->target);
|
|
|
|
/* First, unlink the symlink target. Symlinks change and
|
|
* therefore we have no guarantees that pre-existing
|
|
* symlink is still valid. */
|
|
if (unlink(data->file) < 0 &&
|
|
errno != ENOENT) {
|
|
virReportSystemError(errno,
|
|
_("Unable to remove symlink %s"),
|
|
data->file);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (symlink(data->target, data->file) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to create symlink %s (pointing to %s)"),
|
|
data->file, data->target);
|
|
goto cleanup;
|
|
} else {
|
|
delDevice = true;
|
|
}
|
|
} else if (isDev) {
|
|
VIR_DEBUG("Creating dev %s (%d,%d)",
|
|
data->file, major(data->sb.st_rdev), minor(data->sb.st_rdev));
|
|
unlink(data->file);
|
|
if (mknod(data->file, data->sb.st_mode, data->sb.st_rdev) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to create device %s"),
|
|
data->file);
|
|
goto cleanup;
|
|
} else {
|
|
delDevice = true;
|
|
}
|
|
} else if (isReg || isDir) {
|
|
/* We are not cleaning up disks on virDomainDetachDevice
|
|
* because disk might be still in use by different disk
|
|
* as its backing chain. This might however clash here.
|
|
* Therefore do the cleanup here. */
|
|
if (umount(data->file) < 0 &&
|
|
errno != ENOENT && errno != EINVAL) {
|
|
virReportSystemError(errno,
|
|
_("Unable to umount %s"),
|
|
data->file);
|
|
goto cleanup;
|
|
}
|
|
if ((isReg && virFileTouch(data->file, data->sb.st_mode) < 0) ||
|
|
(isDir && virFileMakePathWithMode(data->file, data->sb.st_mode) < 0))
|
|
goto cleanup;
|
|
delDevice = true;
|
|
/* Just create the file here so that code below sets
|
|
* proper owner and mode. Move the mount only after that. */
|
|
} else {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("unsupported device type %s 0%o"),
|
|
data->file, data->sb.st_mode);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (lchown(data->file, data->sb.st_uid, data->sb.st_gid) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Failed to chown device %s"),
|
|
data->file);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Symlinks don't have mode */
|
|
if (!isLink &&
|
|
chmod(data->file, data->sb.st_mode) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Failed to set permissions for device %s"),
|
|
data->file);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Symlinks don't have ACLs. */
|
|
if (!isLink &&
|
|
virFileSetACLs(data->file, data->acl) < 0 &&
|
|
errno != ENOTSUP) {
|
|
virReportSystemError(errno,
|
|
_("Unable to set ACLs on %s"), data->file);
|
|
goto cleanup;
|
|
}
|
|
|
|
# ifdef WITH_SELINUX
|
|
if (data->tcon &&
|
|
lsetfilecon_raw(data->file, (const char *)data->tcon) < 0) {
|
|
VIR_WARNINGS_NO_WLOGICALOP_EQUAL_EXPR
|
|
if (errno != EOPNOTSUPP && errno != ENOTSUP) {
|
|
VIR_WARNINGS_RESET
|
|
virReportSystemError(errno,
|
|
_("Unable to set SELinux label on %s"),
|
|
data->file);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
# endif
|
|
|
|
/* Finish mount process started earlier. */
|
|
if ((isReg || isDir) &&
|
|
virFileMoveMount(data->target, data->file) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0 && delDevice) {
|
|
if (isDir)
|
|
virFileDeleteTree(data->file);
|
|
else
|
|
unlink(data->file);
|
|
}
|
|
# ifdef WITH_SELINUX
|
|
freecon(data->tcon);
|
|
# endif
|
|
virFileFreeACLs(&data->acl);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainAttachDeviceMknodRecursive(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
const char *file,
|
|
char * const *devMountsPath,
|
|
size_t ndevMountsPath,
|
|
unsigned int ttl)
|
|
{
|
|
g_autoptr(virQEMUDriverConfig) cfg = NULL;
|
|
struct qemuDomainAttachDeviceMknodData data;
|
|
int ret = -1;
|
|
g_autofree char *target = NULL;
|
|
bool isLink;
|
|
bool isReg;
|
|
bool isDir;
|
|
|
|
if (!ttl) {
|
|
virReportSystemError(ELOOP,
|
|
_("Too many levels of symbolic links: %s"),
|
|
file);
|
|
return ret;
|
|
}
|
|
|
|
memset(&data, 0, sizeof(data));
|
|
|
|
data.driver = driver;
|
|
data.vm = vm;
|
|
data.file = file;
|
|
|
|
if (g_lstat(file, &data.sb) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to access %s"), file);
|
|
return ret;
|
|
}
|
|
|
|
isLink = S_ISLNK(data.sb.st_mode);
|
|
isReg = S_ISREG(data.sb.st_mode) || S_ISFIFO(data.sb.st_mode) || S_ISSOCK(data.sb.st_mode);
|
|
isDir = S_ISDIR(data.sb.st_mode);
|
|
|
|
if ((isReg || isDir) && STRPREFIX(file, QEMU_DEVPREFIX)) {
|
|
cfg = virQEMUDriverGetConfig(driver);
|
|
if (!(target = qemuDomainGetPreservedMountPath(cfg, vm, file)))
|
|
goto cleanup;
|
|
|
|
if (virFileBindMountDevice(file, target) < 0)
|
|
goto cleanup;
|
|
|
|
data.target = target;
|
|
} else if (isLink) {
|
|
g_autoptr(GError) gerr = NULL;
|
|
|
|
if (!(target = g_file_read_link(file, &gerr))) {
|
|
virReportError(VIR_ERR_SYSTEM_ERROR,
|
|
_("failed to resolve symlink %s: %s"), file, gerr->message);
|
|
return ret;
|
|
}
|
|
|
|
if (!g_path_is_absolute(target)) {
|
|
g_autofree char *fileTmp = g_strdup(file);
|
|
char *c = NULL, *tmp = NULL;
|
|
|
|
if ((c = strrchr(fileTmp, '/')))
|
|
*(c + 1) = '\0';
|
|
|
|
tmp = g_strdup_printf("%s%s", fileTmp, target);
|
|
VIR_FREE(target);
|
|
target = g_steal_pointer(&tmp);
|
|
}
|
|
|
|
data.target = target;
|
|
}
|
|
|
|
/* Symlinks don't have ACLs. */
|
|
if (!isLink &&
|
|
virFileGetACLs(file, &data.acl) < 0 &&
|
|
errno != ENOTSUP) {
|
|
virReportSystemError(errno,
|
|
_("Unable to get ACLs on %s"), file);
|
|
goto cleanup;
|
|
}
|
|
|
|
# ifdef WITH_SELINUX
|
|
if (lgetfilecon_raw(file, &data.tcon) < 0 &&
|
|
(errno != ENOTSUP && errno != ENODATA)) {
|
|
virReportSystemError(errno,
|
|
_("Unable to get SELinux label from %s"), file);
|
|
goto cleanup;
|
|
}
|
|
# endif
|
|
|
|
if (STRPREFIX(file, QEMU_DEVPREFIX)) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < ndevMountsPath; i++) {
|
|
if (STREQ(devMountsPath[i], "/dev"))
|
|
continue;
|
|
if (STRPREFIX(file, devMountsPath[i]))
|
|
break;
|
|
}
|
|
|
|
if (i == ndevMountsPath) {
|
|
if (qemuSecurityPreFork(driver->securityManager) < 0)
|
|
goto cleanup;
|
|
|
|
if (virProcessRunInMountNamespace(vm->pid,
|
|
qemuDomainAttachDeviceMknodHelper,
|
|
&data) < 0) {
|
|
qemuSecurityPostFork(driver->securityManager);
|
|
goto cleanup;
|
|
}
|
|
qemuSecurityPostFork(driver->securityManager);
|
|
} else {
|
|
VIR_DEBUG("Skipping dev %s because of %s mount point",
|
|
file, devMountsPath[i]);
|
|
}
|
|
}
|
|
|
|
if (isLink &&
|
|
qemuDomainAttachDeviceMknodRecursive(driver, vm, target,
|
|
devMountsPath, ndevMountsPath,
|
|
ttl -1) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
# ifdef WITH_SELINUX
|
|
freecon(data.tcon);
|
|
# endif
|
|
virFileFreeACLs(&data.acl);
|
|
if (isReg && target)
|
|
umount(target);
|
|
return ret;
|
|
}
|
|
|
|
|
|
#else /* !defined(__linux__) */
|
|
|
|
|
|
static int
|
|
qemuDomainAttachDeviceMknodRecursive(virQEMUDriverPtr driver G_GNUC_UNUSED,
|
|
virDomainObjPtr vm G_GNUC_UNUSED,
|
|
const char *file G_GNUC_UNUSED,
|
|
char * const *devMountsPath G_GNUC_UNUSED,
|
|
size_t ndevMountsPath G_GNUC_UNUSED,
|
|
unsigned int ttl G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Namespaces are not supported on this platform."));
|
|
return -1;
|
|
}
|
|
|
|
|
|
#endif /* !defined(__linux__) */
|
|
|
|
|
|
static int
|
|
qemuDomainAttachDeviceMknod(virQEMUDriverPtr driver,
|
|
virDomainObjPtr vm,
|
|
const char *file,
|
|
char * const *devMountsPath,
|
|
size_t ndevMountsPath)
|
|
{
|
|
long symloop_max = sysconf(_SC_SYMLOOP_MAX);
|
|
|
|
return qemuDomainAttachDeviceMknodRecursive(driver, vm, file,
|
|
devMountsPath, ndevMountsPath,
|
|
symloop_max);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDetachDeviceUnlinkHelper(pid_t pid G_GNUC_UNUSED,
|
|
void *opaque)
|
|
{
|
|
const char *path = opaque;
|
|
|
|
VIR_DEBUG("Unlinking %s", path);
|
|
if (unlink(path) < 0 && errno != ENOENT) {
|
|
virReportSystemError(errno,
|
|
_("Unable to remove device %s"), path);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDetachDeviceUnlink(virQEMUDriverPtr driver G_GNUC_UNUSED,
|
|
virDomainObjPtr vm,
|
|
const char *file,
|
|
char * const *devMountsPath,
|
|
size_t ndevMountsPath)
|
|
{
|
|
size_t i;
|
|
|
|
if (STRPREFIX(file, QEMU_DEVPREFIX)) {
|
|
for (i = 0; i < ndevMountsPath; i++) {
|
|
if (STREQ(devMountsPath[i], "/dev"))
|
|
continue;
|
|
if (STRPREFIX(file, devMountsPath[i]))
|
|
break;
|
|
}
|
|
|
|
if (i == ndevMountsPath) {
|
|
if (virProcessRunInMountNamespace(vm->pid,
|
|
qemuDomainDetachDeviceUnlinkHelper,
|
|
(void *)file) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainNamespaceMknodPaths(virDomainObjPtr vm,
|
|
const char **paths,
|
|
size_t npaths)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virQEMUDriverPtr driver = priv->driver;
|
|
g_autoptr(virQEMUDriverConfig) cfg = NULL;
|
|
char **devMountsPath = NULL;
|
|
size_t ndevMountsPath = 0;
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
if (!qemuDomainNamespaceEnabled(vm, QEMU_DOMAIN_NS_MOUNT) ||
|
|
!npaths)
|
|
return 0;
|
|
|
|
cfg = virQEMUDriverGetConfig(driver);
|
|
if (qemuDomainGetPreservedMounts(cfg, vm,
|
|
&devMountsPath, NULL,
|
|
&ndevMountsPath) < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < npaths; i++) {
|
|
if (qemuDomainAttachDeviceMknod(driver,
|
|
vm,
|
|
paths[i],
|
|
devMountsPath, ndevMountsPath) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virStringListFreeCount(devMountsPath, ndevMountsPath);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainNamespaceMknodPath(virDomainObjPtr vm,
|
|
const char *path)
|
|
{
|
|
const char *paths[] = { path };
|
|
|
|
return qemuDomainNamespaceMknodPaths(vm, paths, 1);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainNamespaceUnlinkPaths(virDomainObjPtr vm,
|
|
const char **paths,
|
|
size_t npaths)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virQEMUDriverPtr driver = priv->driver;
|
|
g_autoptr(virQEMUDriverConfig) cfg = NULL;
|
|
char **devMountsPath = NULL;
|
|
size_t ndevMountsPath = 0;
|
|
size_t i;
|
|
int ret = -1;
|
|
|
|
if (!qemuDomainNamespaceEnabled(vm, QEMU_DOMAIN_NS_MOUNT) ||
|
|
!npaths)
|
|
return 0;
|
|
|
|
cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
if (qemuDomainGetPreservedMounts(cfg, vm,
|
|
&devMountsPath, NULL,
|
|
&ndevMountsPath) < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < npaths; i++) {
|
|
if (qemuDomainDetachDeviceUnlink(driver, vm, paths[i],
|
|
devMountsPath, ndevMountsPath) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virStringListFreeCount(devMountsPath, ndevMountsPath);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainNamespaceUnlinkPath(virDomainObjPtr vm,
|
|
const char *path)
|
|
{
|
|
const char *paths[] = { path };
|
|
|
|
return qemuDomainNamespaceUnlinkPaths(vm, paths, 1);
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceSetupDisk(virDomainObjPtr vm,
|
|
virStorageSourcePtr src)
|
|
{
|
|
virStorageSourcePtr next;
|
|
char **paths = NULL;
|
|
size_t npaths = 0;
|
|
bool hasNVMe = false;
|
|
g_autofree char *dmPath = NULL;
|
|
g_autofree char *vfioPath = NULL;
|
|
int ret = -1;
|
|
|
|
for (next = src; virStorageSourceIsBacking(next); next = next->backingStore) {
|
|
g_autofree char *tmpPath = NULL;
|
|
|
|
if (next->type == VIR_STORAGE_TYPE_NVME) {
|
|
hasNVMe = true;
|
|
|
|
if (!(tmpPath = virPCIDeviceAddressGetIOMMUGroupDev(&next->nvme->pciAddr)))
|
|
goto cleanup;
|
|
} else {
|
|
if (virStorageSourceIsEmpty(next) ||
|
|
!virStorageSourceIsLocalStorage(next)) {
|
|
/* Not creating device. Just continue. */
|
|
continue;
|
|
}
|
|
|
|
tmpPath = g_strdup(next->path);
|
|
}
|
|
|
|
if (VIR_APPEND_ELEMENT(paths, npaths, tmpPath) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* qemu-pr-helper might require access to /dev/mapper/control. */
|
|
if (src->pr) {
|
|
dmPath = g_strdup(QEMU_DEVICE_MAPPER_CONTROL_PATH);
|
|
if (VIR_APPEND_ELEMENT_COPY(paths, npaths, dmPath) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (hasNVMe) {
|
|
vfioPath = g_strdup(QEMU_DEV_VFIO);
|
|
if (VIR_APPEND_ELEMENT(paths, npaths, vfioPath) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (qemuDomainNamespaceMknodPaths(vm, (const char **) paths, npaths) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virStringListFreeCount(paths, npaths);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceTeardownDisk(virDomainObjPtr vm G_GNUC_UNUSED,
|
|
virStorageSourcePtr src G_GNUC_UNUSED)
|
|
{
|
|
/* While in hotplug case we create the whole backing chain,
|
|
* here we must limit ourselves. The disk we want to remove
|
|
* might be a part of backing chain of another disk.
|
|
* If you are reading these lines and have some spare time
|
|
* you can come up with and algorithm that checks for that.
|
|
* I don't, therefore: */
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainNamespaceSetupHostdev:
|
|
* @vm: domain object
|
|
* @hostdev: hostdev to create in @vm's namespace
|
|
*
|
|
* For given @hostdev, create its devfs representation (if it has one) in
|
|
* domain namespace. Note, @hostdev must not be in @vm's definition.
|
|
*
|
|
* Returns: 0 on success,
|
|
* -1 otherwise.
|
|
*/
|
|
int
|
|
qemuDomainNamespaceSetupHostdev(virDomainObjPtr vm,
|
|
virDomainHostdevDefPtr hostdev)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
|
|
if (qemuDomainGetHostdevPath(hostdev, &path, NULL) < 0)
|
|
return -1;
|
|
|
|
if (path && qemuDomainNamespaceMknodPath(vm, path) < 0)
|
|
return -1;
|
|
|
|
if (qemuHostdevNeedsVFIO(hostdev) &&
|
|
!qemuDomainNeedsVFIO(vm->def) &&
|
|
qemuDomainNamespaceMknodPath(vm, QEMU_DEV_VFIO) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainNamespaceTeardownHostdev:
|
|
* @vm: domain object
|
|
* @hostdev: hostdev to remove in @vm's namespace
|
|
*
|
|
* For given @hostdev, remove its devfs representation (if it has one) in
|
|
* domain namespace. Note, @hostdev must not be in @vm's definition.
|
|
*
|
|
* Returns: 0 on success,
|
|
* -1 otherwise.
|
|
*/
|
|
int
|
|
qemuDomainNamespaceTeardownHostdev(virDomainObjPtr vm,
|
|
virDomainHostdevDefPtr hostdev)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
|
|
if (qemuDomainGetHostdevPath(hostdev, &path, NULL) < 0)
|
|
return -1;
|
|
|
|
if (path && qemuDomainNamespaceUnlinkPath(vm, path) < 0)
|
|
return -1;
|
|
|
|
if (qemuHostdevNeedsVFIO(hostdev) &&
|
|
!qemuDomainNeedsVFIO(vm->def) &&
|
|
qemuDomainNamespaceUnlinkPath(vm, QEMU_DEV_VFIO) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceSetupMemory(virDomainObjPtr vm,
|
|
virDomainMemoryDefPtr mem)
|
|
{
|
|
if (mem->model != VIR_DOMAIN_MEMORY_MODEL_NVDIMM)
|
|
return 0;
|
|
|
|
if (qemuDomainNamespaceMknodPath(vm, mem->nvdimmPath) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceTeardownMemory(virDomainObjPtr vm,
|
|
virDomainMemoryDefPtr mem)
|
|
{
|
|
if (mem->model != VIR_DOMAIN_MEMORY_MODEL_NVDIMM)
|
|
return 0;
|
|
|
|
if (qemuDomainNamespaceUnlinkPath(vm, mem->nvdimmPath) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceSetupChardev(virDomainObjPtr vm,
|
|
virDomainChrDefPtr chr)
|
|
{
|
|
const char *path;
|
|
|
|
if (!(path = virDomainChrSourceDefGetPath(chr->source)))
|
|
return 0;
|
|
|
|
/* Socket created by qemu. It doesn't exist upfront. */
|
|
if (chr->source->type == VIR_DOMAIN_CHR_TYPE_UNIX &&
|
|
chr->source->data.nix.listen)
|
|
return 0;
|
|
|
|
if (qemuDomainNamespaceMknodPath(vm, path) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceTeardownChardev(virDomainObjPtr vm,
|
|
virDomainChrDefPtr chr)
|
|
{
|
|
const char *path = NULL;
|
|
|
|
if (chr->source->type != VIR_DOMAIN_CHR_TYPE_DEV)
|
|
return 0;
|
|
|
|
path = chr->source->data.file.path;
|
|
|
|
if (qemuDomainNamespaceUnlinkPath(vm, path) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceSetupRNG(virDomainObjPtr vm,
|
|
virDomainRNGDefPtr rng)
|
|
{
|
|
const char *path = NULL;
|
|
|
|
switch ((virDomainRNGBackend) rng->backend) {
|
|
case VIR_DOMAIN_RNG_BACKEND_RANDOM:
|
|
path = rng->source.file;
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_EGD:
|
|
case VIR_DOMAIN_RNG_BACKEND_BUILTIN:
|
|
case VIR_DOMAIN_RNG_BACKEND_LAST:
|
|
break;
|
|
}
|
|
|
|
if (path && qemuDomainNamespaceMknodPath(vm, path) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceTeardownRNG(virDomainObjPtr vm,
|
|
virDomainRNGDefPtr rng)
|
|
{
|
|
const char *path = NULL;
|
|
|
|
switch ((virDomainRNGBackend) rng->backend) {
|
|
case VIR_DOMAIN_RNG_BACKEND_RANDOM:
|
|
path = rng->source.file;
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_EGD:
|
|
case VIR_DOMAIN_RNG_BACKEND_BUILTIN:
|
|
case VIR_DOMAIN_RNG_BACKEND_LAST:
|
|
break;
|
|
}
|
|
|
|
if (path && qemuDomainNamespaceUnlinkPath(vm, path) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceSetupInput(virDomainObjPtr vm,
|
|
virDomainInputDefPtr input)
|
|
{
|
|
const char *path = NULL;
|
|
|
|
if (!(path = virDomainInputDefGetPath(input)))
|
|
return 0;
|
|
|
|
if (path && qemuDomainNamespaceMknodPath(vm, path) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainNamespaceTeardownInput(virDomainObjPtr vm,
|
|
virDomainInputDefPtr input)
|
|
{
|
|
const char *path = NULL;
|
|
|
|
if (!(path = virDomainInputDefGetPath(input)))
|
|
return 0;
|
|
|
|
if (path && qemuDomainNamespaceUnlinkPath(vm, path) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDiskLookupByNodename:
|
|
* @def: domain definition to look for the disk
|
|
* @nodename: block backend node name to find
|
|
* @src: filled with the specific backing store element if provided
|
|
* @idx: index of @src in the backing chain, if provided
|
|
*
|
|
* Looks up the disk in the domain via @nodename and returns its definition.
|
|
* Optionally fills @src and @idx if provided with the specific backing chain
|
|
* element which corresponds to the node name.
|
|
*/
|
|
virDomainDiskDefPtr
|
|
qemuDomainDiskLookupByNodename(virDomainDefPtr def,
|
|
const char *nodename,
|
|
virStorageSourcePtr *src,
|
|
unsigned int *idx)
|
|
{
|
|
size_t i;
|
|
unsigned int srcindex;
|
|
virStorageSourcePtr tmp = NULL;
|
|
|
|
if (!idx)
|
|
idx = &srcindex;
|
|
|
|
if (src)
|
|
*src = NULL;
|
|
|
|
*idx = 0;
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
if ((tmp = virStorageSourceFindByNodeName(def->disks[i]->src,
|
|
nodename, idx))) {
|
|
if (src)
|
|
*src = tmp;
|
|
|
|
return def->disks[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDiskBackingStoreGetName:
|
|
*
|
|
* Creates a name using the indexed syntax (vda[1])for the given backing store
|
|
* entry for a disk.
|
|
*/
|
|
char *
|
|
qemuDomainDiskBackingStoreGetName(virDomainDiskDefPtr disk,
|
|
virStorageSourcePtr src G_GNUC_UNUSED,
|
|
unsigned int idx)
|
|
{
|
|
char *ret = NULL;
|
|
|
|
if (idx)
|
|
ret = g_strdup_printf("%s[%d]", disk->dst, idx);
|
|
else
|
|
ret = g_strdup(disk->dst);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
virStorageSourcePtr
|
|
qemuDomainGetStorageSourceByDevstr(const char *devstr,
|
|
virDomainDefPtr def)
|
|
{
|
|
virDomainDiskDefPtr disk = NULL;
|
|
virStorageSourcePtr src = NULL;
|
|
g_autofree char *target = NULL;
|
|
unsigned int idx;
|
|
size_t i;
|
|
|
|
if (virStorageFileParseBackingStoreStr(devstr, &target, &idx) < 0) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("failed to parse block device '%s'"), devstr);
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
if (STREQ(target, def->disks[i]->dst)) {
|
|
disk = def->disks[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!disk) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("failed to find disk '%s'"), target);
|
|
return NULL;
|
|
}
|
|
|
|
if (idx == 0)
|
|
src = disk->src;
|
|
else
|
|
src = virStorageFileChainLookup(disk->src, NULL, NULL, idx, NULL);
|
|
|
|
return src;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainSaveCookieDispose(void *obj)
|
|
{
|
|
qemuDomainSaveCookiePtr cookie = obj;
|
|
|
|
VIR_DEBUG("cookie=%p", cookie);
|
|
|
|
virCPUDefFree(cookie->cpu);
|
|
}
|
|
|
|
|
|
qemuDomainSaveCookiePtr
|
|
qemuDomainSaveCookieNew(virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
g_autoptr(qemuDomainSaveCookie) cookie = NULL;
|
|
|
|
if (qemuDomainInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(cookie = virObjectNew(qemuDomainSaveCookieClass)))
|
|
return NULL;
|
|
|
|
if (priv->origCPU && !(cookie->cpu = virCPUDefCopy(vm->def->cpu)))
|
|
return NULL;
|
|
|
|
cookie->slirpHelper = qemuDomainGetSlirpHelperOk(vm);
|
|
|
|
VIR_DEBUG("Save cookie %p, cpu=%p, slirpHelper=%d",
|
|
cookie, cookie->cpu, cookie->slirpHelper);
|
|
|
|
return g_steal_pointer(&cookie);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSaveCookieParse(xmlXPathContextPtr ctxt G_GNUC_UNUSED,
|
|
virObjectPtr *obj)
|
|
{
|
|
g_autoptr(qemuDomainSaveCookie) cookie = NULL;
|
|
|
|
if (qemuDomainInitialize() < 0)
|
|
return -1;
|
|
|
|
if (!(cookie = virObjectNew(qemuDomainSaveCookieClass)))
|
|
return -1;
|
|
|
|
if (virCPUDefParseXML(ctxt, "./cpu[1]", VIR_CPU_TYPE_GUEST,
|
|
&cookie->cpu) < 0)
|
|
return -1;
|
|
|
|
cookie->slirpHelper = virXPathBoolean("boolean(./slirpHelper)", ctxt) > 0;
|
|
|
|
*obj = (virObjectPtr) g_steal_pointer(&cookie);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainSaveCookieFormat(virBufferPtr buf,
|
|
virObjectPtr obj)
|
|
{
|
|
qemuDomainSaveCookiePtr cookie = (qemuDomainSaveCookiePtr) obj;
|
|
|
|
if (cookie->cpu &&
|
|
virCPUDefFormatBufFull(buf, cookie->cpu, NULL) < 0)
|
|
return -1;
|
|
|
|
if (cookie->slirpHelper)
|
|
virBufferAddLit(buf, "<slirpHelper/>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
virSaveCookieCallbacks virQEMUDriverDomainSaveCookie = {
|
|
.parse = qemuDomainSaveCookieParse,
|
|
.format = qemuDomainSaveCookieFormat,
|
|
};
|
|
|
|
|
|
/**
|
|
* qemuDomainUpdateCPU:
|
|
* @vm: domain which is being started
|
|
* @cpu: CPU updated when the domain was running previously (before migration,
|
|
* snapshot, or save)
|
|
* @origCPU: where to store the original CPU from vm->def in case @cpu was
|
|
* used instead
|
|
*
|
|
* Replace the CPU definition with the updated one when QEMU is new enough to
|
|
* allow us to check extra features it is about to enable or disable when
|
|
* starting a domain. The original CPU is stored in @origCPU.
|
|
*
|
|
* Returns 0 on success, -1 on error.
|
|
*/
|
|
int
|
|
qemuDomainUpdateCPU(virDomainObjPtr vm,
|
|
virCPUDefPtr cpu,
|
|
virCPUDefPtr *origCPU)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
*origCPU = NULL;
|
|
|
|
if (!cpu || !vm->def->cpu ||
|
|
!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_QUERY_CPU_MODEL_EXPANSION) ||
|
|
virCPUDefIsEqual(vm->def->cpu, cpu, false))
|
|
return 0;
|
|
|
|
if (!(cpu = virCPUDefCopy(cpu)))
|
|
return -1;
|
|
|
|
VIR_DEBUG("Replacing CPU def with the updated one");
|
|
|
|
*origCPU = vm->def->cpu;
|
|
vm->def->cpu = cpu;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainFixupCPUS:
|
|
* @vm: domain object
|
|
* @origCPU: original CPU used when the domain was started
|
|
*
|
|
* Libvirt older than 3.9.0 could have messed up the expansion of host-model
|
|
* CPU when reconnecting to a running domain by adding features QEMU does not
|
|
* support (such as cmt). This API fixes both the actual CPU provided by QEMU
|
|
* (stored in the domain object) and the @origCPU used when starting the
|
|
* domain.
|
|
*
|
|
* This is safe even if the original CPU definition used mode='custom' (rather
|
|
* than host-model) since we know QEMU was able to start the domain and thus
|
|
* the CPU definitions do not contain any features unknown to QEMU.
|
|
*
|
|
* This function can only be used on an active domain or when restoring a
|
|
* domain which was running.
|
|
*
|
|
* Returns 0 on success, -1 on error.
|
|
*/
|
|
int
|
|
qemuDomainFixupCPUs(virDomainObjPtr vm,
|
|
virCPUDefPtr *origCPU)
|
|
{
|
|
virCPUDefPtr fixedCPU = NULL;
|
|
virCPUDefPtr fixedOrig = NULL;
|
|
virArch arch = vm->def->os.arch;
|
|
int ret = -1;
|
|
|
|
if (!ARCH_IS_X86(arch))
|
|
return 0;
|
|
|
|
if (!vm->def->cpu ||
|
|
vm->def->cpu->mode != VIR_CPU_MODE_CUSTOM ||
|
|
!vm->def->cpu->model)
|
|
return 0;
|
|
|
|
/* Missing origCPU means QEMU created exactly the same virtual CPU which
|
|
* we asked for or libvirt was too old to mess up the translation from
|
|
* host-model.
|
|
*/
|
|
if (!*origCPU)
|
|
return 0;
|
|
|
|
if (virCPUDefFindFeature(vm->def->cpu, "cmt") &&
|
|
(!(fixedCPU = virCPUDefCopyWithoutModel(vm->def->cpu)) ||
|
|
virCPUDefCopyModelFilter(fixedCPU, vm->def->cpu, false,
|
|
virQEMUCapsCPUFilterFeatures, &arch) < 0))
|
|
goto cleanup;
|
|
|
|
if (virCPUDefFindFeature(*origCPU, "cmt") &&
|
|
(!(fixedOrig = virCPUDefCopyWithoutModel(*origCPU)) ||
|
|
virCPUDefCopyModelFilter(fixedOrig, *origCPU, false,
|
|
virQEMUCapsCPUFilterFeatures, &arch) < 0))
|
|
goto cleanup;
|
|
|
|
if (fixedCPU) {
|
|
virCPUDefFree(vm->def->cpu);
|
|
vm->def->cpu = g_steal_pointer(&fixedCPU);
|
|
}
|
|
|
|
if (fixedOrig) {
|
|
virCPUDefFree(*origCPU);
|
|
*origCPU = g_steal_pointer(&fixedOrig);
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virCPUDefFree(fixedCPU);
|
|
virCPUDefFree(fixedOrig);
|
|
return ret;
|
|
}
|
|
|
|
|
|
char *
|
|
qemuDomainGetMachineName(virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virQEMUDriverPtr driver = priv->driver;
|
|
char *ret = NULL;
|
|
|
|
if (vm->pid > 0) {
|
|
ret = virSystemdGetMachineNameByPID(vm->pid);
|
|
if (!ret)
|
|
virResetLastError();
|
|
}
|
|
|
|
if (!ret)
|
|
ret = virDomainGenerateMachineName("qemu", vm->def->id, vm->def->name,
|
|
virQEMUDriverIsPrivileged(driver));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Check whether the device address is using either 'ccw' or default s390
|
|
* address format and whether that's "legal" for the current qemu and/or
|
|
* guest os.machine type. This is the corollary to the code which doesn't
|
|
* find the address type set using an emulator that supports either 'ccw'
|
|
* or s390 and sets the address type based on the capabilities.
|
|
*
|
|
* If the address is using 'ccw' or s390 and it's not supported, generate
|
|
* an error and return false; otherwise, return true.
|
|
*/
|
|
bool
|
|
qemuDomainCheckCCWS390AddressSupport(const virDomainDef *def,
|
|
const virDomainDeviceInfo *info,
|
|
virQEMUCapsPtr qemuCaps,
|
|
const char *devicename)
|
|
{
|
|
if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW) {
|
|
if (!qemuDomainIsS390CCW(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("cannot use CCW address type for device "
|
|
"'%s' using machine type '%s'"),
|
|
devicename, def->os.machine);
|
|
return false;
|
|
} else if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CCW)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("CCW address type is not supported by "
|
|
"this QEMU"));
|
|
return false;
|
|
}
|
|
} else if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390) {
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_S390)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("virtio S390 address type is not supported by "
|
|
"this QEMU"));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainPrepareDiskSourceData:
|
|
*
|
|
* @disk: Disk config object
|
|
* @src: source to start from
|
|
*
|
|
* Prepares various aspects of a storage source belonging to a disk backing
|
|
* chain based on the disk configuration. This function should be also called
|
|
* for detected backing chain members.
|
|
*/
|
|
void
|
|
qemuDomainPrepareDiskSourceData(virDomainDiskDefPtr disk,
|
|
virStorageSourcePtr src)
|
|
{
|
|
if (!disk)
|
|
return;
|
|
|
|
/* transfer properties valid only for the top level image */
|
|
if (src == disk->src)
|
|
src->detect_zeroes = disk->detect_zeroes;
|
|
|
|
/* transfer properties valid for the full chain */
|
|
src->iomode = disk->iomode;
|
|
src->cachemode = disk->cachemode;
|
|
src->discard = disk->discard;
|
|
|
|
if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY)
|
|
src->floppyimg = true;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuDomainPrepareDiskCachemode(virDomainDiskDefPtr disk)
|
|
{
|
|
if (disk->cachemode == VIR_DOMAIN_DISK_CACHE_DEFAULT &&
|
|
disk->src->shared && !disk->src->readonly)
|
|
disk->cachemode = VIR_DOMAIN_DISK_CACHE_DISABLE;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainPrepareStorageSourcePR(virStorageSourcePtr src,
|
|
qemuDomainObjPrivatePtr priv,
|
|
const char *parentalias)
|
|
{
|
|
if (!src->pr)
|
|
return 0;
|
|
|
|
if (virStoragePRDefIsManaged(src->pr)) {
|
|
VIR_FREE(src->pr->path);
|
|
if (!(src->pr->path = qemuDomainGetManagedPRSocketPath(priv)))
|
|
return -1;
|
|
src->pr->mgralias = g_strdup(qemuDomainGetManagedPRAlias());
|
|
} else {
|
|
if (!(src->pr->mgralias = qemuDomainGetUnmanagedPRAlias(parentalias)))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainPrepareDiskSourceLegacy:
|
|
* @disk: disk to prepare
|
|
* @priv: VM private data
|
|
* @cfg: qemu driver config
|
|
*
|
|
* Prepare any disk source relevant data for use with the -drive command line.
|
|
*/
|
|
static int
|
|
qemuDomainPrepareDiskSourceLegacy(virDomainDiskDefPtr disk,
|
|
qemuDomainObjPrivatePtr priv,
|
|
virQEMUDriverConfigPtr cfg)
|
|
{
|
|
if (qemuDomainValidateStorageSource(disk->src, priv->qemuCaps) < 0)
|
|
return -1;
|
|
|
|
qemuDomainPrepareStorageSourceConfig(disk->src, cfg, priv->qemuCaps);
|
|
qemuDomainPrepareDiskSourceData(disk, disk->src);
|
|
|
|
if (qemuDomainSecretStorageSourcePrepare(priv, disk->src,
|
|
disk->info.alias,
|
|
disk->info.alias) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainPrepareStorageSourcePR(disk->src, priv, disk->info.alias) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainPrepareStorageSourceTLS(disk->src, cfg, disk->info.alias,
|
|
priv->qemuCaps) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainPrepareStorageSourceBlockdev(virDomainDiskDefPtr disk,
|
|
virStorageSourcePtr src,
|
|
qemuDomainObjPrivatePtr priv,
|
|
virQEMUDriverConfigPtr cfg)
|
|
{
|
|
src->id = qemuDomainStorageIdNew(priv);
|
|
|
|
src->nodestorage = g_strdup_printf("libvirt-%u-storage", src->id);
|
|
src->nodeformat = g_strdup_printf("libvirt-%u-format", src->id);
|
|
|
|
if (src->sliceStorage &&
|
|
src->format != VIR_STORAGE_FILE_RAW)
|
|
src->sliceStorage->nodename = g_strdup_printf("libvirt-%u-slice-sto", src->id);
|
|
|
|
if (qemuDomainValidateStorageSource(src, priv->qemuCaps) < 0)
|
|
return -1;
|
|
|
|
qemuDomainPrepareStorageSourceConfig(src, cfg, priv->qemuCaps);
|
|
qemuDomainPrepareDiskSourceData(disk, src);
|
|
|
|
if (qemuDomainSecretStorageSourcePrepare(priv, src,
|
|
src->nodestorage,
|
|
src->nodeformat) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainPrepareStorageSourcePR(src, priv, src->nodestorage) < 0)
|
|
return -1;
|
|
|
|
if (qemuDomainPrepareStorageSourceTLS(src, cfg, src->nodestorage,
|
|
priv->qemuCaps) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainPrepareDiskSourceBlockdev(virDomainDiskDefPtr disk,
|
|
qemuDomainObjPrivatePtr priv,
|
|
virQEMUDriverConfigPtr cfg)
|
|
{
|
|
qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
virStorageSourcePtr n;
|
|
|
|
if (disk->copy_on_read == VIR_TRISTATE_SWITCH_ON &&
|
|
!diskPriv->nodeCopyOnRead)
|
|
diskPriv->nodeCopyOnRead = g_strdup_printf("libvirt-CoR-%s", disk->dst);
|
|
|
|
for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
if (qemuDomainPrepareStorageSourceBlockdev(disk, n, priv, cfg) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuDomainPrepareDiskSource(virDomainDiskDefPtr disk,
|
|
qemuDomainObjPrivatePtr priv,
|
|
virQEMUDriverConfigPtr cfg)
|
|
{
|
|
qemuDomainPrepareDiskCachemode(disk);
|
|
|
|
/* set default format for storage pool based disks */
|
|
if (disk->src->type == VIR_STORAGE_TYPE_VOLUME &&
|
|
disk->src->format <= VIR_STORAGE_FILE_NONE) {
|
|
int actualType = virStorageSourceGetActualType(disk->src);
|
|
|
|
if (actualType == VIR_STORAGE_TYPE_DIR)
|
|
disk->src->format = VIR_STORAGE_FILE_FAT;
|
|
else
|
|
disk->src->format = VIR_STORAGE_FILE_RAW;
|
|
}
|
|
|
|
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV)) {
|
|
if (qemuDomainPrepareDiskSourceBlockdev(disk, priv, cfg) < 0)
|
|
return -1;
|
|
} else {
|
|
if (qemuDomainPrepareDiskSourceLegacy(disk, priv, cfg) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDiskCachemodeFlags:
|
|
*
|
|
* Converts disk cachemode to the cache mode options for qemu. Returns -1 for
|
|
* invalid @cachemode values and fills the flags and returns 0 on success.
|
|
* Flags may be NULL.
|
|
*/
|
|
int
|
|
qemuDomainDiskCachemodeFlags(int cachemode,
|
|
bool *writeback,
|
|
bool *direct,
|
|
bool *noflush)
|
|
{
|
|
bool dummy;
|
|
|
|
if (!writeback)
|
|
writeback = &dummy;
|
|
|
|
if (!direct)
|
|
direct = &dummy;
|
|
|
|
if (!noflush)
|
|
noflush = &dummy;
|
|
|
|
/* Mapping of cache modes to the attributes according to qemu-options.hx
|
|
* │ cache.writeback cache.direct cache.no-flush
|
|
* ─────────────┼─────────────────────────────────────────────────
|
|
* writeback │ true false false
|
|
* none │ true true false
|
|
* writethrough │ false false false
|
|
* directsync │ false true false
|
|
* unsafe │ true false true
|
|
*/
|
|
switch ((virDomainDiskCache) cachemode) {
|
|
case VIR_DOMAIN_DISK_CACHE_DISABLE: /* 'none' */
|
|
*writeback = true;
|
|
*direct = true;
|
|
*noflush = false;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DISK_CACHE_WRITETHRU:
|
|
*writeback = false;
|
|
*direct = false;
|
|
*noflush = false;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DISK_CACHE_WRITEBACK:
|
|
*writeback = true;
|
|
*direct = false;
|
|
*noflush = false;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DISK_CACHE_DIRECTSYNC:
|
|
*writeback = false;
|
|
*direct = true;
|
|
*noflush = false;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DISK_CACHE_UNSAFE:
|
|
*writeback = true;
|
|
*direct = false;
|
|
*noflush = true;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DISK_CACHE_DEFAULT:
|
|
case VIR_DOMAIN_DISK_CACHE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainDiskCache, cachemode);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
qemuProcessEventFree(struct qemuProcessEvent *event)
|
|
{
|
|
if (!event)
|
|
return;
|
|
|
|
switch (event->eventType) {
|
|
case QEMU_PROCESS_EVENT_GUESTPANIC:
|
|
qemuMonitorEventPanicInfoFree(event->data);
|
|
break;
|
|
case QEMU_PROCESS_EVENT_RDMA_GID_STATUS_CHANGED:
|
|
qemuMonitorEventRdmaGidStatusFree(event->data);
|
|
break;
|
|
case QEMU_PROCESS_EVENT_WATCHDOG:
|
|
case QEMU_PROCESS_EVENT_DEVICE_DELETED:
|
|
case QEMU_PROCESS_EVENT_NIC_RX_FILTER_CHANGED:
|
|
case QEMU_PROCESS_EVENT_SERIAL_CHANGED:
|
|
case QEMU_PROCESS_EVENT_BLOCK_JOB:
|
|
case QEMU_PROCESS_EVENT_MONITOR_EOF:
|
|
case QEMU_PROCESS_EVENT_GUEST_CRASHLOADED:
|
|
VIR_FREE(event->data);
|
|
break;
|
|
case QEMU_PROCESS_EVENT_JOB_STATUS_CHANGE:
|
|
virObjectUnref(event->data);
|
|
break;
|
|
case QEMU_PROCESS_EVENT_PR_DISCONNECT:
|
|
case QEMU_PROCESS_EVENT_LAST:
|
|
break;
|
|
}
|
|
VIR_FREE(event);
|
|
}
|
|
|
|
|
|
char *
|
|
qemuDomainGetManagedPRSocketPath(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
char *ret = NULL;
|
|
|
|
ret = g_strdup_printf("%s/%s.sock", priv->libDir, qemuDomainGetManagedPRAlias());
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainStorageIdNew:
|
|
* @priv: qemu VM private data object.
|
|
*
|
|
* Generate a new unique id for a storage object. Useful for node name generation.
|
|
*/
|
|
unsigned int
|
|
qemuDomainStorageIdNew(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
return ++priv->nodenameindex;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainStorageIdReset:
|
|
* @priv: qemu VM private data object.
|
|
*
|
|
* Resets the data for the node name generator. The node names need to be unique
|
|
* for a single instance, so can be reset on VM shutdown.
|
|
*/
|
|
void
|
|
qemuDomainStorageIdReset(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
priv->nodenameindex = 0;
|
|
}
|
|
|
|
|
|
virDomainEventResumedDetailType
|
|
qemuDomainRunningReasonToResumeEvent(virDomainRunningReason reason)
|
|
{
|
|
switch (reason) {
|
|
case VIR_DOMAIN_RUNNING_RESTORED:
|
|
case VIR_DOMAIN_RUNNING_FROM_SNAPSHOT:
|
|
return VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT;
|
|
|
|
case VIR_DOMAIN_RUNNING_MIGRATED:
|
|
case VIR_DOMAIN_RUNNING_MIGRATION_CANCELED:
|
|
return VIR_DOMAIN_EVENT_RESUMED_MIGRATED;
|
|
|
|
case VIR_DOMAIN_RUNNING_POSTCOPY:
|
|
return VIR_DOMAIN_EVENT_RESUMED_POSTCOPY;
|
|
|
|
case VIR_DOMAIN_RUNNING_UNKNOWN:
|
|
case VIR_DOMAIN_RUNNING_SAVE_CANCELED:
|
|
case VIR_DOMAIN_RUNNING_BOOTED:
|
|
case VIR_DOMAIN_RUNNING_UNPAUSED:
|
|
case VIR_DOMAIN_RUNNING_WAKEUP:
|
|
case VIR_DOMAIN_RUNNING_CRASHED:
|
|
case VIR_DOMAIN_RUNNING_LAST:
|
|
break;
|
|
}
|
|
|
|
return VIR_DOMAIN_EVENT_RESUMED_UNPAUSED;
|
|
}
|
|
|
|
|
|
/* qemuDomainIsUsingNoShutdown:
|
|
* @priv: Domain private data
|
|
*
|
|
* We can receive an event when QEMU stops. If we use no-shutdown, then
|
|
* we can watch for this event and do a soft/warm reboot.
|
|
*
|
|
* Returns: @true when -no-shutdown either should be or was added to the
|
|
* command line.
|
|
*/
|
|
bool
|
|
qemuDomainIsUsingNoShutdown(qemuDomainObjPrivatePtr priv)
|
|
{
|
|
return priv->allowReboot == VIR_TRISTATE_BOOL_YES;
|
|
}
|
|
|
|
|
|
bool
|
|
qemuDomainDiskIsMissingLocalOptional(virDomainDiskDefPtr disk)
|
|
{
|
|
return disk->startupPolicy == VIR_DOMAIN_STARTUP_POLICY_OPTIONAL &&
|
|
virStorageSourceIsLocalStorage(disk->src) && disk->src->path &&
|
|
!virFileExists(disk->src->path);
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainNVRAMPathFormat(virQEMUDriverConfigPtr cfg,
|
|
virDomainDefPtr def,
|
|
char **path)
|
|
{
|
|
*path = g_strdup_printf("%s/%s_VARS.fd", cfg->nvramDir, def->name);
|
|
}
|
|
|
|
|
|
void
|
|
qemuDomainNVRAMPathGenerate(virQEMUDriverConfigPtr cfg,
|
|
virDomainDefPtr def)
|
|
{
|
|
if (virDomainDefHasOldStyleROUEFI(def) &&
|
|
!def->os.loader->nvram)
|
|
qemuDomainNVRAMPathFormat(cfg, def, &def->os.loader->nvram);
|
|
}
|
|
|
|
|
|
virDomainEventSuspendedDetailType
|
|
qemuDomainPausedReasonToSuspendedEvent(virDomainPausedReason reason)
|
|
{
|
|
switch (reason) {
|
|
case VIR_DOMAIN_PAUSED_MIGRATION:
|
|
return VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED;
|
|
|
|
case VIR_DOMAIN_PAUSED_FROM_SNAPSHOT:
|
|
return VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT;
|
|
|
|
case VIR_DOMAIN_PAUSED_POSTCOPY_FAILED:
|
|
return VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED;
|
|
|
|
case VIR_DOMAIN_PAUSED_POSTCOPY:
|
|
return VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY;
|
|
|
|
case VIR_DOMAIN_PAUSED_UNKNOWN:
|
|
case VIR_DOMAIN_PAUSED_USER:
|
|
case VIR_DOMAIN_PAUSED_SAVE:
|
|
case VIR_DOMAIN_PAUSED_DUMP:
|
|
case VIR_DOMAIN_PAUSED_IOERROR:
|
|
case VIR_DOMAIN_PAUSED_WATCHDOG:
|
|
case VIR_DOMAIN_PAUSED_SHUTTING_DOWN:
|
|
case VIR_DOMAIN_PAUSED_SNAPSHOT:
|
|
case VIR_DOMAIN_PAUSED_CRASHED:
|
|
case VIR_DOMAIN_PAUSED_STARTING_UP:
|
|
case VIR_DOMAIN_PAUSED_LAST:
|
|
break;
|
|
}
|
|
|
|
return VIR_DOMAIN_EVENT_SUSPENDED_PAUSED;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuDomainDefHasManagedPRBlockjobIterator(void *payload,
|
|
const void *name G_GNUC_UNUSED,
|
|
void *opaque)
|
|
{
|
|
qemuBlockJobDataPtr job = payload;
|
|
bool *hasPR = opaque;
|
|
|
|
if (job->disk)
|
|
return 0;
|
|
|
|
if ((job->chain && virStorageSourceChainHasManagedPR(job->chain)) ||
|
|
(job->mirrorChain && virStorageSourceChainHasManagedPR(job->mirrorChain)))
|
|
*hasPR = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainDefHasManagedPR:
|
|
* @vm: domain object
|
|
*
|
|
* @vm must be an active VM. Returns true if @vm has any storage source with
|
|
* managed persistent reservations.
|
|
*/
|
|
bool
|
|
qemuDomainDefHasManagedPR(virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
bool jobPR = false;
|
|
|
|
if (virDomainDefHasManagedPR(vm->def))
|
|
return true;
|
|
|
|
virHashForEach(priv->blockjobs, qemuDomainDefHasManagedPRBlockjobIterator, &jobPR);
|
|
|
|
return jobPR;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuDomainSupportsCheckpointsBlockjobs:
|
|
* @vm: domain object
|
|
*
|
|
* Checks whether a block job is supported in possible combination with
|
|
* checkpoints (qcow2 bitmaps). Returns -1 if unsupported and reports an error
|
|
* 0 in case everything is supported.
|
|
*/
|
|
int
|
|
qemuDomainSupportsCheckpointsBlockjobs(virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP) &&
|
|
virDomainListCheckpoints(vm->checkpoints, NULL, NULL, NULL, 0) > 0) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
_("cannot perform block operations while checkpoint exists"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* qemuDomainInitializePflashStorageSource:
|
|
*
|
|
* This helper converts the specification of the source of the 'loader' in case
|
|
* PFLASH is required to virStorageSources in case QEMU_CAPS_BLOCKDEV is present.
|
|
*
|
|
* This helper is used in the intermediate state when we don't support full
|
|
* backing chains for pflash drives in the XML.
|
|
*
|
|
* The nodenames used here have a different prefix to allow for a later
|
|
* conversion. The prefixes are 'libvirt-pflash0-storage',
|
|
* 'libvirt-pflash0-format' for pflash0 and 'libvirt-pflash1-storage' and
|
|
* 'libvirt-pflash1-format' for pflash1.
|
|
*/
|
|
int
|
|
qemuDomainInitializePflashStorageSource(virDomainObjPtr vm)
|
|
{
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
virDomainDefPtr def = vm->def;
|
|
g_autoptr(virStorageSource) pflash0 = NULL;
|
|
g_autoptr(virStorageSource) pflash1 = NULL;
|
|
|
|
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV))
|
|
return 0;
|
|
|
|
if (!virDomainDefHasOldStyleUEFI(def))
|
|
return 0;
|
|
|
|
if (!(pflash0 = virStorageSourceNew()))
|
|
return -1;
|
|
|
|
pflash0->type = VIR_STORAGE_TYPE_FILE;
|
|
pflash0->format = VIR_STORAGE_FILE_RAW;
|
|
pflash0->path = g_strdup(def->os.loader->path);
|
|
pflash0->readonly = def->os.loader->readonly;
|
|
pflash0->nodeformat = g_strdup("libvirt-pflash0-format");
|
|
pflash0->nodestorage = g_strdup("libvirt-pflash0-storage");
|
|
|
|
|
|
if (def->os.loader->nvram) {
|
|
if (!(pflash1 = virStorageSourceNew()))
|
|
return -1;
|
|
|
|
pflash1->type = VIR_STORAGE_TYPE_FILE;
|
|
pflash1->format = VIR_STORAGE_FILE_RAW;
|
|
pflash1->path = g_strdup(def->os.loader->nvram);
|
|
pflash1->readonly = false;
|
|
pflash1->nodeformat = g_strdup("libvirt-pflash1-format");
|
|
pflash1->nodestorage = g_strdup("libvirt-pflash1-storage");
|
|
}
|
|
|
|
priv->pflash0 = g_steal_pointer(&pflash0);
|
|
priv->pflash1 = g_steal_pointer(&pflash1);
|
|
|
|
return 0;
|
|
}
|