2020-12-02 19:48:34 +00:00
|
|
|
/*
|
|
|
|
* domain_validate.c: domain general validation functions
|
|
|
|
*
|
|
|
|
* Copyright IBM Corp, 2020
|
|
|
|
*
|
|
|
|
* 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_validate.h"
|
|
|
|
#include "domain_conf.h"
|
2020-12-10 20:36:27 +00:00
|
|
|
#include "virconftypes.h"
|
2020-12-02 19:48:34 +00:00
|
|
|
#include "virlog.h"
|
|
|
|
#include "virutil.h"
|
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_DOMAIN
|
|
|
|
|
|
|
|
VIR_LOG_INIT("conf.domain_validate");
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefBootValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
if (def->os.bm_timeout_set && def->os.bm_timeout > 65535) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("invalid value for boot menu timeout, "
|
|
|
|
"must be in range [0,65535]"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (def->os.bios.rt_set &&
|
|
|
|
(def->os.bios.rt_delay < -1 || def->os.bios.rt_delay > 65535)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("invalid value for rebootTimeout, "
|
|
|
|
"must be in range [-1,65535]"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-12-02 20:23:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefVideoValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (def->nvideos == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Any video marked as primary will be put in index 0 by the
|
|
|
|
* parser. Ensure that we have only one primary set by the user. */
|
|
|
|
if (def->videos[0]->primary) {
|
|
|
|
for (i = 1; i < def->nvideos; i++) {
|
|
|
|
if (def->videos[i]->primary) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("Only one primary video device is supported"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-12-08 19:45:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainVideoDefValidate(const virDomainVideoDef *video,
|
|
|
|
const virDomainDef *def)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (video->type == VIR_DOMAIN_VIDEO_TYPE_DEFAULT) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("missing video model and cannot determine default"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* it doesn't make sense to pair video device type 'none' with any other
|
|
|
|
* types, there can be only a single video device in such case
|
|
|
|
*/
|
|
|
|
for (i = 0; i < def->nvideos; i++) {
|
|
|
|
if (def->videos[i]->type == VIR_DOMAIN_VIDEO_TYPE_NONE &&
|
|
|
|
def->nvideos > 1) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("a 'none' video type must be the only video device "
|
|
|
|
"defined for the domain"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (video->backend) {
|
|
|
|
case VIR_DOMAIN_VIDEO_BACKEND_TYPE_VHOSTUSER:
|
|
|
|
if (video->type != VIR_DOMAIN_VIDEO_TYPE_VIRTIO) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("'vhostuser' driver is only supported with 'virtio' device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_VIDEO_BACKEND_TYPE_DEFAULT:
|
|
|
|
case VIR_DOMAIN_VIDEO_BACKEND_TYPE_QEMU:
|
|
|
|
if (video->accel && video->accel->rendernode) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("unsupported rendernode accel attribute without 'vhostuser'"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_VIDEO_BACKEND_TYPE_LAST:
|
|
|
|
default:
|
|
|
|
virReportEnumRangeError(virDomainInputType, video->backend);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (video->res && (video->res->x == 0 || video->res->y == 0)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("video resolution values must be greater than 0"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-12-02 20:36:28 +00:00
|
|
|
if (video->type != VIR_DOMAIN_VIDEO_TYPE_QXL) {
|
|
|
|
if (video->ram != 0) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("ram attribute only supported for video type qxl"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (video->vram64 != 0) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("vram64 attribute only supported for video type qxl"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (video->vgamem != 0) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("vgamem attribute only supported for video type qxl"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-08 19:45:25 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2020-12-08 20:04:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virDomainDiskAddressDiskBusCompatibility:
|
|
|
|
* @bus: disk bus type
|
|
|
|
* @addressType: disk address type
|
|
|
|
*
|
|
|
|
* Check if the specified disk address type @addressType is compatible
|
|
|
|
* with the specified disk bus type @bus. This function checks
|
|
|
|
* compatibility with the bus types SATA, SCSI, FDC, and IDE only,
|
|
|
|
* because only these are handled in common code.
|
|
|
|
*
|
|
|
|
* Returns true if compatible or can't be decided in common code,
|
|
|
|
* false if known to be not compatible.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
virDomainDiskAddressDiskBusCompatibility(virDomainDiskBus bus,
|
|
|
|
virDomainDeviceAddressType addressType)
|
|
|
|
{
|
|
|
|
if (addressType == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
switch (bus) {
|
|
|
|
case VIR_DOMAIN_DISK_BUS_IDE:
|
|
|
|
case VIR_DOMAIN_DISK_BUS_FDC:
|
|
|
|
case VIR_DOMAIN_DISK_BUS_SCSI:
|
|
|
|
case VIR_DOMAIN_DISK_BUS_SATA:
|
|
|
|
return addressType == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
|
|
|
|
case VIR_DOMAIN_DISK_BUS_VIRTIO:
|
|
|
|
case VIR_DOMAIN_DISK_BUS_XEN:
|
|
|
|
case VIR_DOMAIN_DISK_BUS_USB:
|
|
|
|
case VIR_DOMAIN_DISK_BUS_UML:
|
|
|
|
case VIR_DOMAIN_DISK_BUS_SD:
|
|
|
|
case VIR_DOMAIN_DISK_BUS_LAST:
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("unexpected bus type '%d'"),
|
|
|
|
bus);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
2020-12-08 20:21:38 +00:00
|
|
|
virSecurityDeviceLabelDefValidate(virSecurityDeviceLabelDefPtr *seclabels,
|
|
|
|
size_t nseclabels,
|
|
|
|
virSecurityLabelDefPtr *vmSeclabels,
|
|
|
|
size_t nvmSeclabels)
|
2020-12-08 20:04:05 +00:00
|
|
|
{
|
|
|
|
virSecurityDeviceLabelDefPtr seclabel;
|
|
|
|
size_t i;
|
|
|
|
size_t j;
|
|
|
|
|
|
|
|
for (i = 0; i < nseclabels; i++) {
|
|
|
|
seclabel = seclabels[i];
|
|
|
|
|
|
|
|
/* find the security label that it's being overridden */
|
|
|
|
for (j = 0; j < nvmSeclabels; j++) {
|
|
|
|
if (STRNEQ_NULLABLE(vmSeclabels[j]->model, seclabel->model))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!vmSeclabels[j]->relabel) {
|
2020-12-08 20:21:38 +00:00
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
2020-12-08 20:04:05 +00:00
|
|
|
_("label overrides require relabeling to be "
|
|
|
|
"enabled at the domain level"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-03 16:54:58 +00:00
|
|
|
#define VENDOR_LEN 8
|
|
|
|
#define PRODUCT_LEN 16
|
|
|
|
|
2020-12-08 20:04:05 +00:00
|
|
|
int
|
|
|
|
virDomainDiskDefValidate(const virDomainDef *def,
|
|
|
|
const virDomainDiskDef *disk)
|
|
|
|
{
|
|
|
|
virStorageSourcePtr next;
|
|
|
|
|
|
|
|
/* Validate LUN configuration */
|
|
|
|
if (disk->device == VIR_DOMAIN_DISK_DEVICE_LUN) {
|
|
|
|
/* volumes haven't been translated at this point, so accept them */
|
|
|
|
if (!(disk->src->type == VIR_STORAGE_TYPE_BLOCK ||
|
|
|
|
disk->src->type == VIR_STORAGE_TYPE_VOLUME ||
|
|
|
|
(disk->src->type == VIR_STORAGE_TYPE_NETWORK &&
|
|
|
|
disk->src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI))) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("disk '%s' improperly configured for a "
|
|
|
|
"device='lun'"), disk->dst);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (disk->src->pr &&
|
|
|
|
disk->device != VIR_DOMAIN_DISK_DEVICE_LUN) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("<reservations/> allowed only for lun devices"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reject disks with a bus type that is not compatible with the
|
|
|
|
* given address type. The function considers only buses that are
|
|
|
|
* handled in common code. For other bus types it's not possible
|
|
|
|
* to decide compatibility in common code.
|
|
|
|
*/
|
|
|
|
if (!virDomainDiskAddressDiskBusCompatibility(disk->bus, disk->info.type)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("Invalid address type '%s' for the disk '%s' with the bus type '%s'"),
|
|
|
|
virDomainDeviceAddressTypeToString(disk->info.type),
|
|
|
|
disk->dst,
|
|
|
|
virDomainDiskBusTypeToString(disk->bus));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (disk->queues && disk->bus != VIR_DOMAIN_DISK_BUS_VIRTIO) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("queues attribute in disk driver element is only "
|
|
|
|
"supported by virtio-blk"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (disk->bus != VIR_DOMAIN_DISK_BUS_VIRTIO &&
|
|
|
|
(disk->model == VIR_DOMAIN_DISK_MODEL_VIRTIO ||
|
|
|
|
disk->model == VIR_DOMAIN_DISK_MODEL_VIRTIO_TRANSITIONAL ||
|
|
|
|
disk->model == VIR_DOMAIN_DISK_MODEL_VIRTIO_NON_TRANSITIONAL)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("disk model '%s' not supported for bus '%s'"),
|
|
|
|
virDomainDiskModelTypeToString(disk->model),
|
|
|
|
virDomainDiskBusTypeToString(disk->bus));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (disk->src->type == VIR_STORAGE_TYPE_NVME) {
|
|
|
|
/* NVMe namespaces start from 1 */
|
|
|
|
if (disk->src->nvme->namespc == 0) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
|
|
_("NVMe namespace can't be zero"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (next = disk->src; next; next = next->backingStore) {
|
2020-12-08 20:21:38 +00:00
|
|
|
if (virSecurityDeviceLabelDefValidate(next->seclabels,
|
|
|
|
next->nseclabels,
|
|
|
|
def->seclabels,
|
|
|
|
def->nseclabels) < 0)
|
2020-12-08 20:04:05 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-12-03 16:54:58 +00:00
|
|
|
if (disk->tray_status &&
|
|
|
|
disk->device != VIR_DOMAIN_DISK_DEVICE_FLOPPY &&
|
|
|
|
disk->device != VIR_DOMAIN_DISK_DEVICE_CDROM) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("tray is only valid for cdrom and floppy"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (disk->vendor && strlen(disk->vendor) > VENDOR_LEN) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("disk vendor is more than %d characters"),
|
|
|
|
VENDOR_LEN);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (disk->product && strlen(disk->product) > PRODUCT_LEN) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("disk product is more than %d characters"),
|
|
|
|
PRODUCT_LEN);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-12-08 20:04:05 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2020-12-08 20:32:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
#define SERIAL_CHANNEL_NAME_CHARS \
|
|
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-."
|
|
|
|
|
|
|
|
static int
|
|
|
|
virDomainChrSourceDefValidate(const virDomainChrSourceDef *src_def,
|
|
|
|
const virDomainChrDef *chr_def,
|
|
|
|
const virDomainDef *def)
|
|
|
|
{
|
|
|
|
switch ((virDomainChrType) src_def->type) {
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_NULL:
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_VC:
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_STDIO:
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_LAST:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
|
|
if (!src_def->data.file.path) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing source path attribute for char device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
|
|
if (!src_def->data.nmdm.master) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing master path attribute for nmdm device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!src_def->data.nmdm.slave) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing slave path attribute for nmdm device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_TCP:
|
|
|
|
if (!src_def->data.tcp.host) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing source host attribute for char device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!src_def->data.tcp.service) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing source service attribute for char device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (src_def->data.tcp.listen && src_def->data.tcp.reconnect.enabled) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("chardev reconnect is possible only for connect mode"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UDP:
|
|
|
|
if (!src_def->data.udp.connectService) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing source service attribute for char device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
|
|
/* The source path can be auto generated for certain specific
|
|
|
|
* types of channels, but in most cases we should report an
|
|
|
|
* error if the user didn't provide it */
|
|
|
|
if (!src_def->data.nix.path &&
|
|
|
|
!(chr_def &&
|
|
|
|
chr_def->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL &&
|
|
|
|
(chr_def->targetType == VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XEN ||
|
|
|
|
chr_def->targetType == VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Missing source path attribute for char device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (src_def->data.nix.listen && src_def->data.nix.reconnect.enabled) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("chardev reconnect is possible only for connect mode"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
|
|
|
|
if (!src_def->data.spiceport.channel) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
|
|
_("Missing source channel attribute for char device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (strspn(src_def->data.spiceport.channel,
|
|
|
|
SERIAL_CHANNEL_NAME_CHARS) < strlen(src_def->data.spiceport.channel)) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
|
|
_("Invalid character in source channel for char device"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virSecurityDeviceLabelDefValidate(src_def->seclabels,
|
|
|
|
src_def->nseclabels,
|
|
|
|
def->seclabels,
|
|
|
|
def->nseclabels) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainRedirdevDefValidate(const virDomainDef *def,
|
|
|
|
const virDomainRedirdevDef *redirdev)
|
|
|
|
{
|
|
|
|
if (redirdev->bus == VIR_DOMAIN_REDIRDEV_BUS_USB &&
|
|
|
|
!virDomainDefHasUSB(def)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("cannot add redirected USB device: "
|
|
|
|
"USB is disabled for this domain"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return virDomainChrSourceDefValidate(redirdev->source, NULL, def);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainChrDefValidate(const virDomainChrDef *chr,
|
|
|
|
const virDomainDef *def)
|
|
|
|
{
|
|
|
|
return virDomainChrSourceDefValidate(chr->source, chr, def);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainRNGDefValidate(const virDomainRNGDef *rng,
|
|
|
|
const virDomainDef *def)
|
|
|
|
{
|
|
|
|
if (rng->backend == VIR_DOMAIN_RNG_BACKEND_EGD)
|
|
|
|
return virDomainChrSourceDefValidate(rng->source.chardev, NULL, def);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainSmartcardDefValidate(const virDomainSmartcardDef *smartcard,
|
|
|
|
const virDomainDef *def)
|
|
|
|
{
|
2020-12-08 20:37:28 +00:00
|
|
|
if (smartcard->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
|
|
smartcard->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("Controllers must use the 'ccid' address type"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-12-08 20:32:23 +00:00
|
|
|
if (smartcard->type == VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH)
|
|
|
|
return virDomainChrSourceDefValidate(smartcard->data.passthru, NULL, def);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-12-04 14:28:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefTunablesValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
size_t i, j;
|
|
|
|
|
|
|
|
for (i = 0; i < def->blkio.ndevices; i++) {
|
|
|
|
for (j = 0; j < i; j++) {
|
|
|
|
if (STREQ(def->blkio.devices[j].path,
|
|
|
|
def->blkio.devices[i].path)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("duplicate blkio device path '%s'"),
|
|
|
|
def->blkio.devices[i].path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-12-08 20:52:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainControllerDefValidate(const virDomainControllerDef *controller)
|
|
|
|
{
|
|
|
|
if (controller->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
|
|
|
|
const virDomainPCIControllerOpts *opts = &controller->opts.pciopts;
|
|
|
|
|
2020-12-08 21:01:37 +00:00
|
|
|
if (controller->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT ||
|
|
|
|
controller->model == VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT) {
|
|
|
|
if (controller->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("pci-root and pcie-root controllers "
|
|
|
|
"should not have an address"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-08 20:52:24 +00:00
|
|
|
if (controller->idx > 255) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("PCI controller index %d too high, maximum is 255"),
|
|
|
|
controller->idx);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only validate the target index if it's been set */
|
|
|
|
if (opts->targetIndex != -1) {
|
|
|
|
|
|
|
|
if (opts->targetIndex < 0 || opts->targetIndex > 30) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
|
|
_("PCI controller target index '%d' out of "
|
|
|
|
"range - must be 0-30"),
|
|
|
|
opts->targetIndex);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((controller->idx == 0 && opts->targetIndex != 0) ||
|
|
|
|
(controller->idx != 0 && opts->targetIndex == 0)) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
|
|
_("Only the PCI controller with index 0 can "
|
|
|
|
"have target index 0, and vice versa"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2020-12-04 21:11:51 +00:00
|
|
|
|
|
|
|
if (opts->chassisNr != -1) {
|
|
|
|
if (opts->chassisNr < 1 || opts->chassisNr > 255) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("PCI controller chassisNr '%d' out of range "
|
|
|
|
"- must be 1-255"),
|
|
|
|
opts->chassisNr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts->chassis != -1) {
|
|
|
|
if (opts->chassis < 0 || opts->chassis > 255) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("PCI controller chassis '%d' out of range "
|
|
|
|
"- must be 0-255"),
|
|
|
|
opts->chassis);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts->port != -1) {
|
|
|
|
if (opts->port < 0 || opts->port > 255) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("PCI controller port '%d' out of range "
|
|
|
|
"- must be 0-255"),
|
|
|
|
opts->port);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts->busNr != -1) {
|
|
|
|
if (opts->busNr < 1 || opts->busNr > 254) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("PCI controller busNr '%d' out of range "
|
|
|
|
"- must be 1-254"),
|
|
|
|
opts->busNr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opts->numaNode >= 0 && controller->idx == 0) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("The PCI controller with index=0 can't "
|
|
|
|
"be associated with a NUMA node"));
|
|
|
|
return -1;
|
|
|
|
}
|
2020-12-08 20:52:24 +00:00
|
|
|
}
|
2020-12-04 21:11:51 +00:00
|
|
|
|
2020-12-08 20:52:24 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2020-12-07 12:48:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefIdMapValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
if ((def->idmap.uidmap && !def->idmap.gidmap) ||
|
|
|
|
(!def->idmap.uidmap && def->idmap.gidmap)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("uid and gid should be mapped both"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((def->idmap.uidmap && def->idmap.uidmap[0].start != 0) ||
|
|
|
|
(def->idmap.gidmap && def->idmap.gidmap[0].start != 0)) {
|
|
|
|
/* Root user of container hasn't been mapped to any user of host,
|
|
|
|
* return error. */
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
|
|
_("You must map the root user of container"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-12-10 18:32:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefDuplicateDiskInfoValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
size_t j;
|
|
|
|
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
|
|
for (j = i + 1; j < def->ndisks; j++) {
|
|
|
|
if (virDomainDiskDefCheckDuplicateInfo(def->disks[i],
|
|
|
|
def->disks[j]) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virDomainDefDuplicateDriveAddressesValidate:
|
|
|
|
* @def: domain definition to check against
|
|
|
|
*
|
|
|
|
* This function checks @def for duplicate drive addresses. Drive
|
|
|
|
* addresses are only in use for disks and hostdevs at the moment.
|
|
|
|
*
|
|
|
|
* Returns 0 in case of there are no duplicate drive addresses, -1
|
|
|
|
* otherwise.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
virDomainDefDuplicateDriveAddressesValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
size_t j;
|
|
|
|
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
|
|
virDomainDiskDefPtr disk_i = def->disks[i];
|
|
|
|
virDomainDeviceInfoPtr disk_info_i = &disk_i->info;
|
|
|
|
|
|
|
|
if (disk_info_i->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (j = i + 1; j < def->ndisks; j++) {
|
|
|
|
virDomainDiskDefPtr disk_j = def->disks[j];
|
|
|
|
virDomainDeviceInfoPtr disk_info_j = &disk_j->info;
|
|
|
|
|
|
|
|
if (disk_i->bus != disk_j->bus)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (disk_info_j->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (virDomainDeviceInfoAddressIsEqual(disk_info_i, disk_info_j)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("Found duplicate drive address for disk with "
|
|
|
|
"target name '%s' controller='%u' bus='%u' "
|
|
|
|
"target='%u' unit='%u'"),
|
|
|
|
disk_i->dst,
|
|
|
|
disk_info_i->addr.drive.controller,
|
|
|
|
disk_info_i->addr.drive.bus,
|
|
|
|
disk_info_i->addr.drive.target,
|
|
|
|
disk_info_i->addr.drive.unit);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Note: There is no need to check for conflicts with SCSI
|
|
|
|
* hostdevs above, because conflicts with hostdevs are checked
|
|
|
|
* in the next loop.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
|
|
virDomainHostdevDefPtr hdev_i = def->hostdevs[i];
|
|
|
|
virDomainDeviceInfoPtr hdev_info_i = hdev_i->info;
|
|
|
|
virDomainDeviceDriveAddressPtr hdev_addr_i;
|
|
|
|
|
|
|
|
if (!virHostdevIsSCSIDevice(hdev_i))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (hdev_i->info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
hdev_addr_i = &hdev_info_i->addr.drive;
|
|
|
|
for (j = i + 1; j < def->nhostdevs; j++) {
|
|
|
|
virDomainHostdevDefPtr hdev_j = def->hostdevs[j];
|
|
|
|
virDomainDeviceInfoPtr hdev_info_j = hdev_j->info;
|
|
|
|
|
|
|
|
if (!virHostdevIsSCSIDevice(hdev_j))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Address type check for hdev_j will be done implicitly
|
|
|
|
* in virDomainDeviceInfoAddressIsEqual() */
|
|
|
|
|
|
|
|
if (virDomainDeviceInfoAddressIsEqual(hdev_info_i, hdev_info_j)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("SCSI host address controller='%u' "
|
|
|
|
"bus='%u' target='%u' unit='%u' in "
|
|
|
|
"use by another SCSI host device"),
|
|
|
|
hdev_addr_i->bus,
|
|
|
|
hdev_addr_i->controller,
|
|
|
|
hdev_addr_i->target,
|
|
|
|
hdev_addr_i->unit);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virDomainDriveAddressIsUsedByDisk(def, VIR_DOMAIN_DISK_BUS_SCSI,
|
|
|
|
hdev_addr_i)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("SCSI host address controller='%u' "
|
|
|
|
"bus='%u' target='%u' unit='%u' in "
|
|
|
|
"use by another SCSI disk"),
|
|
|
|
hdev_addr_i->bus,
|
|
|
|
hdev_addr_i->controller,
|
|
|
|
hdev_addr_i->target,
|
|
|
|
hdev_addr_i->unit);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-12-10 18:58:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct virDomainDefValidateAliasesData {
|
|
|
|
GHashTable *aliases;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virDomainDeviceDefValidateAliasesIterator(virDomainDefPtr def,
|
|
|
|
virDomainDeviceDefPtr dev,
|
|
|
|
virDomainDeviceInfoPtr info,
|
|
|
|
void *opaque)
|
|
|
|
{
|
|
|
|
struct virDomainDefValidateAliasesData *data = opaque;
|
|
|
|
const char *alias = info->alias;
|
|
|
|
|
|
|
|
if (!virDomainDeviceAliasIsUserAlias(alias))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Some crazy backcompat for consoles. */
|
|
|
|
if (def->nserials && def->nconsoles &&
|
|
|
|
def->consoles[0]->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE &&
|
|
|
|
def->consoles[0]->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL &&
|
|
|
|
dev->type == VIR_DOMAIN_DEVICE_CHR &&
|
|
|
|
virDomainChrEquals(def->serials[0], dev->data.chr))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV &&
|
|
|
|
dev->data.hostdev->parentnet) {
|
|
|
|
/* This hostdev is a copy of some previous interface.
|
|
|
|
* Aliases are duplicated. */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virHashLookup(data->aliases, alias)) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
|
|
_("non unique alias detected: %s"),
|
|
|
|
alias);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virHashAddEntry(data->aliases, alias, (void *) 1) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("Unable to construct table of device aliases"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virDomainDefValidateAliases:
|
|
|
|
*
|
|
|
|
* Check for uniqueness of device aliases. If @aliases is not
|
|
|
|
* NULL return hash table of all the aliases in it.
|
|
|
|
*
|
|
|
|
* Returns 0 on success,
|
|
|
|
* -1 otherwise (with error reported).
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
virDomainDefValidateAliases(const virDomainDef *def,
|
|
|
|
GHashTable **aliases)
|
|
|
|
{
|
|
|
|
struct virDomainDefValidateAliasesData data;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
/* We are not storing copies of aliases. Don't free them. */
|
|
|
|
if (!(data.aliases = virHashNew(NULL)))
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (virDomainDeviceInfoIterateFlags((virDomainDefPtr) def,
|
|
|
|
virDomainDeviceDefValidateAliasesIterator,
|
|
|
|
DOMAIN_DEVICE_ITERATE_ALL_CONSOLES,
|
|
|
|
&data) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (aliases)
|
|
|
|
*aliases = g_steal_pointer(&data.aliases);
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
|
|
virHashFree(data.aliases);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virDomainDeviceValidateAliasImpl(const virDomainDef *def,
|
|
|
|
virDomainDeviceDefPtr dev)
|
|
|
|
{
|
|
|
|
GHashTable *aliases = NULL;
|
|
|
|
virDomainDeviceInfoPtr info = virDomainDeviceGetInfo(dev);
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
if (!info || !info->alias)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (virDomainDefValidateAliases(def, &aliases) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (virHashLookup(aliases, info->alias)) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
|
|
_("non unique alias detected: %s"),
|
|
|
|
info->alias);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
|
|
|
|
|
|
virHashFree(aliases);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDeviceValidateAliasForHotplug(virDomainObjPtr vm,
|
|
|
|
virDomainDeviceDefPtr dev,
|
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
virDomainDefPtr persDef = NULL;
|
|
|
|
virDomainDefPtr liveDef = NULL;
|
|
|
|
|
|
|
|
if (virDomainObjGetDefs(vm, flags, &liveDef, &persDef) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (persDef &&
|
|
|
|
virDomainDeviceValidateAliasImpl(persDef, dev) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (liveDef &&
|
|
|
|
virDomainDeviceValidateAliasImpl(liveDef, dev) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-12-10 20:36:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefLifecycleActionValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
if (!virDomainDefLifecycleActionAllowed(VIR_DOMAIN_LIFECYCLE_POWEROFF,
|
|
|
|
def->onPoweroff)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!virDomainDefLifecycleActionAllowed(VIR_DOMAIN_LIFECYCLE_REBOOT,
|
|
|
|
def->onReboot)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!virDomainDefLifecycleActionAllowed(VIR_DOMAIN_LIFECYCLE_CRASH,
|
|
|
|
def->onCrash)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefMemtuneValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
const virDomainMemtune *mem = &(def->mem);
|
|
|
|
size_t i;
|
|
|
|
ssize_t pos = virDomainNumaGetNodeCount(def->numa) - 1;
|
|
|
|
|
|
|
|
for (i = 0; i < mem->nhugepages; i++) {
|
|
|
|
size_t j;
|
|
|
|
ssize_t nextBit;
|
|
|
|
|
|
|
|
for (j = 0; j < i; j++) {
|
|
|
|
if (mem->hugepages[i].nodemask &&
|
|
|
|
mem->hugepages[j].nodemask &&
|
|
|
|
virBitmapOverlaps(mem->hugepages[i].nodemask,
|
|
|
|
mem->hugepages[j].nodemask)) {
|
|
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
|
|
_("nodeset attribute of hugepages "
|
|
|
|
"of sizes %llu and %llu intersect"),
|
|
|
|
mem->hugepages[i].size,
|
|
|
|
mem->hugepages[j].size);
|
|
|
|
return -1;
|
|
|
|
} else if (!mem->hugepages[i].nodemask &&
|
|
|
|
!mem->hugepages[j].nodemask) {
|
|
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
|
|
_("two master hugepages detected: "
|
|
|
|
"%llu and %llu"),
|
|
|
|
mem->hugepages[i].size,
|
|
|
|
mem->hugepages[j].size);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mem->hugepages[i].nodemask) {
|
|
|
|
/* This is the master hugepage to use. Skip it as it has no
|
|
|
|
* nodemask anyway. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
nextBit = virBitmapNextSetBit(mem->hugepages[i].nodemask, pos);
|
|
|
|
if (nextBit >= 0) {
|
|
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
|
|
_("hugepages: node %zd not found"),
|
|
|
|
nextBit);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefOSValidate(const virDomainDef *def,
|
|
|
|
virDomainXMLOptionPtr xmlopt)
|
|
|
|
{
|
|
|
|
if (!def->os.loader)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (def->os.firmware &&
|
|
|
|
!(xmlopt->config.features & VIR_DOMAIN_DEF_FEATURE_FW_AUTOSELECT)) {
|
|
|
|
virReportError(VIR_ERR_XML_DETAIL, "%s",
|
|
|
|
_("firmware auto selection not implemented for this driver"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!def->os.loader->path &&
|
|
|
|
def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_NONE) {
|
|
|
|
virReportError(VIR_ERR_XML_DETAIL, "%s",
|
|
|
|
_("no loader path specified and firmware auto selection disabled"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define CPUTUNE_VALIDATE_PERIOD(name) \
|
|
|
|
do { \
|
|
|
|
if (def->cputune.name > 0 && \
|
|
|
|
(def->cputune.name < 1000 || def->cputune.name > 1000000)) { \
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \
|
|
|
|
_("Value of cputune '%s' must be in range " \
|
|
|
|
"[1000, 1000000]"), #name); \
|
|
|
|
return -1; \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
#define CPUTUNE_VALIDATE_QUOTA(name) \
|
|
|
|
do { \
|
|
|
|
if (def->cputune.name > 0 && \
|
|
|
|
(def->cputune.name < 1000 || \
|
|
|
|
def->cputune.name > 18446744073709551LL)) { \
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \
|
|
|
|
_("Value of cputune '%s' must be in range " \
|
|
|
|
"[1000, 18446744073709551]"), #name); \
|
|
|
|
return -1; \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
int
|
|
|
|
virDomainDefCputuneValidate(const virDomainDef *def)
|
|
|
|
{
|
|
|
|
CPUTUNE_VALIDATE_PERIOD(period);
|
|
|
|
CPUTUNE_VALIDATE_PERIOD(global_period);
|
|
|
|
CPUTUNE_VALIDATE_PERIOD(emulator_period);
|
|
|
|
CPUTUNE_VALIDATE_PERIOD(iothread_period);
|
|
|
|
|
|
|
|
CPUTUNE_VALIDATE_QUOTA(quota);
|
|
|
|
CPUTUNE_VALIDATE_QUOTA(global_quota);
|
|
|
|
CPUTUNE_VALIDATE_QUOTA(emulator_quota);
|
|
|
|
CPUTUNE_VALIDATE_QUOTA(iothread_quota);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#undef CPUTUNE_VALIDATE_PERIOD
|
|
|
|
#undef CPUTUNE_VALIDATE_QUOTA
|
2020-12-10 22:12:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virDomainDefValidateInternal(const virDomainDef *def,
|
|
|
|
virDomainXMLOptionPtr xmlopt)
|
|
|
|
{
|
|
|
|
if (virDomainDefDuplicateDiskInfoValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefDuplicateDriveAddressesValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefGetVcpusTopology(def, NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefValidateAliases(def, NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (def->iommu &&
|
|
|
|
def->iommu->intremap == VIR_TRISTATE_SWITCH_ON &&
|
|
|
|
def->features[VIR_DOMAIN_FEATURE_IOAPIC] != VIR_DOMAIN_IOAPIC_QEMU) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
|
|
_("IOMMU interrupt remapping requires split I/O APIC "
|
|
|
|
"(ioapic driver='qemu')"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (def->iommu &&
|
|
|
|
def->iommu->eim == VIR_TRISTATE_SWITCH_ON &&
|
|
|
|
def->iommu->intremap != VIR_TRISTATE_SWITCH_ON) {
|
|
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
|
|
_("IOMMU eim requires interrupt remapping to be enabled"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virDomainDefLifecycleActionValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefMemtuneValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefOSValidate(def, xmlopt) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefCputuneValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefBootValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefVideoValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefTunablesValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefIdMapValidate(def) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainNumaDefValidate(def->numa) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virDomainDefValidateDeviceIterator(virDomainDefPtr def,
|
|
|
|
virDomainDeviceDefPtr dev,
|
|
|
|
virDomainDeviceInfoPtr info G_GNUC_UNUSED,
|
|
|
|
void *opaque)
|
|
|
|
{
|
|
|
|
struct virDomainDefPostParseDeviceIteratorData *data = opaque;
|
|
|
|
return virDomainDeviceDefValidate(dev, def,
|
|
|
|
data->parseFlags, data->xmlopt,
|
|
|
|
data->parseOpaque);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* virDomainDefValidate:
|
|
|
|
* @def: domain definition
|
|
|
|
* @caps: driver capabilities object
|
|
|
|
* @parseFlags: virDomainDefParseFlags
|
|
|
|
* @xmlopt: XML parser option object
|
|
|
|
* @parseOpaque: hypervisor driver specific data for this validation run
|
|
|
|
*
|
|
|
|
* This validation function is designed to take checks of globally invalid
|
|
|
|
* configurations that the parser needs to accept so that VMs don't vanish upon
|
|
|
|
* daemon restart. Such definition can be rejected upon startup or define, where
|
|
|
|
* this function shall be called.
|
|
|
|
*
|
|
|
|
* Returns 0 if domain definition is valid, -1 on error and reports an
|
|
|
|
* appropriate message.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
virDomainDefValidate(virDomainDefPtr def,
|
|
|
|
unsigned int parseFlags,
|
|
|
|
virDomainXMLOptionPtr xmlopt,
|
|
|
|
void *parseOpaque)
|
|
|
|
{
|
|
|
|
struct virDomainDefPostParseDeviceIteratorData data = {
|
|
|
|
.xmlopt = xmlopt,
|
|
|
|
.parseFlags = parseFlags,
|
|
|
|
.parseOpaque = parseOpaque,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* validate configuration only in certain places */
|
|
|
|
if (parseFlags & VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* call the domain config callback */
|
|
|
|
if (xmlopt->config.domainValidateCallback &&
|
|
|
|
xmlopt->config.domainValidateCallback(def, xmlopt->config.priv, parseOpaque) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* iterate the devices */
|
|
|
|
if (virDomainDeviceInfoIterateFlags(def,
|
|
|
|
virDomainDefValidateDeviceIterator,
|
|
|
|
(DOMAIN_DEVICE_ITERATE_ALL_CONSOLES |
|
|
|
|
DOMAIN_DEVICE_ITERATE_MISSING_INFO),
|
|
|
|
&data) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainDefValidateInternal(def, xmlopt) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|