/* * 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 "qemu_namespace.h" #include "viralloc.h" #include "virlog.h" #include "virerror.h" #include "viridentity.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 "domain_validate.h" #include "virtime.h" #include "virnetdevbandwidth.h" #include "virnetdevopenvswitch.h" #include "virstoragefile.h" #include "storage_source.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 "virqemu.h" #include "virsecureerase.h" #include #include #define QEMU_QXL_VGAMEM_DEFAULT 16 * 1024 #define VIR_FROM_THIS VIR_FROM_QEMU VIR_LOG_INIT("qemu.qemu_domain"); static void * qemuJobAllocPrivate(void) { return g_new0(qemuDomainJobPrivate, 1); } void qemuDomainJobPrivateMigrateTempBitmapFree(qemuDomainJobPrivateMigrateTempBitmap *bmp) { if (!bmp) return; g_free(bmp->nodename); g_free(bmp->bitmapname); g_free(bmp); } static void qemuJobFreePrivate(void *opaque) { qemuDomainJobPrivate *priv = opaque; if (!priv) return; qemuMigrationParamsFree(priv->migParams); if (priv->migTempBitmaps) g_slist_free_full(priv->migTempBitmaps, (GDestroyNotify) qemuDomainJobPrivateMigrateTempBitmapFree); g_free(priv); } static void qemuJobResetPrivate(void *opaque) { qemuDomainJobPrivate *priv = opaque; priv->spiceMigration = false; priv->spiceMigrated = false; priv->dumpCompleted = false; qemuMigrationParamsFree(priv->migParams); priv->migParams = NULL; } static int qemuDomainObjPrivateXMLFormatNBDMigrationSource(virBuffer *buf, virStorageSource *src, virDomainXMLOption *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, false, false, xmlopt) < 0) return -1; virXMLFormatElement(buf, "migrationSource", &attrBuf, &childBuf); return 0; } static int qemuDomainObjPrivateXMLFormatNBDMigration(virBuffer *buf, virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; size_t i; virDomainDiskDef *disk; qemuDomainDiskPrivate *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 void qemuDomainObjPrivateXMLFormatMigrateTempBitmap(virBuffer *buf, GSList *bitmaps) { g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); GSList *next; for (next = bitmaps; next; next = next->next) { qemuDomainJobPrivateMigrateTempBitmap *t = next->data; g_auto(virBuffer) bitmapBuf = VIR_BUFFER_INITIALIZER; virBufferAsprintf(&bitmapBuf, " name='%s'", t->bitmapname); virBufferAsprintf(&bitmapBuf, " nodename='%s'", t->nodename); virXMLFormatElement(&childBuf, "bitmap", &bitmapBuf, NULL); } virXMLFormatElement(buf, "tempBlockDirtyBitmaps", NULL, &childBuf); } static int qemuDomainFormatJobPrivate(virBuffer *buf, qemuDomainJobObj *job, virDomainObj *vm) { qemuDomainJobPrivate *priv = job->privateData; if (job->asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) { if (qemuDomainObjPrivateXMLFormatNBDMigration(buf, vm) < 0) return -1; qemuDomainObjPrivateXMLFormatMigrateTempBitmap(buf, priv->migTempBitmaps); } if (priv->migParams) qemuMigrationParamsFormat(buf, priv->migParams); return 0; } static int qemuDomainObjPrivateXMLParseJobNBDSource(xmlNodePtr node, xmlXPathContextPtr ctxt, virDomainDiskDef *disk, virDomainXMLOption *xmlopt) { VIR_XPATH_NODE_AUTORESTORE(ctxt) qemuDomainDiskPrivate *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(virDomainObj *vm, xmlXPathContextPtr ctxt) { qemuDomainObjPrivate *priv = vm->privateData; 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++) { virDomainDiskDef *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 qemuDomainObjPrivateXMLParseMigrateTempBitmap(qemuDomainJobPrivate *jobPriv, xmlXPathContextPtr ctxt) { g_autoslist(qemuDomainJobPrivateMigrateTempBitmap) bitmaps = NULL; g_autofree xmlNodePtr *nodes = NULL; size_t i; int n; if ((n = virXPathNodeSet("./tempBlockDirtyBitmaps/bitmap", ctxt, &nodes)) < 0) return -1; if (n == 0) return 0; for (i = 0; i < n; i++) { qemuDomainJobPrivateMigrateTempBitmap *bmp; bmp = g_new0(qemuDomainJobPrivateMigrateTempBitmap, 1); bmp->nodename = virXMLPropString(nodes[i], "nodename"); bmp->bitmapname = virXMLPropString(nodes[i], "name"); bitmaps = g_slist_prepend(bitmaps, bmp); if (!bmp->bitmapname || !bmp->nodename) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed in status XML")); return -1; } } jobPriv->migTempBitmaps = g_slist_reverse(g_steal_pointer(&bitmaps)); return 0; } static int qemuDomainParseJobPrivate(xmlXPathContextPtr ctxt, qemuDomainJobObj *job, virDomainObj *vm) { qemuDomainJobPrivate *priv = job->privateData; if (qemuDomainObjPrivateXMLParseJobNBD(vm, ctxt) < 0) return -1; if (qemuDomainObjPrivateXMLParseMigrateTempBitmap(priv, ctxt) < 0) return -1; if (qemuMigrationParamsParse(ctxt, &priv->migParams) < 0) return -1; return 0; } static qemuDomainObjPrivateJobCallbacks qemuPrivateJobCallbacks = { .allocJobPrivate = qemuJobAllocPrivate, .freeJobPrivate = qemuJobFreePrivate, .resetJobPrivate = qemuJobResetPrivate, .formatJob = qemuDomainFormatJobPrivate, .parseJob = qemuDomainParseJobPrivate, }; /** * qemuDomainObjFromDomain: * @domain: Domain pointer that has to be looked up * * This function looks up @domain and returns the appropriate virDomainObj * * that has to be released by calling virDomainObjEndAPI(). * * Returns the domain object with incremented reference counter which is locked * on success, NULL otherwise. */ virDomainObj * qemuDomainObjFromDomain(virDomainPtr domain) { virDomainObj *vm; virQEMUDriver *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; virLogManager *manager; }; G_DEFINE_TYPE(qemuDomainLogContext, qemu_domain_log_context, G_TYPE_OBJECT); static virClass *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) { qemuDomainLogContext *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); } /* 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(virQEMUDriver *driver, virDomainObj *vm) { g_autofree char *path = NULL; VIR_AUTOCLOSE fd = -1; qemuDomainObjPrivate *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")); return -1; } if (safewrite(fd, priv->masterKey, priv->masterKeyLen) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("failed to write master key file for domain")); return -1; } if (qemuSecurityDomainSetPathLabel(driver, vm, path, false) < 0) return -1; return 0; } static void qemuDomainMasterKeyFree(qemuDomainObjPrivate *priv) { if (!priv->masterKey) return; virSecureErase(priv->masterKey, priv->masterKeyLen); g_clear_pointer(&priv->masterKey, g_free); } /* 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(qemuDomainObjPrivate *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; } masterKey = g_new0(uint8_t, 1024); 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; } masterKey = g_renew(uint8_t, 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(qemuDomainObjPrivate *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(virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; g_autofree uint8_t *key = NULL; /* If we don't have the capability, then do nothing. */ if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET)) return 0; key = g_new0(uint8_t, QEMU_DOMAIN_MASTER_KEY_LEN); if (virRandomBytes(key, QEMU_DOMAIN_MASTER_KEY_LEN) < 0) return -1; priv->masterKey = g_steal_pointer(&key); priv->masterKeyLen = QEMU_DOMAIN_MASTER_KEY_LEN; return 0; } static void qemuDomainSecretPlainClear(struct _qemuDomainSecretPlain *secret) { VIR_FREE(secret->username); virSecureErase(secret->secret, secret->secretlen); g_clear_pointer(&secret->secret, g_free); } static void qemuDomainSecretAESClear(struct _qemuDomainSecretAES *secret, bool keepAlias) { if (!keepAlias) VIR_FREE(secret->alias); VIR_FREE(secret->username); VIR_FREE(secret->iv); VIR_FREE(secret->ciphertext); } static void qemuDomainSecretInfoClear(qemuDomainSecretInfo *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(qemuDomainSecretInfo *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(qemuDomainSecretInfo *secinfo) { qemuDomainSecretInfoClear(secinfo, true); } static virClass *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 virObject * qemuDomainDiskPrivateNew(void) { qemuDomainDiskPrivate *priv; if (qemuDomainDiskPrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainDiskPrivateClass))) return NULL; return (virObject *) priv; } static void qemuDomainDiskPrivateDispose(void *obj) { qemuDomainDiskPrivate *priv = obj; virObjectUnref(priv->migrSource); g_free(priv->qomName); g_free(priv->nodeCopyOnRead); virObjectUnref(priv->blockjob); } static virClass *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); virObject * qemuDomainStorageSourcePrivateNew(void) { qemuDomainStorageSourcePrivate *priv; if (qemuDomainStorageSourcePrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainStorageSourcePrivateClass))) return NULL; return (virObject *) priv; } static void qemuDomainStorageSourcePrivateDispose(void *obj) { qemuDomainStorageSourcePrivate *priv = obj; g_clear_pointer(&priv->secinfo, qemuDomainSecretInfoFree); g_clear_pointer(&priv->encinfo, qemuDomainSecretInfoFree); g_clear_pointer(&priv->httpcookie, qemuDomainSecretInfoFree); g_clear_pointer(&priv->tlsKeySecret, qemuDomainSecretInfoFree); } qemuDomainStorageSourcePrivate * qemuDomainStorageSourcePrivateFetch(virStorageSource *src) { if (!src->privateData) src->privateData = qemuDomainStorageSourcePrivateNew(); return QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); } static virClass *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 virObject * qemuDomainVcpuPrivateNew(void) { qemuDomainVcpuPrivate *priv; if (qemuDomainVcpuPrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainVcpuPrivateClass))) return NULL; return (virObject *) priv; } static void qemuDomainVcpuPrivateDispose(void *obj) { qemuDomainVcpuPrivate *priv = obj; g_free(priv->type); g_free(priv->alias); virJSONValueFree(priv->props); return; } static virClass *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 virObject * qemuDomainChrSourcePrivateNew(void) { qemuDomainChrSourcePrivate *priv; if (qemuDomainChrSourcePrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainChrSourcePrivateClass))) return NULL; return (virObject *) priv; } static void qemuDomainChrSourcePrivateDispose(void *obj) { qemuDomainChrSourcePrivate *priv = obj; g_clear_pointer(&priv->secinfo, qemuDomainSecretInfoFree); } static virClass *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 virObject * qemuDomainVsockPrivateNew(void) { qemuDomainVsockPrivate *priv; if (qemuDomainVsockPrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainVsockPrivateClass))) return NULL; priv->vhostfd = -1; return (virObject *) priv; } static void qemuDomainVsockPrivateDispose(void *obj G_GNUC_UNUSED) { qemuDomainVsockPrivate *priv = obj; VIR_FORCE_CLOSE(priv->vhostfd); } static virClass *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 virObject * qemuDomainGraphicsPrivateNew(void) { qemuDomainGraphicsPrivate *priv; if (qemuDomainGraphicsPrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainGraphicsPrivateClass))) return NULL; return (virObject *) priv; } static void qemuDomainGraphicsPrivateDispose(void *obj) { qemuDomainGraphicsPrivate *priv = obj; g_free(priv->tlsAlias); g_clear_pointer(&priv->secinfo, qemuDomainSecretInfoFree); } static virClass *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 virObject * qemuDomainNetworkPrivateNew(void) { qemuDomainNetworkPrivate *priv; if (qemuDomainNetworkPrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainNetworkPrivateClass))) return NULL; return (virObject *) priv; } static void qemuDomainNetworkPrivateDispose(void *obj G_GNUC_UNUSED) { qemuDomainNetworkPrivate *priv = obj; qemuSlirpFree(priv->slirp); } static virClass *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 virObject * qemuDomainFSPrivateNew(void) { qemuDomainFSPrivate *priv; if (qemuDomainFSPrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainFSPrivateClass))) return NULL; return (virObject *) priv; } static void qemuDomainFSPrivateDispose(void *obj) { qemuDomainFSPrivate *priv = obj; g_free(priv->vhostuser_fs_sock); } static virClass *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 virObject * qemuDomainVideoPrivateNew(void) { qemuDomainVideoPrivate *priv; if (qemuDomainVideoPrivateInitialize() < 0) return NULL; if (!(priv = virObjectNew(qemuDomainVideoPrivateClass))) return NULL; priv->vhost_user_fd = -1; return (virObject *) priv; } static void qemuDomainVideoPrivateDispose(void *obj) { qemuDomainVideoPrivate *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(qemuDomainSecretInfo *secinfo, virSecretUsageType usageType, const char *username, virSecretLookupTypeDef *seclookupdef) { VIR_IDENTITY_AUTORESTORE virIdentity *oldident = virIdentityElevateCurrent(); g_autoptr(virConnect) conn = virGetConnectSecret(); int ret = -1; if (!oldident) return -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 qemuDomainSecretInfo *filled with the necessary information. */ static qemuDomainSecretInfo * qemuDomainSecretAESSetup(qemuDomainObjPrivate *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 qemuDomainSecretInfo *from it. @use describes the usage of the * secret in case if @srcalias requires more secrets for various usage cases. */ static qemuDomainSecretInfo * qemuDomainSecretAESSetupFromSecret(qemuDomainObjPrivate *priv, const char *srcalias, const char *secretuse, virSecretUsageType usageType, const char *username, virSecretLookupTypeDef *seclookupdef) { qemuDomainSecretInfo *secinfo; g_autofree char *alias = qemuAliasForSecret(srcalias, secretuse); g_autofree uint8_t *secret = NULL; size_t secretlen = 0; VIR_IDENTITY_AUTORESTORE virIdentity *oldident = virIdentityElevateCurrent(); g_autoptr(virConnect) conn = virGetConnectSecret(); if (!oldident) return NULL; if (!conn) return NULL; if (virSecretGetSecretString(conn, seclookupdef, usageType, &secret, &secretlen) < 0) return NULL; secinfo = qemuDomainSecretAESSetup(priv, alias, username, secret, secretlen); virSecureErase(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(qemuDomainObjPrivate *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 qemuDomainSecretInfo * qemuDomainSecretInfoNewPlain(virSecretUsageType usageType, const char *username, virSecretLookupTypeDef *lookupDef) { qemuDomainSecretInfo *secinfo = NULL; secinfo = g_new0(qemuDomainSecretInfo, 1); 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 qemuDomainSecretInfo *for a TLS based secret. * * Returns qemuDomainSecretInfo *or NULL on error. */ qemuDomainSecretInfo * qemuDomainSecretInfoTLSNew(qemuDomainObjPrivate *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(virDomainDiskDef *disk) { qemuDomainStorageSourcePrivate *srcPriv; virStorageSource *n; for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) { if ((srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(n))) { qemuDomainSecretInfoDestroy(srcPriv->secinfo); qemuDomainSecretInfoDestroy(srcPriv->encinfo); qemuDomainSecretInfoDestroy(srcPriv->tlsKeySecret); } } } bool qemuDomainStorageSourceHasAuth(virStorageSource *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; } static bool qemuDomainDiskHasEncryptionSecret(virStorageSource *src) { if (!virStorageSourceIsEmpty(src) && src->encryption && src->encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS && src->encryption->nsecrets > 0) return true; return false; } static qemuDomainSecretInfo * qemuDomainSecretStorageSourcePrepareCookies(qemuDomainObjPrivate *priv, virStorageSource *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(qemuDomainObjPrivate *priv, virStorageSource *src, const char *aliasprotocol, const char *aliasformat) { qemuDomainStorageSourcePrivate *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(virDomainHostdevDef *hostdev) { qemuDomainStorageSourcePrivate *srcPriv; if (virHostdevIsSCSIDevice(hostdev)) { virDomainHostdevSubsysSCSI *scsisrc = &hostdev->source.subsys.u.scsi; virDomainHostdevSubsysSCSIiSCSI *iscsisrc = &scsisrc->u.iscsi; if (scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI) { srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(iscsisrc->src); if (srcPriv) qemuDomainSecretInfoDestroy(srcPriv->secinfo); } } } void qemuDomainSecretChardevDestroy(virDomainChrSourceDef *dev) { qemuDomainChrSourcePrivate *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(virQEMUDriverConfig *cfg, qemuDomainObjPrivate *priv, const char *chrAlias, virDomainChrSourceDef *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) { qemuDomainChrSourcePrivate *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(virDomainGraphicsDef *graphics) { qemuDomainGraphicsPrivate *gfxPriv = QEMU_DOMAIN_GRAPHICS_PRIVATE(graphics); if (!gfxPriv) return; VIR_FREE(gfxPriv->tlsAlias); g_clear_pointer(&gfxPriv->secinfo, qemuDomainSecretInfoFree); } static int qemuDomainSecretGraphicsPrepare(virQEMUDriverConfig *cfg, qemuDomainObjPrivate *priv, virDomainGraphicsDef *graphics) { virQEMUCaps *qemuCaps = priv->qemuCaps; qemuDomainGraphicsPrivate *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(virDomainObj *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(virQEMUDriver *driver, virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); size_t i; /* disk and hostdev secrets are prepared when preparing internal data */ 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(virQEMUDriver *driver, virDomainObj *vm) { qemuDomainObjPrivate *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(virQEMUDriver *driver, virDomainObj *vm) { g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); qemuDomainObjPrivate *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(virDomainObj *dom) { qemuDomainObjPrivate *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(virDomainObj *dom) { qemuDomainObjPrivate *priv = dom->privateData; virEventThread *eventThread; if (!priv->eventThread) return; /* * We are dropping the only reference here so that the event loop thread * is going to be exited synchronously. In order to avoid deadlocks we * need to unlock the VM so that any handler being called can finish * execution and thus even loop thread be finished too. */ eventThread = g_steal_pointer(&priv->eventThread); virObjectUnlock(dom); g_object_unref(eventThread); virObjectLock(dom); } /** * qemuDomainObjPrivateDataClear: * @priv: domain private data * * Clears private data entries, which are not necessary or stale if the VM is * not running. */ void qemuDomainObjPrivateDataClear(qemuDomainObjPrivate *priv) { g_strfreev(priv->qemuDevices); priv->qemuDevices = NULL; virCgroupFree(priv->cgroup); priv->cgroup = NULL; 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; if (priv->dbusVMStateIds) g_slist_free_full(g_steal_pointer(&priv->dbusVMStateIds), g_free); priv->dbusVMState = false; } static void qemuDomainObjPrivateFree(void *data) { qemuDomainObjPrivate *priv = data; qemuDomainObjPrivateDataClear(priv); virObjectUnref(priv->monConfig); qemuDomainObjClearJob(&priv->job); g_free(priv->lockState); g_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); } g_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); } g_free(priv); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuDomainObjPrivate, qemuDomainObjPrivateFree); static void * qemuDomainObjPrivateAlloc(void *opaque) { g_autoptr(qemuDomainObjPrivate) priv = g_new0(qemuDomainObjPrivate, 1); if (qemuDomainObjInitJob(&priv->job, &qemuPrivateJobCallbacks) < 0) { virReportSystemError(errno, "%s", _("Unable to init qemu driver mutexes")); return NULL; } if (!(priv->devs = virChrdevAlloc())) return NULL; priv->blockjobs = virHashNew(virObjectFreeHashData); /* 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 g_steal_pointer(&priv); } static int qemuStorageSourcePrivateDataAssignSecinfo(qemuDomainSecretInfo **secinfo, char **alias) { if (!*alias) return 0; if (!*secinfo) { *secinfo = g_new0(qemuDomainSecretInfo, 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, virStorageSource *src) { qemuDomainStorageSourcePrivate *priv; g_autofree char *authalias = NULL; g_autofree char *encalias = NULL; g_autofree char *httpcookiealias = NULL; g_autofree char *tlskeyalias = NULL; g_autofree char *thresholdEventWithIndex = 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); tlskeyalias = virXPathString("string(./objects/secret[@type='tlskey']/@alias)", ctxt); if (authalias || encalias || httpcookiealias || tlskeyalias) { 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 (qemuStorageSourcePrivateDataAssignSecinfo(&priv->tlsKeySecret, &tlskeyalias) < 0) return -1; } if (virStorageSourcePrivateDataParseRelPath(ctxt, src) < 0) return -1; if ((thresholdEventWithIndex = virXPathString("string(./thresholdEvent/@indexUsed)", ctxt)) && virTristateBoolTypeFromString(thresholdEventWithIndex) == VIR_TRISTATE_BOOL_YES) src->thresholdEventWithIndex = true; return 0; } static void qemuStorageSourcePrivateDataFormatSecinfo(virBuffer *buf, qemuDomainSecretInfo *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(virStorageSource *src, virBuffer *buf) { g_auto(virBuffer) tmp = VIR_BUFFER_INIT_CHILD(buf); qemuDomainStorageSourcePrivate *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"); qemuStorageSourcePrivateDataFormatSecinfo(&tmp, srcPriv->tlsKeySecret, "tlskey"); } if (src->tlsAlias) virBufferAsprintf(&tmp, "\n", src->tlsAlias); virXMLFormatElement(buf, "objects", NULL, &tmp); if (src->thresholdEventWithIndex) virBufferAddLit(buf, "\n"); return 0; } static int qemuDomainDiskPrivateParse(xmlXPathContextPtr ctxt, virDomainDiskDef *disk) { qemuDomainDiskPrivate *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(virDomainDiskDef *disk, virBuffer *buf) { qemuDomainDiskPrivate *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(virBuffer *buf, virDomainDef *def) { size_t i; size_t maxvcpus = virDomainDefGetVcpusMax(def); virDomainVcpuDef *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(virBuffer *buf, qemuDomainObjPrivate *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 { virDomainXMLOption *xmlopt; virBuffer *buf; } qemuDomainPrivateBlockJobFormatData; static int qemuDomainObjPrivateXMLFormatBlockjobFormatSource(virBuffer *buf, const char *element, virStorageSource *src, virDomainXMLOption *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, false, false, xmlopt) < 0) return -1; if (chain && virDomainDiskBackingStoreFormat(&childBuf, src, xmlopt, xmlflags) < 0) return -1; virXMLFormatElement(buf, element, &attrBuf, &childBuf); return 0; } static void qemuDomainPrivateBlockJobFormatCommit(qemuBlockJobData *job, virBuffer *buf) { g_auto(virBuffer) disabledBitmapsBuf = VIR_BUFFER_INIT_CHILD(buf); 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"); virXMLFormatElement(buf, "disabledBaseBitmaps", NULL, &disabledBitmapsBuf); } static int qemuDomainObjPrivateXMLFormatBlockjobIterator(void *payload, const char *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); qemuBlockJobData *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(virBuffer *buf, virDomainObj *vm) { qemuDomainObjPrivate *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) && virHashForEachSorted(priv->blockjobs, qemuDomainObjPrivateXMLFormatBlockjobIterator, &iterdata) < 0) return -1; virXMLFormatElement(buf, "blockjobs", &attrBuf, &childBuf); return 0; } static int qemuDomainObjPrivateXMLFormatBackups(virBuffer *buf, virDomainObj *vm) { qemuDomainObjPrivate *priv = vm->privateData; g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf); if (priv->backup && virDomainBackupDefFormat(&childBuf, priv->backup, true) < 0) return -1; virXMLFormatElement(buf, "backups", &attrBuf, &childBuf); return 0; } void qemuDomainObjPrivateXMLFormatAllowReboot(virBuffer *buf, virTristateBool allowReboot) { virBufferAsprintf(buf, "\n", virTristateBoolTypeToString(allowReboot)); } static void qemuDomainObjPrivateXMLFormatPR(virBuffer *buf, qemuDomainObjPrivate *priv) { if (priv->prDaemonRunning) virBufferAddLit(buf, "\n"); } static bool qemuDomainHasSlirp(virDomainObj *vm) { size_t i; for (i = 0; i < vm->def->nnets; i++) { virDomainNetDef *net = vm->def->nets[i]; if (QEMU_DOMAIN_NETWORK_PRIVATE(net)->slirp) return true; } return false; } static bool qemuDomainGetSlirpHelperOk(virDomainObj *vm) { size_t i; for (i = 0; i < vm->def->nnets; i++) { virDomainNetDef *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(virBuffer *buf, virDomainObj *vm) { size_t i; if (!qemuDomainHasSlirp(vm)) return 0; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); for (i = 0; i < vm->def->nnets; i++) { virDomainNetDef *net = vm->def->nets[i]; qemuSlirp *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(virBuffer *buf, virDomainObj *vm) { qemuDomainObjPrivate *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) < 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, virDomainDef *def) { virDomainVcpuDef *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, qemuDomainObjPrivate *priv) { 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 = virCapabilitiesHostNUMANewHost())) 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++) { virCapsHostNUMACell *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 virStorageSource * qemuDomainObjPrivateXMLParseBlockjobChain(xmlNodePtr node, xmlXPathContextPtr ctxt, virDomainXMLOption *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); } /** * qemuDomainVirStorageSourceFindByNodeName: * @top: backing chain top * @nodeName: node name to find in backing chain * * Looks up the given storage source in the backing chain and returns the * pointer to it. * On failure NULL is returned and no error is reported. */ static virStorageSource * qemuDomainVirStorageSourceFindByNodeName(virStorageSource *top, const char *nodeName) { virStorageSource *tmp; for (tmp = top; virStorageSourceIsBacking(tmp); tmp = tmp->backingStore) { if ((tmp->nodeformat && STREQ(tmp->nodeformat, nodeName)) || (tmp->nodestorage && STREQ(tmp->nodestorage, nodeName))) return tmp; } return NULL; } static void qemuDomainObjPrivateXMLParseBlockjobNodename(qemuBlockJobData *job, const char *xpath, virStorageSource **src, xmlXPathContextPtr ctxt) { g_autofree char *nodename = NULL; *src = NULL; if (!(nodename = virXPathString(xpath, ctxt))) return; if (job->disk && (*src = qemuDomainVirStorageSourceFindByNodeName(job->disk->src, nodename))) return; if (job->chain && (*src = qemuDomainVirStorageSourceFindByNodeName(job->chain, nodename))) return; if (job->mirrorChain && (*src = qemuDomainVirStorageSourceFindByNodeName(job->mirrorChain, nodename))) 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(qemuBlockJobData *job, xmlXPathContextPtr ctxt) { 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; return 0; } static void qemuDomainObjPrivateXMLParseBlockjobDataSpecific(qemuBlockJobData *job, xmlXPathContextPtr ctxt, virDomainXMLOption *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(virDomainObj *vm, xmlNodePtr node, xmlXPathContextPtr ctxt, virDomainXMLOption *xmlopt) { VIR_XPATH_NODE_AUTORESTORE(ctxt) virDomainDiskDef *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(virDomainObj *vm, qemuDomainObjPrivate *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(qemuDomainObjPrivate *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 qemuDomainObjPrivateXMLParseSlirpFeatures(xmlNodePtr featuresNode, xmlXPathContextPtr ctxt, qemuSlirp *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, virDomainObj *vm, virDomainDefParserConfig *config) { qemuDomainObjPrivate *priv = vm->privateData; virQEMUDriver *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, 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 */ priv->qemuDevices = g_new0(char *, n + 1); 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) < 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, false) < 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; g_strfreev(priv->qemuDevices); priv->qemuDevices = NULL; return -1; } static void * qemuDomainObjPrivateXMLGetParseOpaque(virDomainObj *vm) { qemuDomainObjPrivate *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(qemuDomainXmlNsDef *def) { size_t i; if (!def) return; for (i = 0; i < def->num_env; i++) { g_free(def->env[i].name); g_free(def->env[i].value); } g_free(def->env); g_strfreev(def->args); g_strfreev(def->capsadd); g_strfreev(def->capsdel); g_free(def->deprecationBehavior); g_free(def); } static void qemuDomainDefNamespaceFree(void *nsdata) { qemuDomainXmlNsDef *cmd = nsdata; qemuDomainXmlNsDefFree(cmd); } static int qemuDomainDefNamespaceParseCommandlineArgs(qemuDomainXmlNsDef *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; nsdef->args = g_new0(char *, nnodes + 1); for (i = 0; i < nnodes; i++) { if (!(nsdef->args[i] = virXMLPropString(nodes[i], "value"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("No qemu command-line argument specified")); return -1; } } return 0; } static int qemuDomainDefNamespaceParseCommandlineEnvValidate(qemuDomainXmlNsEnvTuple *env) { if (!env->name) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("No qemu environment name specified")); return -1; } if (!g_ascii_isalpha(env->name[0]) && env->name[0] != '_') { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid environment name, it must begin with a letter or underscore")); return -1; } if (strspn(env->name, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_") != strlen(env->name)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Invalid environment name, it must contain only alphanumerics and underscore")); return -1; } return 0; } static int qemuDomainDefNamespaceParseCommandlineEnv(qemuDomainXmlNsDef *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; nsdef->env = g_new0(qemuDomainXmlNsEnvTuple, nnodes); for (i = 0; i < nnodes; i++) { qemuDomainXmlNsEnvTuple *env = nsdef->env + i; env->name = virXMLPropString(nodes[i], "name"); env->value = virXMLPropString(nodes[i], "value"); nsdef->num_env++; if (qemuDomainDefNamespaceParseCommandlineEnvValidate(env) < 0) return -1; } return 0; } static int qemuDomainDefNamespaceParseCaps(qemuDomainXmlNsDef *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) { nsdef->capsadd = g_new0(char *, nnodesadd + 1); for (i = 0; i < nnodesadd; i++) { if (!(nsdef->capsadd[i] = virXMLPropString(nodesadd[i], "capability"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing capability name")); return -1; } } } if (nnodesdel > 0) { nsdef->capsdel = g_new0(char *, nnodesdel + 1); for (i = 0; i < nnodesdel; i++) { if (!(nsdef->capsdel[i] = virXMLPropString(nodesdel[i], "capability"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing capability name")); return -1; } } } return 0; } static int qemuDomainDefNamespaceParse(xmlXPathContextPtr ctxt, void **data) { qemuDomainXmlNsDef *nsdata = NULL; int ret = -1; nsdata = g_new0(qemuDomainXmlNsDef, 1); if (qemuDomainDefNamespaceParseCommandlineArgs(nsdata, ctxt) < 0 || qemuDomainDefNamespaceParseCommandlineEnv(nsdata, ctxt) < 0 || qemuDomainDefNamespaceParseCaps(nsdata, ctxt) < 0) goto cleanup; nsdata->deprecationBehavior = virXPathString("string(./qemu:deprecation/@behavior)", ctxt); if (nsdata->args || nsdata->num_env > 0 || nsdata->capsadd || nsdata->capsdel || nsdata->deprecationBehavior) *data = g_steal_pointer(&nsdata); ret = 0; cleanup: qemuDomainDefNamespaceFree(nsdata); return ret; } static void qemuDomainDefNamespaceFormatXMLCommandline(virBuffer *buf, qemuDomainXmlNsDef *cmd) { GStrv n; size_t i; if (!cmd->args && !cmd->num_env) return; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); for (n = cmd->args; n && *n; n++) virBufferEscapeString(buf, "\n", *n); for (i = 0; i < cmd->num_env; i++) { virBufferAsprintf(buf, "env[i].name); virBufferEscapeString(buf, " value='%s'", cmd->env[i].value); virBufferAddLit(buf, "/>\n"); } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); } static void qemuDomainDefNamespaceFormatXMLCaps(virBuffer *buf, qemuDomainXmlNsDef *xmlns) { GStrv n; if (!xmlns->capsadd && !xmlns->capsdel) return; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); for (n = xmlns->capsadd; n && *n; n++) virBufferEscapeString(buf, "\n", *n); for (n = xmlns->capsdel; n && *n; n++) virBufferEscapeString(buf, "\n", *n); virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); } static int qemuDomainDefNamespaceFormatXML(virBuffer *buf, void *nsdata) { qemuDomainXmlNsDef *cmd = nsdata; qemuDomainDefNamespaceFormatXMLCommandline(buf, cmd); qemuDomainDefNamespaceFormatXMLCaps(buf, cmd); virBufferEscapeString(buf, "\n", cmd->deprecationBehavior); 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 qemuDomainDefSuggestDefaultAudioBackend(virQEMUDriver *driver, virDomainDef *def, bool *addAudio, int *audioBackend, int *audioSDLDriver) { size_t i; bool audioPassthrough = false; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); *addAudio = false; *audioBackend = VIR_DOMAIN_AUDIO_TYPE_NONE; *audioSDLDriver = VIR_DOMAIN_AUDIO_SDL_DRIVER_DEFAULT; for (i = 0; i < def->ngraphics; i++) { virDomainGraphicsDef *graph = def->graphics[i]; switch ((virDomainGraphicsType)graph->type) { case VIR_DOMAIN_GRAPHICS_TYPE_VNC: if (cfg->vncAllowHostAudio) { audioPassthrough = true; } else { audioPassthrough = false; *audioBackend = VIR_DOMAIN_AUDIO_TYPE_NONE; } *addAudio = true; break; case VIR_DOMAIN_GRAPHICS_TYPE_SPICE: audioPassthrough = false; *audioBackend = VIR_DOMAIN_AUDIO_TYPE_SPICE; *addAudio = true; break; case VIR_DOMAIN_GRAPHICS_TYPE_SDL: audioPassthrough = true; *addAudio = true; break; case VIR_DOMAIN_GRAPHICS_TYPE_RDP: case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP: case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS: break; case VIR_DOMAIN_GRAPHICS_TYPE_LAST: default: virReportEnumRangeError(virDomainGraphicsType, graph->type); return -1; } } if (!def->ngraphics) { if (cfg->nogfxAllowHostAudio) { audioPassthrough = true; } else { audioPassthrough = false; *audioBackend = VIR_DOMAIN_AUDIO_TYPE_NONE; } *addAudio = true; } if (*addAudio && audioPassthrough) { const char *audioenv = g_getenv("QEMU_AUDIO_DRV"); if (audioenv == NULL) { *addAudio = false; } else { /* * QEMU audio driver names are mostly the same as * libvirt XML audio backend names */ if (STREQ(audioenv, "pa")) { *audioBackend = VIR_DOMAIN_AUDIO_TYPE_PULSEAUDIO; } else { if (((*audioBackend) = virDomainAudioTypeTypeFromString(audioenv)) < 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unknown QEMU_AUDIO_DRV setting %s"), audioenv); return -1; } } } } if (*addAudio && *audioBackend == VIR_DOMAIN_AUDIO_TYPE_SDL) { const char *sdldriver = g_getenv("SDL_AUDIODRIVER"); if (sdldriver != NULL && (((*audioSDLDriver) = virDomainAudioSDLDriverTypeFromString(sdldriver)) <= 0)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unknown SDL_AUDIODRIVER setting %s"), sdldriver); return -1; } } return 0; } static int qemuDomainDefClearDefaultAudioBackend(virQEMUDriver *driver, virDomainDef *def) { bool addAudio; int audioBackend; int audioSDLDriver; virDomainAudioDef *audio; if (def->naudios != 1) { return 0; } if (qemuDomainDefSuggestDefaultAudioBackend(driver, def, &addAudio, &audioBackend, &audioSDLDriver) < 0) return -1; if (!addAudio) return 0; audio = def->audios[0]; if (audio->type != audioBackend) return 0; if (audio->type == VIR_DOMAIN_AUDIO_TYPE_SDL && audio->backend.sdl.driver != audioSDLDriver) return 0; virDomainAudioDefFree(audio); g_free(def->audios); def->naudios = 0; def->audios = NULL; return 0; } static int qemuDomainDefAddDefaultAudioBackend(virQEMUDriver *driver, virDomainDef *def) { bool addAudio; int audioBackend; int audioSDLDriver; if (def->naudios > 0) { return 0; } if (qemuDomainDefSuggestDefaultAudioBackend(driver, def, &addAudio, &audioBackend, &audioSDLDriver) < 0) return -1; if (addAudio) { virDomainAudioDef *audio = g_new0(virDomainAudioDef, 1); audio->type = audioBackend; audio->id = 1; def->naudios = 1; def->audios = g_new0(virDomainAudioDef *, def->naudios); def->audios[0] = audio; if (audioBackend == VIR_DOMAIN_AUDIO_TYPE_SDL) audio->backend.sdl.driver = audioSDLDriver; } return 0; } static int qemuDomainDefAddDefaultDevices(virQEMUDriver *driver, virDomainDef *def, virQEMUCaps *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: addDefaultUSB = false; addDefaultMemballoon = false; break; 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) { virDomainMemballoonDef *memballoon; memballoon = g_new0(virDomainMemballoonDef, 1); memballoon->model = VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO; def->memballoon = memballoon; } 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) { virDomainPanicDef *panic = g_new0(virDomainPanicDef, 1); VIR_APPEND_ELEMENT_COPY(def->panics, def->npanics, panic); } } if (qemuDomainDefAddDefaultAudioBackend(driver, def) < 0) 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(virDomainDef *def, virQEMUCaps *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(virDomainDef *def, virQEMUCaps *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(virDomainDef *def, virQEMUDriverConfig *cfg, unsigned int flags) { size_t i = 0; size_t j = 0; for (i = 0; i < def->ngraphics; ++i) { virDomainGraphicsDef *graphics = def->graphics[i]; for (j = 0; j < graphics->nListens; ++j) { virDomainGraphicsListenDef *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(virDomainDef *def) { unsigned int maxvcpus = virDomainDefGetVcpusMax(def); virDomainVcpuDef *vcpu; virDomainVcpuDef *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(virDomainDef *def, virArch hostarch, virQEMUCaps *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(virDomainDef *def, virQEMUCaps *qemuCaps) { virCPUFeatureDef *sveFeature = NULL; bool sveVectorLengthsProvided = false; size_t i; if (!def->cpu) return 0; if (def->cpu->cache) { virCPUCacheDef *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 && def->cpu->mode != VIR_CPU_MODE_MAXIMUM) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("CPU cache mode '%s' can only be used with " "'%s' / '%s' CPUs"), virCPUCacheModeTypeToString(cache->mode), virCPUModeTypeToString(VIR_CPU_MODE_HOST_PASSTHROUGH), virCPUModeTypeToString(VIR_CPU_MODE_MAXIMUM)); 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++) { virCPUFeatureDef *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 { VIR_RESIZE_N(def->cpu->features, def->cpu->nfeatures_max, def->cpu->nfeatures, 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++; } } /* Running domains were either started before QEMU_CAPS_CPU_MIGRATABLE was * introduced and thus we can't rely on it or they already have the * migratable default set. */ if (def->id == -1 && qemuCaps && def->cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH && def->cpu->migratable == VIR_TRISTATE_SWITCH_ABSENT) { if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_CPU_MIGRATABLE)) def->cpu->migratable = VIR_TRISTATE_SWITCH_ON; else if (ARCH_IS_X86(def->os.arch)) def->cpu->migratable = VIR_TRISTATE_SWITCH_OFF; } /* 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: case VIR_CPU_MODE_MAXIMUM: 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(virDomainDef *def, virQEMUCaps *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; } /** * qemuDomainDefNumaCPUsRectify: * @numa: pointer to numa definition * @maxCpus: number of CPUs this numa is supposed to have * * This function emulates the (to be deprecated) behavior of filling * up in node0 with the remaining CPUs, in case of an incomplete NUMA * setup, up to getVcpusMax. * * Returns: 0 on success, -1 on error */ int qemuDomainDefNumaCPUsRectify(virDomainDef *def, virQEMUCaps *qemuCaps) { unsigned int vcpusMax, numacpus; /* QEMU_CAPS_NUMA tells us if QEMU is able to handle disjointed * NUMA CPU ranges. The filling process will create a disjointed * setup in node0 most of the time. Do not proceed if QEMU * can't handle it.*/ if (virDomainNumaGetNodeCount(def->numa) == 0 || !virQEMUCapsGet(qemuCaps, QEMU_CAPS_NUMA)) return 0; vcpusMax = virDomainDefGetVcpusMax(def); numacpus = virDomainNumaGetCPUCountTotal(def->numa); if (numacpus < vcpusMax) { if (virDomainNumaFillCPUsInNode(def->numa, 0, vcpusMax) < 0) return -1; } return 0; } static int qemuDomainDefNumaCPUsPostParse(virDomainDef *def, virQEMUCaps *qemuCaps) { return qemuDomainDefNumaCPUsRectify(def, qemuCaps); } static int qemuDomainDefTPMsPostParse(virDomainDef *def) { virDomainTPMDef *proxyTPM = NULL; virDomainTPMDef *regularTPM = NULL; size_t i; for (i = 0; i < def->ntpms; i++) { virDomainTPMDef *tpm = def->tpms[i]; /* TPM 1.2 and 2 are not compatible, so we choose a specific version here */ if (tpm->version == VIR_DOMAIN_TPM_VERSION_DEFAULT) { if (tpm->model == VIR_DOMAIN_TPM_MODEL_SPAPR || tpm->model == VIR_DOMAIN_TPM_MODEL_CRB || qemuDomainIsARMVirt(def)) tpm->version = VIR_DOMAIN_TPM_VERSION_2_0; else tpm->version = VIR_DOMAIN_TPM_VERSION_1_2; } if (tpm->model == VIR_DOMAIN_TPM_MODEL_SPAPR_PROXY) { if (proxyTPM) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("only a single TPM Proxy device is supported")); return -1; } else { proxyTPM = tpm; } } else if (regularTPM) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("only a single TPM non-proxy device is supported")); return -1; } else { regularTPM = tpm; } } return 0; } static int qemuDomainDefPostParseBasic(virDomainDef *def, void *opaque G_GNUC_UNUSED) { virQEMUDriver *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(virDomainDef *def, unsigned int parseFlags, void *opaque, void *parseOpaque) { virQEMUDriver *driver = opaque; g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver); virQEMUCaps *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(driver, 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, qemuCaps) < 0) return -1; if (qemuDomainDefTsegPostParse(def, qemuCaps) < 0) return -1; if (qemuDomainDefNumaCPUsPostParse(def, qemuCaps) < 0) return -1; if (qemuDomainDefTPMsPostParse(def) < 0) return -1; return 0; } int qemuDomainValidateActualNetDef(const virDomainNetDef *net, virQEMUCaps *qemuCaps G_GNUC_UNUSED) { /* * 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; } } /* * 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 && 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(virStorageSource *src, virQEMUCaps *qemuCaps, bool maskBlockdev) { int actualType = virStorageSourceGetActualType(src); bool blockdev = virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV); if (maskBlockdev) blockdev = false; 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 && !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 && !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 && !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", _("readahead is supported only with HTTP(S)/FTP(s) protocols")); return -1; } if (!src->detected && !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 && !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; } /* TFTP protocol was not supported for some time, lock it out at least with * -blockdev */ if (actualType == VIR_STORAGE_TYPE_NETWORK && src->protocol == VIR_STORAGE_NET_PROTOCOL_TFTP && blockdev) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("'tftp' protocol is not supported with this QEMU binary")); return -1; } if (actualType == VIR_STORAGE_TYPE_NETWORK && src->protocol == VIR_STORAGE_NET_PROTOCOL_NFS) { /* NFS protocol may only be used if current QEMU supports blockdev */ if (!blockdev) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("'nfs' protocol is not supported with this QEMU binary")); return -1; } /* NFS protocol must have exactly one host */ if (src->nhosts != 1) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("'nfs' protocol requires the usage of exactly one host")); return -1; } /* NFS can only use a TCP protocol */ if (src->hosts[0].transport != VIR_STORAGE_NET_HOST_TRANS_TCP) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("'nfs' host must use TCP protocol")); return -1; } /* NFS host cannot have a port */ if (src->hosts[0].port != 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("port cannot be specified in 'nfs' protocol host")); return -1; } } /* metadata cache size control is currently supported only for qcow2 */ if (src->metadataCacheMaxSize > 0) { if (src->format != VIR_STORAGE_FILE_QCOW2) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("metadata cache max size control is supported only with qcow2 images")); return -1; } if (!blockdev) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("metadata cache max size control is not supported with this QEMU binary")); 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, virQEMUCaps *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(virDomainChrDef *chr, virQEMUDriver *driver) { g_autoptr(virQEMUDriverConfig) cfg = NULL; g_auto(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(virDomainShmemDef *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(virDomainControllerDef *cont, const virDomainDef *def, virQEMUCaps *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 controllers 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) || qemuDomainIsARMVirt(def))) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("pcie-expander-bus controllers are not supported " "with this machine type")); 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_ISA: case VIR_DOMAIN_CONTROLLER_TYPE_LAST: break; } return 0; } static int qemuDomainChrDefPostParse(virDomainChrDef *chr, const virDomainDef *def, virQEMUDriver *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(virDomainDiskDef *disk, virQEMUCaps *qemuCaps, unsigned int parseFlags) { qemuDomainStorageSourcePrivate *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) || 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(virDomainDiskDef *disk, virQEMUCaps *qemuCaps, unsigned int parseFlags) { /* set default disk types and drivers */ if (!virDomainDiskGetDriver(disk)) virDomainDiskSetDriver(disk, "qemu"); /* 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(virDomainNetDef *net, const virDomainDef *def, virQEMUCaps *qemuCaps) { if (net->type == VIR_DOMAIN_NET_TYPE_VDPA && !virDomainNetGetModelString(net)) net->model = VIR_DOMAIN_NET_MODEL_VIRTIO; else if (net->type != VIR_DOMAIN_NET_TYPE_HOSTDEV && !virDomainNetGetModelString(net) && virDomainNetResolveActualType(net) != VIR_DOMAIN_NET_TYPE_HOSTDEV) net->model = qemuDomainDefaultNetModel(def, qemuCaps); return 0; } static int qemuDomainDefaultVideoDevice(const virDomainDef *def, virQEMUCaps *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(virDomainVideoDef *video, const virDomainDef *def, virQEMUCaps *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(virDomainPanicDef *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(virDomainVsockDef *vsock) { if (vsock->model == VIR_DOMAIN_VSOCK_MODEL_DEFAULT) vsock->model = VIR_DOMAIN_VSOCK_MODEL_VIRTIO; return 0; } /** * qemuDomainDeviceHostdevDefPostParseRestoreSecAlias: * * 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 qemuDomainDeviceHostdevDefPostParseRestoreSecAlias(virDomainHostdevDef *hostdev, virQEMUCaps *qemuCaps, unsigned int parseFlags) { qemuDomainStorageSourcePrivate *priv; virDomainHostdevSubsysSCSI *scsisrc = &hostdev->source.subsys.u.scsi; virDomainHostdevSubsysSCSIiSCSI *iscsisrc = &scsisrc->u.iscsi; g_autofree char *authalias = NULL; if (!(parseFlags & VIR_DOMAIN_DEF_PARSE_STATUS) || !virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_SECRET)) return 0; if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS || hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI || scsisrc->protocol != VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI || !virQEMUCapsGet(qemuCaps, QEMU_CAPS_ISCSI_PASSWORD_SECRET) || !qemuDomainStorageSourceHasAuth(iscsisrc->src)) return 0; if (!(priv = qemuDomainStorageSourcePrivateFetch(iscsisrc->src))) return -1; if (priv->secinfo) return 0; authalias = g_strdup_printf("%s-secret0", hostdev->info->alias); if (qemuStorageSourcePrivateDataAssignSecinfo(&priv->secinfo, &authalias) < 0) return -1; return 0; } /** * qemuDomainDeviceHostdevDefPostParseRestoreBackendAlias: * * Re-generate backend alias if it wasn't stored in the status XML by an older * libvirtd. * * Note that qemuCaps should be always present for a status XML. */ static int qemuDomainDeviceHostdevDefPostParseRestoreBackendAlias(virDomainHostdevDef *hostdev, virQEMUCaps *qemuCaps, unsigned int parseFlags) { virDomainHostdevSubsysSCSI *scsisrc = &hostdev->source.subsys.u.scsi; virStorageSource *src; if (!(parseFlags & VIR_DOMAIN_DEF_PARSE_STATUS)) return 0; if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS || hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI || !virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKDEV_HOSTDEV_SCSI)) return 0; switch ((virDomainHostdevSCSIProtocolType) scsisrc->protocol) { case VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_NONE: if (!scsisrc->u.host.src) scsisrc->u.host.src = virStorageSourceNew(); src = scsisrc->u.host.src; break; case VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI: src = scsisrc->u.iscsi.src; break; case VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_LAST: default: virReportEnumRangeError(virDomainHostdevSCSIProtocolType, scsisrc->protocol); return -1; } if (!src->nodestorage) src->nodestorage = g_strdup_printf("libvirt-%s-backend", hostdev->info->alias); return 0; } static int qemuDomainHostdevDefMdevPostParse(virDomainHostdevSubsysMediatedDev *mdevsrc, virQEMUCaps *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(virDomainHostdevDef *hostdev, virQEMUCaps *qemuCaps, unsigned int parseFlags) { virDomainHostdevSubsys *subsys = &hostdev->source.subsys; if (qemuDomainDeviceHostdevDefPostParseRestoreSecAlias(hostdev, qemuCaps, parseFlags) < 0) return -1; if (qemuDomainDeviceHostdevDefPostParseRestoreBackendAlias(hostdev, qemuCaps, parseFlags) < 0) return -1; 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(virDomainTPMDef *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 qemuDomainNVDimmAlignSizePseries(virDomainMemoryDef *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 = 256 * 1024; unsigned long long guestArea = mem->size - mem->labelsize; /* Align down guestArea. We can't align down if guestArea is * smaller than the 256MiB alignment. */ if (guestArea < ppc64AlignSize) { virReportError(VIR_ERR_XML_ERROR, "%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; } static int qemuDomainMemoryDefPostParse(virDomainMemoryDef *mem, virArch arch, unsigned int parseFlags) { /* Memory alignment can't be done for migration or snapshot * scenarios. This logic was defined by commit c7d7ba85a624. * * There is no easy way to replicate at this point the same conditions * used to call qemuDomainAlignMemorySizes(), which means checking if * we're not migrating and not in a snapshot. * * We can use the PARSE_ABI_UPDATE flag, which is more strict - * existing guests will not activate the flag to avoid breaking * boot ABI. This means that any alignment done here will be replicated * later on by qemuDomainAlignMemorySizes() to contemplate existing * guests as well. */ if (parseFlags & VIR_DOMAIN_DEF_PARSE_ABI_UPDATE) { if (ARCH_IS_PPC64(arch)) { unsigned long long ppc64MemModuleAlign = 256 * 1024; if (mem->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM) { if (qemuDomainNVDimmAlignSizePseries(mem) < 0) return -1; } else { mem->size = VIR_ROUND_UP(mem->size, ppc64MemModuleAlign); } } } return 0; } static int qemuDomainDeviceDefPostParse(virDomainDeviceDef *dev, const virDomainDef *def, unsigned int parseFlags, void *opaque, void *parseOpaque) { virQEMUDriver *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. */ virQEMUCaps *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, parseFlags); break; case VIR_DOMAIN_DEVICE_TPM: ret = qemuDomainTPMDefPostParse(dev->data.tpm, def->os.arch); break; case VIR_DOMAIN_DEVICE_MEMORY: ret = qemuDomainMemoryDefPostParse(dev->data.memory, def->os.arch, parseFlags); 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_IOMMU: case VIR_DOMAIN_DEVICE_AUDIO: 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) { virQEMUDriver *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. */ virQEMUCaps *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) { virQEMUDriver *driver = opaque; if (!(*parseOpaque = virQEMUCapsCacheLookup(driver->qemuCapsCache, def->emulator))) return 1; return 0; } static void qemuDomainPostParseDataFree(void *parseOpaque) { virQEMUCaps *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, }; void qemuDomainObjSaveStatus(virQEMUDriver *driver, virDomainObj *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(virDomainObj *obj) { qemuDomainObjSaveStatus(QEMU_DOMAIN_PRIVATE(obj)->driver, obj); } void qemuDomainSaveConfig(virDomainObj *obj) { virQEMUDriver *driver = QEMU_DOMAIN_PRIVATE(obj)->driver; g_autoptr(virQEMUDriverConfig) cfg = NULL; virDomainDef *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); } /* * 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(virQEMUDriver *driver, virDomainObj *obj, qemuDomainAsyncJob asyncJob) { qemuDomainObjPrivate *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(virQEMUDriver *driver, virDomainObj *obj) { qemuDomainObjPrivate *priv = obj->privateData; bool hasRefs; qemuMonitorWatchDispose(); virObjectUnref(priv->mon); hasRefs = !qemuMonitorWasDisposed(); 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(virQEMUDriver *driver, virDomainObj *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(virQEMUDriver *driver, virDomainObj *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(virQEMUDriver *driver, virDomainObj *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 */ qemuAgent * qemuDomainObjEnterAgent(virDomainObj *obj) { qemuDomainObjPrivate *priv = obj->privateData; qemuAgent *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(virDomainObj *obj, qemuAgent *agent) { virObjectUnlock(agent); virObjectUnref(agent); virObjectLock(obj); VIR_DEBUG("Exited agent (agent=%p vm=%p name=%s)", agent, obj, obj->def->name); } void qemuDomainObjEnterRemote(virDomainObj *obj) { VIR_DEBUG("Entering remote (vm=%p name=%s)", obj, obj->def->name); virObjectUnlock(obj); } int qemuDomainObjExitRemote(virDomainObj *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 virDomainDef * qemuDomainDefFromXML(virQEMUDriver *driver, virQEMUCaps *qemuCaps, const char *xml) { virDomainDef *def; def = virDomainDefParseString(xml, driver->xmlopt, qemuCaps, VIR_DOMAIN_DEF_PARSE_INACTIVE | VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE); return def; } virDomainDef * qemuDomainDefCopy(virQEMUDriver *driver, virQEMUCaps *qemuCaps, virDomainDef *src, unsigned int flags) { g_autofree char *xml = NULL; if (!(xml = qemuDomainDefFormatXML(driver, qemuCaps, src, flags))) return NULL; return qemuDomainDefFromXML(driver, qemuCaps, xml); } int qemuDomainMakeCPUMigratable(virCPUDef *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(virQEMUDriver *driver, virQEMUCaps *qemuCaps, virDomainDef *def, virCPUDef *origCPU, unsigned int flags, virBuffer *buf) { g_autoptr(virDomainDef) 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))) return -1; 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))) return -1; } if (virCPUUpdate(def->os.arch, def->cpu, virQEMUCapsGetHostModel(qCaps, def->virtType, VIR_QEMU_CAPS_HOST_CPU_MIGRATABLE)) < 0) return -1; } if ((flags & VIR_DOMAIN_XML_MIGRATABLE)) { size_t i; int toremove = 0; virDomainControllerDef *usb = NULL; virDomainControllerDef *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) { virDomainControllerDef **controllers = def->controllers; int ncontrollers = def->ncontrollers; def->controllers = g_new0(virDomainControllerDef *, ncontrollers - toremove); 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++) { virDomainChrDef *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))) return -1; } if (def->cpu && qemuDomainMakeCPUMigratable(def->cpu) < 0) return -1; /* Old libvirt doesn't understand