/*
* 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
* .
*/
#include
#include "qemu_domain.h"
#include "qemu_alias.h"
#include "qemu_block.h"
#include "qemu_cgroup.h"
#include "qemu_command.h"
#include "qemu_process.h"
#include "qemu_capabilities.h"
#include "qemu_migration.h"
#include "qemu_migration_params.h"
#include "qemu_security.h"
#include "qemu_extdevice.h"
#include "qemu_blockjob.h"
#include "viralloc.h"
#include "virlog.h"
#include "virerror.h"
#include "c-ctype.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 "viratomic.h"
#include "virprocess.h"
#include "vircrypto.h"
#include "virrandom.h"
#include "virsystemd.h"
#include "secret_util.h"
#include "logging/log_manager.h"
#include "locking/domain_lock.h"
#include "virdomainsnapshotobjlist.h"
#include "virdomaincheckpointobjlist.h"
#ifdef MAJOR_IN_MKDEV
# include
#elif MAJOR_IN_SYSMACROS
# include
#endif
#include
#include
#if defined(HAVE_SYS_MOUNT_H)
# include
#endif
#ifdef WITH_SELINUX
# include
#endif
#include
#include "dosname.h"
#define QEMU_QXL_VGAMEM_DEFAULT 16 * 1024
#define VIR_FROM_THIS VIR_FROM_QEMU
VIR_LOG_INIT("qemu.qemu_domain");
#define QEMU_NAMESPACE_HREF "http://libvirt.org/schemas/domain/qemu/1.0"
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",
);
VIR_ENUM_IMPL(qemuDomainNamespace,
QEMU_DOMAIN_NS_LAST,
"mount",
);
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 ATTRIBUTE_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:
ATTRIBUTE_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:
ATTRIBUTE_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;
VIR_STEAL_PTR(job->migParams, priv->job.migParams);
job->apiFlags = priv->job.apiFlags;
qemuDomainObjResetJob(priv);
qemuDomainObjResetAsyncJob(priv);
}
static void
qemuDomainObjFreeJob(qemuDomainObjPrivatePtr 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_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;
}
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_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)
{
char *path;
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);
VIR_FREE(path);
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)
{
char *path;
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);
VIR_FREE(path);
return 0;
error:
if (masterKeyLen > 0)
memset(masterKey, 0, masterKeyLen);
VIR_FREE(masterKey);
VIR_FORCE_CLOSE(fd);
VIR_FREE(path);
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)
{
char *path = NULL;
if (!priv->masterKey)
return;
/* Clear the contents */
qemuDomainMasterKeyFree(priv);
/* Delete the master key file */
path = qemuDomainGetMasterKeyFilePath(priv->libDir);
unlink(path);
VIR_FREE(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);
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 ATTRIBUTE_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);
}
/* 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)
{
virConnectPtr conn;
int ret = -1;
conn = virGetConnectSecret();
if (!conn)
return -1;
secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_PLAIN;
if (VIR_STRDUP(secinfo->s.plain.username, username) < 0)
goto cleanup;
ret = virSecretGetSecretString(conn, seclookupdef, usageType,
&secinfo->s.plain.secret,
&secinfo->s.plain.secretlen);
cleanup:
virObjectUnref(conn);
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)
{
virConnectPtr conn;
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;
conn = virGetConnectSecret();
if (!conn)
return -1;
secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES;
if (VIR_STRDUP(secinfo->s.aes.username, username) < 0)
goto cleanup;
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 */
if (!(secinfo->s.aes.iv = virStringEncodeBase64(raw_iv, ivlen)))
goto cleanup;
/* 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 */
if (!(secinfo->s.aes.ciphertext = virStringEncodeBase64(ciphertext,
ciphertextlen)))
goto cleanup;
ret = 0;
cleanup:
VIR_DISPOSE_N(raw_iv, ivlen);
VIR_DISPOSE_N(secret, secretlen);
VIR_DISPOSE_N(ciphertext, ciphertextlen);
virObjectUnref(conn);
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)
{
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);
VIR_FREE(charAlias);
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;
if (VIR_STRDUP(gfxPriv->tlsAlias, "vnc-tls-creds0") < 0)
return -1;
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;
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
size_t i;
int ret = -1;
/* disk secrets are prepared when preparing disks */
for (i = 0; i < vm->def->nhostdevs; i++) {
if (qemuDomainSecretHostdevPrepare(priv,
vm->def->hostdevs[i]) < 0)
goto cleanup;
}
for (i = 0; i < vm->def->nserials; i++) {
if (qemuDomainSecretChardevPrepare(cfg, priv,
vm->def->serials[i]->info.alias,
vm->def->serials[i]->source) < 0)
goto cleanup;
}
for (i = 0; i < vm->def->nparallels; i++) {
if (qemuDomainSecretChardevPrepare(cfg, priv,
vm->def->parallels[i]->info.alias,
vm->def->parallels[i]->source) < 0)
goto cleanup;
}
for (i = 0; i < vm->def->nchannels; i++) {
if (qemuDomainSecretChardevPrepare(cfg, priv,
vm->def->channels[i]->info.alias,
vm->def->channels[i]->source) < 0)
goto cleanup;
}
for (i = 0; i < vm->def->nconsoles; i++) {
if (qemuDomainSecretChardevPrepare(cfg, priv,
vm->def->consoles[i]->info.alias,
vm->def->consoles[i]->source) < 0)
goto cleanup;
}
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)
goto cleanup;
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)
goto cleanup;
}
for (i = 0; i < vm->def->nredirdevs; i++) {
if (qemuDomainSecretChardevPrepare(cfg, priv,
vm->def->redirdevs[i]->info.alias,
vm->def->redirdevs[i]->source) < 0)
goto cleanup;
}
for (i = 0; i < vm->def->ngraphics; i++) {
if (qemuDomainSecretGraphicsPrepare(cfg, priv, vm->def->graphics[i]) < 0)
goto cleanup;
}
ret = 0;
cleanup:
virObjectUnref(cfg);
return ret;
}
/* This is the old way of setting up per-domain directories */
static int
qemuDomainSetPrivatePathsOld(virQEMUDriverPtr driver,
virDomainObjPtr vm)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
int ret = -1;
if (!priv->libDir &&
virAsprintf(&priv->libDir, "%s/domain-%s",
cfg->libDir, vm->def->name) < 0)
goto cleanup;
if (!priv->channelTargetDir &&
virAsprintf(&priv->channelTargetDir, "%s/domain-%s",
cfg->channelTargetDir, vm->def->name) < 0)
goto cleanup;
ret = 0;
cleanup:
virObjectUnref(cfg);
return ret;
}
int
qemuDomainSetPrivatePaths(virQEMUDriverPtr driver,
virDomainObjPtr vm)
{
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
qemuDomainObjPrivatePtr priv = vm->privateData;
char *domname = virDomainDefGetShortName(vm->def);
int ret = -1;
if (!domname)
goto cleanup;
if (!priv->libDir &&
virAsprintf(&priv->libDir, "%s/domain-%s", cfg->libDir, domname) < 0)
goto cleanup;
if (!priv->channelTargetDir &&
virAsprintf(&priv->channelTargetDir, "%s/domain-%s",
cfg->channelTargetDir, domname) < 0)
goto cleanup;
ret = 0;
cleanup:
virObjectUnref(cfg);
VIR_FREE(domname);
return ret;
}
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;
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;
qemuDomainObjResetJob(priv);
qemuDomainObjResetAsyncJob(priv);
virHashRemoveAll(priv->blockjobs);
}
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);
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)
VIR_STEAL_PTR((*secinfo)->s.aes.alias, *alias);
return 0;
}
static int
qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt,
virStorageSourcePtr src)
{
qemuDomainStorageSourcePrivatePtr priv;
char *authalias = NULL;
char *encalias = NULL;
int ret = -1;
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->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()))
goto cleanup;
priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
if (qemuStorageSourcePrivateDataAssignSecinfo(&priv->secinfo, &authalias) < 0)
goto cleanup;
if (qemuStorageSourcePrivateDataAssignSecinfo(&priv->encinfo, &encalias) < 0)
goto cleanup;
}
if (virStorageSourcePrivateDataParseRelPath(ctxt, src) < 0)
goto cleanup;
ret = 0;
cleanup:
VIR_FREE(authalias);
VIR_FREE(encalias);
return ret;
}
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, "\n",
type, secinfo->s.aes.alias);
}
static int
qemuStorageSourcePrivateDataFormat(virStorageSourcePtr src,
virBufferPtr buf)
{
VIR_AUTOCLEAN(virBuffer) tmp = VIR_BUFFER_INITIALIZER;
qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
int ret = -1;
if (src->nodestorage || src->nodeformat) {
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "\n", src->nodestorage);
virBufferEscapeString(buf, "\n", src->nodeformat);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
if (src->pr)
virBufferAsprintf(buf, "\n", src->pr->mgralias);
if (virStorageSourcePrivateDataFormatRelPath(src, buf) < 0)
goto cleanup;
virBufferSetChildIndent(&tmp, buf);
if (srcPriv) {
qemuStorageSourcePrivateDataFormatSecinfo(&tmp, srcPriv->secinfo, "auth");
qemuStorageSourcePrivateDataFormatSecinfo(&tmp, srcPriv->encinfo, "encryption");
}
if (src->tlsAlias)
virBufferAsprintf(&tmp, "\n", src->tlsAlias);
if (virXMLFormatElement(buf, "objects", NULL, &tmp) < 0)
goto cleanup;
ret = 0;
cleanup:
return ret;
}
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, "\n", priv->qomName);
if (priv->nodeCopyOnRead) {
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "\n",
priv->nodeCopyOnRead);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\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, "\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, "\n", i, tid);
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
static int
qemuDomainObjPrivateXMLFormatAutomaticPlacement(virBufferPtr buf,
qemuDomainObjPrivatePtr priv)
{
char *nodeset = NULL;
char *cpuset = NULL;
int ret = -1;
if (!priv->autoNodeset && !priv->autoCpuset)
return 0;
if (priv->autoNodeset &&
!((nodeset = virBitmapFormat(priv->autoNodeset))))
goto cleanup;
if (priv->autoCpuset &&
!((cpuset = virBitmapFormat(priv->autoCpuset))))
goto cleanup;
virBufferAddLit(buf, "\n");
ret = 0;
cleanup:
VIR_FREE(nodeset);
VIR_FREE(cpuset);
return ret;
}
typedef struct qemuDomainPrivateBlockJobFormatData {
virDomainXMLOptionPtr xmlopt;
virBufferPtr buf;
} qemuDomainPrivateBlockJobFormatData;
static int
qemuDomainObjPrivateXMLFormatBlockjobFormatSource(virBufferPtr buf,
const char *element,
virStorageSourcePtr src,
virDomainXMLOptionPtr xmlopt,
bool chain)
{
VIR_AUTOCLEAN(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
VIR_AUTOCLEAN(virBuffer) childBuf = VIR_BUFFER_INITIALIZER;
unsigned int xmlflags = VIR_DOMAIN_DEF_FORMAT_STATUS;
virBufferSetChildIndent(&childBuf, buf);
virBufferAsprintf(&attrBuf, " type='%s' format='%s'",
virStorageTypeToString(src->type),
virStorageFileFormatTypeToString(src->format));
if (virDomainDiskSourceFormat(&childBuf, src, "source", 0, true, xmlflags, xmlopt) < 0)
return -1;
if (chain &&
virDomainDiskBackingStoreFormat(&childBuf, src, xmlopt, xmlflags) < 0)
return -1;
if (virXMLFormatElement(buf, element, &attrBuf, &childBuf) < 0)
return -1;
return 0;
}
static int
qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload,
const void *name ATTRIBUTE_UNUSED,
void *opaque)
{
VIR_AUTOCLEAN(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
VIR_AUTOCLEAN(virBuffer) childBuf = VIR_BUFFER_INITIALIZER;
VIR_AUTOCLEAN(virBuffer) chainsBuf = VIR_BUFFER_INITIALIZER;
qemuBlockJobDataPtr job = payload;
const char *state = qemuBlockjobStateTypeToString(job->state);
const char *newstate = NULL;
struct qemuDomainPrivateBlockJobFormatData *data = opaque;
if (job->newstate != -1)
newstate = qemuBlockjobStateTypeToString(job->newstate);
virBufferSetChildIndent(&childBuf, data->buf);
virBufferSetChildIndent(&chainsBuf, &childBuf);
virBufferEscapeString(&attrBuf, " name='%s'", job->name);
virBufferEscapeString(&attrBuf, " type='%s'", qemuBlockjobTypeToString(job->type));
virBufferEscapeString(&attrBuf, " state='%s'", state);
virBufferEscapeString(&attrBuf, " newstate='%s'", newstate);
virBufferEscapeString(&childBuf, "%s", job->errmsg);
if (job->disk) {
virBufferEscapeString(&childBuf, "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;
if (virXMLFormatElement(&childBuf, "chains", NULL, &chainsBuf) < 0)
return -1;
}
switch ((qemuBlockJobType) job->type) {
case QEMU_BLOCKJOB_TYPE_PULL:
if (job->data.pull.base)
virBufferAsprintf(&childBuf, "\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, "\n", job->data.commit.base->nodeformat);
if (job->data.commit.top)
virBufferAsprintf(&childBuf, "\n", job->data.commit.top->nodeformat);
if (job->data.commit.topparent)
virBufferAsprintf(&childBuf, "\n", job->data.commit.topparent->nodeformat);
break;
case QEMU_BLOCKJOB_TYPE_CREATE:
if (job->data.create.storage)
virBufferAddLit(&childBuf, "\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:
case QEMU_BLOCKJOB_TYPE_NONE:
case QEMU_BLOCKJOB_TYPE_INTERNAL:
case QEMU_BLOCKJOB_TYPE_LAST:
break;
}
return virXMLFormatElement(data->buf, "blockjob", &attrBuf, &childBuf);
}
static int
qemuDomainObjPrivateXMLFormatBlockjobs(virBufferPtr buf,
virDomainObjPtr vm)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
VIR_AUTOCLEAN(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
VIR_AUTOCLEAN(virBuffer) childBuf = VIR_BUFFER_INITIALIZER;
bool bj = qemuDomainHasBlockjob(vm, false);
struct qemuDomainPrivateBlockJobFormatData iterdata = { priv->driver->xmlopt,
&childBuf };
virBufferAsprintf(&attrBuf, " active='%s'",
virTristateBoolTypeToString(virTristateBoolFromBool(bj)));
virBufferSetChildIndent(&childBuf, buf);
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) &&
virHashForEach(priv->blockjobs,
qemuDomainObjPrivateXMLFormatBlockjobIterator,
&iterdata) < 0)
return -1;
return virXMLFormatElement(buf, "blockjobs", &attrBuf, &childBuf);
}
void
qemuDomainObjPrivateXMLFormatAllowReboot(virBufferPtr buf,
virTristateBool allowReboot)
{
virBufferAsprintf(buf, "\n",
virTristateBoolTypeToString(allowReboot));
}
static void
qemuDomainObjPrivateXMLFormatPR(virBufferPtr buf,
qemuDomainObjPrivatePtr priv)
{
if (priv->prDaemonRunning)
virBufferAddLit(buf, "\n");
}
static int
qemuDomainObjPrivateXMLFormatNBDMigrationSource(virBufferPtr buf,
virStorageSourcePtr src,
virDomainXMLOptionPtr xmlopt)
{
VIR_AUTOCLEAN(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
VIR_AUTOCLEAN(virBuffer) childBuf = VIR_BUFFER_INITIALIZER;
int ret = -1;
virBufferSetChildIndent(&childBuf, 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, xmlopt) < 0)
goto cleanup;
if (virXMLFormatElement(buf, "migrationSource", &attrBuf, &childBuf) < 0)
goto cleanup;
ret = 0;
cleanup:
return ret;
}
static int
qemuDomainObjPrivateXMLFormatNBDMigration(virBufferPtr buf,
virDomainObjPtr vm)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
VIR_AUTOCLEAN(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
VIR_AUTOCLEAN(virBuffer) childBuf = VIR_BUFFER_INITIALIZER;
size_t i;
virDomainDiskDefPtr disk;
qemuDomainDiskPrivatePtr diskPriv;
int ret = -1;
for (i = 0; i < vm->def->ndisks; i++) {
disk = vm->def->disks[i];
diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
virBufferSetChildIndent(&childBuf, buf);
virBufferAsprintf(&attrBuf, " dev='%s' migrating='%s'",
disk->dst, diskPriv->migrating ? "yes" : "no");
if (diskPriv->migrSource &&
qemuDomainObjPrivateXMLFormatNBDMigrationSource(&childBuf,
diskPriv->migrSource,
priv->driver->xmlopt) < 0)
goto cleanup;
if (virXMLFormatElement(buf, "disk", &attrBuf, &childBuf) < 0)
goto cleanup;
}
ret = 0;
cleanup:
return ret;
}
static int
qemuDomainObjPrivateXMLFormatJob(virBufferPtr buf,
virDomainObjPtr vm,
qemuDomainObjPrivatePtr priv)
{
VIR_AUTOCLEAN(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
VIR_AUTOCLEAN(virBuffer) childBuf = VIR_BUFFER_INITIALIZER;
qemuDomainJob job = priv->job.active;
int ret = -1;
if (!qemuDomainTrackJob(job))
job = QEMU_JOB_NONE;
if (job == QEMU_JOB_NONE &&
priv->job.asyncJob == QEMU_ASYNC_JOB_NONE)
return 0;
virBufferSetChildIndent(&childBuf, buf);
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)
goto cleanup;
if (priv->job.migParams)
qemuMigrationParamsFormat(&childBuf, priv->job.migParams);
if (virXMLFormatElement(buf, "job", &attrBuf, &childBuf) < 0)
goto cleanup;
ret = 0;
cleanup:
return ret;
}
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, "\n",
virDomainChrTypeToString(priv->monConfig->type));
}
if (priv->namespaces) {
ssize_t ns = -1;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
while ((ns = virBitmapNextSetBit(priv->namespaces, ns)) >= 0)
virBufferAsprintf(buf, "<%s/>\n", qemuDomainNamespaceTypeToString(ns));
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
qemuDomainObjPrivateXMLFormatVcpus(buf, vm->def);
if (priv->qemuCaps) {
size_t i;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
for (i = 0; i < QEMU_CAPS_LAST; i++) {
if (virQEMUCapsGet(priv->qemuCaps, i)) {
virBufferAsprintf(buf, "\n",
virQEMUCapsTypeToString(i));
}
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
if (priv->lockState)
virBufferAsprintf(buf, "%s\n", priv->lockState);
if (qemuDomainObjPrivateXMLFormatJob(buf, vm, priv) < 0)
return -1;
if (priv->fakeReboot)
virBufferAddLit(buf, "\n");
if (priv->qemuDevices && *priv->qemuDevices) {
char **tmp = priv->qemuDevices;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
while (*tmp) {
virBufferAsprintf(buf, "\n", *tmp);
tmp++;
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
if (qemuDomainObjPrivateXMLFormatAutomaticPlacement(buf, priv) < 0)
return -1;
/* Various per-domain paths */
virBufferEscapeString(buf, "\n", priv->libDir);
virBufferEscapeString(buf, "\n",
priv->channelTargetDir);
virCPUDefFormatBufFull(buf, priv->origCPU, NULL);
if (priv->chardevStdioLogd)
virBufferAddLit(buf, "\n");
if (priv->rememberOwner)
virBufferAddLit(buf, "\n");
qemuDomainObjPrivateXMLFormatAllowReboot(buf, priv->allowReboot);
qemuDomainObjPrivateXMLFormatPR(buf, priv);
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV))
virBufferAsprintf(buf, "\n", priv->nodenameindex);
if (priv->memPrealloc)
virBufferAddLit(buf, "\n");
if (qemuDomainObjPrivateXMLFormatBlockjobs(buf, vm) < 0)
return -1;
return 0;
}
static int
qemuDomainObjPrivateXMLParseVcpu(xmlNodePtr node,
unsigned int idx,
virDomainDefPtr def)
{
virDomainVcpuDefPtr vcpu;
char *idstr;
char *pidstr;
unsigned int tmp;
int ret = -1;
idstr = virXMLPropString(node, "id");
if (idstr &&
(virStrToLong_uip(idstr, NULL, 10, &idx) < 0)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse vcpu index '%s'"), idstr);
goto cleanup;
}
if (!(vcpu = virDomainDefGetVcpu(def, idx))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid vcpu index '%u'"), idx);
goto cleanup;
}
if (!(pidstr = virXMLPropString(node, "pid")))
goto cleanup;
if (virStrToLong_uip(pidstr, NULL, 10, &tmp) < 0)
goto cleanup;
QEMU_DOMAIN_VCPU_PRIVATE(vcpu)->tid = tmp;
ret = 0;
cleanup:
VIR_FREE(idstr);
VIR_FREE(pidstr);
return ret;
}
static int
qemuDomainObjPrivateXMLParseAutomaticPlacement(xmlXPathContextPtr ctxt,
qemuDomainObjPrivatePtr priv,
virQEMUDriverPtr driver)
{
virCapsPtr caps = NULL;
char *nodeset;
char *cpuset;
int nodesetSize = 0;
size_t i;
int ret = -1;
nodeset = virXPathString("string(./numad/@nodeset)", ctxt);
cpuset = virXPathString("string(./numad/@cpuset)", ctxt);
if (!nodeset && !cpuset)
return 0;
if (!(caps = virQEMUDriverGetCapabilities(driver, false)))
goto cleanup;
/* 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->host.nnumaCell; i++)
nodesetSize = MAX(nodesetSize, caps->host.numaCell[i]->num + 1);
if (nodeset &&
virBitmapParse(nodeset, &priv->autoNodeset, nodesetSize) < 0)
goto cleanup;
if (cpuset) {
if (virBitmapParse(cpuset, &priv->autoCpuset, VIR_DOMAIN_CPUMASK_LEN) < 0)
goto cleanup;
} else {
/* autoNodeset is present in this case, since otherwise we wouldn't
* reach this code */
if (!(priv->autoCpuset = virCapabilitiesGetCpusForNodemask(caps,
priv->autoNodeset)))
goto cleanup;
}
ret = 0;
cleanup:
virObjectUnref(caps);
VIR_FREE(nodeset);
VIR_FREE(cpuset);
return ret;
}
static virStorageSourcePtr
qemuDomainObjPrivateXMLParseBlockjobChain(xmlNodePtr node,
xmlXPathContextPtr ctxt,
virDomainXMLOptionPtr xmlopt)
{
VIR_XPATH_NODE_AUTORESTORE(ctxt);
VIR_AUTOFREE(char *) format = NULL;
VIR_AUTOFREE(char *) type = NULL;
VIR_AUTOFREE(char *) index = NULL;
VIR_AUTOUNREF(virStorageSourcePtr) 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;
VIR_RETURN_PTR(src);
}
static void
qemuDomainObjPrivateXMLParseBlockjobNodename(qemuBlockJobDataPtr job,
const char *xpath,
virStorageSourcePtr *src,
xmlXPathContextPtr ctxt)
{
VIR_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)
{
VIR_AUTOFREE(char *) createmode = 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;
ATTRIBUTE_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 (!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:
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;
VIR_AUTOUNREF(qemuBlockJobDataPtr) job = NULL;
VIR_AUTOFREE(char *) name = NULL;
VIR_AUTOFREE(char *) typestr = NULL;
int type;
VIR_AUTOFREE(char *) statestr = NULL;
int state = QEMU_BLOCKJOB_STATE_FAILED;
VIR_AUTOFREE(char *) diskdst = NULL;
VIR_AUTOFREE(char *) newstatestr = NULL;
VIR_AUTOFREE(char *) mirror = NULL;
int newstate = -1;
bool invalidData = false;
xmlNodePtr tmp;
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_NONE;
invalidData = true;
}
if (!(job = qemuBlockJobDataNew(type, name)))
return -1;
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 = virDomainDiskByName(vm->def, diskdst, false)))
invalidData = true;
if ((mirror = virXPathString("string(./disk/@mirror)", ctxt)) &&
STRNEQ(mirror, "yes"))
invalidData = 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;
}
job->state = state;
job->newstate = newstate;
job->errmsg = virXPathString("string(./errmsg)", ctxt);
job->invalidData = invalidData;
job->disk = disk;
if (mirror)
job->mirrorChain = virObjectRef(job->disk->mirror);
qemuDomainObjPrivateXMLParseBlockjobDataSpecific(job, ctxt, xmlopt);
if (qemuBlockJobRegister(job, vm, disk, false) < 0)
return -1;
return 0;
}
static int
qemuDomainObjPrivateXMLParseBlockjobs(virDomainObjPtr vm,
qemuDomainObjPrivatePtr priv,
xmlXPathContextPtr ctxt)
{
VIR_AUTOFREE(xmlNodePtr *) nodes = NULL;
ssize_t nnodes = 0;
VIR_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;
}
int
qemuDomainObjPrivateXMLParseAllowReboot(xmlXPathContextPtr ctxt,
virTristateBool *allowReboot)
{
int ret = -1;
int val;
char *valStr;
if ((valStr = virXPathString("string(./allowReboot/@value)", ctxt))) {
if ((val = virTristateBoolTypeFromString(valStr)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid allowReboot value '%s'"), valStr);
goto cleanup;
}
*allowReboot = val;
}
ret = 0;
cleanup:
VIR_FREE(valStr);
return ret;
}
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);
VIR_AUTOFREE(char *) format = NULL;
VIR_AUTOFREE(char *) type = NULL;
VIR_AUTOUNREF(virStorageSourcePtr) 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