[PATCH 2/3] conf: Separate domain post parse code into domain_postparse.c

Michal Privoznik mprivozn at redhat.com
Thu Jul 7 10:07:08 UTC 2022


The domain post parse functions currently live in domain_conf.c
which thus grows always larger. Mimic what we've done for the
validation code and move the post parse code into a separate
file: domain_postparse.c.

I've started by moving every function with PostParse in its name
into the new file and then compile hunting for helper functions
only to move them as well.

In the end, I've moved virDomainDefPostParse symbol in
libvirt_private.syms into a new section. And while
virDomainDeviceDefPostParseOne() is made 'public' in
domain_postparse.h too, I'm not exporting it because it has no
caller outside src/conf/ and it's unlikely it ever will.

Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
---
 po/POTFILES                 |    1 +
 src/conf/domain_conf.c      | 1453 +---------------------------------
 src/conf/domain_conf.h      |    4 -
 src/conf/domain_postparse.c | 1483 +++++++++++++++++++++++++++++++++++
 src/conf/domain_postparse.h |   37 +
 src/conf/meson.build        |    1 +
 src/libvirt_private.syms    |    5 +-
 src/libxl/xen_xl.c          |    1 +
 src/libxl/xen_xm.c          |    1 +
 src/lxc/lxc_native.c        |    1 +
 src/qemu/qemu_driver.c      |    1 +
 src/qemu/qemu_process.c     |    1 +
 src/vmx/vmx.c               |    1 +
 13 files changed, 1533 insertions(+), 1457 deletions(-)
 create mode 100644 src/conf/domain_postparse.c
 create mode 100644 src/conf/domain_postparse.h

diff --git a/po/POTFILES b/po/POTFILES
index faaba53c8f..9621efb0d3 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -32,6 +32,7 @@ src/conf/domain_addr.c
 src/conf/domain_capabilities.c
 src/conf/domain_conf.c
 src/conf/domain_event.c
+src/conf/domain_postparse.c
 src/conf/domain_validate.c
 src/conf/interface_conf.c
 src/conf/netdev_bandwidth_conf.c
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index c67fcd337d..b639022396 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -33,6 +33,7 @@
 #include "datatypes.h"
 #include "domain_addr.h"
 #include "domain_conf.h"
+#include "domain_postparse.h"
 #include "domain_validate.h"
 #include "viralloc.h"
 #include "virxml.h"
@@ -1796,49 +1797,6 @@ virDomainBlkioDeviceParseXML(xmlNodePtr root,
 }
 
 
-/**
- * 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;
-}
-
-
 bool virDomainObjTaint(virDomainObj *obj,
                        virDomainTaintFlags taint)
 {
@@ -4733,204 +4691,6 @@ virDomainDefHasDeviceAddress(virDomainDef *def,
 }
 
 
-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 '%s' controllers with index '%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 '%s'"),
-                           virDomainPanicModelTypeToString(model));
-            return -1;
-        }
-        exists[model] = true;
-    }
-
-    return 0;
-}
-
-
-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 || def->mem.memory_slots) &&
-        !(def->mem.max_memory && def->mem.memory_slots)) {
-        virReportError(VIR_ERR_XML_ERROR, "%s",
-                       _("both maximum memory size and "
-                         "memory slot count must be specified"));
-        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.firmwareFeatures &&
-        def->os.firmwareFeatures[VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_ENROLLED_KEYS] == VIR_TRISTATE_BOOL_YES) {
-
-        if (def->os.firmwareFeatures[VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_SECURE_BOOT] == VIR_TRISTATE_BOOL_NO) {
-            virReportError(VIR_ERR_XML_DETAIL, "%s",
-                           _("firmware feature 'enrolled-keys' cannot be enabled when "
-                             "firmware feature 'secure-boot' is disabled"));
-            return -1;
-        }
-
-        /* For all non-broken firmware builds, enrolled-keys implies
-         * secure-boot, and having the Secure Boot keys in the NVRAM file
-         * when the firmware doesn't support the Secure Boot feature doesn't
-         * make sense anyway. Reflect this fact explicitly in the XML */
-        def->os.firmwareFeatures[VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_SECURE_BOOT] = VIR_TRISTATE_BOOL_YES;
-    }
-
-    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
 virDomainDefAddConsoleCompat(virDomainDef *def)
 {
@@ -5046,96 +4806,6 @@ virDomainDefAddConsoleCompat(virDomainDef *def)
 }
 
 
