2022-07-07 10:03:33 +02:00
|
|
|
/*
|
|
|
|
* 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",
|
2022-07-07 10:44:13 +02:00
|
|
|
_("Total size of memory devices exceeds the total memory size"));
|
2022-07-07 10:03:33 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virDomainDefGetMemoryInitial(def) == 0) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
2022-07-07 10:44:13 +02:00
|
|
|
_("Memory size must be specified via <memory> or in the <numa> configuration"));
|
2022-07-07 10:03:33 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (def->mem.cur_balloon > virDomainDefGetMemoryTotal(def) ||
|
|
|
|
def->mem.cur_balloon == 0)
|
|
|
|
def->mem.cur_balloon = virDomainDefGetMemoryTotal(def);
|
|
|
|
|
2023-06-21 16:01:26 +02:00
|
|
|
if (def->mem.max_memory == 0 && def->mem.memory_slots > 0) {
|
2022-07-07 10:03:33 +02:00
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
2023-06-21 16:01:26 +02:00
|
|
|
_("maximum memory size must be specified when specifying number of memory slots"));
|
2022-07-07 10:03:33 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (def->mem.max_memory &&
|
|
|
|
def->mem.max_memory < virDomainDefGetMemoryTotal(def)) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
2022-07-07 10:44:13 +02:00
|
|
|
_("maximum memory size must be equal or greater than the actual memory size"));
|
2022-07-07 10:03:33 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virDomainDefPostParseOs(virDomainDef *def)
|
|
|
|
{
|
|
|
|
if (!def->os.loader)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (def->os.loader->path &&
|
|
|
|
def->os.loader->type == VIR_DOMAIN_LOADER_TYPE_NONE) {
|
|
|
|
/* By default, loader is type of 'rom' */
|
|
|
|
def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_ROM;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
virDomainDefPostParseMemtune(virDomainDef *def)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (virDomainNumaGetNodeCount(def->numa) == 0) {
|
|
|
|
/* If guest NUMA is not configured and any hugepage page has nodemask
|
|
|
|
* set to "0" free and clear that nodemas, otherwise we would rise
|
|
|
|
* an error that there is no guest NUMA node configured. */
|
|
|
|
for (i = 0; i < def->mem.nhugepages; i++) {
|
|
|
|
ssize_t nextBit;
|
|
|
|
|
|
|
|
if (!def->mem.hugepages[i].nodemask)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
nextBit = virBitmapNextSetBit(def->mem.hugepages[i].nodemask, 0);
|
|
|
|
if (nextBit < 0) {
|
|
|
|
g_clear_pointer(&def->mem.hugepages[i].nodemask,
|
|
|
|
virBitmapFree);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virDomainDefPostParseTimer(virDomainDef *def)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
/* verify settings of guest timers */
|
|
|
|
for (i = 0; i < def->clock.ntimers; i++) {
|
|
|
|
virDomainTimerDef *timer = def->clock.timers[i];
|
|
|
|
|
|
|
|
if (timer->name == VIR_DOMAIN_TIMER_NAME_KVMCLOCK ||
|
|
|
|
timer->name == VIR_DOMAIN_TIMER_NAME_HYPERVCLOCK) {
|
|
|
|
if (timer->tickpolicy) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("timer %1$s doesn't support setting of timer tickpolicy"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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",
|
2022-07-07 10:44:13 +02:00
|
|
|
_("setting of timer catchup policies is only supported with tickpolicy='catchup'"));
|
2022-07-07 10:03:33 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (timer->name != VIR_DOMAIN_TIMER_NAME_TSC) {
|
|
|
|
if (timer->frequency != 0) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("timer %1$s doesn't support setting of timer frequency"),
|
2022-07-07 10:03:33 +02:00
|
|
|
virDomainTimerNameTypeToString(timer->name));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (timer->mode) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("timer %1$s doesn't support setting of timer mode"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("timer %1$s doesn't support setting of timer track"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("SCSI host address controller='%1$u' bus='%2$u' target='%3$u' unit='%4$u' in use by a SCSI disk"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("Unsupported address type '%1$s' with mediated device model '%2$s'"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("Maximum supported number of ISA serial ports is '%1$d'"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("target port '%1$d' already allocated"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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",
|
2022-07-07 10:44:13 +02:00
|
|
|
_("'iothread' attribute only supported for virtio scsi controllers"));
|
2022-07-07 10:03:33 +02:00
|
|
|
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) &&
|
2023-07-28 11:40:37 +02:00
|
|
|
!mem->target.nvdimm.uuid) {
|
2022-07-07 10:03:33 +02:00
|
|
|
|
2023-07-28 11:40:37 +02:00
|
|
|
mem->target.nvdimm.uuid = g_new0(unsigned char, VIR_UUID_BUFLEN);
|
|
|
|
if (virUUIDGenerate(mem->target.nvdimm.uuid) < 0) {
|
2022-07-07 10:03:33 +02:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
"%s", _("Failed to generate UUID"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM:
|
2022-11-10 17:21:23 -08:00
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
|
2022-07-07 10:03:33 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-04-22 23:25:30 +05:30
|
|
|
static void
|
|
|
|
virDomainInputDefPostParse(virDomainInputDef *input,
|
|
|
|
const virDomainDef *def)
|
|
|
|
{
|
|
|
|
if (input->bus == VIR_DOMAIN_INPUT_BUS_DEFAULT) {
|
|
|
|
if (def->os.type == VIR_DOMAIN_OSTYPE_HVM) {
|
|
|
|
if ((input->type == VIR_DOMAIN_INPUT_TYPE_MOUSE ||
|
|
|
|
input->type == VIR_DOMAIN_INPUT_TYPE_KBD) &&
|
|
|
|
(ARCH_IS_X86(def->os.arch) || def->os.arch == VIR_ARCH_NONE)) {
|
2024-01-03 09:26:59 -06:00
|
|
|
input->bus = VIR_DOMAIN_INPUT_BUS_PS2;
|
2023-04-22 23:25:30 +05:30
|
|
|
} else if (ARCH_IS_S390(def->os.arch) ||
|
|
|
|
input->type == VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH) {
|
|
|
|
input->bus = VIR_DOMAIN_INPUT_BUS_VIRTIO;
|
|
|
|
} else if (input->type == VIR_DOMAIN_INPUT_TYPE_EVDEV) {
|
|
|
|
input->bus = VIR_DOMAIN_INPUT_BUS_NONE;
|
|
|
|
} else {
|
|
|
|
input->bus = VIR_DOMAIN_INPUT_BUS_USB;
|
|
|
|
}
|
|
|
|
} else if (def->os.type == VIR_DOMAIN_OSTYPE_XEN ||
|
|
|
|
def->os.type == VIR_DOMAIN_OSTYPE_XENPVH) {
|
|
|
|
input->bus = VIR_DOMAIN_INPUT_BUS_XEN;
|
|
|
|
} else {
|
|
|
|
if ((def->virtType == VIR_DOMAIN_VIRT_VZ ||
|
|
|
|
def->virtType == VIR_DOMAIN_VIRT_PARALLELS))
|
|
|
|
input->bus = VIR_DOMAIN_INPUT_BUS_PARALLELS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-07 10:03:33 +02:00
|
|
|
|
|
|
|
static int
|
|
|
|
virDomainDeviceDefPostParseCommon(virDomainDeviceDef *dev,
|
|
|
|
const virDomainDef *def,
|
|
|
|
unsigned int parseFlags G_GNUC_UNUSED,
|
|
|
|
virDomainXMLOption *xmlopt)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
|
2023-09-13 17:11:12 +02:00
|
|
|
switch (dev->type) {
|
2022-07-07 10:03:33 +02:00
|
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
|
|
ret = virDomainChrDefPostParse(dev->data.chr, def);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
|
|
virDomainRNGDefPostParse(dev->data.rng);
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
|
|
ret = virDomainDiskDefPostParse(dev->data.disk, def, xmlopt);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
|
|
virDomainVideoDefPostParse(dev->data.video, def);
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
|
|
ret = virDomainHostdevDefPostParse(dev->data.hostdev, def, xmlopt);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
|
|
ret = virDomainControllerDefPostParse(dev->data.controller);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
|
|
virDomainVsockDefPostParse(dev->data.vsock);
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
|
|
ret = virDomainMemoryDefPostParse(dev->data.memory, def);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
|
|
ret = virDomainFSDefPostParse(dev->data.fs);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
2023-04-22 23:25:30 +05:30
|
|
|
virDomainInputDefPostParse(dev->data.input, def);
|
|
|
|
ret = 0;
|
|
|
|
break;
|
2023-05-26 16:15:56 +02:00
|
|
|
|
|
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
|
|
case VIR_DOMAIN_DEVICE_NET:
|
2022-07-07 10:03:33 +02:00
|
|
|
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:
|
2023-01-17 09:46:50 +08:00
|
|
|
case VIR_DOMAIN_DEVICE_CRYPTO:
|
2022-07-07 10:03:33 +02:00
|
|
|
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",
|
2022-07-07 10:44:13 +02:00
|
|
|
_("memory hotplug tunables <maxMemory> are not supported by this hypervisor driver"));
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("name %1$s cannot contain '/'"), def->name);
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("driver does not support net model '%1$s'"),
|
2022-07-07 10:03:33 +02:00
|
|
|
dev->data.net->modelstr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-05-02 18:51:45 +02:00
|
|
|
if (dev->type == VIR_DOMAIN_DEVICE_DISK &&
|
|
|
|
dev->data.disk->src->fdgroup &&
|
|
|
|
UNSUPPORTED(VIR_DOMAIN_DEF_FEATURE_DISK_FD)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("driver does not support FD passing for disk '%1$s'"),
|
2022-05-02 18:51:45 +02:00
|
|
|
dev->data.disk->dst);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("vcpu '%1$zu' is both offline and not hotpluggable"), i);
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("boot order '%1$s' used for more than one device"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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",
|
2022-07-07 10:44:13 +02:00
|
|
|
_("per-device boot elements cannot be used together with os/boot elements"));
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("Multiple '%1$s' controllers with index '%2$d'"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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,
|
2023-03-09 11:20:54 +01:00
|
|
|
_("Multiple panic devices with model '%1$s'"),
|
2022-07-07 10:03:33 +02:00
|
|
|
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;
|
2023-12-18 22:10:35 +01:00
|
|
|
bool reinsert = false;
|
2022-07-07 10:03:33 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-12-18 22:10:35 +01:00
|
|
|
/* virDomainControllerInsert enforces an ordering of USB2 controllers
|
|
|
|
* based on their master port, which doesn't happen on the initial
|
|
|
|
* parse if the index wasn't yet allocated. If we encounter a USB2
|
|
|
|
* controller where we populated the index, we need to re-shuffle
|
|
|
|
* the controllers after allocating the index */
|
|
|
|
reinsert = true;
|
|
|
|
|
2022-07-07 10:03:33 +02:00
|
|
|
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);
|
|
|
|
}
|
2023-12-18 22:10:35 +01:00
|
|
|
|
|
|
|
if (reinsert) {
|
|
|
|
g_autofree virDomainControllerDef **controllers = g_steal_pointer(&def->controllers);
|
|
|
|
size_t ncontrollers = def->ncontrollers;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
def->controllers = g_new0(virDomainControllerDef *, ncontrollers);
|
|
|
|
def->ncontrollers = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < ncontrollers; i++) {
|
|
|
|
virDomainControllerInsertPreAlloced(def, controllers[i]);
|
|
|
|
}
|
|
|
|
}
|
2022-07-07 10:03:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|