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 - * 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 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 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 or in the " - " 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 + * . + */ + +#include + +#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 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 or in the " + " 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 + * 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 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 + * . + */ + +#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");