-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 %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 %s doesn't support setting of "
-                                 "timer frequency"),
-                               virDomainTimerNameTypeToString(timer->name));
-                return -1;
-             }
-
-            if (timer->mode) {
-                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
-                               _("timer %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 %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;
-            }
-        }
-    }
-}
-
 
 /**
  * virDomainDriveAddressIsUsedByDisk:
@@ -5282,1127 +4952,6 @@ virDomainSCSIDriveAddressIsUsed(const virDomainDef *def,
 }
 
 
-/* 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;
-}
-
-
-/**
- * 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);
-}
-
-
-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='%u' "
-                             "bus='%u' target='%u' unit='%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 '%s' with mediated "
-                             "device model '%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 '%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 '%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->uuid) {
-
-            mem->uuid = g_new0(unsigned char, VIR_UUID_BUFLEN);
-            if (virUUIDGenerate(mem->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_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 int
-virDomainDeviceDefPostParseCommon(virDomainDeviceDef *dev,
-                                  const virDomainDef *def,
-                                  unsigned int parseFlags G_GNUC_UNUSED,
-                                  virDomainXMLOption *xmlopt)
-{
-    int ret = -1;
-
-    switch ((virDomainDeviceType)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_LEASE:
-    case VIR_DOMAIN_DEVICE_NET:
-    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_SHMEM:
-    case VIR_DOMAIN_DEVICE_TPM:
-    case VIR_DOMAIN_DEVICE_PANIC:
-    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;
-}
-
-
-/**
- * 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);
-        }
-    }
-}
-
-
-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;
-
-    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;
-
-            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);
-    }
-}
-
-
-#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 %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 '%s'"),
-                       dev->data.net->modelstr);
-        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;
-}
-
-static 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 '%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 '%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
-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;
-}
-
-
-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;
-}
-
-
 bool
 virDomainDefHasUSB(const virDomainDef *def)
 {
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 543b343be9..c56b84683c 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -3306,10 +3306,6 @@ bool
 virDomainSCSIDriveAddressIsUsed(const virDomainDef *def,
                                 const virDomainDeviceDriveAddress *addr);
 
-int virDomainDefPostParse(virDomainDef *def,
-                          unsigned int parseFlags,
-                          virDomainXMLOption *xmlopt,
-                          void *parseOpaque);
 bool virDomainDefHasUSB(const virDomainDef *def);
 
 bool virDomainDeviceAliasIsUserAlias(const char *aliasStr);
diff --git a/src/conf/domain_postparse.c b/src/conf/domain_postparse.c
new file mode 100644
index 0000000000..18f06dcca8
--- /dev/null
+++ b/src/conf/domain_postparse.c
@@ -0,0 +1,1483 @@
+/*
+ * 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 || def->mem.memory_slots) &&
+        !(def->mem.max_memory && def->mem.memory_slots)) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("both maximum memory size and "
+                         "memory slot count must be specified"));
+        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.firmwareFeatures &&
+        def->os.firmwareFeatures[VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_ENROLLED_KEYS] == VIR_TRISTATE_BOOL_YES) {
+
+        if (def->os.firmwareFeatures[VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_SECURE_BOOT] == VIR_TRISTATE_BOOL_NO) {
+            virReportError(VIR_ERR_XML_DETAIL, "%s",
+                           _("firmware feature 'enrolled-keys' cannot be enabled when "
+                             "firmware feature 'secure-boot' is disabled"));
+            return -1;
+        }
+
+        /* For all non-broken firmware builds, enrolled-keys implies
+         * secure-boot, and having the Secure Boot keys in the NVRAM file
+         * when the firmware doesn't support the Secure Boot feature doesn't
+         * make sense anyway. Reflect this fact explicitly in the XML */
+        def->os.firmwareFeatures[VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_SECURE_BOOT] = VIR_TRISTATE_BOOL_YES;
+    }
+
+    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 %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 %s doesn't support setting of "
+                                 "timer frequency"),
+                               virDomainTimerNameTypeToString(timer->name));
+                return -1;
+             }
+
+            if (timer->mode) {
+                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                               _("timer %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 %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='%u' "
+                             "bus='%u' target='%u' unit='%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 '%s' with mediated "
+                             "device model '%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 '%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 '%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->uuid) {
+
+            mem->uuid = g_new0(unsigned char, VIR_UUID_BUFLEN);
+            if (virUUIDGenerate(mem->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_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 int
+virDomainDeviceDefPostParseCommon(virDomainDeviceDef *dev,
+                                  const virDomainDef *def,
+                                  unsigned int parseFlags G_GNUC_UNUSED,
+                                  virDomainXMLOption *xmlopt)
+{
+    int ret = -1;
+
+    switch ((virDomainDeviceType)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_LEASE:
+    case VIR_DOMAIN_DEVICE_NET:
+    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_SHMEM:
+    case VIR_DOMAIN_DEVICE_TPM:
+    case VIR_DOMAIN_DEVICE_PANIC:
+    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;
+}
+
+
+/**
+ * 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 %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 '%s'"),
+                       dev->data.net->modelstr);
+        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 '%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 '%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 '%s' controllers with index '%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 '%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;
+
+    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;
+
+            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);
+    }
+}
+
+
+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;
+}
diff --git a/src/conf/domain_postparse.h b/src/conf/domain_postparse.h
new file mode 100644
index 0000000000..fe5e28ea7c
--- /dev/null
+++ b/src/conf/domain_postparse.h
@@ -0,0 +1,37 @@
+/*
+ * domain_postparse.h: 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/>.
+ */
+
+#pragma once
+
+#include "domain_conf.h"
+#include "virconftypes.h"
+
+int
+virDomainDeviceDefPostParseOne(virDomainDeviceDef *dev,
+                               const virDomainDef *def,
+                               unsigned int flags,
+                               virDomainXMLOption *xmlopt,
+                               void *parseOpaque);
+
+int
+virDomainDefPostParse(virDomainDef *def,
+                      unsigned int parseFlags,
+                      virDomainXMLOption *xmlopt,
+                      void *parseOpaque);
diff --git a/src/conf/meson.build b/src/conf/meson.build
index 82d265e975..5ef494c3ba 100644
--- a/src/conf/meson.build
+++ b/src/conf/meson.build
@@ -14,6 +14,7 @@ domain_conf_sources = [
   'domain_capabilities.c',
   'domain_conf.c',
   'domain_nwfilter.c',
+  'domain_postparse.c',
   'domain_validate.c',
   'moment_conf.c',
   'numa_conf.c',
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 76bcc64eb0..1e757389e6 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -343,7 +343,6 @@ virDomainDefNew;
 virDomainDefParseFile;
 virDomainDefParseNode;
 virDomainDefParseString;
-virDomainDefPostParse;
 virDomainDefSave;
 virDomainDefSetMemoryTotal;
 virDomainDefSetVcpus;
@@ -774,6 +773,10 @@ virDomainConfNWFilterTeardown;
 virDomainConfVMNWFilterTeardown;
 
 
+# conf/domain_postparse.h
+virDomainDefPostParse;
+
+
 # conf/domain_validate.h
 virDomainActualNetDefValidate;
 virDomainDefValidate;
diff --git a/src/libxl/xen_xl.c b/src/libxl/xen_xl.c
index e62f591dcd..4de4e3140f 100644
--- a/src/libxl/xen_xl.c
+++ b/src/libxl/xen_xl.c
@@ -27,6 +27,7 @@
 #include "virerror.h"
 #include "virlog.h"
 #include "domain_conf.h"
+#include "domain_postparse.h"
 #include "viralloc.h"
 #include "virstring.h"
 #include "storage_source_backingstore.h"
diff --git a/src/libxl/xen_xm.c b/src/libxl/xen_xm.c
index d6213a852d..081d323c2a 100644
--- a/src/libxl/xen_xm.c
+++ b/src/libxl/xen_xm.c
@@ -29,6 +29,7 @@
 #include "xenxs_private.h"
 #include "xen_xm.h"
 #include "domain_conf.h"
+#include "domain_postparse.h"
 #include "xen_common.h"
 
 #define VIR_FROM_THIS VIR_FROM_XENXM
diff --git a/src/lxc/lxc_native.c b/src/lxc/lxc_native.c
index 025c376ad2..3d0060e1b0 100644
--- a/src/lxc/lxc_native.c
+++ b/src/lxc/lxc_native.c
@@ -29,6 +29,7 @@
 #include "util/virstring.h"
 #include "util/virconf.h"
 #include "conf/domain_conf.h"
+#include "conf/domain_postparse.h"
 #include "virutil.h"
 
 #define VIR_FROM_THIS VIR_FROM_LXC
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 97c6ed95af..c4418df1ed 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -67,6 +67,7 @@
 #include "domain_audit.h"
 #include "domain_cgroup.h"
 #include "domain_driver.h"
+#include "domain_postparse.h"
 #include "domain_validate.h"
 #include "virpci.h"
 #include "virpidfile.h"
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 771a623ef7..486be9344a 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -75,6 +75,7 @@
 #include "domain_audit.h"
 #include "domain_cgroup.h"
 #include "domain_nwfilter.h"
+#include "domain_postparse.h"
 #include "domain_validate.h"
 #include "locking/domain_lock.h"
 #include "viruuid.h"
diff --git a/src/vmx/vmx.c b/src/vmx/vmx.c
index 43ddee5bb6..f7261f5d2d 100644
--- a/src/vmx/vmx.c
+++ b/src/vmx/vmx.c
@@ -31,6 +31,7 @@
 #include "viruri.h"
 #include "virstring.h"
 #include "virutil.h"
+#include "domain_postparse.h"
 
 VIR_LOG_INIT("vmx.vmx");
 
-- 
2.35.1



More information about the libvir-list mailing list