1
0
mirror of https://gitlab.com/libvirt/libvirt.git synced 2025-03-07 17:28:15 +00:00
libvirt/src/conf/domain_postparse.c

1514 lines
48 KiB
C
Raw Normal View History

/*
* domain_postparse.c: domain post parsing helpers
*
* Copyright (C) 2022 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "domain_postparse.h"
#include "viralloc.h"
#include "virlog.h"
#define VIR_FROM_THIS VIR_FROM_DOMAIN
VIR_LOG_INIT("conf.domain_postparse");
static int
virDomainDefPostParseMemory(virDomainDef *def,
unsigned int parseFlags)
{
size_t i;
unsigned long long numaMemory = 0;
unsigned long long hotplugMemory = 0;
/* Attempt to infer the initial memory size from the sum NUMA memory sizes
* in case ABI updates are allowed or the <memory> element wasn't specified */
if (def->mem.total_memory == 0 ||
parseFlags & VIR_DOMAIN_DEF_PARSE_ABI_UPDATE ||
parseFlags & VIR_DOMAIN_DEF_PARSE_ABI_UPDATE_MIGRATION)
numaMemory = virDomainNumaGetMemorySize(def->numa);
/* calculate the sizes of hotplug memory */
for (i = 0; i < def->nmems; i++)
hotplugMemory += def->mems[i]->size;
if (numaMemory) {
/* update the sizes in XML if nothing was set in the XML or ABI update
* is supported */
virDomainDefSetMemoryTotal(def, numaMemory + hotplugMemory);
} else {
/* verify that the sum of memory modules doesn't exceed the total
* memory. This is necessary for virDomainDefGetMemoryInitial to work
* properly. */
if (hotplugMemory > def->mem.total_memory) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("Total size of memory devices exceeds the total memory size"));
return -1;
}
}
if (virDomainDefGetMemoryInitial(def) == 0) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("Memory size must be specified via <memory> or in the <numa> configuration"));
return -1;
}
if (def->mem.cur_balloon > virDomainDefGetMemoryTotal(def) ||
def->mem.cur_balloon == 0)
def->mem.cur_balloon = virDomainDefGetMemoryTotal(def);
if (def->mem.max_memory == 0 && def->mem.memory_slots > 0) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("maximum memory size must be specified when specifying number of memory slots"));
return -1;
}
if (def->mem.max_memory &&
def->mem.max_memory < virDomainDefGetMemoryTotal(def)) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("maximum memory size must be equal or greater than the actual memory size"));
return -1;
}
return 0;
}
static int
virDomainDefPostParseOs(virDomainDef *def)
{
if (!def->os.loader)
return 0;
if (def->os.loader->path &&
def->os.loader->type == VIR_DOMAIN_LOADER_TYPE_NONE) {
/* By default, loader is type of 'rom' */
def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_ROM;
}
return 0;
}
static void
virDomainDefPostParseMemtune(virDomainDef *def)
{
size_t i;
if (virDomainNumaGetNodeCount(def->numa) == 0) {
/* If guest NUMA is not configured and any hugepage page has nodemask
* set to "0" free and clear that nodemas, otherwise we would rise
* an error that there is no guest NUMA node configured. */
for (i = 0; i < def->mem.nhugepages; i++) {
ssize_t nextBit;
if (!def->mem.hugepages[i].nodemask)
continue;
nextBit = virBitmapNextSetBit(def->mem.hugepages[i].nodemask, 0);
if (nextBit < 0) {
g_clear_pointer(&def->mem.hugepages[i].nodemask,
virBitmapFree);
}
}
}
}
static int
virDomainDefPostParseTimer(virDomainDef *def)
{
size_t i;
/* verify settings of guest timers */
for (i = 0; i < def->clock.ntimers; i++) {
virDomainTimerDef *timer = def->clock.timers[i];
if (timer->name == VIR_DOMAIN_TIMER_NAME_KVMCLOCK ||
timer->name == VIR_DOMAIN_TIMER_NAME_HYPERVCLOCK) {
if (timer->tickpolicy) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("timer %1$s doesn't support setting of timer tickpolicy"),
virDomainTimerNameTypeToString(timer->name));
return -1;
}
}
if (timer->tickpolicy != VIR_DOMAIN_TIMER_TICKPOLICY_CATCHUP &&
(timer->catchup.threshold != 0 ||
timer->catchup.limit != 0 ||
timer->catchup.slew != 0)) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("setting of timer catchup policies is only supported with tickpolicy='catchup'"));
return -1;
}
if (timer->name != VIR_DOMAIN_TIMER_NAME_TSC) {
if (timer->frequency != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("timer %1$s doesn't support setting of timer frequency"),
virDomainTimerNameTypeToString(timer->name));
return -1;
}
if (timer->mode) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("timer %1$s doesn't support setting of timer mode"),
virDomainTimerNameTypeToString(timer->name));
return -1;
}
}
if (timer->name != VIR_DOMAIN_TIMER_NAME_PLATFORM &&
timer->name != VIR_DOMAIN_TIMER_NAME_RTC) {
if (timer->track != VIR_DOMAIN_TIMER_TRACK_NONE) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("timer %1$s doesn't support setting of timer track"),
virDomainTimerNameTypeToString(timer->name));
return -1;
}
}
}
return 0;
}
static void
virDomainDefPostParseGraphics(virDomainDef *def)
{
size_t i;
for (i = 0; i < def->ngraphics; i++) {
virDomainGraphicsDef *graphics = def->graphics[i];
/* If spice graphics is configured without ports and with autoport='no'
* then we start qemu with Spice to not listen anywhere. Let's convert
* this configuration to the new listen type='none' which does the
* same. */
if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) {
virDomainGraphicsListenDef *glisten = &graphics->listens[0];
if (glisten->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS &&
graphics->data.spice.port == 0 &&
graphics->data.spice.tlsPort == 0 &&
!graphics->data.spice.autoport) {
VIR_FREE(glisten->address);
glisten->type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE;
}
}
}
}
/**
* virDomainPostParseCheckISCSIPath
* @srcpath: Source path read (a/k/a, IQN) either disk or hostdev
*
* The details of an IQN is defined by RFC 3720 and 3721, but
* we just need to make sure there's a lun provided. If not
* provided, then default to zero. For an ISCSI LUN that is
* is provided by /dev/disk/by-path/... , then that path will
* have the specific lun requested.
*/
static void
virDomainPostParseCheckISCSIPath(char **srcpath)
{
char *path = NULL;
if (strchr(*srcpath, '/'))
return;
path = g_strdup_printf("%s/0", *srcpath);
g_free(*srcpath);
*srcpath = g_steal_pointer(&path);
}
/* Find out the next usable "unit" of a specific controller */
static int
virDomainControllerSCSINextUnit(const virDomainDef *def,
unsigned int controller)
{
size_t i;
for (i = 0; i < def->scsiBusMaxUnit; i++) {
/* Default to assigning addresses using bus = target = 0 */
const virDomainDeviceDriveAddress addr = {controller, 0, 0, i, 0};
if (!virDomainSCSIDriveAddressIsUsed(def, &addr))
return i;
}
return -1;
}
static void
virDomainHostdevAssignAddress(virDomainXMLOption *xmlopt G_GNUC_UNUSED,
const virDomainDef *def,
virDomainHostdevDef *hostdev)
{
int next_unit = 0;
int controller = 0;
/* NB: Do not attempt calling virDomainDefMaybeAddController to
* automagically add a "new" controller. Doing so will result in
* qemuDomainFindOrCreateSCSIDiskController "finding" the controller
* in the domain def list and thus not hotplugging the controller as
* well as the hostdev in the event that there are either no SCSI
* controllers defined or there was no space on an existing one.
*
* Because we cannot add a controller, then we should not walk the
* defined controllers list in order to find empty space. Doing
* so fails to return the valid next unit number for the 2nd
* hostdev being added to the as yet to be created controller.
*/
do {
next_unit = virDomainControllerSCSINextUnit(def, controller);
if (next_unit < 0)
controller++;
} while (next_unit < 0);
hostdev->info->type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
hostdev->info->addr.drive.controller = controller;
hostdev->info->addr.drive.bus = 0;
hostdev->info->addr.drive.target = 0;
hostdev->info->addr.drive.unit = next_unit;
}
static int
virDomainHostdevDefPostParse(virDomainHostdevDef *dev,
const virDomainDef *def,
virDomainXMLOption *xmlopt)
{
virDomainHostdevSubsysSCSI *scsisrc;
virDomainDeviceDriveAddress *addr = NULL;
if (dev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
return 0;
switch (dev->source.subsys.type) {
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI:
scsisrc = &dev->source.subsys.u.scsi;
if (scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI) {
virDomainHostdevSubsysSCSIiSCSI *iscsisrc = &scsisrc->u.iscsi;
virDomainPostParseCheckISCSIPath(&iscsisrc->src->path);
}
if (dev->info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
virDomainHostdevAssignAddress(xmlopt, def, dev);
/* Ensure provided address doesn't conflict with existing
* scsi disk drive address
*/
addr = &dev->info->addr.drive;
if (virDomainDriveAddressIsUsedByDisk(def,
VIR_DOMAIN_DISK_BUS_SCSI,
addr)) {
virReportError(VIR_ERR_XML_ERROR,
_("SCSI host address controller='%1$u' bus='%2$u' target='%3$u' unit='%4$u' in use by a SCSI disk"),
addr->controller, addr->bus,
addr->target, addr->unit);
return -1;
}
break;
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: {
int model = dev->source.subsys.u.mdev.model;
if (dev->info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
return 0;
if ((model == VIR_MDEV_MODEL_TYPE_VFIO_PCI &&
dev->info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) ||
(model == VIR_MDEV_MODEL_TYPE_VFIO_CCW &&
dev->info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW)) {
virReportError(VIR_ERR_XML_ERROR,
_("Unsupported address type '%1$s' with mediated device model '%2$s'"),
virDomainDeviceAddressTypeToString(dev->info->type),
virMediatedDeviceModelTypeToString(model));
return -1;
}
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST:
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
break;
}
return 0;
}
static int
virDomainChrIsaSerialDefPostParse(virDomainDef *def)
{
size_t i;
size_t isa_serial_count = 0;
bool used_serial_port[VIR_MAX_ISA_SERIAL_PORTS] = { false };
/* Perform all the required checks. */
for (i = 0; i < def->nserials; i++) {
if (def->serials[i]->targetType != VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_ISA_SERIAL)
continue;
if (isa_serial_count++ >= VIR_MAX_ISA_SERIAL_PORTS ||
def->serials[i]->target.port >= VIR_MAX_ISA_SERIAL_PORTS) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Maximum supported number of ISA serial ports is '%1$d'"),
VIR_MAX_ISA_SERIAL_PORTS);
return -1;
}
if (def->serials[i]->target.port != -1) {
if (used_serial_port[def->serials[i]->target.port]) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("target port '%1$d' already allocated"),
def->serials[i]->target.port);
return -1;
}
used_serial_port[def->serials[i]->target.port] = true;
}
}
/* Assign the ports to the devices. */
for (i = 0; i < def->nserials; i++) {
size_t j;
if (def->serials[i]->targetType != VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_ISA_SERIAL ||
def->serials[i]->target.port != -1)
continue;
for (j = 0; j < VIR_MAX_ISA_SERIAL_PORTS; j++) {
if (!used_serial_port[j]) {
def->serials[i]->target.port = j;
used_serial_port[j] = true;
break;
}
}
}
return 0;
}
static int
virDomainChrDefPostParse(virDomainChrDef *chr,
const virDomainDef *def)
{
const virDomainChrDef **arrPtr;
size_t i, cnt;
virDomainChrGetDomainPtrs(def, chr->deviceType, &arrPtr, &cnt);
if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE &&
chr->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_NONE) {
chr->targetType = VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL;
}
if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL &&
chr->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA_DEBUG &&
!ARCH_IS_X86(def->os.arch)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("isa-debug serial type only valid on x86 architecture"));
return -1;
}
if (chr->target.port == -1 &&
(chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL ||
chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL ||
chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE)) {
int maxport = -1;
for (i = 0; i < cnt; i++) {
if (arrPtr[i]->target.port > maxport)
maxport = arrPtr[i]->target.port;
}
chr->target.port = maxport + 1;
}
return 0;
}
static void
virDomainRNGDefPostParse(virDomainRNGDef *rng)
{
/* set default path for virtio-rng "random" backend to /dev/random */
if (rng->backend == VIR_DOMAIN_RNG_BACKEND_RANDOM &&
!rng->source.file) {
rng->source.file = g_strdup("/dev/random");
}
}
static void
virDomainDiskExpandGroupIoTune(virDomainDiskDef *disk,
const virDomainDef *def)
{
size_t i;
if (!disk->blkdeviotune.group_name ||
virDomainBlockIoTuneInfoHasAny(&disk->blkdeviotune))
return;
for (i = 0; i < def->ndisks; i++) {
virDomainDiskDef *d = def->disks[i];
if (STRNEQ_NULLABLE(disk->blkdeviotune.group_name, d->blkdeviotune.group_name) ||
!virDomainBlockIoTuneInfoHasAny(&d->blkdeviotune))
continue;
VIR_FREE(disk->blkdeviotune.group_name);
virDomainBlockIoTuneInfoCopy(&d->blkdeviotune, &disk->blkdeviotune);
return;
}
}
static int
virDomainDiskDefPostParse(virDomainDiskDef *disk,
const virDomainDef *def,
virDomainXMLOption *xmlopt)
{
if (disk->dst) {
char *newdst;
/* Work around for compat with Xen driver in previous libvirt releases */
if ((newdst = g_strdup(STRSKIP(disk->dst, "ioemu:")))) {
g_free(disk->dst);
disk->dst = newdst;
}
}
/* Force CDROM to be listed as read only */
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM)
disk->src->readonly = true;
if (disk->bus == VIR_DOMAIN_DISK_BUS_NONE) {
disk->bus = VIR_DOMAIN_DISK_BUS_IDE;
if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) {
disk->bus = VIR_DOMAIN_DISK_BUS_FDC;
} else if (disk->dst) {
if (STRPREFIX(disk->dst, "hd"))
disk->bus = VIR_DOMAIN_DISK_BUS_IDE;
else if (STRPREFIX(disk->dst, "sd"))
disk->bus = VIR_DOMAIN_DISK_BUS_SCSI;
else if (STRPREFIX(disk->dst, "vd"))
disk->bus = VIR_DOMAIN_DISK_BUS_VIRTIO;
else if (STRPREFIX(disk->dst, "xvd"))
disk->bus = VIR_DOMAIN_DISK_BUS_XEN;
else if (STRPREFIX(disk->dst, "ubd"))
disk->bus = VIR_DOMAIN_DISK_BUS_UML;
}
}
if (disk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT &&
disk->src->readonly)
disk->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_NO;
if (disk->src->type == VIR_STORAGE_TYPE_NETWORK &&
disk->src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI) {
virDomainPostParseCheckISCSIPath(&disk->src->path);
}
if (disk->src->type == VIR_STORAGE_TYPE_NVME) {
if (disk->src->nvme->managed == VIR_TRISTATE_BOOL_ABSENT)
disk->src->nvme->managed = VIR_TRISTATE_BOOL_YES;
}
/* vhost-user doesn't allow us to snapshot, disable snapshots by default */
if (disk->src->type == VIR_STORAGE_TYPE_VHOST_USER &&
disk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT) {
disk->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_NO;
}
if (disk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
disk->dst &&
virDomainDiskDefAssignAddress(xmlopt, disk, def) < 0) {
return -1;
}
virDomainDiskExpandGroupIoTune(disk, def);
return 0;
}
static void
virDomainVideoDefPostParse(virDomainVideoDef *video,
const virDomainDef *def)
{
/* Fill out (V)RAM if the driver-specific callback did not do so */
if (video->ram == 0 && video->type == VIR_DOMAIN_VIDEO_TYPE_QXL)
video->ram = virDomainVideoDefaultRAM(def, video->type);
if (video->vram == 0)
video->vram = virDomainVideoDefaultRAM(def, video->type);
video->ram = VIR_ROUND_UP_POWER_OF_TWO(video->ram);
video->vram = VIR_ROUND_UP_POWER_OF_TWO(video->vram);
}
static int
virDomainControllerDefPostParse(virDomainControllerDef *cdev)
{
if (cdev->iothread &&
cdev->model != VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI &&
cdev->model != VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_TRANSITIONAL &&
cdev->model != VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_NON_TRANSITIONAL) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("'iothread' attribute only supported for virtio scsi controllers"));
return -1;
}
return 0;
}
static void
virDomainVsockDefPostParse(virDomainVsockDef *vsock)
{
if (vsock->auto_cid == VIR_TRISTATE_BOOL_ABSENT) {
if (vsock->guest_cid != 0)
vsock->auto_cid = VIR_TRISTATE_BOOL_NO;
else
vsock->auto_cid = VIR_TRISTATE_BOOL_YES;
}
}
static int
virDomainMemoryDefPostParse(virDomainMemoryDef *mem,
const virDomainDef *def)
{
switch (mem->model) {
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
/* Virtio-pmem mandates shared access so that guest writes get
* reflected in the underlying file. */
if (mem->access == VIR_DOMAIN_MEMORY_ACCESS_DEFAULT)
mem->access = VIR_DOMAIN_MEMORY_ACCESS_SHARED;
break;
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
/* If no NVDIMM UUID was provided in XML, generate one. */
if (ARCH_IS_PPC64(def->os.arch) &&
!mem->target.nvdimm.uuid) {
mem->target.nvdimm.uuid = g_new0(unsigned char, VIR_UUID_BUFLEN);
if (virUUIDGenerate(mem->target.nvdimm.uuid) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("Failed to generate UUID"));
return -1;
}
}
break;
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM:
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
}
return 0;
}
static int
virDomainFSDefPostParse(virDomainFSDef *fs)
{
if (fs->accessmode == VIR_DOMAIN_FS_ACCESSMODE_DEFAULT && !fs->sock)
fs->accessmode = VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH;
return 0;
}
static void
virDomainInputDefPostParse(virDomainInputDef *input,
const virDomainDef *def)
{
if (input->bus == VIR_DOMAIN_INPUT_BUS_DEFAULT) {
if (def->os.type == VIR_DOMAIN_OSTYPE_HVM) {
if ((input->type == VIR_DOMAIN_INPUT_TYPE_MOUSE ||
input->type == VIR_DOMAIN_INPUT_TYPE_KBD) &&
(ARCH_IS_X86(def->os.arch) || def->os.arch == VIR_ARCH_NONE)) {
input->bus = VIR_DOMAIN_INPUT_BUS_PS2;
} else if (ARCH_IS_S390(def->os.arch) ||
input->type == VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH) {
input->bus = VIR_DOMAIN_INPUT_BUS_VIRTIO;
} else if (input->type == VIR_DOMAIN_INPUT_TYPE_EVDEV) {
input->bus = VIR_DOMAIN_INPUT_BUS_NONE;
} else {
input->bus = VIR_DOMAIN_INPUT_BUS_USB;
}
} else if (def->os.type == VIR_DOMAIN_OSTYPE_XEN ||
def->os.type == VIR_DOMAIN_OSTYPE_XENPVH) {
input->bus = VIR_DOMAIN_INPUT_BUS_XEN;
} else {
if ((def->virtType == VIR_DOMAIN_VIRT_VZ ||
def->virtType == VIR_DOMAIN_VIRT_PARALLELS))
input->bus = VIR_DOMAIN_INPUT_BUS_PARALLELS;
}
}
}
static int
virDomainDeviceDefPostParseCommon(virDomainDeviceDef *dev,
const virDomainDef *def,
unsigned int parseFlags G_GNUC_UNUSED,
virDomainXMLOption *xmlopt)
{
int ret = -1;
switch (dev->type) {
case VIR_DOMAIN_DEVICE_CHR:
ret = virDomainChrDefPostParse(dev->data.chr, def);
break;
case VIR_DOMAIN_DEVICE_RNG:
virDomainRNGDefPostParse(dev->data.rng);
ret = 0;
break;
case VIR_DOMAIN_DEVICE_DISK:
ret = virDomainDiskDefPostParse(dev->data.disk, def, xmlopt);
break;
case VIR_DOMAIN_DEVICE_VIDEO:
virDomainVideoDefPostParse(dev->data.video, def);
ret = 0;
break;
case VIR_DOMAIN_DEVICE_HOSTDEV:
ret = virDomainHostdevDefPostParse(dev->data.hostdev, def, xmlopt);
break;
case VIR_DOMAIN_DEVICE_CONTROLLER:
ret = virDomainControllerDefPostParse(dev->data.controller);
break;
case VIR_DOMAIN_DEVICE_VSOCK:
virDomainVsockDefPostParse(dev->data.vsock);
ret = 0;
break;
case VIR_DOMAIN_DEVICE_MEMORY:
ret = virDomainMemoryDefPostParse(dev->data.memory, def);
break;
case VIR_DOMAIN_DEVICE_FS:
ret = virDomainFSDefPostParse(dev->data.fs);
break;
case VIR_DOMAIN_DEVICE_INPUT:
virDomainInputDefPostParse(dev->data.input, def);
ret = 0;
break;
case VIR_DOMAIN_DEVICE_LEASE:
case VIR_DOMAIN_DEVICE_NET:
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_SHMEM:
case VIR_DOMAIN_DEVICE_TPM:
case VIR_DOMAIN_DEVICE_PANIC:
case VIR_DOMAIN_DEVICE_IOMMU:
case VIR_DOMAIN_DEVICE_AUDIO:
case VIR_DOMAIN_DEVICE_CRYPTO:
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;
}
/**
* virDomainDefCheckUnsupportedMemoryHotplug:
* @def: domain definition
*
* Returns -1 if the domain definition would enable memory hotplug via the
* <maxMemory> tunable and reports an error. Otherwise returns 0.
*/
static int
virDomainDefCheckUnsupportedMemoryHotplug(virDomainDef *def)
{
/* memory hotplug tunables are not supported by this driver */
if (virDomainDefHasMemoryHotplug(def)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("memory hotplug tunables <maxMemory> are not supported by this hypervisor driver"));
return -1;
}
return 0;
}
/**
* virDomainDeviceDefCheckUnsupportedMemoryDevice:
* @dev: device definition
*
* Returns -1 if the device definition describes a memory device and reports an
* error. Otherwise returns 0.
*/
static int
virDomainDeviceDefCheckUnsupportedMemoryDevice(virDomainDeviceDef *dev)
{
/* This driver doesn't yet know how to handle memory devices */
if (dev->type == VIR_DOMAIN_DEVICE_MEMORY) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("memory devices are not supported by this driver"));
return -1;
}
return 0;
}
/**
* virDomainDefRemoveOfflineVcpuPin:
* @def: domain definition
*
* This function removes vcpu pinning information from offline vcpus. This is
* designed to be used for drivers which don't support offline vcpupin.
*/
static void
virDomainDefRemoveOfflineVcpuPin(virDomainDef *def)
{
size_t i;
virDomainVcpuDef *vcpu;
for (i = 0; i < virDomainDefGetVcpusMax(def); i++) {
vcpu = virDomainDefGetVcpu(def, i);
if (vcpu && !vcpu->online && vcpu->cpumask) {
g_clear_pointer(&vcpu->cpumask, virBitmapFree);
VIR_WARN("Ignoring unsupported vcpupin for offline vcpu '%zu'", i);
}
}
}
#define UNSUPPORTED(FEATURE) (!((FEATURE) & xmlopt->config.features))
/**
* virDomainDefPostParseCheckFeatures:
* @def: domain definition
* @xmlopt: XML parser option object
*
* This function checks that the domain configuration is supported according to
* the supported features for a given hypervisor. See virDomainDefFeatures and
* virDomainDefParserConfig.
*
* Returns 0 on success and -1 on error with an appropriate libvirt error.
*/
static int
virDomainDefPostParseCheckFeatures(virDomainDef *def,
virDomainXMLOption *xmlopt)
{
if (UNSUPPORTED(VIR_DOMAIN_DEF_FEATURE_MEMORY_HOTPLUG) &&
virDomainDefCheckUnsupportedMemoryHotplug(def) < 0)
return -1;
if (UNSUPPORTED(VIR_DOMAIN_DEF_FEATURE_OFFLINE_VCPUPIN))
virDomainDefRemoveOfflineVcpuPin(def);
if (UNSUPPORTED(VIR_DOMAIN_DEF_FEATURE_NAME_SLASH)) {
if (def->name && strchr(def->name, '/')) {
virReportError(VIR_ERR_XML_ERROR,
_("name %1$s cannot contain '/'"), def->name);
return -1;
}
}
if (UNSUPPORTED(VIR_DOMAIN_DEF_FEATURE_INDIVIDUAL_VCPUS) &&
def->individualvcpus) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("individual CPU state configuration is not supported"));
return -1;
}
return 0;
}
/**
* virDomainDeviceDefPostParseCheckFeatures:
* @dev: device definition
* @xmlopt: XML parser option object
*
* This function checks that the device configuration is supported according to
* the supported features for a given hypervisor. See virDomainDefFeatures and
* virDomainDefParserConfig.
*
* Returns 0 on success and -1 on error with an appropriate libvirt error.
*/
static int
virDomainDeviceDefPostParseCheckFeatures(virDomainDeviceDef *dev,
virDomainXMLOption *xmlopt)
{
if (UNSUPPORTED(VIR_DOMAIN_DEF_FEATURE_MEMORY_HOTPLUG) &&
virDomainDeviceDefCheckUnsupportedMemoryDevice(dev) < 0)
return -1;
if (UNSUPPORTED(VIR_DOMAIN_DEF_FEATURE_NET_MODEL_STRING) &&
dev->type == VIR_DOMAIN_DEVICE_NET &&
dev->data.net->modelstr) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("driver does not support net model '%1$s'"),
dev->data.net->modelstr);
return -1;
}
if (dev->type == VIR_DOMAIN_DEVICE_DISK &&
dev->data.disk->src->fdgroup &&
UNSUPPORTED(VIR_DOMAIN_DEF_FEATURE_DISK_FD)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("driver does not support FD passing for disk '%1$s'"),
dev->data.disk->dst);
return -1;
}
return 0;
}
#undef UNSUPPORTED
static int
virDomainDeviceDefPostParse(virDomainDeviceDef *dev,
const virDomainDef *def,
unsigned int flags,
virDomainXMLOption *xmlopt,
void *parseOpaque)
{
int ret;
if (xmlopt->config.devicesPostParseCallback) {
ret = xmlopt->config.devicesPostParseCallback(dev, def, flags,
xmlopt->config.priv,
parseOpaque);
if (ret < 0)
return ret;
}
if ((ret = virDomainDeviceDefPostParseCommon(dev, def, flags, xmlopt)) < 0)
return ret;
if (virDomainDeviceDefPostParseCheckFeatures(dev, xmlopt) < 0)
return -1;
return 0;
}
int
virDomainDeviceDefPostParseOne(virDomainDeviceDef *dev,
const virDomainDef *def,
unsigned int flags,
virDomainXMLOption *xmlopt,
void *parseOpaque)
{
void *data = NULL;
int ret;
if (!parseOpaque && xmlopt->config.domainPostParseDataAlloc) {
if (xmlopt->config.domainPostParseDataAlloc(def, flags,
xmlopt->config.priv,
&data) < 0)
return -1;
parseOpaque = data;
}
ret = virDomainDeviceDefPostParse(dev, def, flags, xmlopt, parseOpaque);
if (data && xmlopt->config.domainPostParseDataFree)
xmlopt->config.domainPostParseDataFree(data);
return ret;
}
struct virDomainDefPostParseDeviceIteratorData {
virDomainXMLOption *xmlopt;
void *parseOpaque;
unsigned int parseFlags;
};
static int
virDomainDefPostParseDeviceIterator(virDomainDef *def,
virDomainDeviceDef *dev,
virDomainDeviceInfo *info G_GNUC_UNUSED,
void *opaque)
{
struct virDomainDefPostParseDeviceIteratorData *data = opaque;
return virDomainDeviceDefPostParse(dev, def,
data->parseFlags, data->xmlopt,
data->parseOpaque);
}
static int
virDomainVcpuDefPostParse(virDomainDef *def)
{
virDomainVcpuDef *vcpu;
size_t maxvcpus = virDomainDefGetVcpusMax(def);
size_t i;
for (i = 0; i < maxvcpus; i++) {
vcpu = virDomainDefGetVcpu(def, i);
/* impossible but some compilers don't like it */
if (!vcpu)
continue;
switch (vcpu->hotpluggable) {
case VIR_TRISTATE_BOOL_ABSENT:
if (vcpu->online)
vcpu->hotpluggable = VIR_TRISTATE_BOOL_NO;
else
vcpu->hotpluggable = VIR_TRISTATE_BOOL_YES;
break;
case VIR_TRISTATE_BOOL_NO:
if (!vcpu->online) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("vcpu '%1$zu' is both offline and not hotpluggable"), i);
return -1;
}
break;
case VIR_TRISTATE_BOOL_YES:
case VIR_TRISTATE_BOOL_LAST:
break;
}
}
return 0;
}
static int
virDomainDefPostParseCPU(virDomainDef *def)
{
if (!def->cpu)
return 0;
if (def->cpu->mode == VIR_CPU_MODE_CUSTOM &&
!def->cpu->model &&
def->cpu->check != VIR_CPU_CHECK_DEFAULT) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("check attribute specified for CPU with no model"));
return -1;
}
return 0;
}
static int
virDomainDefCollectBootOrder(virDomainDef *def G_GNUC_UNUSED,
virDomainDeviceDef *dev G_GNUC_UNUSED,
virDomainDeviceInfo *info,
void *data)
{
GHashTable *bootHash = data;
g_autofree char *order = NULL;
if (info->bootIndex == 0)
return 0;
if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV &&
dev->data.hostdev->parentnet) {
/* This hostdev is a child of a higher level device
* (e.g. interface), and thus already being counted on the
* list for the other device type.
*/
return 0;
}
order = g_strdup_printf("%u", info->bootIndex);
if (virHashLookup(bootHash, order)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("boot order '%1$s' used for more than one device"),
order);
return -1;
}
if (virHashAddEntry(bootHash, order, (void *) 1) < 0)
return -1;
return 0;
}
static int
virDomainDefBootOrderPostParse(virDomainDef *def)
{
g_autoptr(GHashTable) bootHash = virHashNew(NULL);
if (virDomainDeviceInfoIterate(def, virDomainDefCollectBootOrder, bootHash) < 0)
return -1;
if (def->os.nBootDevs > 0 && virHashSize(bootHash) > 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("per-device boot elements cannot be used together with os/boot elements"));
return -1;
}
if (def->os.nBootDevs == 0 && virHashSize(bootHash) == 0) {
def->os.nBootDevs = 1;
def->os.bootDevs[0] = VIR_DOMAIN_BOOT_DISK;
}
return 0;
}
static int
virDomainDefPostParseVideo(virDomainDef *def,
void *opaque)
{
if (def->nvideos == 0)
return 0;
if (def->videos[0]->type == VIR_DOMAIN_VIDEO_TYPE_NONE) {
char *alias;
/* we don't want to format any values we automatically fill in for
* videos into the XML, so clear them, but retain any user-assigned
* alias */
alias = g_steal_pointer(&def->videos[0]->info.alias);
virDomainVideoDefClear(def->videos[0]);
def->videos[0]->type = VIR_DOMAIN_VIDEO_TYPE_NONE;
def->videos[0]->info.alias = g_steal_pointer(&alias);
} else {
virDomainDeviceDef device = {
.type = VIR_DOMAIN_DEVICE_VIDEO,
.data.video = def->videos[0],
};
/* Mark the first video as primary. If the user specified
* primary="yes", the parser already inserted the device at
* def->videos[0]
*/
def->videos[0]->primary = true;
/* videos[0] might have been added in AddImplicitDevices, after we've
* done the per-device post-parse */
if (virDomainDefPostParseDeviceIterator(def, &device,
NULL, opaque) < 0)
return -1;
}
return 0;
}
static int
virDomainDefRejectDuplicateControllers(virDomainDef *def)
{
int max_idx[VIR_DOMAIN_CONTROLLER_TYPE_LAST];
virBitmap *bitmaps[VIR_DOMAIN_CONTROLLER_TYPE_LAST] = { NULL };
virDomainControllerDef *cont;
size_t nbitmaps = 0;
int ret = -1;
size_t i;
memset(max_idx, -1, sizeof(max_idx));
for (i = 0; i < def->ncontrollers; i++) {
cont = def->controllers[i];
if (cont->idx > max_idx[cont->type])
max_idx[cont->type] = cont->idx;
}
/* multiple USB controllers with the same index are allowed */
max_idx[VIR_DOMAIN_CONTROLLER_TYPE_USB] = -1;
for (i = 0; i < VIR_DOMAIN_CONTROLLER_TYPE_LAST; i++) {
if (max_idx[i] >= 0)
bitmaps[i] = virBitmapNew(max_idx[i] + 1);
nbitmaps++;
}
for (i = 0; i < def->ncontrollers; i++) {
cont = def->controllers[i];
if (max_idx[cont->type] == -1)
continue;
if (virBitmapIsBitSet(bitmaps[cont->type], cont->idx)) {
virReportError(VIR_ERR_XML_ERROR,
_("Multiple '%1$s' controllers with index '%2$d'"),
virDomainControllerTypeToString(cont->type),
cont->idx);
goto cleanup;
}
ignore_value(virBitmapSetBit(bitmaps[cont->type], cont->idx));
}
ret = 0;
cleanup:
for (i = 0; i < nbitmaps; i++)
virBitmapFree(bitmaps[i]);
return ret;
}
static int
virDomainDefRejectDuplicatePanics(virDomainDef *def)
{
bool exists[VIR_DOMAIN_PANIC_MODEL_LAST];
size_t i;
for (i = 0; i < VIR_DOMAIN_PANIC_MODEL_LAST; i++)
exists[i] = false;
for (i = 0; i < def->npanics; i++) {
virDomainPanicModel model = def->panics[i]->model;
if (exists[model]) {
virReportError(VIR_ERR_XML_ERROR,
_("Multiple panic devices with model '%1$s'"),
virDomainPanicModelTypeToString(model));
return -1;
}
exists[model] = true;
}
return 0;
}
static int
virDomainDefPostParseCommon(virDomainDef *def,
struct virDomainDefPostParseDeviceIteratorData *data,
virDomainXMLOption *xmlopt)
{
size_t i;
/* verify init path for container based domains */
if (def->os.type == VIR_DOMAIN_OSTYPE_EXE && !def->os.init) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("init binary must be specified"));
return -1;
}
if (virDomainVcpuDefPostParse(def) < 0)
return -1;
if (virDomainDefPostParseMemory(def, data->parseFlags) < 0)
return -1;
if (virDomainDefPostParseOs(def) < 0)
return -1;
virDomainDefPostParseMemtune(def);
if (virDomainDefRejectDuplicateControllers(def) < 0)
return -1;
if (virDomainDefRejectDuplicatePanics(def) < 0)
return -1;
if (def->os.type == VIR_DOMAIN_OSTYPE_HVM &&
!(data->xmlopt->config.features & VIR_DOMAIN_DEF_FEATURE_NO_BOOT_ORDER) &&
virDomainDefBootOrderPostParse(def) < 0)
return -1;
if (virDomainDefPostParseTimer(def) < 0)
return -1;
if (virDomainDefAddImplicitDevices(def, xmlopt) < 0)
return -1;
if (virDomainDefPostParseVideo(def, data) < 0)
return -1;
if (def->nserials != 0) {
virDomainDeviceDef device = {
.type = VIR_DOMAIN_DEVICE_CHR,
.data.chr = def->serials[0],
};
/* serials[0] might have been added in AddImplicitDevices, after we've
* done the per-device post-parse */
if (virDomainDefPostParseDeviceIterator(def, &device, NULL, data) < 0)
return -1;
}
/* Implicit SCSI controllers without a defined model might have
* been added in AddImplicitDevices, after we've done the per-device
* post-parse. */
for (i = 0; i < def->ncontrollers; i++) {
if (def->controllers[i]->model == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_DEFAULT &&
def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI) {
virDomainDeviceDef device = {
.type = VIR_DOMAIN_DEVICE_CONTROLLER,
.data.controller = def->controllers[i],
};
if (virDomainDefPostParseDeviceIterator(def, &device, NULL, data) < 0)
return -1;
}
}
/* clean up possibly duplicated metadata entries */
virXMLNodeSanitizeNamespaces(def->metadata);
virDomainDefPostParseGraphics(def);
if (virDomainDefPostParseCPU(def) < 0)
return -1;
return 0;
}
static int
virDomainDefPostParseCheckFailure(virDomainDef *def,
unsigned int parseFlags,
int ret)
{
if (ret != 0)
def->postParseFailed = true;
if (ret <= 0)
return ret;
if (!(parseFlags & VIR_DOMAIN_DEF_PARSE_ALLOW_POST_PARSE_FAIL))
return -1;
virResetLastError();
return 0;
}
static void
virDomainAssignControllerIndexes(virDomainDef *def)
{
/* the index attribute of a controller is optional in the XML, but
* is required to be valid at any time after parse. When no index
* is provided for a controller, assign one automatically by
* looking at what indexes are already used for that controller
* type in the domain - the unindexed controller gets the lowest
* unused index.
*/
size_t outer;
bool reinsert = false;
for (outer = 0; outer < def->ncontrollers; outer++) {
virDomainControllerDef *cont = def->controllers[outer];
virDomainControllerDef *prev = NULL;
size_t inner;
if (cont->idx != -1)
continue;
if (outer > 0 && IS_USB2_CONTROLLER(cont)) {
/* USB2 controllers are the only exception to the simple
* "assign the lowest unused index". A group of USB2
* "companions" should all be at the same index as other
* USB2 controllers in the group, but only do this
* automatically if it appears to be the intent. To prove
* intent: the USB controller on the list just prior to
* this one must also be a USB2 controller, and there must
* not yet be a controller with the exact same model of
* this one and the same index as the previously added
* controller (e.g., if this controller is a UHCI1, then
* the previous controller must be an EHCI1 or a UHCI[23],
* and there must not already be a UHCI1 controller with
* the same index as the previous controller). If all of
* these are satisfied, set this controller to the same
* index as the previous controller.
*/
int prevIdx;
/* virDomainControllerInsert enforces an ordering of USB2 controllers
* based on their master port, which doesn't happen on the initial
* parse if the index wasn't yet allocated. If we encounter a USB2
* controller where we populated the index, we need to re-shuffle
* the controllers after allocating the index */
reinsert = true;
prevIdx = outer - 1;
while (prevIdx >= 0 &&
def->controllers[prevIdx]->type != VIR_DOMAIN_CONTROLLER_TYPE_USB)
prevIdx--;
if (prevIdx >= 0)
prev = def->controllers[prevIdx];
/* if the last USB controller isn't USB2, that breaks
* the chain, so we need a new index for this new
* controller
*/
if (prev && !IS_USB2_CONTROLLER(prev))
prev = NULL;
/* if prev != NULL, we've found a potential index to
* use. Make sure this index isn't already used by an
* existing USB2 controller of the same model as the new
* one.
*/
for (inner = 0; prev && inner < def->ncontrollers; inner++) {
if (def->controllers[inner]->type == VIR_DOMAIN_CONTROLLER_TYPE_USB &&
def->controllers[inner]->idx == prev->idx &&
def->controllers[inner]->model == cont->model) {
/* we already have a controller of this model with
* the proposed index, so we need to move to a new
* index for this controller
*/
prev = NULL;
}
}
if (prev)
cont->idx = prev->idx;
}
/* if none of the above applied, prev will be NULL */
if (!prev)
cont->idx = virDomainControllerFindUnusedIndex(def, cont->type);
}
if (reinsert) {
g_autofree virDomainControllerDef **controllers = g_steal_pointer(&def->controllers);
size_t ncontrollers = def->ncontrollers;
size_t i;
def->controllers = g_new0(virDomainControllerDef *, ncontrollers);
def->ncontrollers = 0;
for (i = 0; i < ncontrollers; i++) {
virDomainControllerInsertPreAlloced(def, controllers[i]);
}
}
}
int
virDomainDefPostParse(virDomainDef *def,
unsigned int parseFlags,
virDomainXMLOption *xmlopt,
void *parseOpaque)
{
int ret = -1;
bool localParseOpaque = false;
struct virDomainDefPostParseDeviceIteratorData data = {
.xmlopt = xmlopt,
.parseFlags = parseFlags,
.parseOpaque = parseOpaque,
};
def->postParseFailed = false;
/* call the basic post parse callback */
if (xmlopt->config.domainPostParseBasicCallback) {
ret = xmlopt->config.domainPostParseBasicCallback(def,
xmlopt->config.priv);
if (virDomainDefPostParseCheckFailure(def, parseFlags, ret) < 0)
goto cleanup;
}
if (!data.parseOpaque &&
xmlopt->config.domainPostParseDataAlloc) {
ret = xmlopt->config.domainPostParseDataAlloc(def, parseFlags,
xmlopt->config.priv,
&data.parseOpaque);
if (virDomainDefPostParseCheckFailure(def, parseFlags, ret) < 0)
goto cleanup;
localParseOpaque = true;
}
/* this must be done before the hypervisor-specific callback,
* in case presence of a controller at a specific index is checked
*/
virDomainAssignControllerIndexes(def);
/* call the domain config callback */
if (xmlopt->config.domainPostParseCallback) {
ret = xmlopt->config.domainPostParseCallback(def, parseFlags,
xmlopt->config.priv,
data.parseOpaque);
if (virDomainDefPostParseCheckFailure(def, parseFlags, ret) < 0)
goto cleanup;
}
if (virDomainChrIsaSerialDefPostParse(def) < 0)
return -1;
/* iterate the devices */
ret = virDomainDeviceInfoIterateFlags(def,
virDomainDefPostParseDeviceIterator,
DOMAIN_DEVICE_ITERATE_ALL_CONSOLES |
DOMAIN_DEVICE_ITERATE_MISSING_INFO,
&data);
if (virDomainDefPostParseCheckFailure(def, parseFlags, ret) < 0)
goto cleanup;
if ((ret = virDomainDefPostParseCommon(def, &data, xmlopt)) < 0)
goto cleanup;
if (xmlopt->config.assignAddressesCallback) {
ret = xmlopt->config.assignAddressesCallback(def, parseFlags,
xmlopt->config.priv,
data.parseOpaque);
if (virDomainDefPostParseCheckFailure(def, parseFlags, ret) < 0)
goto cleanup;
}
if ((ret = virDomainDefPostParseCheckFeatures(def, xmlopt)) < 0)
goto cleanup;
ret = 0;
cleanup:
if (localParseOpaque && xmlopt->config.domainPostParseDataFree)
xmlopt->config.domainPostParseDataFree(data.parseOpaque);
if (ret == 1)
ret = -1;
return ret;
}