/* * 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_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 "qemu_validate.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_driver.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" #include "virdevmapper.h" #ifdef __linux__ # include #endif #include #include #if defined(HAVE_SYS_MOUNT_H) # include #endif #ifdef WITH_SELINUX # include #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 { GObject 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; }; G_DEFINE_TYPE(qemuDomainLogContext, qemu_domain_log_context, G_TYPE_OBJECT); static virClassPtr qemuDomainSaveCookieClass; static void qemuDomainLogContextFinalize(GObject *obj); static void qemuDomainSaveCookieDispose(void *obj); static int qemuDomainOnceInit(void) { if (!VIR_CLASS_NEW(qemuDomainSaveCookie, virClassForObject())) return -1; return 0; } static void qemu_domain_log_context_init(qemuDomainLogContext *logctxt G_GNUC_UNUSED) { } static void qemu_domain_log_context_class_init(qemuDomainLogContextClass *klass) { GObjectClass *obj = G_OBJECT_CLASS(klass); obj->finalize = qemuDomainLogContextFinalize; } VIR_ONCE_GLOBAL_INIT(qemuDomain); static void qemuDomainLogContextFinalize(GObject *object) { qemuDomainLogContextPtr ctxt = QEMU_DOMAIN_LOG_CONTEXT(object); VIR_DEBUG("ctxt=%p", ctxt); virLogManagerFree(ctxt->manager); VIR_FREE(ctxt->path); VIR_FORCE_CLOSE(ctxt->writefd); VIR_FORCE_CLOSE(ctxt->readfd); G_OBJECT_CLASS(qemu_domain_log_context_parent_class)->finalize(object); } 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) { qemuDomainSecretInfoClear(secinfo, false); g_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; g_clear_pointer(&priv->secinfo, qemuDomainSecretInfoFree); g_clear_pointer(&priv->encinfo, qemuDomainSecretInfoFree); } 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; g_clear_pointer(&priv->secinfo, qemuDomainSecretInfoFree); } 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); g_clear_pointer(&priv->secinfo, qemuDomainSecretInfoFree); } 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 * @alias: alias of the secret * @username: username to use (may be NULL) * @secret: secret data * @secretlen: length of @secret * * Encrypts @secret for use with qemu. * * Returns qemuDomainSecretInfoPtr filled with the necessary information. */ static qemuDomainSecretInfoPtr qemuDomainSecretAESSetup(qemuDomainObjPrivatePtr priv, const char *alias, const char *username, uint8_t *secret, size_t secretlen) { g_autoptr(qemuDomainSecretInfo) secinfo = NULL; g_autofree uint8_t *raw_iv = NULL; size_t ivlen = QEMU_DOMAIN_AES_IV_LEN; g_autofree uint8_t *ciphertext = NULL; size_t ciphertextlen = 0; if (!qemuDomainSupportsEncryptedSecret(priv)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("encrypted secrets are not supported")); return NULL; } secinfo = g_new0(qemuDomainSecretInfo, 1); secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES; secinfo->s.aes.alias = g_strdup(alias); secinfo->s.aes.username = g_strdup(username); raw_iv = g_new0(uint8_t, ivlen); /* Create a random initialization vector */ if (virRandomBytes(raw_iv, ivlen) < 0) return NULL; /* Encode the IV and save that since qemu will need it */ secinfo->s.aes.iv = g_base64_encode(raw_iv, ivlen); if (virCryptoEncryptData(VIR_CRYPTO_CIPHER_AES256CBC, priv->masterKey, QEMU_DOMAIN_MASTER_KEY_LEN, raw_iv, ivlen, secret, secretlen, &ciphertext, &ciphertextlen) < 0) return NULL; /* Now encode the ciphertext and store to be passed to qemu */ secinfo->s.aes.ciphertext = g_base64_encode(ciphertext, ciphertextlen); return g_steal_pointer(&secinfo); } /** * qemuDomainSecretAESSetupFromSecret: * @priv: pointer to domain private object * @srcalias: Alias of the disk/hostdev used to generate the secret alias * @secretuse: specific usage for the secret (may be NULL if main object is using it) * @usageType: The virSecretUsageType * @username: username to use for authentication (may be NULL) * @seclookupdef: Pointer to seclookupdef data * * Looks up a secret in the secret driver based on @usageType and @seclookupdef * and builds qemuDomainSecretInfoPtr from it. @use describes the usage of the * secret in case if @srcalias requires more secrets for various usage cases. */ static qemuDomainSecretInfoPtr qemuDomainSecretAESSetupFromSecret(qemuDomainObjPrivatePtr priv, const char *srcalias, const char *secretuse, virSecretUsageType usageType, const char *username, virSecretLookupTypeDefPtr seclookupdef) { g_autoptr(virConnect) conn = virGetConnectSecret(); qemuDomainSecretInfoPtr secinfo; g_autofree char *alias = qemuAliasForSecret(srcalias, secretuse); uint8_t *secret = NULL; size_t secretlen = 0; if (!conn) return NULL; if (virSecretGetSecretString(conn, seclookupdef, usageType, &secret, &secretlen) < 0) return NULL; secinfo = qemuDomainSecretAESSetup(priv, alias, username, secret, secretlen); VIR_DISPOSE_N(secret, secretlen); return secinfo; } /** * 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) { g_clear_pointer(&secinfo, qemuDomainSecretInfoFree); 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 qemuDomainSecretAESSetupFromSecret(priv, srcAlias, NULL, VIR_SECRET_USAGE_TYPE_TLS, NULL, &seclookupdef); } 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; } static qemuDomainSecretInfoPtr qemuDomainSecretStorageSourcePrepareCookies(qemuDomainObjPrivatePtr priv, virStorageSourcePtr src, const char *aliasprotocol) { g_autofree char *secretalias = qemuAliasForSecret(aliasprotocol, "httpcookie"); g_autofree char *cookies = qemuBlockStorageSourceGetCookieString(src); return qemuDomainSecretAESSetup(priv, secretalias, NULL, (uint8_t *) cookies, strlen(cookies)); } /** * 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 *aliasprotocol, const char *aliasformat) { qemuDomainStorageSourcePrivatePtr srcPriv; bool iscsiHasPS = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_ISCSI_PASSWORD_SECRET); bool hasAuth = qemuDomainStorageSourceHasAuth(src); bool hasEnc = qemuDomainDiskHasEncryptionSecret(src); if (!hasAuth && !hasEnc && src->ncookies == 0) 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 = qemuDomainSecretAESSetupFromSecret(priv, aliasprotocol, "auth", usageType, src->auth->username, &src->auth->seclookupdef); } if (!srcPriv->secinfo) return -1; } if (hasEnc) { if (!(srcPriv->encinfo = qemuDomainSecretAESSetupFromSecret(priv, aliasformat, "encryption", VIR_SECRET_USAGE_TYPE_VOLUME, NULL, &src->encryption->secrets[0]->seclookupdef))) return -1; } if (src->ncookies && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) && !(srcPriv->httpcookie = qemuDomainSecretStorageSourcePrepareCookies(priv, src, aliasprotocol))) 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) g_clear_pointer(&srcPriv->secinfo, qemuDomainSecretInfoFree); } } } /* 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) { bool iscsiHasPS = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_ISCSI_PASSWORD_SECRET); virSecretUsageType usageType = VIR_SECRET_USAGE_TYPE_ISCSI; qemuDomainStorageSourcePrivatePtr srcPriv; if (!(src->privateData = qemuDomainStorageSourcePrivateNew())) return -1; srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); if (!qemuDomainSupportsEncryptedSecret(priv) || !iscsiHasPS) { srcPriv->secinfo = qemuDomainSecretInfoNewPlain(usageType, src->auth->username, &src->auth->seclookupdef); } else { srcPriv->secinfo = qemuDomainSecretAESSetupFromSecret(priv, hostdev->info->alias, NULL, usageType, src->auth->username, &src->auth->seclookupdef); } if (!srcPriv->secinfo) return -1; } } return 0; } void qemuDomainSecretChardevDestroy(virDomainChrSourceDefPtr dev) { qemuDomainChrSourcePrivatePtr chrSourcePriv = QEMU_DOMAIN_CHR_SOURCE_PRIVATE(dev); if (!chrSourcePriv || !chrSourcePriv->secinfo) return; g_clear_pointer(&chrSourcePriv->secinfo, qemuDomainSecretInfoFree); } /* 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); g_clear_pointer(&gfxPriv->secinfo, qemuDomainSecretInfoFree); } 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; } int qemuDomainObjStartWorker(virDomainObjPtr dom) { qemuDomainObjPrivatePtr priv = dom->privateData; if (!priv->eventThread) { g_autofree char *threadName = g_strdup_printf("vm-%s", dom->def->name); if (!(priv->eventThread = virEventThreadNew(threadName))) return -1; } return 0; } void qemuDomainObjStopWorker(virDomainObjPtr dom) { qemuDomainObjPrivatePtr priv = dom->privateData; if (priv->eventThread) { g_object_unref(priv->eventThread); priv->eventThread = NULL; } } 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; /* 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); virObjectUnref(priv->pflash0); priv->pflash0 = NULL; virObjectUnref(priv->pflash1); priv->pflash1 = NULL; virDomainBackupDefFree(priv->backup); priv->backup = NULL; /* reset node name allocator */ qemuDomainStorageIdReset(priv); priv->dbusDaemonRunning = false; virStringListFree(priv->dbusVMStateIds); priv->dbusVMStateIds = NULL; priv->dbusVMState = false; } 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); g_clear_pointer(&priv->migSecinfo, qemuDomainSecretInfoFree); qemuDomainMasterKeyFree(priv); virHashFree(priv->blockjobs); /* This should never be non-NULL if we get here, but just in case... */ if (priv->eventThread) { VIR_ERROR(_("Unexpected event thread still active during domain deletion")); g_object_unref(priv->eventThread); } 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; g_autofree char *httpcookiealias = 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); httpcookiealias = virXPathString("string(./objects/secret[@type='httpcookie']/@alias)", ctxt); if (authalias || encalias || httpcookiealias) { 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 (qemuStorageSourcePrivateDataAssignSecinfo(&priv->httpcookie, &httpcookiealias) < 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, "\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, "\n", src->nodestorage); virBufferEscapeString(&nodenamesChildBuf, "\n", src->nodeformat); if (src->sliceStorage) virBufferEscapeString(&nodenamesChildBuf, "\n", src->sliceStorage->nodename); virXMLFormatElement(buf, "nodenames", NULL, &nodenamesChildBuf); if (src->pr) virBufferAsprintf(buf, "\n", src->pr->mgralias); if (virStorageSourcePrivateDataFormatRelPath(src, buf) < 0) return -1; if (srcPriv) { qemuStorageSourcePrivateDataFormatSecinfo(&tmp, srcPriv->secinfo, "auth"); qemuStorageSourcePrivateDataFormatSecinfo(&tmp, srcPriv->encinfo, "encryption"); qemuStorageSourcePrivateDataFormatSecinfo(&tmp, srcPriv->httpcookie, "httpcookie"); } if (src->tlsAlias) virBufferAsprintf(&tmp, "\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, "\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) { 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, "\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 void qemuDomainPrivateBlockJobFormatCommit(qemuBlockJobDataPtr job, virBufferPtr buf) { g_auto(virBuffer) disabledBitmapsBuf = VIR_BUFFER_INIT_CHILD(buf); char **bitmaps = job->data.commit.disabledBitmapsBase; if (job->data.commit.base) virBufferAsprintf(buf, "\n", job->data.commit.base->nodeformat); if (job->data.commit.top) virBufferAsprintf(buf, "\n", job->data.commit.top->nodeformat); if (job->data.commit.topparent) virBufferAsprintf(buf, "\n", job->data.commit.topparent->nodeformat); if (job->data.commit.deleteCommittedImages) virBufferAddLit(buf, "\n"); while (bitmaps && *bitmaps) virBufferEscapeString(&disabledBitmapsBuf, "\n", *(bitmaps++)); virXMLFormatElement(buf, "disabledBaseBitmaps", NULL, &disabledBitmapsBuf); } 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, "%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; virXMLFormatElement(&childBuf, "chains", NULL, &chainsBuf); } 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: qemuDomainPrivateBlockJobFormatCommit(job, &childBuf); 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: if (job->data.copy.shallownew) virBufferAddLit(&attrBuf, " shallownew='yes'"); break; case QEMU_BLOCKJOB_TYPE_BACKUP: virBufferEscapeString(&childBuf, "\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, "\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) { 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, "\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, "\n", net->info.alias, slirp->pid); virBufferAdjustIndent(buf, 2); for (j = 0; j < QEMU_SLIRP_FEATURE_LAST; j++) { if (qemuSlirpHasFeature(slirp, j)) { virBufferAsprintf(buf, "\n", qemuSlirpFeatureTypeToString(j)); } } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\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, "\n", virDomainChrTypeToString(priv->monConfig->type)); } if (priv->dbusDaemonRunning) virBufferAddLit(buf, "\n"); if (priv->dbusVMState) virBufferAddLit(buf, "\n"); 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; if (qemuDomainObjPrivateXMLFormatSlirp(buf, vm) < 0) return -1; virBufferAsprintf(buf, "%i\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 int qemuDomainObjPrivateXMLParseBlockjobDataCommit(qemuBlockJobDataPtr job, xmlXPathContextPtr ctxt) { g_autofree xmlNodePtr *nodes = NULL; ssize_t nnodes; if (job->type == QEMU_BLOCKJOB_TYPE_COMMIT) { qemuDomainObjPrivateXMLParseBlockjobNodename(job, "string(./topparent/@node)", &job->data.commit.topparent, ctxt); if (!job->data.commit.topparent) return -1; } 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) return -1; if ((nnodes = virXPathNodeSet("./disabledBaseBitmaps/bitmap", ctxt, &nodes)) > 0) { size_t i; job->data.commit.disabledBitmapsBase = g_new0(char *, nnodes + 1); for (i = 0; i < nnodes; i++) { char *tmp; if (!(tmp = virXMLPropString(nodes[i], "name"))) return -1; job->data.commit.disabledBitmapsBase[i] = g_steal_pointer(&tmp); } } return 0; } 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: case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT: if (qemuDomainObjPrivateXMLParseBlockjobDataCommit(job, ctxt) < 0) 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 subelement instead of formatting the * source directly into */ 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; } priv->dbusDaemonRunning = virXPathBoolean("boolean(./dbusDaemon)", ctxt) > 0; priv->dbusVMState = virXPathBoolean("boolean(./dbusVMState)", ctxt) > 0; 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, "\n"); virBufferAdjustIndent(buf, 2); for (i = 0; i < cmd->num_args; i++) virBufferEscapeString(buf, "\n", cmd->args[i]); for (i = 0; i < cmd->num_env; i++) { virBufferAsprintf(buf, "env_name[i]); if (cmd->env_value[i]) virBufferEscapeString(buf, " value='%s'", cmd->env_value[i]); virBufferAddLit(buf, "/>\n"); } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); } static void qemuDomainDefNamespaceFormatXMLCaps(virBufferPtr buf, qemuDomainXmlNsDefPtr xmlns) { size_t i; if (!xmlns->ncapsadd && !xmlns->ncapsdel) return; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); for (i = 0; i < xmlns->ncapsadd; i++) virBufferEscapeString(buf, "\n", xmlns->capsadd[i]); for (i = 0; i < xmlns->ncapsdel; i++) virBufferEscapeString(buf, "\n", xmlns->capsdel[i]); virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\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); if (!machine) { virReportError(VIR_ERR_INVALID_ARG, _("could not get preferred machine for %s type=%s"), def->emulator, virDomainVirtTypeToString(def->virtType)); return -1; } 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; } int qemuDomainValidateActualNetDef(const virDomainNetDef *net, virQEMUCapsPtr qemuCaps) { /* * Validations that can only be properly checked at runtime (after * an 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 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; } 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; } } if (src->sslverify != VIR_TRISTATE_BOOL_ABSENT) { if (actualType != VIR_STORAGE_TYPE_NETWORK || (src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTPS && src->protocol != VIR_STORAGE_NET_PROTOCOL_FTPS)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("ssl verification is supported only with HTTPS/FTPS protocol")); return -1; } if (!src->detected && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("ssl verification setting is not supported by this QEMU binary")); return -1; } } if (src->ncookies > 0) { if (actualType != VIR_STORAGE_TYPE_NETWORK || (src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTPS && src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("http cookies are supported only with HTTP(S) protocol")); return -1; } if (!src->detected && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("http cookies are not supported by this QEMU binary")); return -1; } if (virStorageSourceNetCookiesValidate(src) < 0) return -1; } if (src->readahead > 0) { if (actualType != VIR_STORAGE_TYPE_NETWORK || (src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTPS && src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP && src->protocol != VIR_STORAGE_NET_PROTOCOL_FTP && src->protocol != VIR_STORAGE_NET_PROTOCOL_FTPS)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("readaehad is supported only with HTTP(S)/FTP(s) protocols")); return -1; } if (!src->detected && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("readahead setting is not supported with this QEMU binary")); return -1; } } if (src->timeout > 0) { if (actualType != VIR_STORAGE_TYPE_NETWORK || (src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTPS && src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP && src->protocol != VIR_STORAGE_NET_PROTOCOL_FTP && src->protocol != VIR_STORAGE_NET_PROTOCOL_FTPS)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("timeout is supported only with HTTP(S)/FTP(s) protocols")); return -1; } if (!src->detected && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("timeout setting is not supported with this QEMU binary")); return -1; } } if (src->query && (actualType != VIR_STORAGE_TYPE_NETWORK || (src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTPS && src->protocol != VIR_STORAGE_NET_PROTOCOL_HTTP))) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("query is supported only with HTTP(S) protocols")); return -1; } return 0; } /** * 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 * 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 " " 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) { authalias = g_strdup_printf("%s-secret0", disk->info.alias); if (qemuStorageSourcePrivateDataAssignSecinfo(&priv->secinfo, &authalias) < 0) return -1; } if (restoreEncSecret) { encalias = g_strdup_printf("%s-luks-secret0", disk->info.alias); 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 = qemuValidateDomainDef, .deviceValidateCallback = qemuValidateDomainDeviceDef, .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 (<= 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 . */ 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 (driver->privileged && (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 = QEMU_DOMAIN_LOG_CONTEXT(g_object_new(QEMU_TYPE_DOMAIN_LOG_CONTEXT, 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(driver->privileged); 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 && !driver->privileged && 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: g_clear_object(&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(driver->privileged))) 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 *qemuimgbin; size_t i; bool skipped = false; qemuimgbin = qemuFindQemuImgBinary(driver); if (qemuimgbin == NULL) { /* qemuFindQemuImgBinary set the error */ return -1; } for (i = 0; i < ndisks; i++) { g_autoptr(virCommand) cmd = virCommandNewArgList(qemuimgbin, "snapshot", op, name, NULL); /* 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; } virCommandAddArg(cmd, virDomainDiskGetSource(def->disks[i])); if (virCommandRun(cmd, 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, /* whether the image is the top image of the backing chain (e.g. disk source) */ QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN_TOP = 1 << 6, } 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; bool chain_top = flags & QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN_TOP; 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, chain_top) < 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 | QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN_TOP; 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 | QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN_TOP; 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 * @chainTop: @elem is top parent of backing chain * * 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. * * The @chainTop flag must be set if the @elem image is the topmost image of a * given backing chain or meant to become the topmost image (for e.g. * snapshots, or blockcopy or even in the end for active layer block commit, * where we discard the top of the backing chain so one of the intermediates * (the base) becomes the top of the chain). */ int qemuDomainStorageSourceAccessAllow(virQEMUDriverPtr driver, virDomainObjPtr vm, virStorageSourcePtr elem, bool readonly, bool newSource, bool chainTop) { 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; if (chainTop) flags |= QEMU_DOMAIN_STORAGE_SOURCE_ACCESS_CHAIN_TOP; 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; } static int qemuDomainNVDimmAlignSizePseries(virDomainDefPtr def, virDomainMemoryDefPtr mem) { /* For NVDIMMs in ppc64 in we want to align down the guest * visible space, instead of align up, to avoid writing * beyond the end of file by adding a potential 256MiB * to the user specified size. * * The label-size is mandatory for ppc64 as well, meaning that * the guest visible space will be target_size-label_size. * * Finally, target_size must include label_size. * * The above can be summed up as follows: * * target_size = AlignDown(target_size - label_size) + label_size */ unsigned long long ppc64AlignSize = qemuDomainGetMemorySizeAlignment(def); unsigned long long guestArea = mem->size - mem->labelsize; /* Align down guest_area. 256MiB is the minimum size. Error * out if target_size is smaller than 256MiB + label_size, * since aligning it up will cause QEMU errors. */ if (mem->size < (ppc64AlignSize + mem->labelsize)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("minimum target size for the NVDIMM " "must be 256MB plus the label size")); return -1; } guestArea = (guestArea/ppc64AlignSize) * ppc64AlignSize; mem->size = guestArea + mem->labelsize; return 0; } 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++) { if (def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM && ARCH_IS_PPC64(def->os.arch)) { if (qemuDomainNVDimmAlignSizePseries(def, def->mems[i]) < 0) return -1; } else { 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). */ int qemuDomainMemoryDeviceAlignSize(virDomainDefPtr def, virDomainMemoryDefPtr mem) { if (mem->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM && ARCH_IS_PPC64(def->os.arch)) { return qemuDomainNVDimmAlignSizePseries(def, mem); } else { mem->size = VIR_ROUND_UP(mem->size, qemuDomainGetMemorySizeAlignment(def)); } return 0; } /** * 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 " "(node=%d socket=%d die=%d core=%d thread=%d)", i, (unsigned long long)info[i].tid, info[i].node_id, info[i].socket_id, info[i].die_id, info[i].core_id, info[i].thread_id); } 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) { virReportSystemError(errno, _("Unable to make dir %s"), devicePath); 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) { VIR_AUTOSTRINGLIST targetPaths = NULL; size_t i; 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; if (virDevMapperGetTargets(next->path, &targetPaths) < 0 && errno != ENOSYS && errno != EBADF) { virReportSystemError(errno, _("Unable to get devmapper targets for %s"), next->path); return -1; } for (i = 0; targetPaths && targetPaths[i]; i++) { if (qemuDomainCreateDevice(targetPaths[i], 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; const 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++) { #if defined(__linux__) umount(devMountsSavePath[i]); #endif /* defined(__linux__) */ /* The path can be either a regular file or a dir. */ if (virFileIsDir(devMountsSavePath[i])) virFileDeleteTree(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; VIR_AUTOSTRINGLIST paths = NULL; size_t npaths = 0; bool hasNVMe = false; 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))) return -1; } else { VIR_AUTOSTRINGLIST targetPaths = NULL; if (virStorageSourceIsEmpty(next) || !virStorageSourceIsLocalStorage(next)) { /* Not creating device. Just continue. */ continue; } tmpPath = g_strdup(next->path); if (virDevMapperGetTargets(next->path, &targetPaths) < 0 && errno != ENOSYS && errno != EBADF) { virReportSystemError(errno, _("Unable to get devmapper targets for %s"), next->path); return -1; } if (virStringListMerge(&paths, &targetPaths) < 0) return -1; } if (virStringListAdd(&paths, tmpPath) < 0) return -1; } /* qemu-pr-helper might require access to /dev/mapper/control. */ if (src->pr && virStringListAdd(&paths, QEMU_DEVICE_MAPPER_CONTROL_PATH) < 0) return -1; if (hasNVMe && virStringListAdd(&paths, QEMU_DEV_VFIO) < 0) return -1; npaths = virStringListLength((const char **) paths); if (qemuDomainNamespaceMknodPaths(vm, (const char **) paths, npaths) < 0) return -1; return 0; } 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, "\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 = virDomainDriverGenerateMachineName("qemu", driver->embeddedRoot, vm->def->id, vm->def->name, driver->privileged); 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 (qemuBlockStorageSourceNeedsStorageSliceLayer(src)) 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; }