mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-09 06:25:19 +00:00
f1149b8d3a
Iterating over all child elements of a node does not require xpath. By doing away with xpath for this code, the code can be simplified. Signed-off-by: Tim Wiederhake <twiederh@redhat.com> Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
31144 lines
972 KiB
C
31144 lines
972 KiB
C
/*
|
|
* domain_conf.c: domain XML processing
|
|
*
|
|
* Copyright (C) 2006-2016 Red Hat, Inc.
|
|
* Copyright (C) 2006-2008 Daniel P. Berrange
|
|
* Copyright (c) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany.
|
|
*
|
|
* 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 <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "configmake.h"
|
|
#include "internal.h"
|
|
#include "virerror.h"
|
|
#include "checkpoint_conf.h"
|
|
#include "datatypes.h"
|
|
#include "domain_addr.h"
|
|
#include "domain_conf.h"
|
|
#include "domain_validate.h"
|
|
#include "snapshot_conf.h"
|
|
#include "viralloc.h"
|
|
#include "virxml.h"
|
|
#include "viruuid.h"
|
|
#include "virbuffer.h"
|
|
#include "virlog.h"
|
|
#include "nwfilter_conf.h"
|
|
#include "virnetworkportdef.h"
|
|
#include "storage_conf.h"
|
|
#include "storage_source_conf.h"
|
|
#include "virfile.h"
|
|
#include "virbitmap.h"
|
|
#include "secret_conf.h"
|
|
#include "netdev_vport_profile_conf.h"
|
|
#include "netdev_bandwidth_conf.h"
|
|
#include "netdev_vlan_conf.h"
|
|
#include "device_conf.h"
|
|
#include "network_conf.h"
|
|
#include "virtpm.h"
|
|
#include "virsecret.h"
|
|
#include "virstring.h"
|
|
#include "virnetdev.h"
|
|
#include "virnetdevtap.h"
|
|
#include "virnetdevmacvlan.h"
|
|
#include "virarptable.h"
|
|
#include "virmdev.h"
|
|
#include "virdomainsnapshotobjlist.h"
|
|
#include "virdomaincheckpointobjlist.h"
|
|
#include "virutil.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_DOMAIN
|
|
|
|
VIR_LOG_INIT("conf.domain_conf");
|
|
|
|
#define VIR_DOMAIN_DEF_FORMAT_COMMON_FLAGS \
|
|
(VIR_DOMAIN_DEF_FORMAT_SECURE | \
|
|
VIR_DOMAIN_DEF_FORMAT_INACTIVE | \
|
|
VIR_DOMAIN_DEF_FORMAT_MIGRATABLE)
|
|
|
|
VIR_ENUM_IMPL(virDomainTaint,
|
|
VIR_DOMAIN_TAINT_LAST,
|
|
"custom-argv",
|
|
"custom-monitor",
|
|
"high-privileges",
|
|
"shell-scripts",
|
|
"disk-probing",
|
|
"external-launch",
|
|
"host-cpu",
|
|
"hook-script",
|
|
"cdrom-passthrough",
|
|
"custom-dtb",
|
|
"custom-ga-command",
|
|
"custom-hypervisor-feature",
|
|
"deprecated-config",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainTaintMessage,
|
|
VIR_DOMAIN_TAINT_LAST,
|
|
N_("custom configuration parameters specified"),
|
|
N_("custom monitor control commands issued"),
|
|
N_("running with undesirable elevated privileges"),
|
|
N_("network configuration using opaque shell scripts"),
|
|
N_("potentially unsafe disk format probing"),
|
|
N_("managing externally launched configuration"),
|
|
N_("potentially unsafe use of host CPU passthrough"),
|
|
N_("configuration potentially modified by hook script"),
|
|
N_("use of host cdrom passthrough"),
|
|
N_("custom device tree blob used"),
|
|
N_("custom guest agent control commands issued"),
|
|
N_("hypervisor feature autodetection override"),
|
|
N_("use of deprecated configuration settings"),
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainVirt,
|
|
VIR_DOMAIN_VIRT_LAST,
|
|
"none",
|
|
"qemu",
|
|
"kqemu",
|
|
"kvm",
|
|
"xen",
|
|
"lxc",
|
|
"uml",
|
|
"openvz",
|
|
"test",
|
|
"vmware",
|
|
"hyperv",
|
|
"vbox",
|
|
"phyp",
|
|
"parallels",
|
|
"bhyve",
|
|
"vz",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainOS,
|
|
VIR_DOMAIN_OSTYPE_LAST,
|
|
"hvm",
|
|
"xen",
|
|
"linux",
|
|
"exe",
|
|
"uml",
|
|
"xenpvh",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainBoot,
|
|
VIR_DOMAIN_BOOT_LAST,
|
|
"fd",
|
|
"cdrom",
|
|
"hd",
|
|
"network",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFeature,
|
|
VIR_DOMAIN_FEATURE_LAST,
|
|
"acpi",
|
|
"apic",
|
|
"pae",
|
|
"hap",
|
|
"viridian",
|
|
"privnet",
|
|
"hyperv",
|
|
"kvm",
|
|
"pvspinlock",
|
|
"capabilities",
|
|
"pmu",
|
|
"vmport",
|
|
"gic",
|
|
"smm",
|
|
"ioapic",
|
|
"hpt",
|
|
"vmcoreinfo",
|
|
"htm",
|
|
"nested-hv",
|
|
"msrs",
|
|
"ccf-assist",
|
|
"xen",
|
|
"cfpc",
|
|
"sbbc",
|
|
"ibs",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainCapabilitiesPolicy,
|
|
VIR_DOMAIN_CAPABILITIES_POLICY_LAST,
|
|
"default",
|
|
"allow",
|
|
"deny",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHyperv,
|
|
VIR_DOMAIN_HYPERV_LAST,
|
|
"relaxed",
|
|
"vapic",
|
|
"spinlocks",
|
|
"vpindex",
|
|
"runtime",
|
|
"synic",
|
|
"stimer",
|
|
"reset",
|
|
"vendor_id",
|
|
"frequencies",
|
|
"reenlightenment",
|
|
"tlbflush",
|
|
"ipi",
|
|
"evmcs",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainKVM,
|
|
VIR_DOMAIN_KVM_LAST,
|
|
"hidden",
|
|
"hint-dedicated",
|
|
"poll-control",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainXen,
|
|
VIR_DOMAIN_XEN_LAST,
|
|
"e820_host",
|
|
"passthrough",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainXenPassthroughMode,
|
|
VIR_DOMAIN_XEN_PASSTHROUGH_MODE_LAST,
|
|
"default",
|
|
"sync_pt",
|
|
"share_pt",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainMsrsUnknown,
|
|
VIR_DOMAIN_MSRS_UNKNOWN_LAST,
|
|
"ignore",
|
|
"fault",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainProcessCapsFeature,
|
|
VIR_DOMAIN_PROCES_CAPS_FEATURE_LAST,
|
|
"audit_control",
|
|
"audit_write",
|
|
"block_suspend",
|
|
"chown",
|
|
"dac_override",
|
|
"dac_read_search",
|
|
"fowner",
|
|
"fsetid",
|
|
"ipc_lock",
|
|
"ipc_owner",
|
|
"kill",
|
|
"lease",
|
|
"linux_immutable",
|
|
"mac_admin",
|
|
"mac_override",
|
|
"mknod",
|
|
"net_admin",
|
|
"net_bind_service",
|
|
"net_broadcast",
|
|
"net_raw",
|
|
"setgid",
|
|
"setfcap",
|
|
"setpcap",
|
|
"setuid",
|
|
"sys_admin",
|
|
"sys_boot",
|
|
"sys_chroot",
|
|
"sys_module",
|
|
"sys_nice",
|
|
"sys_pacct",
|
|
"sys_ptrace",
|
|
"sys_rawio",
|
|
"sys_resource",
|
|
"sys_time",
|
|
"sys_tty_config",
|
|
"syslog",
|
|
"wake_alarm",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainLifecycle,
|
|
VIR_DOMAIN_LIFECYCLE_LAST,
|
|
"poweroff",
|
|
"reboot",
|
|
"crash",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainLifecycleAction,
|
|
VIR_DOMAIN_LIFECYCLE_ACTION_LAST,
|
|
"destroy",
|
|
"restart",
|
|
"rename-restart",
|
|
"preserve",
|
|
"coredump-destroy",
|
|
"coredump-restart",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainLockFailure,
|
|
VIR_DOMAIN_LOCK_FAILURE_LAST,
|
|
"default",
|
|
"poweroff",
|
|
"restart",
|
|
"pause",
|
|
"ignore",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDevice,
|
|
VIR_DOMAIN_DEVICE_LAST,
|
|
"none",
|
|
"disk",
|
|
"lease",
|
|
"filesystem",
|
|
"interface",
|
|
"input",
|
|
"sound",
|
|
"video",
|
|
"hostdev",
|
|
"watchdog",
|
|
"controller",
|
|
"graphics",
|
|
"hub",
|
|
"redirdev",
|
|
"smartcard",
|
|
"chr",
|
|
"memballoon",
|
|
"nvram",
|
|
"rng",
|
|
"shmem",
|
|
"tpm",
|
|
"panic",
|
|
"memory",
|
|
"iommu",
|
|
"vsock",
|
|
"audio",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskDevice,
|
|
VIR_DOMAIN_DISK_DEVICE_LAST,
|
|
"disk",
|
|
"cdrom",
|
|
"floppy",
|
|
"lun",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskGeometryTrans,
|
|
VIR_DOMAIN_DISK_TRANS_LAST,
|
|
"default",
|
|
"none",
|
|
"auto",
|
|
"lba",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskBus,
|
|
VIR_DOMAIN_DISK_BUS_LAST,
|
|
"none",
|
|
"ide",
|
|
"fdc",
|
|
"scsi",
|
|
"virtio",
|
|
"xen",
|
|
"usb",
|
|
"uml",
|
|
"sata",
|
|
"sd",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskCache,
|
|
VIR_DOMAIN_DISK_CACHE_LAST,
|
|
"default",
|
|
"none",
|
|
"writethrough",
|
|
"writeback",
|
|
"directsync",
|
|
"unsafe",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskErrorPolicy,
|
|
VIR_DOMAIN_DISK_ERROR_POLICY_LAST,
|
|
"default",
|
|
"stop",
|
|
"report",
|
|
"ignore",
|
|
"enospace",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskIo,
|
|
VIR_DOMAIN_DISK_IO_LAST,
|
|
"default",
|
|
"native",
|
|
"threads",
|
|
"io_uring",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDeviceSGIO,
|
|
VIR_DOMAIN_DEVICE_SGIO_LAST,
|
|
"default",
|
|
"filtered",
|
|
"unfiltered",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainController,
|
|
VIR_DOMAIN_CONTROLLER_TYPE_LAST,
|
|
"ide",
|
|
"fdc",
|
|
"scsi",
|
|
"sata",
|
|
"virtio-serial",
|
|
"ccid",
|
|
"usb",
|
|
"pci",
|
|
"xenbus",
|
|
"isa",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainControllerModelPCI,
|
|
VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST,
|
|
"pci-root",
|
|
"pcie-root",
|
|
"pci-bridge",
|
|
"dmi-to-pci-bridge",
|
|
"pcie-to-pci-bridge",
|
|
"pcie-root-port",
|
|
"pcie-switch-upstream-port",
|
|
"pcie-switch-downstream-port",
|
|
"pci-expander-bus",
|
|
"pcie-expander-bus",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainControllerPCIModelName,
|
|
VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_LAST,
|
|
"none",
|
|
"pci-bridge",
|
|
"i82801b11-bridge",
|
|
"ioh3420",
|
|
"x3130-upstream",
|
|
"xio3130-downstream",
|
|
"pxb",
|
|
"pxb-pcie",
|
|
"pcie-root-port",
|
|
"spapr-pci-host-bridge",
|
|
"pcie-pci-bridge",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainControllerModelSCSI,
|
|
VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LAST,
|
|
"auto",
|
|
"buslogic",
|
|
"lsilogic",
|
|
"lsisas1068",
|
|
"vmpvscsi",
|
|
"ibmvscsi",
|
|
"virtio-scsi",
|
|
"lsisas1078",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
"ncr53c90",
|
|
"dc390",
|
|
"am53c974",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainControllerModelISA, VIR_DOMAIN_CONTROLLER_MODEL_ISA_LAST,
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainControllerModelUSB,
|
|
VIR_DOMAIN_CONTROLLER_MODEL_USB_LAST,
|
|
"piix3-uhci",
|
|
"piix4-uhci",
|
|
"ehci",
|
|
"ich9-ehci1",
|
|
"ich9-uhci1",
|
|
"ich9-uhci2",
|
|
"ich9-uhci3",
|
|
"vt82c686b-uhci",
|
|
"pci-ohci",
|
|
"nec-xhci",
|
|
"qusb1",
|
|
"qusb2",
|
|
"qemu-xhci",
|
|
"none",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainControllerModelIDE,
|
|
VIR_DOMAIN_CONTROLLER_MODEL_IDE_LAST,
|
|
"piix3",
|
|
"piix4",
|
|
"ich6",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainControllerModelVirtioSerial,
|
|
VIR_DOMAIN_CONTROLLER_MODEL_VIRTIO_SERIAL_LAST,
|
|
"virtio",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFS,
|
|
VIR_DOMAIN_FS_TYPE_LAST,
|
|
"mount",
|
|
"block",
|
|
"file",
|
|
"template",
|
|
"ram",
|
|
"bind",
|
|
"volume",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFSDriver,
|
|
VIR_DOMAIN_FS_DRIVER_TYPE_LAST,
|
|
"default",
|
|
"path",
|
|
"handle",
|
|
"loop",
|
|
"nbd",
|
|
"ploop",
|
|
"virtiofs",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFSAccessMode,
|
|
VIR_DOMAIN_FS_ACCESSMODE_LAST,
|
|
"",
|
|
"passthrough",
|
|
"mapped",
|
|
"squash",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFSWrpolicy,
|
|
VIR_DOMAIN_FS_WRPOLICY_LAST,
|
|
"default",
|
|
"immediate",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFSModel,
|
|
VIR_DOMAIN_FS_MODEL_LAST,
|
|
"default",
|
|
"virtio",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFSMultidevs,
|
|
VIR_DOMAIN_FS_MULTIDEVS_LAST,
|
|
"default",
|
|
"remap",
|
|
"forbid",
|
|
"warn",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFSCacheMode,
|
|
VIR_DOMAIN_FS_CACHE_MODE_LAST,
|
|
"default",
|
|
"none",
|
|
"always",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainFSSandboxMode,
|
|
VIR_DOMAIN_FS_SANDBOX_MODE_LAST,
|
|
"default",
|
|
"namespace",
|
|
"chroot",
|
|
);
|
|
|
|
|
|
VIR_ENUM_IMPL(virDomainNet,
|
|
VIR_DOMAIN_NET_TYPE_LAST,
|
|
"user",
|
|
"ethernet",
|
|
"vhostuser",
|
|
"server",
|
|
"client",
|
|
"mcast",
|
|
"network",
|
|
"bridge",
|
|
"internal",
|
|
"direct",
|
|
"hostdev",
|
|
"udp",
|
|
"vdpa",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainNetModel,
|
|
VIR_DOMAIN_NET_MODEL_LAST,
|
|
"unknown",
|
|
"netfront",
|
|
"rtl8139",
|
|
"virtio",
|
|
"e1000",
|
|
"e1000e",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
"usb-net",
|
|
"spapr-vlan",
|
|
"lan9118",
|
|
"scm91c111",
|
|
"vlance",
|
|
"vmxnet",
|
|
"vmxnet2",
|
|
"vmxnet3",
|
|
"Am79C970A",
|
|
"Am79C973",
|
|
"82540EM",
|
|
"82545EM",
|
|
"82543GC",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainNetBackend,
|
|
VIR_DOMAIN_NET_BACKEND_TYPE_LAST,
|
|
"default",
|
|
"qemu",
|
|
"vhost",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainNetVirtioTxMode,
|
|
VIR_DOMAIN_NET_VIRTIO_TX_MODE_LAST,
|
|
"default",
|
|
"iothread",
|
|
"timer",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainNetTeaming,
|
|
VIR_DOMAIN_NET_TEAMING_TYPE_LAST,
|
|
"none",
|
|
"persistent",
|
|
"transient",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainNetInterfaceLinkState,
|
|
VIR_DOMAIN_NET_INTERFACE_LINK_STATE_LAST,
|
|
"default",
|
|
"up",
|
|
"down",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChrDeviceState,
|
|
VIR_DOMAIN_CHR_DEVICE_STATE_LAST,
|
|
"default",
|
|
"connected",
|
|
"disconnected",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainNetMacType,
|
|
VIR_DOMAIN_NET_MAC_TYPE_LAST,
|
|
"",
|
|
"generated",
|
|
"static",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChrSerialTarget,
|
|
VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_LAST,
|
|
"none",
|
|
"isa-serial",
|
|
"usb-serial",
|
|
"pci-serial",
|
|
"spapr-vio-serial",
|
|
"system-serial",
|
|
"sclp-serial",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChrChannelTarget,
|
|
VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_LAST,
|
|
"none",
|
|
"guestfwd",
|
|
"virtio",
|
|
"xen",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChrConsoleTarget,
|
|
VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_LAST,
|
|
"none",
|
|
"serial",
|
|
"xen",
|
|
"uml",
|
|
"virtio",
|
|
"lxc",
|
|
"openvz",
|
|
"sclp",
|
|
"sclplm",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChrSerialTargetModel,
|
|
VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_LAST,
|
|
"none",
|
|
"isa-serial",
|
|
"usb-serial",
|
|
"pci-serial",
|
|
"spapr-vty",
|
|
"pl011",
|
|
"sclpconsole",
|
|
"sclplmconsole",
|
|
"16550a",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChrDevice,
|
|
VIR_DOMAIN_CHR_DEVICE_TYPE_LAST,
|
|
"parallel",
|
|
"serial",
|
|
"console",
|
|
"channel",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChr,
|
|
VIR_DOMAIN_CHR_TYPE_LAST,
|
|
"null",
|
|
"vc",
|
|
"pty",
|
|
"dev",
|
|
"file",
|
|
"pipe",
|
|
"stdio",
|
|
"udp",
|
|
"tcp",
|
|
"unix",
|
|
"spicevmc",
|
|
"spiceport",
|
|
"nmdm",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChrTcpProtocol,
|
|
VIR_DOMAIN_CHR_TCP_PROTOCOL_LAST,
|
|
"raw",
|
|
"telnet",
|
|
"telnets",
|
|
"tls",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainChrSpicevmc,
|
|
VIR_DOMAIN_CHR_SPICEVMC_LAST,
|
|
"vdagent",
|
|
"smartcard",
|
|
"usbredir",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainSmartcard,
|
|
VIR_DOMAIN_SMARTCARD_TYPE_LAST,
|
|
"host",
|
|
"host-certificates",
|
|
"passthrough",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainSoundCodec,
|
|
VIR_DOMAIN_SOUND_CODEC_TYPE_LAST,
|
|
"duplex",
|
|
"micro",
|
|
"output",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainSoundModel,
|
|
VIR_DOMAIN_SOUND_MODEL_LAST,
|
|
"sb16",
|
|
"es1370",
|
|
"pcspk",
|
|
"ac97",
|
|
"ich6",
|
|
"ich9",
|
|
"usb",
|
|
"ich7",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainAudioType,
|
|
VIR_DOMAIN_AUDIO_TYPE_LAST,
|
|
"none",
|
|
"alsa",
|
|
"coreaudio",
|
|
"jack",
|
|
"oss",
|
|
"pulseaudio",
|
|
"sdl",
|
|
"spice",
|
|
"file",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainAudioSDLDriver,
|
|
VIR_DOMAIN_AUDIO_SDL_DRIVER_LAST,
|
|
"",
|
|
"esd",
|
|
"alsa",
|
|
"arts",
|
|
"pulseaudio",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainAudioFormat,
|
|
VIR_DOMAIN_AUDIO_FORMAT_LAST,
|
|
"",
|
|
"u8",
|
|
"s8",
|
|
"u16",
|
|
"s16",
|
|
"u32",
|
|
"s32",
|
|
"f32",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainKeyWrapCipherName,
|
|
VIR_DOMAIN_KEY_WRAP_CIPHER_NAME_LAST,
|
|
"aes",
|
|
"dea",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainMemballoonModel,
|
|
VIR_DOMAIN_MEMBALLOON_MODEL_LAST,
|
|
"virtio",
|
|
"xen",
|
|
"none",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainSmbiosMode,
|
|
VIR_DOMAIN_SMBIOS_LAST,
|
|
"none",
|
|
"emulate",
|
|
"host",
|
|
"sysinfo",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainWatchdogModel,
|
|
VIR_DOMAIN_WATCHDOG_MODEL_LAST,
|
|
"i6300esb",
|
|
"ib700",
|
|
"diag288",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainWatchdogAction,
|
|
VIR_DOMAIN_WATCHDOG_ACTION_LAST,
|
|
"reset",
|
|
"shutdown",
|
|
"poweroff",
|
|
"pause",
|
|
"dump",
|
|
"none",
|
|
"inject-nmi",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainPanicModel,
|
|
VIR_DOMAIN_PANIC_MODEL_LAST,
|
|
"default",
|
|
"isa",
|
|
"pseries",
|
|
"hyperv",
|
|
"s390",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainVideoBackend,
|
|
VIR_DOMAIN_VIDEO_BACKEND_TYPE_LAST,
|
|
"default",
|
|
"qemu",
|
|
"vhostuser",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainVideo,
|
|
VIR_DOMAIN_VIDEO_TYPE_LAST,
|
|
"default",
|
|
"vga",
|
|
"cirrus",
|
|
"vmvga",
|
|
"xen",
|
|
"vbox",
|
|
"qxl",
|
|
"parallels",
|
|
"virtio",
|
|
"gop",
|
|
"none",
|
|
"bochs",
|
|
"ramfb",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainVideoVGAConf,
|
|
VIR_DOMAIN_VIDEO_VGACONF_LAST,
|
|
"io",
|
|
"on",
|
|
"off",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainInput,
|
|
VIR_DOMAIN_INPUT_TYPE_LAST,
|
|
"mouse",
|
|
"tablet",
|
|
"keyboard",
|
|
"passthrough",
|
|
"evdev",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainInputBus,
|
|
VIR_DOMAIN_INPUT_BUS_LAST,
|
|
"ps2",
|
|
"usb",
|
|
"xen",
|
|
"parallels",
|
|
"virtio",
|
|
"none",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainInputModel,
|
|
VIR_DOMAIN_INPUT_MODEL_LAST,
|
|
"default",
|
|
"virtio",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainInputSourceGrab,
|
|
VIR_DOMAIN_INPUT_SOURCE_GRAB_LAST,
|
|
"default",
|
|
"all",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphics,
|
|
VIR_DOMAIN_GRAPHICS_TYPE_LAST,
|
|
"sdl",
|
|
"vnc",
|
|
"rdp",
|
|
"desktop",
|
|
"spice",
|
|
"egl-headless",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsListen,
|
|
VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST,
|
|
"none",
|
|
"address",
|
|
"network",
|
|
"socket",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsAuthConnected,
|
|
VIR_DOMAIN_GRAPHICS_AUTH_CONNECTED_LAST,
|
|
"default",
|
|
"fail",
|
|
"disconnect",
|
|
"keep",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsVNCSharePolicy,
|
|
VIR_DOMAIN_GRAPHICS_VNC_SHARE_LAST,
|
|
"default",
|
|
"allow-exclusive",
|
|
"force-shared",
|
|
"ignore",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsSpiceChannelName,
|
|
VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_LAST,
|
|
"main",
|
|
"display",
|
|
"inputs",
|
|
"cursor",
|
|
"playback",
|
|
"record",
|
|
"smartcard",
|
|
"usbredir",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsSpiceChannelMode,
|
|
VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_LAST,
|
|
"any",
|
|
"secure",
|
|
"insecure",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsSpiceImageCompression,
|
|
VIR_DOMAIN_GRAPHICS_SPICE_IMAGE_COMPRESSION_LAST,
|
|
"default",
|
|
"auto_glz",
|
|
"auto_lz",
|
|
"quic",
|
|
"glz",
|
|
"lz",
|
|
"off",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsSpiceJpegCompression,
|
|
VIR_DOMAIN_GRAPHICS_SPICE_JPEG_COMPRESSION_LAST,
|
|
"default",
|
|
"auto",
|
|
"never",
|
|
"always",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsSpiceZlibCompression,
|
|
VIR_DOMAIN_GRAPHICS_SPICE_ZLIB_COMPRESSION_LAST,
|
|
"default",
|
|
"auto",
|
|
"never",
|
|
"always",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsSpiceMouseMode,
|
|
VIR_DOMAIN_GRAPHICS_SPICE_MOUSE_MODE_LAST,
|
|
"default",
|
|
"server",
|
|
"client",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainGraphicsSpiceStreamingMode,
|
|
VIR_DOMAIN_GRAPHICS_SPICE_STREAMING_MODE_LAST,
|
|
"default",
|
|
"filter",
|
|
"all",
|
|
"off",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHostdevMode,
|
|
VIR_DOMAIN_HOSTDEV_MODE_LAST,
|
|
"subsystem",
|
|
"capabilities",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHostdevSubsys,
|
|
VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST,
|
|
"usb",
|
|
"pci",
|
|
"scsi",
|
|
"scsi_host",
|
|
"mdev",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHostdevSubsysPCIBackend,
|
|
VIR_DOMAIN_HOSTDEV_PCI_BACKEND_TYPE_LAST,
|
|
"default",
|
|
"kvm",
|
|
"vfio",
|
|
"xen",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHostdevSubsysSCSIProtocol,
|
|
VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_LAST,
|
|
"adapter",
|
|
"iscsi",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHostdevSubsysSCSIHostProtocol,
|
|
VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_HOST_PROTOCOL_TYPE_LAST,
|
|
"none",
|
|
"vhost",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHostdevSubsysSCSIVHostModel,
|
|
VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_VHOST_MODEL_TYPE_LAST,
|
|
"default",
|
|
"virtio",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHostdevCaps,
|
|
VIR_DOMAIN_HOSTDEV_CAPS_TYPE_LAST,
|
|
"storage",
|
|
"misc",
|
|
"net",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHub,
|
|
VIR_DOMAIN_HUB_TYPE_LAST,
|
|
"usb",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainRedirdevBus,
|
|
VIR_DOMAIN_REDIRDEV_BUS_LAST,
|
|
"usb",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainState,
|
|
VIR_DOMAIN_LAST,
|
|
"nostate",
|
|
"running",
|
|
"blocked",
|
|
"paused",
|
|
"shutdown",
|
|
"shutoff",
|
|
"crashed",
|
|
"pmsuspended",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainNostateReason,
|
|
VIR_DOMAIN_NOSTATE_LAST,
|
|
"unknown",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainRunningReason,
|
|
VIR_DOMAIN_RUNNING_LAST,
|
|
"unknown",
|
|
"booted",
|
|
"migrated",
|
|
"restored",
|
|
"from snapshot",
|
|
"unpaused",
|
|
"migration canceled",
|
|
"save canceled",
|
|
"wakeup",
|
|
"crashed",
|
|
"post-copy",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainBlockedReason,
|
|
VIR_DOMAIN_BLOCKED_LAST,
|
|
"unknown",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainPausedReason,
|
|
VIR_DOMAIN_PAUSED_LAST,
|
|
"unknown",
|
|
"user",
|
|
"migration",
|
|
"save",
|
|
"dump",
|
|
"ioerror",
|
|
"watchdog",
|
|
"from snapshot",
|
|
"shutdown",
|
|
"snapshot",
|
|
"panicked",
|
|
"starting up",
|
|
"post-copy",
|
|
"post-copy failed",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainShutdownReason,
|
|
VIR_DOMAIN_SHUTDOWN_LAST,
|
|
"unknown",
|
|
"user",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainShutoffReason,
|
|
VIR_DOMAIN_SHUTOFF_LAST,
|
|
"unknown",
|
|
"shutdown",
|
|
"destroyed",
|
|
"crashed",
|
|
"migrated",
|
|
"saved",
|
|
"failed",
|
|
"from snapshot",
|
|
"daemon",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainCrashedReason,
|
|
VIR_DOMAIN_CRASHED_LAST,
|
|
"unknown",
|
|
"panicked",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainPMSuspendedReason,
|
|
VIR_DOMAIN_PMSUSPENDED_LAST,
|
|
"unknown",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainSeclabel,
|
|
VIR_DOMAIN_SECLABEL_LAST,
|
|
"default",
|
|
"none",
|
|
"dynamic",
|
|
"static",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainClockOffset,
|
|
VIR_DOMAIN_CLOCK_OFFSET_LAST,
|
|
"utc",
|
|
"localtime",
|
|
"variable",
|
|
"timezone",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainClockBasis,
|
|
VIR_DOMAIN_CLOCK_BASIS_LAST,
|
|
"utc",
|
|
"localtime",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainTimerName,
|
|
VIR_DOMAIN_TIMER_NAME_LAST,
|
|
"platform",
|
|
"pit",
|
|
"rtc",
|
|
"hpet",
|
|
"tsc",
|
|
"kvmclock",
|
|
"hypervclock",
|
|
"armvtimer",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainTimerTrack,
|
|
VIR_DOMAIN_TIMER_TRACK_LAST,
|
|
"boot",
|
|
"guest",
|
|
"wall",
|
|
"realtime",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainTimerTickpolicy,
|
|
VIR_DOMAIN_TIMER_TICKPOLICY_LAST,
|
|
"delay",
|
|
"catchup",
|
|
"merge",
|
|
"discard",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainTimerMode,
|
|
VIR_DOMAIN_TIMER_MODE_LAST,
|
|
"auto",
|
|
"native",
|
|
"emulate",
|
|
"paravirt",
|
|
"smpsafe",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainStartupPolicy,
|
|
VIR_DOMAIN_STARTUP_POLICY_LAST,
|
|
"default",
|
|
"mandatory",
|
|
"requisite",
|
|
"optional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainCpuPlacementMode,
|
|
VIR_DOMAIN_CPU_PLACEMENT_MODE_LAST,
|
|
"static",
|
|
"auto",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskTray,
|
|
VIR_DOMAIN_DISK_TRAY_LAST,
|
|
"closed",
|
|
"open",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainRNGModel,
|
|
VIR_DOMAIN_RNG_MODEL_LAST,
|
|
"virtio",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainRNGBackend,
|
|
VIR_DOMAIN_RNG_BACKEND_LAST,
|
|
"random",
|
|
"egd",
|
|
"builtin",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainTPMModel,
|
|
VIR_DOMAIN_TPM_MODEL_LAST,
|
|
"default",
|
|
"tpm-tis",
|
|
"tpm-crb",
|
|
"tpm-spapr",
|
|
"spapr-tpm-proxy",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainTPMBackend,
|
|
VIR_DOMAIN_TPM_TYPE_LAST,
|
|
"passthrough",
|
|
"emulator",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainTPMVersion,
|
|
VIR_DOMAIN_TPM_VERSION_LAST,
|
|
"default",
|
|
"1.2",
|
|
"2.0",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainIOMMUModel,
|
|
VIR_DOMAIN_IOMMU_MODEL_LAST,
|
|
"intel",
|
|
"smmuv3",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainVsockModel,
|
|
VIR_DOMAIN_VSOCK_MODEL_LAST,
|
|
"default",
|
|
"virtio",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskDiscard,
|
|
VIR_DOMAIN_DISK_DISCARD_LAST,
|
|
"default",
|
|
"unmap",
|
|
"ignore",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskDetectZeroes,
|
|
VIR_DOMAIN_DISK_DETECT_ZEROES_LAST,
|
|
"default",
|
|
"off",
|
|
"on",
|
|
"unmap",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskModel,
|
|
VIR_DOMAIN_DISK_MODEL_LAST,
|
|
"default",
|
|
"virtio",
|
|
"virtio-transitional",
|
|
"virtio-non-transitional",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainDiskMirrorState,
|
|
VIR_DOMAIN_DISK_MIRROR_STATE_LAST,
|
|
"none",
|
|
"yes",
|
|
"abort",
|
|
"pivot",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainMemorySource,
|
|
VIR_DOMAIN_MEMORY_SOURCE_LAST,
|
|
"none",
|
|
"file",
|
|
"anonymous",
|
|
"memfd",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainMemoryAllocation,
|
|
VIR_DOMAIN_MEMORY_ALLOCATION_LAST,
|
|
"none",
|
|
"immediate",
|
|
"ondemand",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainLoader,
|
|
VIR_DOMAIN_LOADER_TYPE_LAST,
|
|
"none",
|
|
"rom",
|
|
"pflash",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainIOAPIC,
|
|
VIR_DOMAIN_IOAPIC_LAST,
|
|
"none",
|
|
"qemu",
|
|
"kvm",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainHPTResizing,
|
|
VIR_DOMAIN_HPT_RESIZING_LAST,
|
|
"none",
|
|
"enabled",
|
|
"disabled",
|
|
"required",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainOsDefFirmware,
|
|
VIR_DOMAIN_OS_DEF_FIRMWARE_LAST,
|
|
"none",
|
|
"bios",
|
|
"efi",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainOsDefFirmwareFeature,
|
|
VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_LAST,
|
|
"enrolled-keys",
|
|
"secure-boot",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainCFPC,
|
|
VIR_DOMAIN_CFPC_LAST,
|
|
"none",
|
|
"broken",
|
|
"workaround",
|
|
"fixed",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainSBBC,
|
|
VIR_DOMAIN_SBBC_LAST,
|
|
"none",
|
|
"broken",
|
|
"workaround",
|
|
"fixed",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainIBS,
|
|
VIR_DOMAIN_IBS_LAST,
|
|
"none",
|
|
"broken",
|
|
"workaround",
|
|
"fixed-ibs",
|
|
"fixed-ccd",
|
|
"fixed-na",
|
|
);
|
|
|
|
/* Internal mapping: subset of block job types that can be present in
|
|
* <mirror> XML (remaining types are not two-phase). */
|
|
VIR_ENUM_DECL(virDomainBlockJob);
|
|
VIR_ENUM_IMPL(virDomainBlockJob,
|
|
VIR_DOMAIN_BLOCK_JOB_TYPE_LAST,
|
|
"", "", "copy", "", "active-commit", "",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainMemoryModel,
|
|
VIR_DOMAIN_MEMORY_MODEL_LAST,
|
|
"",
|
|
"dimm",
|
|
"nvdimm",
|
|
"virtio-pmem",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainShmemModel,
|
|
VIR_DOMAIN_SHMEM_MODEL_LAST,
|
|
"ivshmem",
|
|
"ivshmem-plain",
|
|
"ivshmem-doorbell",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainShmemRole,
|
|
VIR_DOMAIN_SHMEM_ROLE_LAST,
|
|
"default",
|
|
"master",
|
|
"peer",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virDomainLaunchSecurity,
|
|
VIR_DOMAIN_LAUNCH_SECURITY_LAST,
|
|
"",
|
|
"sev",
|
|
);
|
|
|
|
static virClass *virDomainObjClass;
|
|
static virClass *virDomainXMLOptionClass;
|
|
static void virDomainObjDispose(void *obj);
|
|
static void virDomainXMLOptionDispose(void *obj);
|
|
|
|
|
|
static void
|
|
virDomainChrSourceDefFormat(virBuffer *buf,
|
|
virDomainChrSourceDef *def,
|
|
unsigned int flags);
|
|
|
|
|
|
static int
|
|
virDomainChrSourceReconnectDefParseXML(virDomainChrSourceReconnectDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt);
|
|
|
|
|
|
static int virDomainObjOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(virDomainObj, virClassForObjectLockable()))
|
|
return -1;
|
|
|
|
if (!VIR_CLASS_NEW(virDomainXMLOption, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(virDomainObj);
|
|
|
|
|
|
static void
|
|
virDomainXMLOptionDispose(void *obj)
|
|
{
|
|
virDomainXMLOption *xmlopt = obj;
|
|
|
|
if (xmlopt->config.privFree)
|
|
(xmlopt->config.privFree)(xmlopt->config.priv);
|
|
}
|
|
|
|
/**
|
|
* virDomainKeyWrapCipherDefParseXML:
|
|
*
|
|
* @def Domain definition
|
|
* @node An XML cipher node
|
|
*
|
|
* Parse the attributes from the cipher node and store the state
|
|
* attribute in @def.
|
|
*
|
|
* A cipher node has the form of
|
|
*
|
|
* <cipher name='aes|dea' state='on|off'/>
|
|
*
|
|
* Returns: 0 if the parse succeeded
|
|
* -1 otherwise
|
|
*/
|
|
static int
|
|
virDomainKeyWrapCipherDefParseXML(virDomainKeyWrapDef *keywrap,
|
|
xmlNodePtr node)
|
|
{
|
|
virDomainKeyWrapCipherName name;
|
|
virTristateSwitch state;
|
|
|
|
if (virXMLPropEnum(node, "name", virDomainKeyWrapCipherNameTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &name) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateSwitch(node, "state", VIR_XML_PROP_REQUIRED,
|
|
&state) < 0)
|
|
return -1;
|
|
|
|
switch (name) {
|
|
case VIR_DOMAIN_KEY_WRAP_CIPHER_NAME_AES:
|
|
if (keywrap->aes != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("A domain definition can have no more than "
|
|
"one cipher node with name %s"),
|
|
virDomainKeyWrapCipherNameTypeToString(name));
|
|
|
|
return -1;
|
|
}
|
|
keywrap->aes = state;
|
|
break;
|
|
|
|
case VIR_DOMAIN_KEY_WRAP_CIPHER_NAME_DEA:
|
|
if (keywrap->dea != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("A domain definition can have no more than "
|
|
"one cipher node with name %s"),
|
|
virDomainKeyWrapCipherNameTypeToString(name));
|
|
|
|
return -1;
|
|
}
|
|
keywrap->dea = state;
|
|
break;
|
|
|
|
case VIR_DOMAIN_KEY_WRAP_CIPHER_NAME_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainKeyWrapDefParseXML(virDomainDef *def, xmlXPathContextPtr ctxt)
|
|
{
|
|
size_t i;
|
|
int n;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree virDomainKeyWrapDef *keywrap = NULL;
|
|
|
|
if ((n = virXPathNodeSet("./keywrap/cipher", ctxt, &nodes)) < 0)
|
|
return n;
|
|
|
|
keywrap = g_new0(virDomainKeyWrapDef, 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainKeyWrapCipherDefParseXML(keywrap, nodes[i]) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (keywrap->aes || keywrap->dea)
|
|
def->keywrap = g_steal_pointer(&keywrap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainXMLOptionNew:
|
|
*
|
|
* Allocate a new domain XML configuration
|
|
*/
|
|
virDomainXMLOption *
|
|
virDomainXMLOptionNew(virDomainDefParserConfig *config,
|
|
virDomainXMLPrivateDataCallbacks *priv,
|
|
virXMLNamespace *xmlns,
|
|
virDomainABIStability *abi,
|
|
virSaveCookieCallbacks *saveCookie)
|
|
{
|
|
virDomainXMLOption *xmlopt;
|
|
|
|
if (virDomainObjInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(xmlopt = virObjectNew(virDomainXMLOptionClass)))
|
|
return NULL;
|
|
|
|
if (priv)
|
|
xmlopt->privateData = *priv;
|
|
|
|
if (config)
|
|
xmlopt->config = *config;
|
|
|
|
if (xmlns)
|
|
xmlopt->ns = *xmlns;
|
|
|
|
if (abi)
|
|
xmlopt->abi = *abi;
|
|
|
|
if (saveCookie)
|
|
xmlopt->saveCookie = *saveCookie;
|
|
|
|
/* Technically this forbids to use one of Xerox's MAC address prefixes in
|
|
* our hypervisor drivers. This shouldn't ever be a problem.
|
|
*
|
|
* Use the KVM prefix as default as it's in the privately administered
|
|
* range */
|
|
if (xmlopt->config.macPrefix[0] == 0 &&
|
|
xmlopt->config.macPrefix[1] == 0 &&
|
|
xmlopt->config.macPrefix[2] == 0) {
|
|
xmlopt->config.macPrefix[0] = 0x52;
|
|
xmlopt->config.macPrefix[1] = 0x54;
|
|
}
|
|
|
|
return xmlopt;
|
|
}
|
|
|
|
/**
|
|
* virDomainXMLOptionGetNamespace:
|
|
*
|
|
* @xmlopt: XML parser configuration object
|
|
*
|
|
* Returns a pointer to the stored namespace structure.
|
|
* The lifetime of the pointer is equal to @xmlopt;
|
|
*/
|
|
virXMLNamespace *
|
|
virDomainXMLOptionGetNamespace(virDomainXMLOption *xmlopt)
|
|
{
|
|
return &xmlopt->ns;
|
|
}
|
|
|
|
static int
|
|
virDomainVirtioOptionsParseXML(xmlNodePtr driver,
|
|
virDomainVirtioOptions **virtio)
|
|
{
|
|
if (*virtio || !driver)
|
|
return 0;
|
|
|
|
*virtio = g_new0(virDomainVirtioOptions, 1);
|
|
|
|
if (virXMLPropTristateSwitch(driver, "iommu", VIR_XML_PROP_NONE,
|
|
&(*virtio)->iommu) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateSwitch(driver, "ats", VIR_XML_PROP_NONE,
|
|
&(*virtio)->ats) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateSwitch(driver, "packed", VIR_XML_PROP_NONE,
|
|
&(*virtio)->packed) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
virSaveCookieCallbacks *
|
|
virDomainXMLOptionGetSaveCookie(virDomainXMLOption *xmlopt)
|
|
{
|
|
return &xmlopt->saveCookie;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainXMLOptionSetMomentPostParse(virDomainXMLOption *xmlopt,
|
|
virDomainMomentPostParseCallback cb)
|
|
{
|
|
xmlopt->momentPostParse = cb;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainXMLOptionRunMomentPostParse(virDomainXMLOption *xmlopt,
|
|
virDomainMomentDef *def)
|
|
{
|
|
if (!xmlopt->momentPostParse)
|
|
return virDomainMomentDefPostParse(def);
|
|
return xmlopt->momentPostParse(def);
|
|
}
|
|
|
|
|
|
void
|
|
virBlkioDeviceArrayClear(virBlkioDevice *devices,
|
|
int ndevices)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < ndevices; i++)
|
|
VIR_FREE(devices[i].path);
|
|
}
|
|
|
|
/**
|
|
* virDomainBlkioDeviceParseXML
|
|
*
|
|
* this function parses a XML node:
|
|
*
|
|
* <device>
|
|
* <path>/fully/qualified/device/path</path>
|
|
* <weight>weight</weight>
|
|
* <read_bytes_sec>bps</read_bytes_sec>
|
|
* <write_bytes_sec>bps</write_bytes_sec>
|
|
* <read_iops_sec>iops</read_iops_sec>
|
|
* <write_iops_sec>iops</write_iops_sec>
|
|
* </device>
|
|
*
|
|
* and fills a virBlkioDevice *struct.
|
|
*/
|
|
static int
|
|
virDomainBlkioDeviceParseXML(xmlNodePtr root,
|
|
xmlXPathContextPtr ctxt,
|
|
virBlkioDevice *dev)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
g_autofree char *weight = NULL;
|
|
g_autofree char *read_bytes_sec = NULL;
|
|
g_autofree char *write_bytes_sec = NULL;
|
|
g_autofree char *read_iops_sec = NULL;
|
|
g_autofree char *write_iops_sec = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = root;
|
|
|
|
/* To avoid the need for explicit cleanup on failure,
|
|
* don't set dev->path until we're assured of
|
|
* success. Until then, store it in an autofree pointer.
|
|
*/
|
|
if (!(path = virXPathString("string(./path)", ctxt))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("missing per-device path"));
|
|
return -1;
|
|
}
|
|
|
|
if ((weight = virXPathString("string(./weight)", ctxt)) &&
|
|
(virStrToLong_ui(weight, NULL, 10, &dev->weight) < 0)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("could not parse weight %s"), weight);
|
|
return -1;
|
|
}
|
|
|
|
if ((read_bytes_sec = virXPathString("string(./read_bytes_sec)", ctxt)) &&
|
|
(virStrToLong_ull(read_bytes_sec, NULL, 10, &dev->rbps) < 0)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("could not parse read bytes sec %s"),
|
|
read_bytes_sec);
|
|
return -1;
|
|
}
|
|
|
|
if ((write_bytes_sec = virXPathString("string(./write_bytes_sec)", ctxt)) &&
|
|
(virStrToLong_ull(write_bytes_sec, NULL, 10, &dev->wbps) < 0)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("could not parse write bytes sec %s"),
|
|
write_bytes_sec);
|
|
return -1;
|
|
}
|
|
|
|
if ((read_iops_sec = virXPathString("string(./read_iops_sec)", ctxt)) &&
|
|
(virStrToLong_ui(read_iops_sec, NULL, 10, &dev->riops) < 0)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("could not parse read iops sec %s"),
|
|
read_iops_sec);
|
|
return -1;
|
|
}
|
|
|
|
if ((write_iops_sec = virXPathString("string(./write_iops_sec)", ctxt)) &&
|
|
(virStrToLong_ui(write_iops_sec, NULL, 10, &dev->wiops) < 0)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("could not parse write iops sec %s"),
|
|
write_iops_sec);
|
|
return -1;
|
|
}
|
|
|
|
dev->path = g_steal_pointer(&path);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefCheckUnsupportedMemoryHotplug:
|
|
* @def: domain definition
|
|
*
|
|
* Returns -1 if the domain definition would enable memory hotplug via the
|
|
* <maxMemory> tunable and reports an error. Otherwise returns 0.
|
|
*/
|
|
static int
|
|
virDomainDefCheckUnsupportedMemoryHotplug(virDomainDef *def)
|
|
{
|
|
/* memory hotplug tunables are not supported by this driver */
|
|
if (virDomainDefHasMemoryHotplug(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("memory hotplug tunables <maxMemory> are not "
|
|
"supported by this hypervisor driver"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDeviceDefCheckUnsupportedMemoryDevice:
|
|
* @dev: device definition
|
|
*
|
|
* Returns -1 if the device definition describes a memory device and reports an
|
|
* error. Otherwise returns 0.
|
|
*/
|
|
static int
|
|
virDomainDeviceDefCheckUnsupportedMemoryDevice(virDomainDeviceDef *dev)
|
|
{
|
|
/* This driver doesn't yet know how to handle memory devices */
|
|
if (dev->type == VIR_DOMAIN_DEVICE_MEMORY) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("memory devices are not supported by this driver"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool virDomainObjTaint(virDomainObj *obj,
|
|
virDomainTaintFlags taint)
|
|
{
|
|
unsigned int flag = (1 << taint);
|
|
|
|
if (obj->taint & flag)
|
|
return false;
|
|
|
|
obj->taint |= flag;
|
|
return true;
|
|
}
|
|
|
|
|
|
void virDomainObjDeprecation(virDomainObj *obj,
|
|
const char *msg)
|
|
{
|
|
obj->deprecations = g_renew(char *, obj->deprecations,
|
|
obj->ndeprecations + 1);
|
|
obj->deprecations[obj->ndeprecations++] = g_strdup(msg);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainGraphicsAuthDefClear(virDomainGraphicsAuthDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
VIR_FREE(def->passwd);
|
|
|
|
/* Don't free def */
|
|
}
|
|
|
|
static void
|
|
virDomainGraphicsListenDefClear(virDomainGraphicsListenDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
VIR_FREE(def->address);
|
|
VIR_FREE(def->network);
|
|
VIR_FREE(def->socket);
|
|
return;
|
|
}
|
|
|
|
|
|
void virDomainGraphicsDefFree(virDomainGraphicsDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
if (!def)
|
|
return;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
|
|
g_free(def->data.vnc.keymap);
|
|
virDomainGraphicsAuthDefClear(&def->data.vnc.auth);
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
|
|
g_free(def->data.sdl.display);
|
|
g_free(def->data.sdl.xauth);
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
|
|
g_free(def->data.desktop.display);
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
|
|
g_free(def->data.spice.rendernode);
|
|
g_free(def->data.spice.keymap);
|
|
virDomainGraphicsAuthDefClear(&def->data.spice.auth);
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
|
|
g_free(def->data.egl_headless.rendernode);
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < def->nListens; i++)
|
|
virDomainGraphicsListenDefClear(&def->listens[i]);
|
|
g_free(def->listens);
|
|
|
|
virObjectUnref(def->privateData);
|
|
g_free(def);
|
|
}
|
|
|
|
const char *virDomainInputDefGetPath(virDomainInputDef *input)
|
|
{
|
|
switch ((virDomainInputType) input->type) {
|
|
case VIR_DOMAIN_INPUT_TYPE_MOUSE:
|
|
case VIR_DOMAIN_INPUT_TYPE_TABLET:
|
|
case VIR_DOMAIN_INPUT_TYPE_KBD:
|
|
case VIR_DOMAIN_INPUT_TYPE_LAST:
|
|
return NULL;
|
|
|
|
case VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH:
|
|
case VIR_DOMAIN_INPUT_TYPE_EVDEV:
|
|
return input->source.evdev;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void virDomainInputDefFree(virDomainInputDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
g_free(def->source.evdev);
|
|
g_free(def->virtio);
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainLeaseDefFree(virDomainLeaseDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def->lockspace);
|
|
g_free(def->key);
|
|
g_free(def->path);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
static virDomainVcpuDef *
|
|
virDomainVcpuDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainVcpuDef *ret = NULL;
|
|
g_autoptr(virObject) priv = NULL;
|
|
|
|
if (xmlopt && xmlopt->privateData.vcpuNew &&
|
|
!(priv = xmlopt->privateData.vcpuNew()))
|
|
return NULL;
|
|
|
|
ret = g_new0(virDomainVcpuDef, 1);
|
|
|
|
ret->privateData = g_steal_pointer(&priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainVcpuDefFree(virDomainVcpuDef *info)
|
|
{
|
|
if (!info)
|
|
return;
|
|
|
|
virBitmapFree(info->cpumask);
|
|
virObjectUnref(info->privateData);
|
|
g_free(info);
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDefSetVcpusMax(virDomainDef *def,
|
|
unsigned int maxvcpus,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
size_t oldmax = def->maxvcpus;
|
|
size_t i;
|
|
|
|
if (def->maxvcpus == maxvcpus)
|
|
return 0;
|
|
|
|
if (def->maxvcpus < maxvcpus) {
|
|
VIR_EXPAND_N(def->vcpus, def->maxvcpus, maxvcpus - def->maxvcpus);
|
|
|
|
for (i = oldmax; i < def->maxvcpus; i++) {
|
|
if (!(def->vcpus[i] = virDomainVcpuDefNew(xmlopt)))
|
|
return -1;
|
|
}
|
|
} else {
|
|
for (i = maxvcpus; i < def->maxvcpus; i++)
|
|
virDomainVcpuDefFree(def->vcpus[i]);
|
|
|
|
VIR_SHRINK_N(def->vcpus, def->maxvcpus, def->maxvcpus - maxvcpus);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasVcpusOffline(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->maxvcpus; i++) {
|
|
if (!def->vcpus[i]->online)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
unsigned int
|
|
virDomainDefGetVcpusMax(const virDomainDef *def)
|
|
{
|
|
return def->maxvcpus;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDefSetVcpus(virDomainDef *def,
|
|
unsigned int vcpus)
|
|
{
|
|
size_t i;
|
|
|
|
if (vcpus > def->maxvcpus) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("maximum vCPU count must not be less than current "
|
|
"vCPU count"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < vcpus; i++)
|
|
def->vcpus[i]->online = true;
|
|
|
|
for (i = vcpus; i < def->maxvcpus; i++)
|
|
def->vcpus[i]->online = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
unsigned int
|
|
virDomainDefGetVcpus(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
unsigned int ret = 0;
|
|
|
|
for (i = 0; i < def->maxvcpus; i++) {
|
|
if (def->vcpus[i]->online)
|
|
ret++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefGetOnlineVcpumap:
|
|
* @def: domain definition
|
|
*
|
|
* Returns a bitmap representing state of individual vcpus.
|
|
*/
|
|
virBitmap *
|
|
virDomainDefGetOnlineVcpumap(const virDomainDef *def)
|
|
{
|
|
virBitmap *ret = virBitmapNew(def->maxvcpus);
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->maxvcpus; i++) {
|
|
if (def->vcpus[i]->online)
|
|
ignore_value(virBitmapSetBit(ret, i));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
virDomainVcpuDef *
|
|
virDomainDefGetVcpu(virDomainDef *def,
|
|
unsigned int vcpu)
|
|
{
|
|
if (vcpu >= def->maxvcpus)
|
|
return NULL;
|
|
|
|
return def->vcpus[vcpu];
|
|
}
|
|
|
|
|
|
static virDomainThreadSchedParam *
|
|
virDomainDefGetVcpuSched(virDomainDef *def,
|
|
unsigned int vcpu)
|
|
{
|
|
virDomainVcpuDef *vcpuinfo;
|
|
|
|
if (!(vcpuinfo = virDomainDefGetVcpu(def, vcpu))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("vCPU '%u' is not present in domain definition"),
|
|
vcpu);
|
|
return NULL;
|
|
}
|
|
|
|
return &vcpuinfo->sched;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefHasVcpuPin:
|
|
* @def: domain definition
|
|
*
|
|
* This helper returns true if any of the domain's vcpus has cpu pinning set
|
|
*/
|
|
static bool
|
|
virDomainDefHasVcpuPin(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->maxvcpus; i++) {
|
|
if (def->vcpus[i]->cpumask)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefGetVcpuPinInfoHelper:
|
|
* @def: domain definition
|
|
* @maplen: length of one cpumap passed from caller (@cpumaps)
|
|
* @ncpumaps: count of cpumaps of @maplen length in @cpumaps
|
|
* @cpumaps: array of pinning information bitmaps to be filled
|
|
* @hostcpus: default CPU pinning bitmap based on host CPUs
|
|
* @autoCpuset: Cpu pinning bitmap used in case of automatic cpu pinning
|
|
*
|
|
* Fills the @cpumaps array as documented by the virDomainGetVcpuPinInfo API.
|
|
* In case when automatic cpu pinning is supported, the bitmap should be passed
|
|
* as @autoCpuset.
|
|
*
|
|
* Returns number of filled entries.
|
|
*/
|
|
int
|
|
virDomainDefGetVcpuPinInfoHelper(virDomainDef *def,
|
|
int maplen,
|
|
int ncpumaps,
|
|
unsigned char *cpumaps,
|
|
virBitmap *hostcpus,
|
|
virBitmap *autoCpuset)
|
|
{
|
|
int maxvcpus = virDomainDefGetVcpusMax(def);
|
|
size_t i;
|
|
|
|
for (i = 0; i < maxvcpus && i < ncpumaps; i++) {
|
|
virDomainVcpuDef *vcpu = virDomainDefGetVcpu(def, i);
|
|
virBitmap *bitmap = NULL;
|
|
|
|
if (vcpu && vcpu->cpumask)
|
|
bitmap = vcpu->cpumask;
|
|
else if (def->placement_mode == VIR_DOMAIN_CPU_PLACEMENT_MODE_AUTO &&
|
|
autoCpuset)
|
|
bitmap = autoCpuset;
|
|
else if (def->cpumask)
|
|
bitmap = def->cpumask;
|
|
else
|
|
bitmap = hostcpus;
|
|
|
|
virBitmapToDataBuf(bitmap, VIR_GET_CPUMAP(cpumaps, maplen, i), maplen);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDeGetVcpusTopology:
|
|
* @def: domain definition
|
|
* @maxvcpus: optionally filled with number of vcpus the domain topology describes
|
|
*
|
|
* Calculates and validates that the vcpu topology is in sane bounds and
|
|
* optionally returns the total number of vcpus described by given topology.
|
|
*
|
|
* Returns 0 on success, 1 if topology is not configured and -1 on error.
|
|
*/
|
|
int
|
|
virDomainDefGetVcpusTopology(const virDomainDef *def,
|
|
unsigned int *maxvcpus)
|
|
{
|
|
unsigned long long tmp;
|
|
|
|
if (!def->cpu || def->cpu->sockets == 0)
|
|
return 1;
|
|
|
|
tmp = def->cpu->sockets;
|
|
|
|
/* multiplication of 32bit numbers fits into a 64bit variable */
|
|
if ((tmp *= def->cpu->dies) > UINT_MAX ||
|
|
(tmp *= def->cpu->cores) > UINT_MAX ||
|
|
(tmp *= def->cpu->threads) > UINT_MAX) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("cpu topology results in more than %u cpus"), UINT_MAX);
|
|
return -1;
|
|
}
|
|
|
|
if (maxvcpus)
|
|
*maxvcpus = tmp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virDomainDiskDef *
|
|
virDomainDiskDefNewSource(virDomainXMLOption *xmlopt,
|
|
virStorageSource **src)
|
|
{
|
|
void *privateData = NULL;
|
|
virDomainDiskDef *ret;
|
|
|
|
if (xmlopt &&
|
|
xmlopt->privateData.diskNew &&
|
|
!(privateData = xmlopt->privateData.diskNew()))
|
|
return NULL;
|
|
|
|
ret = g_new0(virDomainDiskDef, 1);
|
|
ret->src = g_steal_pointer(src);
|
|
ret->privateData = privateData;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
virDomainDiskDef *
|
|
virDomainDiskDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
g_autoptr(virStorageSource) src = virStorageSourceNew();
|
|
|
|
return virDomainDiskDefNewSource(xmlopt, &src);
|
|
}
|
|
|
|
|
|
void
|
|
virDomainDiskDefFree(virDomainDiskDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virObjectUnref(def->src);
|
|
g_free(def->serial);
|
|
g_free(def->dst);
|
|
virObjectUnref(def->mirror);
|
|
g_free(def->wwn);
|
|
g_free(def->driverName);
|
|
g_free(def->vendor);
|
|
g_free(def->product);
|
|
g_free(def->domain_name);
|
|
g_free(def->blkdeviotune.group_name);
|
|
g_free(def->virtio);
|
|
virDomainDeviceInfoClear(&def->info);
|
|
virObjectUnref(def->privateData);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDiskGetType(virDomainDiskDef *def)
|
|
{
|
|
return def->src->type;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainDiskSetType(virDomainDiskDef *def, int type)
|
|
{
|
|
def->src->type = type;
|
|
}
|
|
|
|
|
|
const char *
|
|
virDomainDiskGetSource(virDomainDiskDef const *def)
|
|
{
|
|
return def->src->path;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainDiskSetSource(virDomainDiskDef *def, const char *src)
|
|
{
|
|
char *tmp = g_strdup(src);
|
|
g_free(def->src->path);
|
|
def->src->path = tmp;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainDiskEmptySource(virDomainDiskDef *def)
|
|
{
|
|
virStorageSource *src = def->src;
|
|
bool readonly = src->readonly;
|
|
|
|
virStorageSourceClear(src);
|
|
src->type = VIR_STORAGE_TYPE_FILE;
|
|
/* readonly property is necessary for CDROMs and thus can't be cleared */
|
|
src->readonly = readonly;
|
|
}
|
|
|
|
|
|
const char *
|
|
virDomainDiskGetDriver(const virDomainDiskDef *def)
|
|
{
|
|
return def->driverName;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainDiskSetDriver(virDomainDiskDef *def, const char *name)
|
|
{
|
|
char *tmp = g_strdup(name);
|
|
g_free(def->driverName);
|
|
def->driverName = tmp;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDiskGetFormat(virDomainDiskDef *def)
|
|
{
|
|
return def->src->format;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainDiskSetFormat(virDomainDiskDef *def, int format)
|
|
{
|
|
def->src->format = format;
|
|
}
|
|
|
|
|
|
virDomainControllerDef *
|
|
virDomainControllerDefNew(virDomainControllerType type)
|
|
{
|
|
virDomainControllerDef *def;
|
|
|
|
def = g_new0(virDomainControllerDef, 1);
|
|
|
|
def->type = type;
|
|
|
|
/* initialize anything that has a non-0 default */
|
|
def->model = -1;
|
|
def->idx = -1;
|
|
|
|
switch ((virDomainControllerType) def->type) {
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL:
|
|
def->opts.vioserial.ports = -1;
|
|
def->opts.vioserial.vectors = -1;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_USB:
|
|
def->opts.usbopts.ports = -1;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_PCI:
|
|
def->opts.pciopts.chassisNr = -1;
|
|
def->opts.pciopts.chassis = -1;
|
|
def->opts.pciopts.port = -1;
|
|
def->opts.pciopts.busNr = -1;
|
|
def->opts.pciopts.targetIndex = -1;
|
|
def->opts.pciopts.numaNode = -1;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS:
|
|
def->opts.xenbusopts.maxGrantFrames = -1;
|
|
def->opts.xenbusopts.maxEventChannels = -1;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_IDE:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_FDC:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_SCSI:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_SATA:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_CCID:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_ISA:
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
|
|
void virDomainControllerDefFree(virDomainControllerDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
g_free(def->virtio);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainControllerIsPSeriesPHB:
|
|
* @cont: controller
|
|
*
|
|
* Checks whether @cont is a PCI Host Bridge (PHB), a specific type
|
|
* of PCI controller used by pSeries guests.
|
|
*
|
|
* Returns: true if @cont is a PHB, false otherwise.
|
|
*/
|
|
bool
|
|
virDomainControllerIsPSeriesPHB(const virDomainControllerDef *cont)
|
|
{
|
|
virDomainControllerPCIModelName name;
|
|
|
|
/* PHBs are pci-root controllers */
|
|
if (cont->type != VIR_DOMAIN_CONTROLLER_TYPE_PCI ||
|
|
cont->model != VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT) {
|
|
return false;
|
|
}
|
|
|
|
name = cont->opts.pciopts.modelName;
|
|
|
|
/* The actual device used for PHBs is spapr-pci-host-bridge */
|
|
if (name != VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
virDomainFSDef *
|
|
virDomainFSDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainFSDef *ret;
|
|
|
|
ret = g_new0(virDomainFSDef, 1);
|
|
|
|
ret->src = virStorageSourceNew();
|
|
|
|
if (xmlopt &&
|
|
xmlopt->privateData.fsNew &&
|
|
!(ret->privateData = xmlopt->privateData.fsNew()))
|
|
goto cleanup;
|
|
|
|
return ret;
|
|
|
|
cleanup:
|
|
virDomainFSDefFree(ret);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
void virDomainFSDefFree(virDomainFSDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virObjectUnref(def->src);
|
|
g_free(def->dst);
|
|
virDomainDeviceInfoClear(&def->info);
|
|
g_free(def->virtio);
|
|
virObjectUnref(def->privateData);
|
|
g_free(def->binary);
|
|
g_free(def->sock);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
void
|
|
virDomainActualNetDefFree(virDomainActualNetDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
g_free(def->data.bridge.brname);
|
|
break;
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
g_free(def->data.direct.linkdev);
|
|
break;
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
virDomainHostdevDefClear(&def->data.hostdev.def);
|
|
break;
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
g_free(def->virtPortProfile);
|
|
virNetDevBandwidthFree(def->bandwidth);
|
|
virNetDevVlanClear(&def->vlan);
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
virDomainVsockDef *
|
|
virDomainVsockDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainVsockDef *ret = NULL;
|
|
virDomainVsockDef *vsock;
|
|
|
|
vsock = g_new0(virDomainVsockDef, 1);
|
|
|
|
if (xmlopt &&
|
|
xmlopt->privateData.vsockNew &&
|
|
!(vsock->privateData = xmlopt->privateData.vsockNew()))
|
|
goto cleanup;
|
|
|
|
ret = g_steal_pointer(&vsock);
|
|
cleanup:
|
|
virDomainVsockDefFree(vsock);
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainVsockDefFree(virDomainVsockDef *vsock)
|
|
{
|
|
if (!vsock)
|
|
return;
|
|
|
|
virObjectUnref(vsock->privateData);
|
|
virDomainDeviceInfoClear(&vsock->info);
|
|
g_free(vsock->virtio);
|
|
g_free(vsock);
|
|
}
|
|
|
|
|
|
void
|
|
virDomainNetTeamingInfoFree(virDomainNetTeamingInfo *teaming)
|
|
{
|
|
if (!teaming)
|
|
return;
|
|
|
|
g_free(teaming->persistent);
|
|
g_free(teaming);
|
|
}
|
|
|
|
|
|
void
|
|
virDomainNetDefFree(virDomainNetDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def->modelstr);
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
virObjectUnref(def->data.vhostuser);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
g_free(def->data.vdpa.devicepath);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
g_free(def->data.socket.address);
|
|
g_free(def->data.socket.localaddr);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
g_free(def->data.network.name);
|
|
g_free(def->data.network.portgroup);
|
|
virDomainActualNetDefFree(def->data.network.actual);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
g_free(def->data.bridge.brname);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
g_free(def->data.internal.name);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
g_free(def->data.direct.linkdev);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
virDomainHostdevDefClear(&def->data.hostdev.def);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
g_free(def->backend.tap);
|
|
g_free(def->backend.vhost);
|
|
virDomainNetTeamingInfoFree(def->teaming);
|
|
g_free(def->virtPortProfile);
|
|
g_free(def->script);
|
|
g_free(def->downscript);
|
|
g_free(def->domain_name);
|
|
g_free(def->ifname);
|
|
g_free(def->ifname_guest);
|
|
g_free(def->ifname_guest_actual);
|
|
g_free(def->virtio);
|
|
g_free(def->coalesce);
|
|
|
|
virNetDevIPInfoClear(&def->guestIP);
|
|
virNetDevIPInfoClear(&def->hostIP);
|
|
virDomainDeviceInfoClear(&def->info);
|
|
|
|
g_free(def->filter);
|
|
virHashFree(def->filterparams);
|
|
|
|
virNetDevBandwidthFree(def->bandwidth);
|
|
virNetDevVlanClear(&def->vlan);
|
|
|
|
virObjectUnref(def->privateData);
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
const char *
|
|
virDomainChrSourceDefGetPath(virDomainChrSourceDef *chr)
|
|
{
|
|
if (!chr)
|
|
return NULL;
|
|
|
|
switch ((virDomainChrType) chr->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
return chr->data.file.path;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
return chr->data.nix.path;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_TCP:
|
|
case VIR_DOMAIN_CHR_TYPE_UDP:
|
|
case VIR_DOMAIN_CHR_TYPE_NULL:
|
|
case VIR_DOMAIN_CHR_TYPE_VC:
|
|
case VIR_DOMAIN_CHR_TYPE_STDIO:
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
|
|
case VIR_DOMAIN_CHR_TYPE_LAST:
|
|
return NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void ATTRIBUTE_NONNULL(1)
|
|
virDomainChrSourceDefClear(virDomainChrSourceDef *def)
|
|
{
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
VIR_FREE(def->data.file.path);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
VIR_FREE(def->data.nmdm.master);
|
|
VIR_FREE(def->data.nmdm.slave);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UDP:
|
|
VIR_FREE(def->data.udp.bindHost);
|
|
VIR_FREE(def->data.udp.bindService);
|
|
VIR_FREE(def->data.udp.connectHost);
|
|
VIR_FREE(def->data.udp.connectService);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_TCP:
|
|
VIR_FREE(def->data.tcp.host);
|
|
VIR_FREE(def->data.tcp.service);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
VIR_FREE(def->data.nix.path);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
|
|
VIR_FREE(def->data.spiceport.channel);
|
|
break;
|
|
}
|
|
|
|
VIR_FREE(def->logfile);
|
|
}
|
|
|
|
/* Deep copies the contents of src into dest. Return -1 and report
|
|
* error on failure. */
|
|
int
|
|
virDomainChrSourceDefCopy(virDomainChrSourceDef *dest,
|
|
virDomainChrSourceDef *src)
|
|
{
|
|
if (!dest || !src)
|
|
return -1;
|
|
|
|
virDomainChrSourceDefClear(dest);
|
|
|
|
switch (src->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
if (src->type == VIR_DOMAIN_CHR_TYPE_FILE)
|
|
dest->data.file.append = src->data.file.append;
|
|
dest->data.file.path = g_strdup(src->data.file.path);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UDP:
|
|
dest->data.udp.bindHost = g_strdup(src->data.udp.bindHost);
|
|
dest->data.udp.bindService = g_strdup(src->data.udp.bindService);
|
|
dest->data.udp.connectHost = g_strdup(src->data.udp.connectHost);
|
|
dest->data.udp.connectService = g_strdup(src->data.udp.connectService);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_TCP:
|
|
dest->data.tcp.host = g_strdup(src->data.tcp.host);
|
|
dest->data.tcp.service = g_strdup(src->data.tcp.service);
|
|
|
|
dest->data.tcp.haveTLS = src->data.tcp.haveTLS;
|
|
dest->data.tcp.tlsFromConfig = src->data.tcp.tlsFromConfig;
|
|
|
|
dest->data.tcp.reconnect.enabled = src->data.tcp.reconnect.enabled;
|
|
dest->data.tcp.reconnect.timeout = src->data.tcp.reconnect.timeout;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
dest->data.nix.path = g_strdup(src->data.nix.path);
|
|
|
|
dest->data.nix.reconnect.enabled = src->data.nix.reconnect.enabled;
|
|
dest->data.nix.reconnect.timeout = src->data.nix.reconnect.timeout;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
dest->data.nmdm.master = g_strdup(src->data.nmdm.master);
|
|
dest->data.nmdm.slave = g_strdup(src->data.nmdm.slave);
|
|
|
|
break;
|
|
}
|
|
|
|
dest->type = src->type;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virDomainChrSourceDefDispose(void *obj)
|
|
{
|
|
virDomainChrSourceDef *def = obj;
|
|
size_t i;
|
|
|
|
virDomainChrSourceDefClear(def);
|
|
virObjectUnref(def->privateData);
|
|
|
|
if (def->seclabels) {
|
|
for (i = 0; i < def->nseclabels; i++)
|
|
virSecurityDeviceLabelDefFree(def->seclabels[i]);
|
|
g_free(def->seclabels);
|
|
}
|
|
}
|
|
|
|
|
|
/* virDomainChrSourceDefIsEqual:
|
|
* @src: Source
|
|
* @tgt: Target
|
|
*
|
|
* Compares source and target if they contain
|
|
* the same information.
|
|
*/
|
|
static bool
|
|
virDomainChrSourceDefIsEqual(const virDomainChrSourceDef *src,
|
|
const virDomainChrSourceDef *tgt)
|
|
{
|
|
if (tgt->type != src->type)
|
|
return false;
|
|
|
|
switch ((virDomainChrType)src->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
return src->data.file.append == tgt->data.file.append &&
|
|
STREQ_NULLABLE(src->data.file.path, tgt->data.file.path);
|
|
break;
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
return STREQ_NULLABLE(src->data.file.path, tgt->data.file.path);
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
return STREQ_NULLABLE(src->data.nmdm.master, tgt->data.nmdm.master) &&
|
|
STREQ_NULLABLE(src->data.nmdm.slave, tgt->data.nmdm.slave);
|
|
break;
|
|
case VIR_DOMAIN_CHR_TYPE_UDP:
|
|
return STREQ_NULLABLE(src->data.udp.bindHost, tgt->data.udp.bindHost) &&
|
|
STREQ_NULLABLE(src->data.udp.bindService, tgt->data.udp.bindService) &&
|
|
STREQ_NULLABLE(src->data.udp.connectHost, tgt->data.udp.connectHost) &&
|
|
STREQ_NULLABLE(src->data.udp.connectService, tgt->data.udp.connectService);
|
|
break;
|
|
case VIR_DOMAIN_CHR_TYPE_TCP:
|
|
return src->data.tcp.listen == tgt->data.tcp.listen &&
|
|
src->data.tcp.protocol == tgt->data.tcp.protocol &&
|
|
STREQ_NULLABLE(src->data.tcp.host, tgt->data.tcp.host) &&
|
|
STREQ_NULLABLE(src->data.tcp.service, tgt->data.tcp.service) &&
|
|
src->data.tcp.reconnect.enabled == tgt->data.tcp.reconnect.enabled &&
|
|
src->data.tcp.reconnect.timeout == tgt->data.tcp.reconnect.timeout;
|
|
break;
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
return src->data.nix.listen == tgt->data.nix.listen &&
|
|
STREQ_NULLABLE(src->data.nix.path, tgt->data.nix.path) &&
|
|
src->data.nix.reconnect.enabled == tgt->data.nix.reconnect.enabled &&
|
|
src->data.nix.reconnect.timeout == tgt->data.nix.reconnect.timeout;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
|
|
return STREQ_NULLABLE(src->data.spiceport.channel,
|
|
tgt->data.spiceport.channel);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
|
|
return src->data.spicevmc == tgt->data.spicevmc;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_NULL:
|
|
case VIR_DOMAIN_CHR_TYPE_VC:
|
|
case VIR_DOMAIN_CHR_TYPE_STDIO:
|
|
case VIR_DOMAIN_CHR_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void virDomainChrDefFree(virDomainChrDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
switch (def->deviceType) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
switch (def->targetType) {
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD:
|
|
g_free(def->target.addr);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XEN:
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO:
|
|
g_free(def->target.name);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
virObjectUnref(def->source);
|
|
virDomainDeviceInfoClear(&def->info);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainSmartcardDefFree(virDomainSmartcardDef *def)
|
|
{
|
|
size_t i;
|
|
if (!def)
|
|
return;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
|
|
break;
|
|
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
|
|
for (i = 0; i < VIR_DOMAIN_SMARTCARD_NUM_CERTIFICATES; i++)
|
|
g_free(def->data.cert.file[i]);
|
|
g_free(def->data.cert.database);
|
|
break;
|
|
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
|
|
virObjectUnref(def->data.passthru);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainSoundCodecDefFree(virDomainSoundCodecDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainSoundDefFree(virDomainSoundDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
|
|
for (i = 0; i < def->ncodecs; i++)
|
|
virDomainSoundCodecDefFree(def->codecs[i]);
|
|
g_free(def->codecs);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
static void
|
|
virDomainAudioIOALSAFree(virDomainAudioIOALSA *def)
|
|
{
|
|
g_free(def->dev);
|
|
}
|
|
|
|
static void
|
|
virDomainAudioIOJackFree(virDomainAudioIOJack *def)
|
|
{
|
|
g_free(def->serverName);
|
|
g_free(def->clientName);
|
|
g_free(def->connectPorts);
|
|
}
|
|
|
|
static void
|
|
virDomainAudioIOOSSFree(virDomainAudioIOOSS *def)
|
|
{
|
|
g_free(def->dev);
|
|
}
|
|
|
|
static void
|
|
virDomainAudioIOPulseAudioFree(virDomainAudioIOPulseAudio *def)
|
|
{
|
|
g_free(def->name);
|
|
g_free(def->streamName);
|
|
}
|
|
|
|
void
|
|
virDomainAudioDefFree(virDomainAudioDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_AUDIO_TYPE_NONE:
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_ALSA:
|
|
virDomainAudioIOALSAFree(&def->backend.alsa.input);
|
|
virDomainAudioIOALSAFree(&def->backend.alsa.output);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_COREAUDIO:
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_JACK:
|
|
virDomainAudioIOJackFree(&def->backend.jack.input);
|
|
virDomainAudioIOJackFree(&def->backend.jack.output);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_OSS:
|
|
virDomainAudioIOOSSFree(&def->backend.oss.input);
|
|
virDomainAudioIOOSSFree(&def->backend.oss.output);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_PULSEAUDIO:
|
|
virDomainAudioIOPulseAudioFree(&def->backend.pulseaudio.input);
|
|
virDomainAudioIOPulseAudioFree(&def->backend.pulseaudio.output);
|
|
g_free(def->backend.pulseaudio.serverName);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_SDL:
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_SPICE:
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_FILE:
|
|
g_free(def->backend.file.path);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
virDomainSoundDef *
|
|
virDomainSoundDefRemove(virDomainDef *def, size_t idx)
|
|
{
|
|
virDomainSoundDef *ret = def->sounds[idx];
|
|
VIR_DELETE_ELEMENT(def->sounds, idx, def->nsounds);
|
|
return ret;
|
|
}
|
|
|
|
void virDomainMemballoonDefFree(virDomainMemballoonDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
g_free(def->virtio);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainNVRAMDefFree(virDomainNVRAMDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainWatchdogDefFree(virDomainWatchdogDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainShmemDefFree(virDomainShmemDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
virDomainChrSourceDefClear(&def->server.chr);
|
|
g_free(def->name);
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
virDomainVideoDef *
|
|
virDomainVideoDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainVideoDef *def;
|
|
|
|
def = g_new0(virDomainVideoDef, 1);
|
|
|
|
if (xmlopt && xmlopt->privateData.videoNew &&
|
|
!(def->privateData = xmlopt->privateData.videoNew())) {
|
|
VIR_FREE(def);
|
|
return NULL;
|
|
}
|
|
|
|
def->heads = 1;
|
|
return def;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainVideoDefClear(virDomainVideoDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
|
|
if (def->accel)
|
|
VIR_FREE(def->accel->rendernode);
|
|
VIR_FREE(def->accel);
|
|
VIR_FREE(def->res);
|
|
VIR_FREE(def->virtio);
|
|
if (def->driver)
|
|
VIR_FREE(def->driver->vhost_user_binary);
|
|
VIR_FREE(def->driver);
|
|
virObjectUnref(def->privateData);
|
|
|
|
memset(def, 0, sizeof(*def));
|
|
}
|
|
|
|
|
|
void virDomainVideoDefFree(virDomainVideoDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainVideoDefClear(def);
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
virDomainHostdevDef *
|
|
virDomainHostdevDefNew(void)
|
|
{
|
|
virDomainHostdevDef *def;
|
|
|
|
def = g_new0(virDomainHostdevDef, 1);
|
|
|
|
def->info = g_new0(virDomainDeviceInfo, 1);
|
|
|
|
return def;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainHostdevSubsysSCSIClear(virDomainHostdevSubsysSCSI *scsisrc)
|
|
{
|
|
if (scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI) {
|
|
virObjectUnref(scsisrc->u.iscsi.src);
|
|
scsisrc->u.iscsi.src = NULL;
|
|
} else {
|
|
VIR_FREE(scsisrc->u.host.adapter);
|
|
virObjectUnref(scsisrc->u.host.src);
|
|
scsisrc->u.host.src = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void virDomainHostdevDefClear(virDomainHostdevDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
/* Free all resources in the hostdevdef. Currently the only
|
|
* such resource is the virDomainDeviceInfo.
|
|
*/
|
|
|
|
/* If there is a parentnet device object, it will handle freeing
|
|
* def->info.
|
|
*/
|
|
if (!def->parentnet)
|
|
virDomainDeviceInfoFree(def->info);
|
|
|
|
virDomainNetTeamingInfoFree(def->teaming);
|
|
|
|
switch (def->mode) {
|
|
case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
|
|
switch ((virDomainHostdevCapsType) def->source.caps.type) {
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_STORAGE:
|
|
VIR_FREE(def->source.caps.u.storage.block);
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_MISC:
|
|
VIR_FREE(def->source.caps.u.misc.chardev);
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET:
|
|
VIR_FREE(def->source.caps.u.net.ifname);
|
|
virNetDevIPInfoClear(&def->source.caps.u.net.ip);
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_LAST:
|
|
break;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
|
|
switch ((virDomainHostdevSubsysType) def->source.subsys.type) {
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI:
|
|
virDomainHostdevSubsysSCSIClear(&def->source.subsys.u.scsi);
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST:
|
|
VIR_FREE(def->source.subsys.u.scsi_host.wwpn);
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV:
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void virDomainTPMDefFree(virDomainTPMDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
|
|
virDomainChrSourceDefClear(&def->data.passthrough.source);
|
|
break;
|
|
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
|
|
virDomainChrSourceDefClear(&def->data.emulator.source);
|
|
g_free(def->data.emulator.storagepath);
|
|
g_free(def->data.emulator.logfile);
|
|
break;
|
|
case VIR_DOMAIN_TPM_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainHostdevDefFree(virDomainHostdevDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
/* free all subordinate objects */
|
|
virDomainHostdevDefClear(def);
|
|
|
|
/* If there is a parentnet device object, it will handle freeing
|
|
* the memory.
|
|
*/
|
|
if (!def->parentnet)
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainHubDefFree(virDomainHubDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainRedirdevDefFree(virDomainRedirdevDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
virObjectUnref(def->source);
|
|
virDomainDeviceInfoClear(&def->info);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainRedirFilterDefFree(virDomainRedirFilterDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
if (!def)
|
|
return;
|
|
|
|
for (i = 0; i < def->nusbdevs; i++)
|
|
g_free(def->usbdevs[i]);
|
|
|
|
g_free(def->usbdevs);
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainMemoryDefFree(virDomainMemoryDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def->nvdimmPath);
|
|
virBitmapFree(def->sourceNodes);
|
|
g_free(def->uuid);
|
|
virDomainDeviceInfoClear(&def->info);
|
|
g_free(def);
|
|
}
|
|
|
|
void virDomainDeviceDefFree(virDomainDeviceDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
switch ((virDomainDeviceType) def->type) {
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
virDomainDiskDefFree(def->data.disk);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
virDomainLeaseDefFree(def->data.lease);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
virDomainNetDefFree(def->data.net);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
virDomainInputDefFree(def->data.input);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
virDomainSoundDefFree(def->data.sound);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
virDomainVideoDefFree(def->data.video);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
virDomainHostdevDefFree(def->data.hostdev);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
virDomainWatchdogDefFree(def->data.watchdog);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
virDomainControllerDefFree(def->data.controller);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
virDomainGraphicsDefFree(def->data.graphics);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
virDomainHubDefFree(def->data.hub);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
virDomainRedirdevDefFree(def->data.redirdev);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
virDomainRNGDefFree(def->data.rng);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
virDomainChrDefFree(def->data.chr);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
virDomainFSDefFree(def->data.fs);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
virDomainSmartcardDefFree(def->data.smartcard);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_MEMBALLOON:
|
|
virDomainMemballoonDefFree(def->data.memballoon);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NVRAM:
|
|
virDomainNVRAMDefFree(def->data.nvram);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SHMEM:
|
|
virDomainShmemDefFree(def->data.shmem);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_TPM:
|
|
virDomainTPMDefFree(def->data.tpm);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_PANIC:
|
|
virDomainPanicDefFree(def->data.panic);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
virDomainMemoryDefFree(def->data.memory);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
g_free(def->data.iommu);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
virDomainVsockDefFree(def->data.vsock);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_AUDIO:
|
|
virDomainAudioDefFree(def->data.audio);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_LAST:
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
break;
|
|
}
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
static void
|
|
virDomainClockDefClear(virDomainClockDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
if (def->offset == VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE)
|
|
VIR_FREE(def->data.timezone);
|
|
|
|
for (i = 0; i < def->ntimers; i++)
|
|
VIR_FREE(def->timers[i]);
|
|
VIR_FREE(def->timers);
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainIOThreadIDArrayHasPin(virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->niothreadids; i++) {
|
|
if (def->iothreadids[i]->cpumask)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainIOThreadIDDefFree(virDomainIOThreadIDDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
virBitmapFree(def->cpumask);
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainIOThreadIDDefArrayFree(virDomainIOThreadIDDef **def,
|
|
int nids)
|
|
{
|
|
size_t i;
|
|
|
|
if (!def)
|
|
return;
|
|
|
|
for (i = 0; i < nids; i++)
|
|
virDomainIOThreadIDDefFree(def[i]);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainIOThreadIDDefArrayInit(virDomainDef *def,
|
|
unsigned int iothreads)
|
|
{
|
|
size_t i;
|
|
ssize_t nxt = -1;
|
|
virDomainIOThreadIDDef *iothrid = NULL;
|
|
g_autoptr(virBitmap) thrmap = NULL;
|
|
|
|
/* Same value (either 0 or some number), then we have none to fill in or
|
|
* the iothreadid array was filled from the XML
|
|
*/
|
|
if (iothreads == def->niothreadids)
|
|
return 0;
|
|
|
|
/* iothread's are numbered starting at 1, account for that */
|
|
thrmap = virBitmapNew(iothreads + 1);
|
|
virBitmapSetAll(thrmap);
|
|
|
|
/* Clear 0 since we don't use it, then mark those which are
|
|
* already provided by the user */
|
|
ignore_value(virBitmapClearBit(thrmap, 0));
|
|
for (i = 0; i < def->niothreadids; i++)
|
|
ignore_value(virBitmapClearBit(thrmap,
|
|
def->iothreadids[i]->iothread_id));
|
|
|
|
/* resize array */
|
|
VIR_REALLOC_N(def->iothreadids, iothreads);
|
|
|
|
/* Populate iothreadids[] using the set bit number from thrmap */
|
|
while (def->niothreadids < iothreads) {
|
|
if ((nxt = virBitmapNextSetBit(thrmap, nxt)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to populate iothreadids"));
|
|
return -1;
|
|
}
|
|
iothrid = g_new0(virDomainIOThreadIDDef, 1);
|
|
iothrid->iothread_id = nxt;
|
|
iothrid->autofill = true;
|
|
def->iothreadids[def->niothreadids++] = iothrid;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainResourceDefFree(virDomainResourceDef *resource)
|
|
{
|
|
if (!resource)
|
|
return;
|
|
|
|
g_free(resource->partition);
|
|
g_free(resource);
|
|
}
|
|
|
|
void
|
|
virDomainPanicDefFree(virDomainPanicDef *panic)
|
|
{
|
|
if (!panic)
|
|
return;
|
|
|
|
virDomainDeviceInfoClear(&panic->info);
|
|
g_free(panic);
|
|
}
|
|
|
|
void
|
|
virDomainLoaderDefFree(virDomainLoaderDef *loader)
|
|
{
|
|
if (!loader)
|
|
return;
|
|
|
|
g_free(loader->path);
|
|
g_free(loader->nvram);
|
|
g_free(loader->templt);
|
|
g_free(loader);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainResctrlMonDefFree(virDomainResctrlMonDef *domresmon)
|
|
{
|
|
if (!domresmon)
|
|
return;
|
|
|
|
virBitmapFree(domresmon->vcpus);
|
|
virObjectUnref(domresmon->instance);
|
|
g_free(domresmon);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainResctrlDefFree(virDomainResctrlDef *resctrl)
|
|
{
|
|
size_t i = 0;
|
|
|
|
if (!resctrl)
|
|
return;
|
|
|
|
for (i = 0; i < resctrl->nmonitors; i++)
|
|
virDomainResctrlMonDefFree(resctrl->monitors[i]);
|
|
|
|
virObjectUnref(resctrl->alloc);
|
|
virBitmapFree(resctrl->vcpus);
|
|
g_free(resctrl->monitors);
|
|
g_free(resctrl);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainSEVDefFree(virDomainSEVDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def->dh_cert);
|
|
g_free(def->session);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
static void
|
|
virDomainOSDefClear(virDomainOSDef *os)
|
|
{
|
|
size_t i;
|
|
|
|
g_free(os->firmwareFeatures);
|
|
g_free(os->machine);
|
|
g_free(os->init);
|
|
for (i = 0; os->initargv && os->initargv[i]; i++)
|
|
g_free(os->initargv[i]);
|
|
g_free(os->initargv);
|
|
for (i = 0; os->initenv && os->initenv[i]; i++) {
|
|
g_free(os->initenv[i]->name);
|
|
g_free(os->initenv[i]->value);
|
|
g_free(os->initenv[i]);
|
|
}
|
|
g_free(os->initdir);
|
|
g_free(os->inituser);
|
|
g_free(os->initgroup);
|
|
g_free(os->initenv);
|
|
g_free(os->kernel);
|
|
g_free(os->initrd);
|
|
g_free(os->cmdline);
|
|
g_free(os->dtb);
|
|
g_free(os->root);
|
|
g_free(os->slic_table);
|
|
virDomainLoaderDefFree(os->loader);
|
|
g_free(os->bootloader);
|
|
g_free(os->bootloaderArgs);
|
|
}
|
|
|
|
|
|
void virDomainDefFree(virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
if (!def)
|
|
return;
|
|
|
|
virDomainResourceDefFree(def->resource);
|
|
|
|
for (i = 0; i < def->maxvcpus; i++)
|
|
virDomainVcpuDefFree(def->vcpus[i]);
|
|
g_free(def->vcpus);
|
|
|
|
/* hostdevs must be freed before nets (or any future "intelligent
|
|
* hostdevs") because the pointer to the hostdev is really
|
|
* pointing into the middle of the higher level device's object,
|
|
* so the original object must still be available during the call
|
|
* to virDomainHostdevDefFree().
|
|
*/
|
|
for (i = 0; i < def->nhostdevs; i++)
|
|
virDomainHostdevDefFree(def->hostdevs[i]);
|
|
g_free(def->hostdevs);
|
|
|
|
for (i = 0; i < def->nleases; i++)
|
|
virDomainLeaseDefFree(def->leases[i]);
|
|
g_free(def->leases);
|
|
|
|
for (i = 0; i < def->ngraphics; i++)
|
|
virDomainGraphicsDefFree(def->graphics[i]);
|
|
g_free(def->graphics);
|
|
|
|
for (i = 0; i < def->ninputs; i++)
|
|
virDomainInputDefFree(def->inputs[i]);
|
|
g_free(def->inputs);
|
|
|
|
for (i = 0; i < def->ndisks; i++)
|
|
virDomainDiskDefFree(def->disks[i]);
|
|
g_free(def->disks);
|
|
|
|
for (i = 0; i < def->ncontrollers; i++)
|
|
virDomainControllerDefFree(def->controllers[i]);
|
|
g_free(def->controllers);
|
|
|
|
for (i = 0; i < def->nfss; i++)
|
|
virDomainFSDefFree(def->fss[i]);
|
|
g_free(def->fss);
|
|
|
|
for (i = 0; i < def->nnets; i++)
|
|
virDomainNetDefFree(def->nets[i]);
|
|
g_free(def->nets);
|
|
|
|
for (i = 0; i < def->nsmartcards; i++)
|
|
virDomainSmartcardDefFree(def->smartcards[i]);
|
|
g_free(def->smartcards);
|
|
|
|
for (i = 0; i < def->nserials; i++)
|
|
virDomainChrDefFree(def->serials[i]);
|
|
g_free(def->serials);
|
|
|
|
for (i = 0; i < def->nparallels; i++)
|
|
virDomainChrDefFree(def->parallels[i]);
|
|
g_free(def->parallels);
|
|
|
|
for (i = 0; i < def->nchannels; i++)
|
|
virDomainChrDefFree(def->channels[i]);
|
|
g_free(def->channels);
|
|
|
|
for (i = 0; i < def->nconsoles; i++)
|
|
virDomainChrDefFree(def->consoles[i]);
|
|
g_free(def->consoles);
|
|
|
|
for (i = 0; i < def->nsounds; i++)
|
|
virDomainSoundDefFree(def->sounds[i]);
|
|
g_free(def->sounds);
|
|
|
|
for (i = 0; i < def->naudios; i++)
|
|
virDomainAudioDefFree(def->audios[i]);
|
|
g_free(def->audios);
|
|
|
|
for (i = 0; i < def->nvideos; i++)
|
|
virDomainVideoDefFree(def->videos[i]);
|
|
g_free(def->videos);
|
|
|
|
for (i = 0; i < def->nhubs; i++)
|
|
virDomainHubDefFree(def->hubs[i]);
|
|
g_free(def->hubs);
|
|
|
|
for (i = 0; i < def->nredirdevs; i++)
|
|
virDomainRedirdevDefFree(def->redirdevs[i]);
|
|
g_free(def->redirdevs);
|
|
|
|
for (i = 0; i < def->nrngs; i++)
|
|
virDomainRNGDefFree(def->rngs[i]);
|
|
g_free(def->rngs);
|
|
|
|
for (i = 0; i < def->nmems; i++)
|
|
virDomainMemoryDefFree(def->mems[i]);
|
|
g_free(def->mems);
|
|
|
|
for (i = 0; i < def->ntpms; i++)
|
|
virDomainTPMDefFree(def->tpms[i]);
|
|
g_free(def->tpms);
|
|
|
|
for (i = 0; i < def->npanics; i++)
|
|
virDomainPanicDefFree(def->panics[i]);
|
|
g_free(def->panics);
|
|
|
|
g_free(def->iommu);
|
|
|
|
g_free(def->idmap.uidmap);
|
|
g_free(def->idmap.gidmap);
|
|
|
|
virDomainOSDefClear(&def->os);
|
|
|
|
virDomainClockDefClear(&def->clock);
|
|
|
|
g_free(def->name);
|
|
virBitmapFree(def->cpumask);
|
|
g_free(def->emulator);
|
|
g_free(def->description);
|
|
g_free(def->title);
|
|
g_free(def->hyperv_vendor_id);
|
|
|
|
virBlkioDeviceArrayClear(def->blkio.devices,
|
|
def->blkio.ndevices);
|
|
g_free(def->blkio.devices);
|
|
|
|
virDomainWatchdogDefFree(def->watchdog);
|
|
|
|
virDomainMemballoonDefFree(def->memballoon);
|
|
virDomainNVRAMDefFree(def->nvram);
|
|
virDomainVsockDefFree(def->vsock);
|
|
|
|
for (i = 0; i < def->mem.nhugepages; i++)
|
|
virBitmapFree(def->mem.hugepages[i].nodemask);
|
|
g_free(def->mem.hugepages);
|
|
|
|
for (i = 0; i < def->nseclabels; i++)
|
|
virSecurityLabelDefFree(def->seclabels[i]);
|
|
g_free(def->seclabels);
|
|
|
|
virCPUDefFree(def->cpu);
|
|
|
|
virDomainIOThreadIDDefArrayFree(def->iothreadids, def->niothreadids);
|
|
|
|
virBitmapFree(def->cputune.emulatorpin);
|
|
g_free(def->cputune.emulatorsched);
|
|
|
|
virDomainNumaFree(def->numa);
|
|
|
|
for (i = 0; i < def->nsysinfo; i++)
|
|
virSysinfoDefFree(def->sysinfo[i]);
|
|
g_free(def->sysinfo);
|
|
|
|
virDomainRedirFilterDefFree(def->redirfilter);
|
|
|
|
for (i = 0; i < def->nshmems; i++)
|
|
virDomainShmemDefFree(def->shmems[i]);
|
|
g_free(def->shmems);
|
|
|
|
for (i = 0; i < def->nresctrls; i++)
|
|
virDomainResctrlDefFree(def->resctrls[i]);
|
|
g_free(def->resctrls);
|
|
|
|
g_free(def->keywrap);
|
|
|
|
if (def->namespaceData && def->ns.free)
|
|
(def->ns.free)(def->namespaceData);
|
|
|
|
virDomainSEVDefFree(def->sev);
|
|
|
|
xmlFreeNode(def->metadata);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
static void virDomainObjDispose(void *obj)
|
|
{
|
|
virDomainObj *dom = obj;
|
|
|
|
VIR_DEBUG("obj=%p", dom);
|
|
virCondDestroy(&dom->cond);
|
|
virDomainDefFree(dom->def);
|
|
virDomainDefFree(dom->newDef);
|
|
|
|
if (dom->privateDataFreeFunc)
|
|
(dom->privateDataFreeFunc)(dom->privateData);
|
|
|
|
virDomainSnapshotObjListFree(dom->snapshots);
|
|
virDomainCheckpointObjListFree(dom->checkpoints);
|
|
}
|
|
|
|
virDomainObj *
|
|
virDomainObjNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainObj *domain;
|
|
|
|
if (virDomainObjInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(domain = virObjectLockableNew(virDomainObjClass)))
|
|
return NULL;
|
|
|
|
if (virCondInit(&domain->cond) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("failed to initialize domain condition"));
|
|
goto error;
|
|
}
|
|
|
|
if (xmlopt->privateData.alloc) {
|
|
domain->privateData = (xmlopt->privateData.alloc)(xmlopt->config.priv);
|
|
if (!domain->privateData)
|
|
goto error;
|
|
domain->privateDataFreeFunc = xmlopt->privateData.free;
|
|
}
|
|
|
|
if (!(domain->snapshots = virDomainSnapshotObjListNew()))
|
|
goto error;
|
|
|
|
if (!(domain->checkpoints = virDomainCheckpointObjListNew()))
|
|
goto error;
|
|
|
|
virObjectLock(domain);
|
|
virDomainObjSetState(domain, VIR_DOMAIN_SHUTOFF,
|
|
VIR_DOMAIN_SHUTOFF_UNKNOWN);
|
|
|
|
VIR_DEBUG("obj=%p", domain);
|
|
return domain;
|
|
|
|
error:
|
|
virObjectUnref(domain);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
virDomainDef *
|
|
virDomainDefNew(void)
|
|
{
|
|
virDomainDef *ret;
|
|
|
|
ret = g_new0(virDomainDef, 1);
|
|
|
|
if (!(ret->numa = virDomainNumaNew()))
|
|
goto error;
|
|
|
|
ret->mem.hard_limit = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
ret->mem.soft_limit = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
ret->mem.swap_hard_limit = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
|
|
return ret;
|
|
|
|
error:
|
|
virDomainDefFree(ret);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void virDomainObjAssignDef(virDomainObj *domain,
|
|
virDomainDef *def,
|
|
bool live,
|
|
virDomainDef **oldDef)
|
|
{
|
|
if (oldDef)
|
|
*oldDef = NULL;
|
|
if (virDomainObjIsActive(domain)) {
|
|
if (oldDef)
|
|
*oldDef = domain->newDef;
|
|
else
|
|
virDomainDefFree(domain->newDef);
|
|
domain->newDef = def;
|
|
} else {
|
|
if (live) {
|
|
/* save current configuration to be restored on domain shutdown */
|
|
if (!domain->newDef)
|
|
domain->newDef = domain->def;
|
|
else
|
|
virDomainDefFree(domain->def);
|
|
domain->def = def;
|
|
} else {
|
|
if (oldDef)
|
|
*oldDef = domain->def;
|
|
else
|
|
virDomainDefFree(domain->def);
|
|
domain->def = def;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainObjEndAPI:
|
|
* @vm: domain object
|
|
*
|
|
* Finish working with a domain object in an API. This function
|
|
* clears whatever was left of a domain that was gathered using
|
|
* virDomainObjListFindByUUID(). Currently that means only unlocking and
|
|
* decrementing the reference counter of that domain. And in order to
|
|
* make sure the caller does not access the domain, the pointer is
|
|
* cleared.
|
|
*/
|
|
void
|
|
virDomainObjEndAPI(virDomainObj **vm)
|
|
{
|
|
if (!*vm)
|
|
return;
|
|
|
|
virObjectUnlock(*vm);
|
|
virObjectUnref(*vm);
|
|
*vm = NULL;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainObjBroadcast(virDomainObj *vm)
|
|
{
|
|
virCondBroadcast(&vm->cond);
|
|
}
|
|
|
|
|
|
int
|
|
virDomainObjWait(virDomainObj *vm)
|
|
{
|
|
if (virCondWait(&vm->cond, &vm->parent.lock) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("failed to wait for domain condition"));
|
|
return -1;
|
|
}
|
|
|
|
if (!virDomainObjIsActive(vm)) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("domain is not running"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Waits for domain condition to be triggered for a specific period of time.
|
|
*
|
|
* Returns:
|
|
* -1 in case of error
|
|
* 0 on success
|
|
* 1 on timeout
|
|
*/
|
|
int
|
|
virDomainObjWaitUntil(virDomainObj *vm,
|
|
unsigned long long whenms)
|
|
{
|
|
if (virCondWaitUntil(&vm->cond, &vm->parent.lock, whenms) < 0) {
|
|
if (errno != ETIMEDOUT) {
|
|
virReportSystemError(errno, "%s",
|
|
_("failed to wait for domain condition"));
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Mark the current VM config as transient. Ensures transient hotplug
|
|
* operations do not persist past shutdown.
|
|
*
|
|
* @param caps pointer to capabilities info
|
|
* @param xmlopt pointer to XML parser configuration object
|
|
* @param domain domain object pointer
|
|
* @return 0 on success, -1 on failure
|
|
*/
|
|
int
|
|
virDomainObjSetDefTransient(virDomainXMLOption *xmlopt,
|
|
virDomainObj *domain,
|
|
void *parseOpaque)
|
|
{
|
|
if (!domain->persistent)
|
|
return 0;
|
|
|
|
if (domain->newDef)
|
|
return 0;
|
|
|
|
if (!(domain->newDef = virDomainDefCopy(domain->def, xmlopt,
|
|
parseOpaque, false)))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Remove the running configuration and replace it with the persistent one.
|
|
*
|
|
* @param domain domain object pointer
|
|
*/
|
|
void
|
|
virDomainObjRemoveTransientDef(virDomainObj *domain)
|
|
{
|
|
if (!domain->newDef)
|
|
return;
|
|
|
|
virDomainDefFree(domain->def);
|
|
domain->def = g_steal_pointer(&domain->newDef);
|
|
domain->def->id = -1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the persistent domain configuration. If domain is transient,
|
|
* return the running config.
|
|
*
|
|
* @param caps pointer to capabilities info
|
|
* @param xmlopt pointer to XML parser configuration object
|
|
* @param domain domain object pointer
|
|
* @return NULL on error, virDomainDef * on success
|
|
*/
|
|
virDomainDef *
|
|
virDomainObjGetPersistentDef(virDomainXMLOption *xmlopt,
|
|
virDomainObj *domain,
|
|
void *parseOpaque)
|
|
{
|
|
if (virDomainObjIsActive(domain) &&
|
|
virDomainObjSetDefTransient(xmlopt, domain, parseOpaque) < 0)
|
|
return NULL;
|
|
|
|
if (domain->newDef)
|
|
return domain->newDef;
|
|
else
|
|
return domain->def;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainObjUpdateModificationImpact:
|
|
*
|
|
* @vm: domain object
|
|
* @flags: flags to update the modification impact on
|
|
*
|
|
* Resolves virDomainModificationImpact flags in @flags so that they correctly
|
|
* apply to the actual state of @vm. @flags may be modified after call to this
|
|
* function.
|
|
*
|
|
* Returns 0 on success if @flags point to a valid combination for @vm or -1 on
|
|
* error.
|
|
*/
|
|
int
|
|
virDomainObjUpdateModificationImpact(virDomainObj *vm,
|
|
unsigned int *flags)
|
|
{
|
|
bool isActive = virDomainObjIsActive(vm);
|
|
|
|
if ((*flags & (VIR_DOMAIN_AFFECT_LIVE | VIR_DOMAIN_AFFECT_CONFIG)) ==
|
|
VIR_DOMAIN_AFFECT_CURRENT) {
|
|
if (isActive)
|
|
*flags |= VIR_DOMAIN_AFFECT_LIVE;
|
|
else
|
|
*flags |= VIR_DOMAIN_AFFECT_CONFIG;
|
|
}
|
|
|
|
if (!isActive && (*flags & VIR_DOMAIN_AFFECT_LIVE)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("domain is not running"));
|
|
return -1;
|
|
}
|
|
|
|
if (!vm->persistent && (*flags & VIR_DOMAIN_AFFECT_CONFIG)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("transient domains do not have any "
|
|
"persistent config"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainObjGetDefs:
|
|
*
|
|
* @vm: domain object
|
|
* @flags: for virDomainModificationImpact
|
|
* @liveDef: Set to the pointer to the live definition of @vm.
|
|
* @persDef: Set to the pointer to the config definition of @vm.
|
|
*
|
|
* Helper function to resolve @flags and retrieve correct domain pointer
|
|
* objects. This function should be used only when the hypervisor driver always
|
|
* creates vm->newDef once the vm is started. (qemu driver does that)
|
|
*
|
|
* If @liveDef or @persDef are set it implies that @flags request modification
|
|
* of thereof.
|
|
*
|
|
* Returns 0 on success and sets @liveDef and @persDef; -1 if @flags are
|
|
* inappropriate.
|
|
*/
|
|
int
|
|
virDomainObjGetDefs(virDomainObj *vm,
|
|
unsigned int flags,
|
|
virDomainDef **liveDef,
|
|
virDomainDef **persDef)
|
|
{
|
|
if (liveDef)
|
|
*liveDef = NULL;
|
|
|
|
if (persDef)
|
|
*persDef = NULL;
|
|
|
|
if (virDomainObjUpdateModificationImpact(vm, &flags) < 0)
|
|
return -1;
|
|
|
|
if (virDomainObjIsActive(vm)) {
|
|
if (liveDef && (flags & VIR_DOMAIN_AFFECT_LIVE))
|
|
*liveDef = vm->def;
|
|
|
|
if (persDef && (flags & VIR_DOMAIN_AFFECT_CONFIG))
|
|
*persDef = vm->newDef;
|
|
} else {
|
|
if (persDef)
|
|
*persDef = vm->def;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainObjGetOneDefState:
|
|
*
|
|
* @vm: Domain object
|
|
* @flags: for virDomainModificationImpact
|
|
* @live: set to true if live config was returned (may be omitted)
|
|
*
|
|
* Helper function to resolve @flags and return the correct domain pointer
|
|
* object. This function returns one of @vm->def or @vm->persistentDef
|
|
* according to @flags. @live is set to true if the live vm config will be
|
|
* returned. This helper should be used only in APIs that guarantee
|
|
* that @flags contains exactly one of VIR_DOMAIN_AFFECT_LIVE or
|
|
* VIR_DOMAIN_AFFECT_CONFIG and not both.
|
|
*
|
|
* Returns the correct definition pointer or NULL on error.
|
|
*/
|
|
virDomainDef *
|
|
virDomainObjGetOneDefState(virDomainObj *vm,
|
|
unsigned int flags,
|
|
bool *live)
|
|
{
|
|
if (flags & VIR_DOMAIN_AFFECT_LIVE &&
|
|
flags & VIR_DOMAIN_AFFECT_CONFIG) {
|
|
virReportInvalidArg(flags, "%s",
|
|
_("Flags 'VIR_DOMAIN_AFFECT_LIVE' and "
|
|
"'VIR_DOMAIN_AFFECT_CONFIG' are mutually "
|
|
"exclusive"));
|
|
return NULL;
|
|
}
|
|
|
|
if (virDomainObjUpdateModificationImpact(vm, &flags) < 0)
|
|
return NULL;
|
|
|
|
if (live) {
|
|
if (flags & VIR_DOMAIN_AFFECT_LIVE)
|
|
*live = true;
|
|
else
|
|
*live = false;
|
|
}
|
|
|
|
if (virDomainObjIsActive(vm) && flags & VIR_DOMAIN_AFFECT_CONFIG)
|
|
return vm->newDef;
|
|
else
|
|
return vm->def;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainObjGetOneDef:
|
|
*
|
|
* @vm: Domain object
|
|
* @flags: for virDomainModificationImpact
|
|
*
|
|
* Helper function to resolve @flags and return the correct domain pointer
|
|
* object. This function returns one of @vm->def or @vm->persistentDef
|
|
* according to @flags. This helper should be used only in APIs that guarantee
|
|
* that @flags contains exactly one of VIR_DOMAIN_AFFECT_LIVE or
|
|
* VIR_DOMAIN_AFFECT_CONFIG and not both.
|
|
*
|
|
* Returns the correct definition pointer or NULL on error.
|
|
*/
|
|
virDomainDef *
|
|
virDomainObjGetOneDef(virDomainObj *vm,
|
|
unsigned int flags)
|
|
{
|
|
return virDomainObjGetOneDefState(vm, flags, NULL);
|
|
}
|
|
|
|
virDomainDeviceInfo *
|
|
virDomainDeviceGetInfo(virDomainDeviceDef *device)
|
|
{
|
|
switch ((virDomainDeviceType) device->type) {
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
return &device->data.disk->info;
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
return &device->data.fs->info;
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
return &device->data.net->info;
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
return &device->data.input->info;
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
return &device->data.sound->info;
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
return &device->data.video->info;
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
return device->data.hostdev->info;
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
return &device->data.watchdog->info;
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
return &device->data.controller->info;
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
return &device->data.hub->info;
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
return &device->data.redirdev->info;
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
return &device->data.smartcard->info;
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
return &device->data.chr->info;
|
|
case VIR_DOMAIN_DEVICE_MEMBALLOON:
|
|
return &device->data.memballoon->info;
|
|
case VIR_DOMAIN_DEVICE_NVRAM:
|
|
return &device->data.nvram->info;
|
|
case VIR_DOMAIN_DEVICE_SHMEM:
|
|
return &device->data.shmem->info;
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
return &device->data.rng->info;
|
|
case VIR_DOMAIN_DEVICE_TPM:
|
|
return &device->data.tpm->info;
|
|
case VIR_DOMAIN_DEVICE_PANIC:
|
|
return &device->data.panic->info;
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
return &device->data.memory->info;
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
return &device->data.vsock->info;
|
|
|
|
/* The following devices do not contain virDomainDeviceInfo */
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
case VIR_DOMAIN_DEVICE_AUDIO:
|
|
case VIR_DOMAIN_DEVICE_LAST:
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDeviceSetData
|
|
* @device: virDomainDeviceDef * with ->type filled in
|
|
* @devicedata: *Def * data for a device. Ex: virDomainDiskDef *
|
|
*
|
|
* Set the data.X variable for the device->type value. Basically
|
|
* a mapping of virDomainDeviceType to the associated name in
|
|
* the virDomainDeviceDef union
|
|
*/
|
|
void
|
|
virDomainDeviceSetData(virDomainDeviceDef *device,
|
|
void *devicedata)
|
|
{
|
|
switch ((virDomainDeviceType) device->type) {
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
device->data.disk = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
device->data.net = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
device->data.sound = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
device->data.hostdev = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
device->data.video = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
device->data.controller = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
device->data.graphics = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
device->data.smartcard = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
device->data.chr = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
device->data.input = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
device->data.fs = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
device->data.watchdog = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_MEMBALLOON:
|
|
device->data.memballoon = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
device->data.rng = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NVRAM:
|
|
device->data.nvram = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
device->data.hub = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SHMEM:
|
|
device->data.shmem = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_TPM:
|
|
device->data.tpm = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_PANIC:
|
|
device->data.panic = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
device->data.memory = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
device->data.redirdev = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
device->data.vsock = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
device->data.iommu = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
device->data.lease = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_AUDIO:
|
|
device->data.audio = devicedata;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
case VIR_DOMAIN_DEVICE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefHasDeviceAddressIterator(virDomainDef *def G_GNUC_UNUSED,
|
|
virDomainDeviceDef *dev G_GNUC_UNUSED,
|
|
virDomainDeviceInfo *info,
|
|
void *opaque)
|
|
{
|
|
virDomainDeviceInfo *needle = opaque;
|
|
|
|
/* break iteration if the info was found */
|
|
if (virDomainDeviceInfoAddressIsEqual(info, needle))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainSkipBackcompatConsole(virDomainDef *def,
|
|
size_t idx,
|
|
bool all)
|
|
{
|
|
virDomainChrDef *console = def->consoles[idx];
|
|
|
|
if (!all && idx == 0 &&
|
|
(console->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL ||
|
|
console->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_NONE) &&
|
|
def->os.type == VIR_DOMAIN_OSTYPE_HVM) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Iterates over domain devices calling @cb on each device. The default
|
|
* behaviour can be altered with virDomainDeviceIterateFlags.
|
|
*/
|
|
int
|
|
virDomainDeviceInfoIterateFlags(virDomainDef *def,
|
|
virDomainDeviceInfoCallback cb,
|
|
unsigned int iteratorFlags,
|
|
void *opaque)
|
|
{
|
|
size_t i;
|
|
int rc;
|
|
virDomainDeviceDef device;
|
|
|
|
device.type = VIR_DOMAIN_DEVICE_DISK;
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
device.data.disk = def->disks[i];
|
|
if ((rc = cb(def, &device, &def->disks[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_NET;
|
|
for (i = 0; i < def->nnets; i++) {
|
|
device.data.net = def->nets[i];
|
|
if ((rc = cb(def, &device, &def->nets[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_SOUND;
|
|
for (i = 0; i < def->nsounds; i++) {
|
|
device.data.sound = def->sounds[i];
|
|
if ((rc = cb(def, &device, &def->sounds[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_HOSTDEV;
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
device.data.hostdev = def->hostdevs[i];
|
|
if ((rc = cb(def, &device, def->hostdevs[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_VIDEO;
|
|
for (i = 0; i < def->nvideos; i++) {
|
|
device.data.video = def->videos[i];
|
|
if ((rc = cb(def, &device, &def->videos[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_CONTROLLER;
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
device.data.controller = def->controllers[i];
|
|
if ((rc = cb(def, &device, &def->controllers[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_SMARTCARD;
|
|
for (i = 0; i < def->nsmartcards; i++) {
|
|
device.data.smartcard = def->smartcards[i];
|
|
if ((rc = cb(def, &device, &def->smartcards[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_CHR;
|
|
for (i = 0; i < def->nserials; i++) {
|
|
device.data.chr = def->serials[i];
|
|
if ((rc = cb(def, &device, &def->serials[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
for (i = 0; i < def->nparallels; i++) {
|
|
device.data.chr = def->parallels[i];
|
|
if ((rc = cb(def, &device, &def->parallels[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
for (i = 0; i < def->nchannels; i++) {
|
|
device.data.chr = def->channels[i];
|
|
if ((rc = cb(def, &device, &def->channels[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
for (i = 0; i < def->nconsoles; i++) {
|
|
bool all = iteratorFlags & DOMAIN_DEVICE_ITERATE_ALL_CONSOLES;
|
|
|
|
if (virDomainSkipBackcompatConsole(def, i, all))
|
|
continue;
|
|
device.data.chr = def->consoles[i];
|
|
if ((rc = cb(def, &device, &def->consoles[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_INPUT;
|
|
for (i = 0; i < def->ninputs; i++) {
|
|
device.data.input = def->inputs[i];
|
|
if ((rc = cb(def, &device, &def->inputs[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_FS;
|
|
for (i = 0; i < def->nfss; i++) {
|
|
device.data.fs = def->fss[i];
|
|
if ((rc = cb(def, &device, &def->fss[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
if (def->watchdog) {
|
|
device.type = VIR_DOMAIN_DEVICE_WATCHDOG;
|
|
device.data.watchdog = def->watchdog;
|
|
if ((rc = cb(def, &device, &def->watchdog->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
if (def->memballoon) {
|
|
device.type = VIR_DOMAIN_DEVICE_MEMBALLOON;
|
|
device.data.memballoon = def->memballoon;
|
|
if ((rc = cb(def, &device, &def->memballoon->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_RNG;
|
|
for (i = 0; i < def->nrngs; i++) {
|
|
device.data.rng = def->rngs[i];
|
|
if ((rc = cb(def, &device, &def->rngs[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
if (def->nvram) {
|
|
device.type = VIR_DOMAIN_DEVICE_NVRAM;
|
|
device.data.nvram = def->nvram;
|
|
if ((rc = cb(def, &device, &def->nvram->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_HUB;
|
|
for (i = 0; i < def->nhubs; i++) {
|
|
device.data.hub = def->hubs[i];
|
|
if ((rc = cb(def, &device, &def->hubs[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_SHMEM;
|
|
for (i = 0; i < def->nshmems; i++) {
|
|
device.data.shmem = def->shmems[i];
|
|
if ((rc = cb(def, &device, &def->shmems[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_TPM;
|
|
for (i = 0; i < def->ntpms; i++) {
|
|
device.data.tpm = def->tpms[i];
|
|
if ((rc = cb(def, &device, &def->tpms[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_PANIC;
|
|
for (i = 0; i < def->npanics; i++) {
|
|
device.data.panic = def->panics[i];
|
|
if ((rc = cb(def, &device, &def->panics[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
|
|
device.type = VIR_DOMAIN_DEVICE_MEMORY;
|
|
for (i = 0; i < def->nmems; i++) {
|
|
device.data.memory = def->mems[i];
|
|
if ((rc = cb(def, &device, &def->mems[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
|
|
device.type = VIR_DOMAIN_DEVICE_REDIRDEV;
|
|
for (i = 0; i < def->nredirdevs; i++) {
|
|
device.data.redirdev = def->redirdevs[i];
|
|
if ((rc = cb(def, &device, &def->redirdevs[i]->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
|
|
device.type = VIR_DOMAIN_DEVICE_VSOCK;
|
|
if (def->vsock) {
|
|
device.data.vsock = def->vsock;
|
|
if ((rc = cb(def, &device, &def->vsock->info, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
|
|
/* If the flag below is set, make sure @cb can handle @info being NULL */
|
|
if (iteratorFlags & DOMAIN_DEVICE_ITERATE_MISSING_INFO) {
|
|
device.type = VIR_DOMAIN_DEVICE_GRAPHICS;
|
|
for (i = 0; i < def->ngraphics; i++) {
|
|
device.data.graphics = def->graphics[i];
|
|
if ((rc = cb(def, &device, NULL, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_AUDIO;
|
|
for (i = 0; i < def->naudios; i++) {
|
|
device.data.audio = def->audios[i];
|
|
if ((rc = cb(def, &device, NULL, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_LEASE;
|
|
for (i = 0; i < def->nleases; i++) {
|
|
device.data.lease = def->leases[i];
|
|
if ((rc = cb(def, &device, NULL, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
device.type = VIR_DOMAIN_DEVICE_IOMMU;
|
|
if (def->iommu) {
|
|
device.data.iommu = def->iommu;
|
|
if ((rc = cb(def, &device, NULL, opaque)) != 0)
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Coverity is not very happy with this - all dead_error_condition */
|
|
#if !STATIC_ANALYSIS
|
|
/* This switch statement is here to trigger compiler warning when adding
|
|
* a new device type. When you are adding a new field to the switch you
|
|
* also have to add an iteration statement above. Otherwise the switch
|
|
* statement has no real function here and should be optimized out by the
|
|
* compiler. */
|
|
i = VIR_DOMAIN_DEVICE_LAST;
|
|
switch ((virDomainDeviceType) i) {
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
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_LAST:
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
case VIR_DOMAIN_DEVICE_AUDIO:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDeviceInfoIterate(virDomainDef *def,
|
|
virDomainDeviceInfoCallback cb,
|
|
void *opaque)
|
|
{
|
|
return virDomainDeviceInfoIterateFlags(def, cb, 0, opaque);
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasDeviceAddress(virDomainDef *def,
|
|
virDomainDeviceInfo *info)
|
|
{
|
|
if (virDomainDeviceInfoIterateFlags(def,
|
|
virDomainDefHasDeviceAddressIterator,
|
|
DOMAIN_DEVICE_ITERATE_ALL_CONSOLES,
|
|
info) < 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefRejectDuplicateControllers(virDomainDef *def)
|
|
{
|
|
int max_idx[VIR_DOMAIN_CONTROLLER_TYPE_LAST];
|
|
virBitmap *bitmaps[VIR_DOMAIN_CONTROLLER_TYPE_LAST] = { NULL };
|
|
virDomainControllerDef *cont;
|
|
size_t nbitmaps = 0;
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
memset(max_idx, -1, sizeof(max_idx));
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
cont = def->controllers[i];
|
|
if (cont->idx > max_idx[cont->type])
|
|
max_idx[cont->type] = cont->idx;
|
|
}
|
|
|
|
/* multiple USB controllers with the same index are allowed */
|
|
max_idx[VIR_DOMAIN_CONTROLLER_TYPE_USB] = -1;
|
|
|
|
for (i = 0; i < VIR_DOMAIN_CONTROLLER_TYPE_LAST; i++) {
|
|
if (max_idx[i] >= 0)
|
|
bitmaps[i] = virBitmapNew(max_idx[i] + 1);
|
|
nbitmaps++;
|
|
}
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
cont = def->controllers[i];
|
|
|
|
if (max_idx[cont->type] == -1)
|
|
continue;
|
|
|
|
if (virBitmapIsBitSet(bitmaps[cont->type], cont->idx)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Multiple '%s' controllers with index '%d'"),
|
|
virDomainControllerTypeToString(cont->type),
|
|
cont->idx);
|
|
goto cleanup;
|
|
}
|
|
ignore_value(virBitmapSetBit(bitmaps[cont->type], cont->idx));
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
for (i = 0; i < nbitmaps; i++)
|
|
virBitmapFree(bitmaps[i]);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
virDomainDefRejectDuplicatePanics(virDomainDef *def)
|
|
{
|
|
bool exists[VIR_DOMAIN_PANIC_MODEL_LAST];
|
|
size_t i;
|
|
|
|
for (i = 0; i < VIR_DOMAIN_PANIC_MODEL_LAST; i++)
|
|
exists[i] = false;
|
|
|
|
for (i = 0; i < def->npanics; i++) {
|
|
virDomainPanicModel model = def->panics[i]->model;
|
|
if (exists[model]) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Multiple panic devices with model '%s'"),
|
|
virDomainPanicModelTypeToString(model));
|
|
return -1;
|
|
}
|
|
exists[model] = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefPostParseMemory(virDomainDef *def,
|
|
unsigned int parseFlags)
|
|
{
|
|
size_t i;
|
|
unsigned long long numaMemory = 0;
|
|
unsigned long long hotplugMemory = 0;
|
|
|
|
/* Attempt to infer the initial memory size from the sum NUMA memory sizes
|
|
* in case ABI updates are allowed or the <memory> element wasn't specified */
|
|
if (def->mem.total_memory == 0 ||
|
|
parseFlags & VIR_DOMAIN_DEF_PARSE_ABI_UPDATE ||
|
|
parseFlags & VIR_DOMAIN_DEF_PARSE_ABI_UPDATE_MIGRATION)
|
|
numaMemory = virDomainNumaGetMemorySize(def->numa);
|
|
|
|
/* calculate the sizes of hotplug memory */
|
|
for (i = 0; i < def->nmems; i++)
|
|
hotplugMemory += def->mems[i]->size;
|
|
|
|
if (numaMemory) {
|
|
/* update the sizes in XML if nothing was set in the XML or ABI update
|
|
* is supported */
|
|
virDomainDefSetMemoryTotal(def, numaMemory + hotplugMemory);
|
|
} else {
|
|
/* verify that the sum of memory modules doesn't exceed the total
|
|
* memory. This is necessary for virDomainDefGetMemoryInitial to work
|
|
* properly. */
|
|
if (hotplugMemory > def->mem.total_memory) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Total size of memory devices exceeds the total "
|
|
"memory size"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (virDomainDefGetMemoryInitial(def) == 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Memory size must be specified via <memory> or in the "
|
|
"<numa> configuration"));
|
|
return -1;
|
|
}
|
|
|
|
if (def->mem.cur_balloon > virDomainDefGetMemoryTotal(def) ||
|
|
def->mem.cur_balloon == 0)
|
|
def->mem.cur_balloon = virDomainDefGetMemoryTotal(def);
|
|
|
|
if ((def->mem.max_memory || def->mem.memory_slots) &&
|
|
!(def->mem.max_memory && def->mem.memory_slots)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("both maximum memory size and "
|
|
"memory slot count must be specified"));
|
|
return -1;
|
|
}
|
|
|
|
if (def->mem.max_memory &&
|
|
def->mem.max_memory < virDomainDefGetMemoryTotal(def)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("maximum memory size must be equal or greater than "
|
|
"the actual memory size"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainDefPostParseOs(virDomainDef *def)
|
|
{
|
|
if (!def->os.loader)
|
|
return;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
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) {
|
|
virBitmapFree(def->mem.hugepages[i].nodemask);
|
|
def->mem.hugepages[i].nodemask = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefAddConsoleCompat(virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
/*
|
|
* Some really crazy backcompat stuff for consoles
|
|
*
|
|
* Historically the first (and only) '<console>' element in an HVM guest
|
|
* was treated as being an alias for a <serial> device.
|
|
*
|
|
* So if we see that this console device should be a serial device, then we
|
|
* move the config over to def->serials[0] (or discard it if that already
|
|
* exists). However, given console can already be filled with aliased data
|
|
* of def->serials[0]. Keep it then.
|
|
*
|
|
* We then fill def->consoles[0] with a stub just so we get sequencing
|
|
* correct for consoles > 0
|
|
*/
|
|
|
|
/* Only the first console (if there are any) can be of type serial,
|
|
* verify that no other console is of type serial
|
|
*/
|
|
for (i = 1; i < def->nconsoles; i++) {
|
|
virDomainChrDef *cons = def->consoles[i];
|
|
|
|
if (cons->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Only the first console can be a serial port"));
|
|
return -1;
|
|
}
|
|
}
|
|
if (def->nconsoles > 0 && def->os.type == VIR_DOMAIN_OSTYPE_HVM &&
|
|
(def->consoles[0]->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL ||
|
|
def->consoles[0]->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_NONE)) {
|
|
|
|
/* If there isn't a corresponding serial port:
|
|
* - create one and set, the console to be an alias for it
|
|
*
|
|
* If there is a corresponding serial port:
|
|
* - Check if the source definition is equal:
|
|
* - if yes: leave it as-is
|
|
* - if no: change the console to be alias of the serial port
|
|
*/
|
|
|
|
/* create the serial port definition from the console definition */
|
|
if (def->nserials == 0) {
|
|
if (VIR_APPEND_ELEMENT(def->serials,
|
|
def->nserials,
|
|
def->consoles[0]) < 0)
|
|
return -1;
|
|
|
|
/* modify it to be a serial port */
|
|
def->serials[0]->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL;
|
|
def->serials[0]->targetType = VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE;
|
|
def->serials[0]->target.port = 0;
|
|
} else {
|
|
/* if the console source doesn't match */
|
|
if (!virDomainChrSourceDefIsEqual(def->serials[0]->source,
|
|
def->consoles[0]->source)) {
|
|
virDomainChrDefFree(def->consoles[0]);
|
|
def->consoles[0] = NULL;
|
|
}
|
|
}
|
|
|
|
if (!def->consoles[0]) {
|
|
/* allocate a new console type for the stolen one */
|
|
if (!(def->consoles[0] = virDomainChrDefNew(NULL)))
|
|
return -1;
|
|
|
|
/* Create an console alias for the serial port */
|
|
def->consoles[0]->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE;
|
|
def->consoles[0]->targetType = VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL;
|
|
}
|
|
} else if (def->os.type == VIR_DOMAIN_OSTYPE_HVM && def->nserials > 0 &&
|
|
def->serials[0]->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL) {
|
|
|
|
switch ((virDomainChrSerialTargetType) def->serials[0]->targetType) {
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_ISA:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SPAPR_VIO:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SYSTEM:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_SCLP:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE: {
|
|
|
|
/* Create a stub console to match the serial port.
|
|
* console[0] either does not exist
|
|
* or has a different type than SERIAL or NONE.
|
|
*/
|
|
virDomainChrDef *chr;
|
|
if (!(chr = virDomainChrDefNew(NULL)))
|
|
return -1;
|
|
|
|
if (VIR_INSERT_ELEMENT(def->consoles,
|
|
0,
|
|
def->nconsoles,
|
|
chr) < 0) {
|
|
virDomainChrDefFree(chr);
|
|
return -1;
|
|
}
|
|
|
|
def->consoles[0]->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE;
|
|
def->consoles[0]->targetType = VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL;
|
|
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_PCI:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB:
|
|
case VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_LAST:
|
|
/* Nothing to do */
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
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 != -1) {
|
|
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 != -1) {
|
|
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 != -1) {
|
|
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:
|
|
* @def: domain definition containing the disks to check
|
|
* @bus_type: bus type
|
|
* @addr: address to check for duplicates
|
|
*
|
|
* Return true if any disk is already using the given address on the
|
|
* given bus, false otherwise.
|
|
*/
|
|
bool
|
|
virDomainDriveAddressIsUsedByDisk(const virDomainDef *def,
|
|
virDomainDiskBus bus_type,
|
|
const virDomainDeviceDriveAddress *addr)
|
|
{
|
|
virDomainDiskDef *disk;
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
disk = def->disks[i];
|
|
|
|
if (disk->bus != bus_type ||
|
|
disk->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
|
|
continue;
|
|
|
|
if (disk->info.addr.drive.controller == addr->controller &&
|
|
disk->info.addr.drive.unit == addr->unit &&
|
|
disk->info.addr.drive.bus == addr->bus &&
|
|
disk->info.addr.drive.target == addr->target)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDriveAddressIsUsedByHostdev:
|
|
* @def: domain definition containing the hostdevs to check
|
|
* @type: bus type
|
|
* @addr: address to check for duplicates
|
|
*
|
|
* Return true if any hostdev is already using the given address on the
|
|
* given bus, false otherwise.
|
|
*/
|
|
static bool
|
|
virDomainDriveAddressIsUsedByHostdev(const virDomainDef *def,
|
|
virDomainHostdevSubsysType type,
|
|
const virDomainDeviceDriveAddress *addr)
|
|
{
|
|
virDomainHostdevDef *hostdev;
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
hostdev = def->hostdevs[i];
|
|
|
|
if (hostdev->source.subsys.type != type ||
|
|
hostdev->info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
|
|
continue;
|
|
|
|
if (hostdev->info->addr.drive.controller == addr->controller &&
|
|
hostdev->info->addr.drive.unit == addr->unit &&
|
|
hostdev->info->addr.drive.bus == addr->bus &&
|
|
hostdev->info->addr.drive.target == addr->target)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainSCSIDriveAddressIsUsed:
|
|
* @def: domain definition to check against
|
|
* @addr: address to check for duplicates
|
|
*
|
|
* Return true if the SCSI drive address is already in use, false
|
|
* otherwise.
|
|
*/
|
|
bool
|
|
virDomainSCSIDriveAddressIsUsed(const virDomainDef *def,
|
|
const virDomainDeviceDriveAddress *addr)
|
|
{
|
|
const virDomainControllerDef *cont;
|
|
|
|
cont = virDomainDeviceFindSCSIController(def, addr);
|
|
if (cont) {
|
|
int max = -1;
|
|
int reserved = -1;
|
|
|
|
/* Different controllers have different limits. These limits here are
|
|
* taken from QEMU source code, but nevertheless they should apply to
|
|
* other hypervisors too. */
|
|
switch ((virDomainControllerModelSCSI) cont->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_TRANSITIONAL:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_NON_TRANSITIONAL:
|
|
max = 16383;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_IBMVSCSI:
|
|
max = 31;
|
|
reserved = 7;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSISAS1068:
|
|
max = 1;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSISAS1078:
|
|
max = 255;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC:
|
|
reserved = 7;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VMPVSCSI:
|
|
reserved = 7;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_BUSLOGIC:
|
|
reserved = 7;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_NCR53C90:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_DC390:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_AM53C974:
|
|
max = 6;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_AUTO:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LAST:
|
|
break;
|
|
}
|
|
|
|
if (max != -1 && addr->unit > max)
|
|
return true;
|
|
if (reserved != -1 && addr->unit == reserved)
|
|
return true;
|
|
}
|
|
|
|
if (virDomainDriveAddressIsUsedByDisk(def, VIR_DOMAIN_DISK_BUS_SCSI,
|
|
addr) ||
|
|
virDomainDriveAddressIsUsedByHostdev(def,
|
|
VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI,
|
|
addr))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Find out the next usable "unit" of a specific controller */
|
|
static int
|
|
virDomainControllerSCSINextUnit(const virDomainDef *def,
|
|
unsigned int max_unit,
|
|
unsigned int controller)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < max_unit; i++) {
|
|
/* Default to assigning addresses using bus = target = 0 */
|
|
const virDomainDeviceDriveAddress addr = {controller, 0, 0, i};
|
|
|
|
if (!virDomainSCSIDriveAddressIsUsed(def, &addr))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
#define SCSI_WIDE_BUS_MAX_CONT_UNIT 16
|
|
#define SCSI_NARROW_BUS_MAX_CONT_UNIT 7
|
|
|
|
static void
|
|
virDomainHostdevAssignAddress(virDomainXMLOption *xmlopt,
|
|
const virDomainDef *def,
|
|
virDomainHostdevDef *hostdev)
|
|
{
|
|
int next_unit = 0;
|
|
int controller = 0;
|
|
unsigned int max_unit;
|
|
|
|
if (xmlopt->config.features & VIR_DOMAIN_DEF_FEATURE_WIDE_SCSI)
|
|
max_unit = SCSI_WIDE_BUS_MAX_CONT_UNIT;
|
|
else
|
|
max_unit = SCSI_NARROW_BUS_MAX_CONT_UNIT;
|
|
|
|
/* 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, max_unit, 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 void
|
|
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->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;
|
|
}
|
|
}
|
|
|
|
|
|
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_NONE;
|
|
|
|
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_NONE;
|
|
}
|
|
|
|
if (disk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
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_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:
|
|
virDomainChrDefPostParse(dev->data.chr, def);
|
|
ret = 0;
|
|
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) {
|
|
virBitmapFree(vcpu->cpumask);
|
|
vcpu->cpumask = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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 = NULL;
|
|
|
|
if (!(bootHash = virHashNew(NULL)))
|
|
return -1;
|
|
|
|
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;
|
|
|
|
virDomainDefPostParseOs(def);
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_USB &&
|
|
def->controllers[i]->model != VIR_DOMAIN_CONTROLLER_MODEL_USB_NONE)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefLifecycleActionAllowed(virDomainLifecycle type,
|
|
virDomainLifecycleAction action)
|
|
{
|
|
switch (type) {
|
|
case VIR_DOMAIN_LIFECYCLE_POWEROFF:
|
|
case VIR_DOMAIN_LIFECYCLE_REBOOT:
|
|
switch (action) {
|
|
case VIR_DOMAIN_LIFECYCLE_ACTION_DESTROY:
|
|
case VIR_DOMAIN_LIFECYCLE_ACTION_RESTART:
|
|
case VIR_DOMAIN_LIFECYCLE_ACTION_RESTART_RENAME:
|
|
case VIR_DOMAIN_LIFECYCLE_ACTION_PRESERVE:
|
|
case VIR_DOMAIN_LIFECYCLE_ACTION_LAST:
|
|
return true;
|
|
case VIR_DOMAIN_LIFECYCLE_ACTION_COREDUMP_DESTROY:
|
|
case VIR_DOMAIN_LIFECYCLE_ACTION_COREDUMP_RESTART:
|
|
break;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_LIFECYCLE_CRASH:
|
|
case VIR_DOMAIN_LIFECYCLE_LAST:
|
|
return true;
|
|
}
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Lifecycle event '%s' doesn't support '%s' action"),
|
|
virDomainLifecycleTypeToString(type),
|
|
virDomainLifecycleActionTypeToString(action));
|
|
return false;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainObjCheckActive(virDomainObj *dom)
|
|
{
|
|
if (!virDomainObjIsActive(dom)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
"%s", _("domain is not running"));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDeviceLoadparmIsValid
|
|
* @loadparm : The string to validate
|
|
*
|
|
* The valid set of values for loadparm are [a-zA-Z0-9.]
|
|
* and blank spaces.
|
|
* The maximum allowed length is 8 characters.
|
|
* An empty string is considered invalid
|
|
*/
|
|
static bool
|
|
virDomainDeviceLoadparmIsValid(const char *loadparm)
|
|
{
|
|
size_t i;
|
|
|
|
if (virStringIsEmpty(loadparm)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("loadparm cannot be an empty string"));
|
|
return false;
|
|
}
|
|
|
|
if (strlen(loadparm) > 8) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("loadparm '%s' exceeds 8 characters"), loadparm);
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < strlen(loadparm); i++) {
|
|
uint8_t c = loadparm[i];
|
|
|
|
if (('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') ||
|
|
(c == '.') || (c == ' ')) {
|
|
continue;
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("invalid loadparm char '%c', expecting chars"
|
|
" in set of [a-zA-Z0-9.] and blank spaces"), c);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainVirtioOptionsFormat(virBuffer *buf,
|
|
virDomainVirtioOptions *virtio)
|
|
{
|
|
if (!virtio)
|
|
return;
|
|
|
|
if (virtio->iommu != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(buf, " iommu='%s'",
|
|
virTristateSwitchTypeToString(virtio->iommu));
|
|
}
|
|
if (virtio->ats != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(buf, " ats='%s'",
|
|
virTristateSwitchTypeToString(virtio->ats));
|
|
}
|
|
if (virtio->packed != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(buf, " packed='%s'",
|
|
virTristateSwitchTypeToString(virtio->packed));
|
|
}
|
|
}
|
|
|
|
|
|
static void ATTRIBUTE_NONNULL(2)
|
|
virDomainDeviceInfoFormat(virBuffer *buf,
|
|
virDomainDeviceInfo *info,
|
|
unsigned int flags)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if ((flags & VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT) && info->bootIndex) {
|
|
virBufferAsprintf(buf, "<boot order='%u'", info->bootIndex);
|
|
|
|
if (info->loadparm)
|
|
virBufferAsprintf(buf, " loadparm='%s'", info->loadparm);
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
if (info->alias)
|
|
virBufferAsprintf(buf, "<alias name='%s'/>\n", info->alias);
|
|
|
|
if (info->mastertype == VIR_DOMAIN_CONTROLLER_MASTER_USB) {
|
|
virBufferAsprintf(buf, "<master startport='%d'/>\n",
|
|
info->master.usb.startport);
|
|
}
|
|
|
|
if ((flags & VIR_DOMAIN_DEF_FORMAT_ALLOW_ROM) &&
|
|
(info->romenabled != VIR_TRISTATE_BOOL_ABSENT ||
|
|
info->rombar != VIR_TRISTATE_SWITCH_ABSENT ||
|
|
info->romfile)) {
|
|
|
|
virBufferAddLit(buf, "<rom");
|
|
if (info->romenabled != VIR_TRISTATE_BOOL_ABSENT) {
|
|
const char *romenabled = virTristateBoolTypeToString(info->romenabled);
|
|
|
|
if (romenabled)
|
|
virBufferAsprintf(buf, " enabled='%s'", romenabled);
|
|
}
|
|
if (info->rombar != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
const char *rombar = virTristateSwitchTypeToString(info->rombar);
|
|
|
|
if (rombar)
|
|
virBufferAsprintf(buf, " bar='%s'", rombar);
|
|
}
|
|
if (info->romfile)
|
|
virBufferEscapeString(buf, " file='%s'", info->romfile);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
if (info->acpiIndex != 0)
|
|
virBufferAsprintf(buf, "<acpi index='%u'/>\n", info->acpiIndex);
|
|
|
|
if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE ||
|
|
info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390)
|
|
/* We're done here */
|
|
return;
|
|
|
|
virBufferAsprintf(&attrBuf, " type='%s'",
|
|
virDomainDeviceAddressTypeToString(info->type));
|
|
|
|
switch ((virDomainDeviceAddressType) info->type) {
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI:
|
|
if (!virPCIDeviceAddressIsEmpty(&info->addr.pci)) {
|
|
virBufferAsprintf(&attrBuf, " domain='0x%04x' bus='0x%02x' "
|
|
"slot='0x%02x' function='0x%d'",
|
|
info->addr.pci.domain,
|
|
info->addr.pci.bus,
|
|
info->addr.pci.slot,
|
|
info->addr.pci.function);
|
|
}
|
|
if (info->addr.pci.multi) {
|
|
virBufferAsprintf(&attrBuf, " multifunction='%s'",
|
|
virTristateSwitchTypeToString(info->addr.pci.multi));
|
|
}
|
|
|
|
if (virZPCIDeviceAddressIsPresent(&info->addr.pci.zpci)) {
|
|
virBufferAsprintf(&childBuf,
|
|
"<zpci uid='0x%.4x' fid='0x%.8x'/>\n",
|
|
info->addr.pci.zpci.uid.value,
|
|
info->addr.pci.zpci.fid.value);
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE:
|
|
virBufferAsprintf(&attrBuf, " controller='%d' bus='%d' target='%d' unit='%d'",
|
|
info->addr.drive.controller,
|
|
info->addr.drive.bus,
|
|
info->addr.drive.target,
|
|
info->addr.drive.unit);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL:
|
|
virBufferAsprintf(&attrBuf, " controller='%d' bus='%d' port='%d'",
|
|
info->addr.vioserial.controller,
|
|
info->addr.vioserial.bus,
|
|
info->addr.vioserial.port);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID:
|
|
virBufferAsprintf(&attrBuf, " controller='%d' slot='%d'",
|
|
info->addr.ccid.controller,
|
|
info->addr.ccid.slot);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB:
|
|
virBufferAsprintf(&attrBuf, " bus='%d'", info->addr.usb.bus);
|
|
if (virDomainUSBAddressPortIsValid(info->addr.usb.port)) {
|
|
virBufferAddLit(&attrBuf, " port='");
|
|
virDomainUSBAddressPortFormatBuf(&attrBuf, info->addr.usb.port);
|
|
virBufferAddLit(&attrBuf, "'");
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO:
|
|
if (info->addr.spaprvio.has_reg)
|
|
virBufferAsprintf(&attrBuf, " reg='0x%08llx'", info->addr.spaprvio.reg);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW:
|
|
virBufferAsprintf(&attrBuf, " cssid='0x%x' ssid='0x%x' devno='0x%04x'",
|
|
info->addr.ccw.cssid,
|
|
info->addr.ccw.ssid,
|
|
info->addr.ccw.devno);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO:
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_ISA:
|
|
if (info->addr.isa.iobase > 0)
|
|
virBufferAsprintf(&attrBuf, " iobase='0x%x'", info->addr.isa.iobase);
|
|
if (info->addr.isa.irq > 0)
|
|
virBufferAsprintf(&attrBuf, " irq='0x%x'", info->addr.isa.irq);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM:
|
|
virBufferAsprintf(&attrBuf, " slot='%u'", info->addr.dimm.slot);
|
|
if (info->addr.dimm.base)
|
|
virBufferAsprintf(&attrBuf, " base='0x%llx'", info->addr.dimm.base);
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_UNASSIGNED:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
virXMLFormatElement(buf, "address", &attrBuf, &childBuf);
|
|
}
|
|
|
|
static int
|
|
virDomainDeviceUSBMasterParseXML(xmlNodePtr node,
|
|
virDomainDeviceUSBMaster *master)
|
|
{
|
|
memset(master, 0, sizeof(*master));
|
|
|
|
if (virXMLPropUInt(node, "startport", 10, VIR_XML_PROP_NONE,
|
|
&master->startport) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainDeviceBootParseXML(xmlNodePtr node,
|
|
virDomainDeviceInfo *info)
|
|
{
|
|
g_autofree char *loadparm = NULL;
|
|
|
|
if (virXMLPropUInt(node, "order", 10,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&info->bootIndex) < 0)
|
|
return -1;
|
|
|
|
loadparm = virXMLPropString(node, "loadparm");
|
|
if (loadparm) {
|
|
if (virStringToUpper(&info->loadparm, loadparm) != 1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Failed to convert loadparm '%s' to upper case"),
|
|
loadparm);
|
|
return -1;
|
|
}
|
|
|
|
if (!virDomainDeviceLoadparmIsValid(info->loadparm)) {
|
|
VIR_FREE(info->loadparm);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainDeviceISAAddressParseXML(xmlNodePtr node,
|
|
virDomainDeviceISAAddress *addr)
|
|
{
|
|
memset(addr, 0, sizeof(*addr));
|
|
|
|
if (virXMLPropUInt(node, "iobase", 16, VIR_XML_PROP_NONE,
|
|
&addr->iobase) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(node, "irq", 16, VIR_XML_PROP_NONE, &addr->irq) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDeviceDimmAddressParseXML(xmlNodePtr node,
|
|
virDomainDeviceDimmAddress *addr)
|
|
{
|
|
if (virXMLPropUInt(node, "slot", 10, VIR_XML_PROP_REQUIRED,
|
|
&addr->slot) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropULongLong(node, "base", 16, VIR_XML_PROP_NONE,
|
|
&addr->base) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDeviceAddressParseXML(xmlNodePtr address,
|
|
virDomainDeviceInfo *info)
|
|
{
|
|
g_autofree char *type = virXMLPropString(address, "type");
|
|
|
|
if (type) {
|
|
if ((info->type = virDomainDeviceAddressTypeFromString(type)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown address type '%s'"), type);
|
|
return -1;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("No type specified for device address"));
|
|
return -1;
|
|
}
|
|
|
|
switch ((virDomainDeviceAddressType) info->type) {
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI:
|
|
if (virPCIDeviceAddressParseXML(address, &info->addr.pci) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE:
|
|
if (virDomainDeviceDriveAddressParseXML(address, &info->addr.drive) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL:
|
|
if (virDomainDeviceVirtioSerialAddressParseXML
|
|
(address, &info->addr.vioserial) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID:
|
|
if (virDomainDeviceCcidAddressParseXML(address, &info->addr.ccid) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB:
|
|
if (virDomainDeviceUSBAddressParseXML(address, &info->addr.usb) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO:
|
|
if (virDomainDeviceSpaprVioAddressParseXML(address, &info->addr.spaprvio) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW:
|
|
if (virDomainDeviceCCWAddressParseXML
|
|
(address, &info->addr.ccw) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO:
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_ISA:
|
|
if (virDomainDeviceISAAddressParseXML(address, &info->addr.isa) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390:
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("virtio-s390 bus doesn't have an address"));
|
|
return -1;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM:
|
|
if (virDomainDeviceDimmAddressParseXML(address, &info->addr.dimm) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_UNASSIGNED:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define USER_ALIAS_PREFIX "ua-"
|
|
#define USER_ALIAS_CHARS \
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
|
|
|
|
bool
|
|
virDomainDeviceAliasIsUserAlias(const char *aliasStr)
|
|
{
|
|
return aliasStr && STRPREFIX(aliasStr, USER_ALIAS_PREFIX);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDeviceInfoParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainDeviceInfo *info,
|
|
unsigned int flags)
|
|
{
|
|
xmlNodePtr acpi = NULL;
|
|
xmlNodePtr address = NULL;
|
|
xmlNodePtr master = NULL;
|
|
xmlNodePtr boot = NULL;
|
|
xmlNodePtr rom = NULL;
|
|
int ret = -1;
|
|
g_autofree char *aliasStr = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
virDomainDeviceInfoClear(info);
|
|
ctxt->node = node;
|
|
|
|
if ((aliasStr = virXPathString("string(./alias/@name)", ctxt)))
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) ||
|
|
(xmlopt->config.features & VIR_DOMAIN_DEF_FEATURE_USER_ALIAS &&
|
|
virDomainDeviceAliasIsUserAlias(aliasStr) &&
|
|
strspn(aliasStr, USER_ALIAS_CHARS) == strlen(aliasStr)))
|
|
info->alias = g_steal_pointer(&aliasStr);
|
|
|
|
if ((master = virXPathNode("./master", ctxt))) {
|
|
info->mastertype = VIR_DOMAIN_CONTROLLER_MASTER_USB;
|
|
if (virDomainDeviceUSBMasterParseXML(master, &info->master.usb) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_ALLOW_BOOT &&
|
|
(boot = virXPathNode("./boot", ctxt))) {
|
|
if (virDomainDeviceBootParseXML(boot, info))
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((flags & VIR_DOMAIN_DEF_PARSE_ALLOW_ROM) &&
|
|
(rom = virXPathNode("./rom", ctxt))) {
|
|
if (virXMLPropTristateBool(rom, "enabled", VIR_XML_PROP_NONE,
|
|
&info->romenabled) < 0)
|
|
goto cleanup;
|
|
|
|
if (virXMLPropTristateSwitch(rom, "bar", VIR_XML_PROP_NONE,
|
|
&info->rombar) < 0)
|
|
goto cleanup;
|
|
|
|
info->romfile = virXMLPropString(rom, "file");
|
|
|
|
if (info->romenabled == VIR_TRISTATE_BOOL_NO &&
|
|
(info->rombar != VIR_TRISTATE_SWITCH_ABSENT || info->romfile)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("ROM tuning is not supported when ROM is disabled"));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if ((acpi = virXPathNode("./acpi", ctxt))) {
|
|
if (virXMLPropUInt(acpi, "index", 10, VIR_XML_PROP_NONE,
|
|
&info->acpiIndex) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((address = virXPathNode("./address", ctxt)) &&
|
|
virDomainDeviceAddressParseXML(address, info) < 0)
|
|
goto cleanup;
|
|
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0)
|
|
virDomainDeviceInfoClear(info);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevSubsysUSBDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainHostdevDef *def)
|
|
{
|
|
virDomainHostdevSubsysUSB *usbsrc = &def->source.subsys.u.usb;
|
|
xmlNodePtr vendorNode;
|
|
xmlNodePtr productNode;
|
|
xmlNodePtr addressNode;
|
|
virTristateBool autoAddress;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropEnum(node, "startupPolicy",
|
|
virDomainStartupPolicyTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->startupPolicy) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateBool(node, "autoAddress", VIR_XML_PROP_NONE,
|
|
&autoAddress) < 0)
|
|
return -1;
|
|
usbsrc->autoAddress = autoAddress == VIR_TRISTATE_BOOL_YES;
|
|
|
|
/* Product can validly be 0, so we need some extra help to determine
|
|
* if it is uninitialized */
|
|
vendorNode = virXPathNode("./vendor", ctxt);
|
|
productNode = virXPathNode("./product", ctxt);
|
|
|
|
if (vendorNode) {
|
|
if (virXMLPropUInt(vendorNode, "id", 0,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&usbsrc->vendor) < 0)
|
|
return -1;
|
|
|
|
if (!productNode) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing product"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (productNode) {
|
|
if (virXMLPropUInt(productNode, "id", 0,
|
|
VIR_XML_PROP_REQUIRED, &usbsrc->product) < 0)
|
|
return -1;
|
|
|
|
if (!vendorNode) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing vendor"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((addressNode = virXPathNode("./address", ctxt))) {
|
|
if (virXMLPropUInt(addressNode, "bus", 0,
|
|
VIR_XML_PROP_REQUIRED, &usbsrc->bus) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(addressNode, "device", 0,
|
|
VIR_XML_PROP_REQUIRED, &usbsrc->device) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* The internal XML for host PCI device's original states:
|
|
*
|
|
* <origstates>
|
|
* <unbind/>
|
|
* <removeslot/>
|
|
* <reprobe/>
|
|
* </origstates>
|
|
*/
|
|
static int
|
|
virDomainHostdevSubsysPCIOrigStatesDefParseXML(xmlNodePtr node,
|
|
virDomainHostdevOrigStates *def)
|
|
{
|
|
xmlNodePtr cur;
|
|
cur = node->children;
|
|
|
|
while (cur != NULL) {
|
|
if (cur->type == XML_ELEMENT_NODE) {
|
|
if (virXMLNodeNameEqual(cur, "unbind")) {
|
|
def->states.pci.unbind_from_stub = true;
|
|
} else if (virXMLNodeNameEqual(cur, "removeslot")) {
|
|
def->states.pci.remove_slot = true;
|
|
} else if (virXMLNodeNameEqual(cur, "reprobe")) {
|
|
def->states.pci.reprobe = true;
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported element '%s' of 'origstates'"),
|
|
cur->name);
|
|
return -1;
|
|
}
|
|
}
|
|
cur = cur->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevSubsysPCIDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainHostdevDef *def,
|
|
unsigned int flags)
|
|
{
|
|
g_autofree char *filtering = NULL;
|
|
xmlNodePtr address = NULL;
|
|
xmlNodePtr origstates = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((filtering = virXMLPropString(node, "writeFiltering"))) {
|
|
int val;
|
|
if ((val = virTristateBoolTypeFromString(filtering)) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown pci writeFiltering setting '%s'"),
|
|
filtering);
|
|
return -1;
|
|
}
|
|
def->writeFiltering = val;
|
|
}
|
|
|
|
if ((address = virXPathNode("./address", ctxt)) &&
|
|
virPCIDeviceAddressParseXML(address, &def->source.subsys.u.pci.addr) < 0)
|
|
return -1;
|
|
|
|
if ((flags & VIR_DOMAIN_DEF_PARSE_PCI_ORIG_STATES) &&
|
|
(origstates = virXPathNode("./origstates", ctxt)) &&
|
|
virDomainHostdevSubsysPCIOrigStatesDefParseXML(origstates, &def->origstates) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainStorageNetworkParseHost(xmlNodePtr hostnode,
|
|
virStorageNetHostDef *host)
|
|
{
|
|
int ret = -1;
|
|
g_autofree char *transport = NULL;
|
|
g_autofree char *port = NULL;
|
|
|
|
memset(host, 0, sizeof(*host));
|
|
host->transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
|
|
|
|
/* transport can be tcp (default), unix or rdma. */
|
|
if ((transport = virXMLPropString(hostnode, "transport"))) {
|
|
host->transport = virStorageNetHostTransportTypeFromString(transport);
|
|
if (host->transport < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown protocol transport type '%s'"),
|
|
transport);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
host->socket = virXMLPropString(hostnode, "socket");
|
|
|
|
if (host->transport == VIR_STORAGE_NET_HOST_TRANS_UNIX &&
|
|
host->socket == NULL) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing socket for unix transport"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (host->transport != VIR_STORAGE_NET_HOST_TRANS_UNIX &&
|
|
host->socket != NULL) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("transport '%s' does not support "
|
|
"socket attribute"),
|
|
transport);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (host->transport != VIR_STORAGE_NET_HOST_TRANS_UNIX) {
|
|
if (!(host->name = virXMLPropString(hostnode, "name"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing name for host"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((port = virXMLPropString(hostnode, "port"))) {
|
|
if (virStringParsePort(port, &host->port) < 0)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (ret < 0)
|
|
virStorageNetHostDefClear(host);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainStorageNetworkParseHosts(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virStorageNetHostDef **hosts,
|
|
size_t *nhosts)
|
|
{
|
|
g_autofree xmlNodePtr *hostnodes = NULL;
|
|
ssize_t nhostnodes;
|
|
size_t i;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((nhostnodes = virXPathNodeSet("./host", ctxt, &hostnodes)) <= 0)
|
|
return nhostnodes;
|
|
|
|
*hosts = g_new0(virStorageNetHostDef, nhostnodes);
|
|
*nhosts = nhostnodes;
|
|
|
|
for (i = 0; i < nhostnodes; i++) {
|
|
if (virDomainStorageNetworkParseHost(hostnodes[i], *hosts + i) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainStorageNetworkParseNFS(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virStorageSource *src)
|
|
{
|
|
xmlNodePtr nfsIdentityNode = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt);
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((nfsIdentityNode = virXPathNode("./identity", ctxt))) {
|
|
src->nfs_user = virXMLPropString(nfsIdentityNode, "user");
|
|
src->nfs_group = virXMLPropString(nfsIdentityNode, "group");
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevSubsysSCSIHostDefParseXML(xmlNodePtr sourcenode,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainHostdevSubsysSCSI *scsisrc,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainHostdevSubsysSCSIHost *scsihostsrc = &scsisrc->u.host;
|
|
xmlNodePtr addressnode = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = sourcenode;
|
|
|
|
if (!(addressnode = virXPathNode("./address", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("'address' must be specified for scsi hostdev source"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropUInt(addressnode, "bus", 0, VIR_XML_PROP_REQUIRED,
|
|
&scsihostsrc->bus) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(addressnode, "target", 0, VIR_XML_PROP_REQUIRED,
|
|
&scsihostsrc->target) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropULongLong(addressnode, "unit", 0, VIR_XML_PROP_REQUIRED,
|
|
&scsihostsrc->unit) < 0)
|
|
return -1;
|
|
|
|
if (!(scsihostsrc->adapter = virXPathString("string(./adapter/@name)", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("'adapter' name must be specified for scsi hostdev source"));
|
|
return -1;
|
|
}
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_STATUS &&
|
|
xmlopt && xmlopt->privateData.storageParse) {
|
|
if ((ctxt->node = virXPathNode("./privateData", ctxt))) {
|
|
if (!scsihostsrc->src)
|
|
scsihostsrc->src = virStorageSourceNew();
|
|
if (xmlopt->privateData.storageParse(ctxt, scsihostsrc->src) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevSubsysSCSIiSCSIDefParseXML(xmlNodePtr sourcenode,
|
|
virDomainHostdevSubsysSCSI *def,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
int auth_secret_usage = -1;
|
|
virDomainHostdevSubsysSCSIiSCSI *iscsisrc = &def->u.iscsi;
|
|
g_autoptr(virStorageAuthDef) authdef = NULL;
|
|
xmlNodePtr node;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = sourcenode;
|
|
|
|
/* For the purposes of command line creation, this needs to look
|
|
* like a disk storage source */
|
|
iscsisrc->src = virStorageSourceNew();
|
|
iscsisrc->src->type = VIR_STORAGE_TYPE_NETWORK;
|
|
iscsisrc->src->protocol = VIR_STORAGE_NET_PROTOCOL_ISCSI;
|
|
|
|
if (!(iscsisrc->src->path = virXMLPropString(sourcenode, "name"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing iSCSI hostdev source path name"));
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainStorageNetworkParseHosts(sourcenode, ctxt, &iscsisrc->src->hosts,
|
|
&iscsisrc->src->nhosts) < 0)
|
|
return -1;
|
|
|
|
if (iscsisrc->src->nhosts < 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing the host address for the iSCSI hostdev"));
|
|
return -1;
|
|
}
|
|
if (iscsisrc->src->nhosts > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one source host address may be specified "
|
|
"for the iSCSI hostdev"));
|
|
return -1;
|
|
}
|
|
|
|
if ((node = virXPathNode("./auth", ctxt))) {
|
|
if (!(authdef = virStorageAuthDefParse(node, ctxt)))
|
|
return -1;
|
|
if ((auth_secret_usage = virSecretUsageTypeFromString(authdef->secrettype)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("invalid secret type %s"),
|
|
authdef->secrettype);
|
|
return -1;
|
|
}
|
|
if (auth_secret_usage != VIR_SECRET_USAGE_TYPE_ISCSI) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("hostdev invalid secret type '%s'"),
|
|
authdef->secrettype);
|
|
return -1;
|
|
}
|
|
iscsisrc->src->auth = g_steal_pointer(&authdef);
|
|
}
|
|
|
|
virStorageSourceInitiatorParseXML(ctxt, &iscsisrc->src->initiator);
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_STATUS &&
|
|
xmlopt && xmlopt->privateData.storageParse) {
|
|
if ((ctxt->node = virXPathNode("./privateData", ctxt)) &&
|
|
xmlopt->privateData.storageParse(ctxt, iscsisrc->src) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevSubsysSCSIDefParseXML(xmlNodePtr sourcenode,
|
|
virDomainHostdevSubsysSCSI *scsisrc,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
g_autofree char *protocol = NULL;
|
|
|
|
if ((protocol = virXMLPropString(sourcenode, "protocol"))) {
|
|
scsisrc->protocol =
|
|
virDomainHostdevSubsysSCSIProtocolTypeFromString(protocol);
|
|
if (scsisrc->protocol < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown SCSI subsystem protocol '%s'"),
|
|
protocol);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
switch ((virDomainHostdevSCSIProtocolType) scsisrc->protocol) {
|
|
case VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_NONE:
|
|
return virDomainHostdevSubsysSCSIHostDefParseXML(sourcenode, ctxt, scsisrc,
|
|
flags, xmlopt);
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI:
|
|
return virDomainHostdevSubsysSCSIiSCSIDefParseXML(sourcenode, scsisrc, ctxt,
|
|
flags, xmlopt);
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainHostdevSCSIProtocolType, scsisrc->protocol);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevSubsysSCSIVHostDefParseXML(xmlNodePtr sourcenode,
|
|
virDomainHostdevDef *def)
|
|
{
|
|
virDomainHostdevSubsysSCSIVHost *hostsrc = &def->source.subsys.u.scsi_host;
|
|
g_autofree char *protocol = NULL;
|
|
g_autofree char *wwpn = NULL;
|
|
|
|
if (!(protocol = virXMLPropString(sourcenode, "protocol"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing scsi_host subsystem protocol"));
|
|
return -1;
|
|
}
|
|
|
|
if ((hostsrc->protocol =
|
|
virDomainHostdevSubsysSCSIHostProtocolTypeFromString(protocol)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown scsi_host subsystem protocol '%s'"),
|
|
protocol);
|
|
return -1;
|
|
}
|
|
|
|
switch ((virDomainHostdevSubsysSCSIHostProtocolType) hostsrc->protocol) {
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_HOST_PROTOCOL_TYPE_VHOST:
|
|
if (!(wwpn = virXMLPropString(sourcenode, "wwpn"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing vhost-scsi hostdev source wwpn"));
|
|
return -1;
|
|
}
|
|
|
|
if (!STRPREFIX(wwpn, "naa.") ||
|
|
!virValidateWWN(wwpn + 4)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s", _("malformed 'wwpn' value"));
|
|
return -1;
|
|
}
|
|
hostsrc->wwpn = g_steal_pointer(&wwpn);
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_HOST_PROTOCOL_TYPE_NONE:
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_HOST_PROTOCOL_TYPE_LAST:
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid hostdev protocol '%s'"),
|
|
virDomainHostdevSubsysSCSIHostProtocolTypeToString(hostsrc->protocol));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevSubsysMediatedDevDefParseXML(virDomainHostdevDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
unsigned char uuid[VIR_UUID_BUFLEN] = {0};
|
|
xmlNodePtr node = NULL;
|
|
virDomainHostdevSubsysMediatedDev *mdevsrc = &def->source.subsys.u.mdev;
|
|
g_autofree char *uuidxml = NULL;
|
|
|
|
if (!(node = virXPathNode("./source/address", ctxt))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Missing <address> element"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(uuidxml = virXMLPropString(node, "uuid"))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Missing 'uuid' attribute for element <address>"));
|
|
return -1;
|
|
}
|
|
|
|
if (virUUIDParse(uuidxml, uuid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s",
|
|
_("Cannot parse uuid attribute of element <address>"));
|
|
return -1;
|
|
}
|
|
|
|
virUUIDFormat(uuid, mdevsrc->uuidstr);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevDefParseXMLSubsys(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
const char *type,
|
|
virDomainHostdevDef *def,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
xmlNodePtr sourcenode;
|
|
int backend;
|
|
virDomainHostdevSubsysPCI *pcisrc = &def->source.subsys.u.pci;
|
|
virDomainHostdevSubsysSCSI *scsisrc = &def->source.subsys.u.scsi;
|
|
virDomainHostdevSubsysSCSIVHost *scsihostsrc = &def->source.subsys.u.scsi_host;
|
|
virDomainHostdevSubsysMediatedDev *mdevsrc = &def->source.subsys.u.mdev;
|
|
g_autofree char *managed = NULL;
|
|
g_autofree char *sgio = NULL;
|
|
g_autofree char *rawio = NULL;
|
|
g_autofree char *backendStr = NULL;
|
|
g_autofree char *model = NULL;
|
|
g_autofree char *display = NULL;
|
|
g_autofree char *ramfb = NULL;
|
|
|
|
/* @managed can be read from the xml document - it is always an
|
|
* attribute of the toplevel element, no matter what type of
|
|
* element that might be (pure hostdev, or higher level device
|
|
* (e.g. <interface>) with type='hostdev')
|
|
*/
|
|
if ((managed = virXMLPropString(node, "managed")) != NULL)
|
|
ignore_value(virStringParseYesNo(managed, &def->managed));
|
|
|
|
sgio = virXMLPropString(node, "sgio");
|
|
rawio = virXMLPropString(node, "rawio");
|
|
model = virXMLPropString(node, "model");
|
|
display = virXMLPropString(node, "display");
|
|
ramfb = virXMLPropString(node, "ramfb");
|
|
|
|
/* @type is passed in from the caller rather than read from the
|
|
* xml document, because it is specified in different places for
|
|
* different kinds of defs - it is an attribute of
|
|
* <source>/<address> for an intelligent hostdev (<interface>),
|
|
* but an attribute of the toplevel element for a standard
|
|
* <hostdev>. (the functions we're going to call expect address
|
|
* type to already be known).
|
|
*/
|
|
if (type) {
|
|
if ((def->source.subsys.type
|
|
= virDomainHostdevSubsysTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown host device source address type '%s'"),
|
|
type);
|
|
return -1;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
"%s", _("missing source address type"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(sourcenode = virXPathNode("./source", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing <source> element in hostdev device"));
|
|
return -1;
|
|
}
|
|
|
|
if (def->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB &&
|
|
virXPathBoolean("boolean(./source/@startupPolicy)", ctxt)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Setting startupPolicy is only allowed for USB"
|
|
" devices"));
|
|
return -1;
|
|
}
|
|
|
|
if (sgio) {
|
|
if (def->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("sgio is only supported for scsi host device"));
|
|
return -1;
|
|
}
|
|
|
|
if ((scsisrc->sgio = virDomainDeviceSGIOTypeFromString(sgio)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown sgio mode '%s'"), sgio);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (rawio) {
|
|
if (def->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("rawio is only supported for scsi host device"));
|
|
return -1;
|
|
}
|
|
|
|
if ((scsisrc->rawio = virTristateBoolTypeFromString(rawio)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown hostdev rawio setting '%s'"),
|
|
rawio);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (def->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV &&
|
|
def->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST) {
|
|
if (model) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("'model' attribute in <hostdev> is only supported "
|
|
"when type='%s'"),
|
|
virDomainHostdevSubsysTypeToString(def->source.subsys.type));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST) {
|
|
if (model &&
|
|
((scsihostsrc->model = virDomainHostdevSubsysSCSIVHostModelTypeFromString(model)) < 0)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown hostdev model '%s'"),
|
|
model);
|
|
return -1;
|
|
}
|
|
} else if (def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV) {
|
|
if (!model) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing 'model' attribute in mediated device's "
|
|
"<hostdev> element"));
|
|
return -1;
|
|
}
|
|
|
|
if ((mdevsrc->model = virMediatedDeviceModelTypeFromString(model)) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown hostdev model '%s'"),
|
|
model);
|
|
return -1;
|
|
}
|
|
|
|
if (display &&
|
|
(mdevsrc->display = virTristateSwitchTypeFromString(display)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown value '%s' for <hostdev> attribute "
|
|
"'display'"),
|
|
display);
|
|
return -1;
|
|
}
|
|
|
|
if (ramfb &&
|
|
(mdevsrc->ramfb = virTristateSwitchTypeFromString(ramfb)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown value '%s' for <hostdev> attribute "
|
|
"'ramfb'"),
|
|
ramfb);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
switch (def->source.subsys.type) {
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
|
|
if (virDomainHostdevSubsysPCIDefParseXML(sourcenode, ctxt, def, flags) < 0)
|
|
return -1;
|
|
|
|
backend = VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT;
|
|
if ((backendStr = virXPathString("string(./driver/@name)", ctxt)) &&
|
|
(((backend = virDomainHostdevSubsysPCIBackendTypeFromString(backendStr)) < 0) ||
|
|
backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown PCI device <driver name='%s'/> "
|
|
"has been specified"), backendStr);
|
|
return -1;
|
|
}
|
|
pcisrc->backend = backend;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
|
|
if (virDomainHostdevSubsysUSBDefParseXML(sourcenode, ctxt, def) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI:
|
|
if (virDomainHostdevSubsysSCSIDefParseXML(sourcenode, scsisrc, ctxt, flags, xmlopt) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST:
|
|
if (virDomainHostdevSubsysSCSIVHostDefParseXML(sourcenode, def) < 0)
|
|
return -1;
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV:
|
|
if (virDomainHostdevSubsysMediatedDevDefParseXML(def, ctxt) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
default:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("address type='%s' not supported in hostdev interfaces"),
|
|
virDomainHostdevSubsysTypeToString(def->source.subsys.type));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static virNetDevIPAddr *
|
|
virDomainNetIPParseXML(xmlNodePtr node)
|
|
{
|
|
/* Parse the prefix in every case */
|
|
unsigned int prefixValue = 0;
|
|
int family = AF_UNSPEC;
|
|
g_autofree virNetDevIPAddr *ip = NULL;
|
|
g_autofree char *prefixStr = NULL;
|
|
g_autofree char *familyStr = NULL;
|
|
g_autofree char *address = NULL;
|
|
g_autofree char *peer = NULL;
|
|
|
|
if (!(address = virXMLPropString(node, "address"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing required address in <ip>"));
|
|
return NULL;
|
|
}
|
|
|
|
familyStr = virXMLPropString(node, "family");
|
|
if (familyStr && STREQ(familyStr, "ipv4"))
|
|
family = AF_INET;
|
|
else if (familyStr && STREQ(familyStr, "ipv6"))
|
|
family = AF_INET6;
|
|
else
|
|
family = virSocketAddrNumericFamily(address);
|
|
|
|
ip = g_new0(virNetDevIPAddr, 1);
|
|
|
|
if (virSocketAddrParse(&ip->address, address, family) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid address '%s' in <ip>"),
|
|
address);
|
|
return NULL;
|
|
}
|
|
|
|
prefixStr = virXMLPropString(node, "prefix");
|
|
if (prefixStr &&
|
|
((virStrToLong_ui(prefixStr, NULL, 10, &prefixValue) < 0) ||
|
|
(family == AF_INET6 && prefixValue > 128) ||
|
|
(family == AF_INET && prefixValue > 32))) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid prefix value '%s' in <ip>"),
|
|
prefixStr);
|
|
return NULL;
|
|
}
|
|
ip->prefix = prefixValue;
|
|
|
|
if ((peer = virXMLPropString(node, "peer")) != NULL &&
|
|
virSocketAddrParse(&ip->peer, peer, family) < 0) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("Invalid peer '%s' in <ip>"), peer);
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&ip);
|
|
}
|
|
|
|
|
|
/* fill in a virNetDevIPInfo *from the <route> and <ip>
|
|
* elements found in the given XML context.
|
|
*
|
|
* return 0 on success (including none found) and -1 on failure.
|
|
*/
|
|
static int
|
|
virDomainNetIPInfoParseXML(const char *source,
|
|
xmlXPathContextPtr ctxt,
|
|
virNetDevIPInfo *def)
|
|
{
|
|
g_autoptr(virNetDevIPRoute) route = NULL;
|
|
int nnodes;
|
|
int ret = -1;
|
|
size_t i;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree virNetDevIPAddr *ip = NULL;
|
|
|
|
if ((nnodes = virXPathNodeSet("./ip", ctxt, &nodes)) < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < nnodes; i++) {
|
|
if (!(ip = virDomainNetIPParseXML(nodes[i])) ||
|
|
VIR_APPEND_ELEMENT(def->ips, def->nips, ip) < 0)
|
|
goto cleanup;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((nnodes = virXPathNodeSet("./route", ctxt, &nodes)) < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < nnodes; i++) {
|
|
if (!(route = virNetDevIPRouteParseXML(source, nodes[i], ctxt)) ||
|
|
VIR_APPEND_ELEMENT(def->routes, def->nroutes, route) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0)
|
|
virNetDevIPInfoClear(def);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainNetDefCoalesceParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virNetDevCoalesce **coalesce)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
unsigned long long tmp = 0;
|
|
g_autofree char *str = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
str = virXPathString("string(./rx/frames/@max)", ctxt);
|
|
if (!str)
|
|
return 0;
|
|
|
|
if (virStrToLong_ullp(str, NULL, 10, &tmp) < 0) {
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
_("cannot parse value '%s' for coalesce parameter"),
|
|
str);
|
|
return -1;
|
|
}
|
|
|
|
if (tmp > UINT32_MAX) {
|
|
virReportError(VIR_ERR_OVERFLOW,
|
|
_("value '%llu' is too big for coalesce "
|
|
"parameter, maximum is '%lu'"),
|
|
tmp, (unsigned long) UINT32_MAX);
|
|
return -1;
|
|
}
|
|
|
|
*coalesce = g_new0(virNetDevCoalesce, 1);
|
|
(*coalesce)->rx_max_coalesced_frames = tmp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virDomainNetDefCoalesceFormatXML(virBuffer *buf,
|
|
virNetDevCoalesce *coalesce)
|
|
{
|
|
if (!coalesce || !coalesce->rx_max_coalesced_frames)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "<coalesce>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
virBufferAddLit(buf, "<rx>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
virBufferAsprintf(buf, "<frames max='%u'/>\n",
|
|
coalesce->rx_max_coalesced_frames);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</rx>\n");
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</coalesce>\n");
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevDefParseXMLCaps(xmlNodePtr node G_GNUC_UNUSED,
|
|
xmlXPathContextPtr ctxt,
|
|
const char *type,
|
|
virDomainHostdevDef *def)
|
|
{
|
|
/* @type is passed in from the caller rather than read from the
|
|
* xml document, because it is specified in different places for
|
|
* different kinds of defs - it is an attribute of
|
|
* <source>/<address> for an intelligent hostdev (<interface>),
|
|
* but an attribute of the toplevel element for a standard
|
|
* <hostdev>. (the functions we're going to call expect address
|
|
* type to already be known).
|
|
*/
|
|
if (type) {
|
|
if ((def->source.caps.type
|
|
= virDomainHostdevCapsTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown host device source address type '%s'"),
|
|
type);
|
|
return -1;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
"%s", _("missing source address type"));
|
|
return -1;
|
|
}
|
|
|
|
if (!virXPathNode("./source", ctxt)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing <source> element in hostdev device"));
|
|
return -1;
|
|
}
|
|
|
|
switch (def->source.caps.type) {
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_STORAGE:
|
|
if (!(def->source.caps.u.storage.block =
|
|
virXPathString("string(./source/block[1])", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing <block> element in hostdev storage device"));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_MISC:
|
|
if (!(def->source.caps.u.misc.chardev =
|
|
virXPathString("string(./source/char[1])", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing <char> element in hostdev character device"));
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET:
|
|
if (!(def->source.caps.u.net.ifname =
|
|
virXPathString("string(./source/interface[1])", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing <interface> element in hostdev net device"));
|
|
return -1;
|
|
}
|
|
if (virDomainNetIPInfoParseXML(_("Domain hostdev device"),
|
|
ctxt, &def->source.caps.u.net.ip) < 0)
|
|
return -1;
|
|
break;
|
|
default:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("address type='%s' not supported in hostdev interfaces"),
|
|
virDomainHostdevCapsTypeToString(def->source.caps.type));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
virDomainControllerDef *
|
|
virDomainDeviceFindSCSIController(const virDomainDef *def,
|
|
const virDomainDeviceDriveAddress *addr)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI &&
|
|
def->controllers[i]->idx == addr->controller)
|
|
return def->controllers[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
virDomainDiskDefAssignAddress(virDomainXMLOption *xmlopt,
|
|
virDomainDiskDef *def,
|
|
const virDomainDef *vmdef)
|
|
{
|
|
int idx = virDiskNameToIndex(def->dst);
|
|
if (idx < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Unknown disk name '%s' and no address specified"),
|
|
def->dst);
|
|
return -1;
|
|
}
|
|
|
|
switch (def->bus) {
|
|
case VIR_DOMAIN_DISK_BUS_SCSI: {
|
|
virDomainDeviceDriveAddress addr = {0, 0, 0, 0};
|
|
unsigned int controller;
|
|
unsigned int unit;
|
|
|
|
def->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
|
|
|
|
if (xmlopt->config.features & VIR_DOMAIN_DEF_FEATURE_WIDE_SCSI) {
|
|
/* For a wide SCSI bus we define the default mapping to be
|
|
* 16 units per bus, 1 bus per controller, many controllers.
|
|
* Unit 7 is the SCSI controller itself. Therefore unit 7
|
|
* cannot be assigned to disks and is skipped.
|
|
*/
|
|
controller = idx / 15;
|
|
unit = idx % 15;
|
|
|
|
/* Skip the SCSI controller at unit 7 */
|
|
if (unit >= 7)
|
|
++unit;
|
|
} else {
|
|
/* For a narrow SCSI bus we define the default mapping to be
|
|
* 7 units per bus, 1 bus per controller, many controllers */
|
|
controller = idx / 7;
|
|
unit = idx % 7;
|
|
}
|
|
|
|
addr.controller = controller;
|
|
addr.unit = unit;
|
|
|
|
if (virDomainDriveAddressIsUsedByHostdev(vmdef,
|
|
VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI,
|
|
&addr)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("using disk target name '%s' conflicts with "
|
|
"SCSI host device address controller='%u' "
|
|
"bus='%u' target='%u' unit='%u"),
|
|
def->dst, controller, 0, 0, unit);
|
|
return -1;
|
|
}
|
|
|
|
memcpy(&def->info.addr.drive, &addr, sizeof(addr));
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_DISK_BUS_IDE:
|
|
/* For IDE we define the default mapping to be 2 units
|
|
* per bus, 2 bus per controller, many controllers */
|
|
def->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
|
|
def->info.addr.drive.controller = idx / 4;
|
|
def->info.addr.drive.bus = (idx % 4) / 2;
|
|
def->info.addr.drive.unit = (idx % 2);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DISK_BUS_SATA:
|
|
/* For SATA we define the default mapping to be 6 units
|
|
* per bus, 1 bus per controller, many controllers */
|
|
def->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
|
|
def->info.addr.drive.controller = idx / 6;
|
|
def->info.addr.drive.bus = 0;
|
|
def->info.addr.drive.unit = idx % 6;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DISK_BUS_FDC:
|
|
/* For FDC we define the default mapping to be 2 units
|
|
* per bus, 1 bus per controller, many controllers */
|
|
def->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE;
|
|
def->info.addr.drive.controller = idx / 2;
|
|
def->info.addr.drive.bus = 0;
|
|
def->info.addr.drive.unit = idx % 2;
|
|
break;
|
|
|
|
case VIR_DOMAIN_DISK_BUS_NONE:
|
|
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:
|
|
default:
|
|
/* Other disk bus's aren't controller based */
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static virSecurityLabelDef *
|
|
virSecurityLabelDefParseXML(xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
char *p;
|
|
virSecurityLabelDef *seclabel = NULL;
|
|
|
|
p = virXMLPropStringLimit(ctxt->node, "model",
|
|
VIR_SECURITY_MODEL_BUFLEN - 1);
|
|
|
|
if (!(seclabel = virSecurityLabelDefNew(p)))
|
|
goto error;
|
|
VIR_FREE(p);
|
|
|
|
/* set default value */
|
|
seclabel->type = VIR_DOMAIN_SECLABEL_DYNAMIC;
|
|
|
|
p = virXMLPropString(ctxt->node, "type");
|
|
if (p) {
|
|
seclabel->type = virDomainSeclabelTypeFromString(p);
|
|
if (seclabel->type <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("invalid security type '%s'"), p);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (seclabel->type == VIR_DOMAIN_SECLABEL_STATIC ||
|
|
seclabel->type == VIR_DOMAIN_SECLABEL_NONE)
|
|
seclabel->relabel = false;
|
|
|
|
VIR_FREE(p);
|
|
p = virXMLPropString(ctxt->node, "relabel");
|
|
if (p) {
|
|
if (virStringParseYesNo(p, &seclabel->relabel) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid security relabel value %s"), p);
|
|
goto error;
|
|
}
|
|
}
|
|
VIR_FREE(p);
|
|
|
|
if (seclabel->type == VIR_DOMAIN_SECLABEL_DYNAMIC &&
|
|
!seclabel->relabel) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("dynamic label type must use resource relabeling"));
|
|
goto error;
|
|
}
|
|
if (seclabel->type == VIR_DOMAIN_SECLABEL_NONE &&
|
|
seclabel->relabel) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("resource relabeling is not compatible with 'none' label type"));
|
|
goto error;
|
|
}
|
|
|
|
/* For the model 'none' none of the following labels is going to be
|
|
* present. Hence, return now. */
|
|
|
|
if (STREQ_NULLABLE(seclabel->model, "none")) {
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) {
|
|
/* Fix older configurations */
|
|
seclabel->type = VIR_DOMAIN_SECLABEL_NONE;
|
|
seclabel->relabel = false;
|
|
} else {
|
|
if (seclabel->type != VIR_DOMAIN_SECLABEL_NONE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported type='%s' to model 'none'"),
|
|
virDomainSeclabelTypeToString(seclabel->type));
|
|
goto error;
|
|
}
|
|
/* combination of relabel='yes' and type='static'
|
|
* is checked a few lines above. */
|
|
}
|
|
return seclabel;
|
|
}
|
|
|
|
/* Only parse label, if using static labels, or
|
|
* if the 'live' VM XML is requested
|
|
*/
|
|
if (seclabel->type == VIR_DOMAIN_SECLABEL_STATIC ||
|
|
(!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) &&
|
|
seclabel->type != VIR_DOMAIN_SECLABEL_NONE)) {
|
|
p = virXPathStringLimit("string(./label[1])",
|
|
VIR_SECURITY_LABEL_BUFLEN-1, ctxt);
|
|
if (p == NULL) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
"%s", _("security label is missing"));
|
|
goto error;
|
|
}
|
|
|
|
seclabel->label = g_steal_pointer(&p);
|
|
}
|
|
|
|
/* Only parse imagelabel, if requested live XML with relabeling */
|
|
if (seclabel->relabel &&
|
|
(!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) &&
|
|
seclabel->type != VIR_DOMAIN_SECLABEL_NONE)) {
|
|
p = virXPathStringLimit("string(./imagelabel[1])",
|
|
VIR_SECURITY_LABEL_BUFLEN-1, ctxt);
|
|
if (p == NULL) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
"%s", _("security imagelabel is missing"));
|
|
goto error;
|
|
}
|
|
seclabel->imagelabel = g_steal_pointer(&p);
|
|
}
|
|
|
|
/* Only parse baselabel for dynamic label type */
|
|
if (seclabel->type == VIR_DOMAIN_SECLABEL_DYNAMIC) {
|
|
p = virXPathStringLimit("string(./baselabel[1])",
|
|
VIR_SECURITY_LABEL_BUFLEN-1, ctxt);
|
|
seclabel->baselabel = g_steal_pointer(&p);
|
|
}
|
|
|
|
return seclabel;
|
|
|
|
error:
|
|
VIR_FREE(p);
|
|
virSecurityLabelDefFree(seclabel);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
virSecurityLabelDefsParseXML(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
size_t i = 0, j;
|
|
int n;
|
|
g_autofree xmlNodePtr *list = NULL;
|
|
|
|
/* Allocate a security labels based on XML */
|
|
if ((n = virXPathNodeSet("./seclabel", ctxt, &list)) < 0)
|
|
goto error;
|
|
if (n == 0)
|
|
return 0;
|
|
|
|
def->seclabels = g_new0(virSecurityLabelDef *, n);
|
|
|
|
/* Parse each "seclabel" tag */
|
|
for (i = 0; i < n; i++) {
|
|
virSecurityLabelDef *seclabel;
|
|
|
|
ctxt->node = list[i];
|
|
if (!(seclabel = virSecurityLabelDefParseXML(ctxt, flags)))
|
|
goto error;
|
|
|
|
for (j = 0; j < i; j++) {
|
|
if (STREQ_NULLABLE(seclabel->model, def->seclabels[j]->model)) {
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
_("seclabel for model %s is already provided"),
|
|
seclabel->model);
|
|
virSecurityLabelDefFree(seclabel);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
def->seclabels[i] = seclabel;
|
|
}
|
|
def->nseclabels = n;
|
|
|
|
/* libvirt versions prior to 0.10.0 support just a single seclabel element
|
|
* in guest's XML and model attribute can be suppressed if type is none or
|
|
* type is dynamic, baselabel is not defined and INACTIVE flag is set.
|
|
*
|
|
* To avoid compatibility issues, for this specific case the first model
|
|
* defined in host's capabilities is used as model for the seclabel.
|
|
*/
|
|
if (def->nseclabels == 1 &&
|
|
!def->seclabels[0]->model &&
|
|
xmlopt != NULL &&
|
|
xmlopt->config.defSecModel != NULL) {
|
|
if (def->seclabels[0]->type == VIR_DOMAIN_SECLABEL_NONE ||
|
|
(def->seclabels[0]->type == VIR_DOMAIN_SECLABEL_DYNAMIC &&
|
|
!def->seclabels[0]->baselabel &&
|
|
(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))) {
|
|
/* Copy model from host. */
|
|
VIR_DEBUG("Found seclabel without a model, using '%s'",
|
|
xmlopt->config.defSecModel);
|
|
def->seclabels[0]->model = g_strdup(xmlopt->config.defSecModel);
|
|
|
|
if (STREQ(def->seclabels[0]->model, "none") &&
|
|
flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) {
|
|
/* Fix older configurations */
|
|
def->seclabels[0]->type = VIR_DOMAIN_SECLABEL_NONE;
|
|
def->seclabels[0]->relabel = false;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing security model in domain seclabel"));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Checking missing model information */
|
|
if (def->nseclabels > 1) {
|
|
for (; n; n--) {
|
|
if (def->seclabels[n - 1]->model == NULL) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing security model "
|
|
"when using multiple labels"));
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
for (; i > 0; i--)
|
|
virSecurityLabelDefFree(def->seclabels[i - 1]);
|
|
VIR_FREE(def->seclabels);
|
|
def->nseclabels = 0;
|
|
return -1;
|
|
}
|
|
|
|
/* Parse the <seclabel> from a disk or character device. */
|
|
static int
|
|
virSecurityDeviceLabelDefParseXML(virSecurityDeviceLabelDef ***seclabels_rtn,
|
|
size_t *nseclabels_rtn,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virSecurityDeviceLabelDef **seclabels = NULL;
|
|
size_t nseclabels = 0;
|
|
int n;
|
|
size_t i, j;
|
|
char *model, *relabel, *label, *labelskip;
|
|
g_autofree xmlNodePtr *list = NULL;
|
|
|
|
if ((n = virXPathNodeSet("./seclabel", ctxt, &list)) < 0)
|
|
goto error;
|
|
if (n == 0)
|
|
return 0;
|
|
|
|
seclabels = g_new0(virSecurityDeviceLabelDef *, n);
|
|
nseclabels = n;
|
|
for (i = 0; i < n; i++)
|
|
seclabels[i] = g_new0(virSecurityDeviceLabelDef, 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
/* get model associated to this override */
|
|
model = virXMLPropString(list[i], "model");
|
|
if (model) {
|
|
/* check for duplicate seclabels */
|
|
for (j = 0; j < i; j++) {
|
|
if (STREQ_NULLABLE(model, seclabels[j]->model)) {
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
_("seclabel for model %s is already provided"), model);
|
|
goto error;
|
|
}
|
|
}
|
|
seclabels[i]->model = model;
|
|
}
|
|
|
|
relabel = virXMLPropString(list[i], "relabel");
|
|
if (relabel != NULL) {
|
|
if (virStringParseYesNo(relabel, &seclabels[i]->relabel) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid security relabel value %s"),
|
|
relabel);
|
|
VIR_FREE(relabel);
|
|
goto error;
|
|
}
|
|
VIR_FREE(relabel);
|
|
} else {
|
|
seclabels[i]->relabel = true;
|
|
}
|
|
|
|
/* labelskip is only parsed on live images */
|
|
labelskip = virXMLPropString(list[i], "labelskip");
|
|
seclabels[i]->labelskip = false;
|
|
if (labelskip && !(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))
|
|
ignore_value(virStringParseYesNo(labelskip, &seclabels[i]->labelskip));
|
|
VIR_FREE(labelskip);
|
|
|
|
ctxt->node = list[i];
|
|
label = virXPathStringLimit("string(./label)",
|
|
VIR_SECURITY_LABEL_BUFLEN-1, ctxt);
|
|
seclabels[i]->label = label;
|
|
|
|
if (label && !seclabels[i]->relabel) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Cannot specify a label if relabelling is "
|
|
"turned off. model=%s"),
|
|
NULLSTR(seclabels[i]->model));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
*nseclabels_rtn = nseclabels;
|
|
*seclabels_rtn = seclabels;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
for (i = 0; i < nseclabels; i++)
|
|
virSecurityDeviceLabelDefFree(seclabels[i]);
|
|
VIR_FREE(seclabels);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* Parse the XML definition for a lease
|
|
*/
|
|
static virDomainLeaseDef *
|
|
virDomainLeaseDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
virDomainLeaseDef *def;
|
|
g_autofree char *lockspace = NULL;
|
|
g_autofree char *key = NULL;
|
|
g_autofree char *path = NULL;
|
|
xmlNodePtr targetNode = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
def = g_new0(virDomainLeaseDef, 1);
|
|
|
|
if (!(key = virXPathString("string(./key)", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing 'key' element for lease"));
|
|
goto error;
|
|
}
|
|
|
|
if (!(lockspace = virXPathString("string(./lockspace)", ctxt)))
|
|
goto error;
|
|
|
|
if (!(targetNode = virXPathNode("./target", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing 'target' element for lease"));
|
|
goto error;
|
|
}
|
|
|
|
if (!(path = virXMLPropString(targetNode, "path"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing 'path' attribute to 'target' element for lease"));
|
|
goto error;
|
|
}
|
|
|
|
if (virXMLPropULongLong(targetNode, "offset", 10,
|
|
VIR_XML_PROP_NONE, &def->offset) < 0)
|
|
goto error;
|
|
|
|
def->key = g_steal_pointer(&key);
|
|
def->lockspace = g_steal_pointer(&lockspace);
|
|
def->path = g_steal_pointer(&path);
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainLeaseDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
virDomainDiskSourcePoolDefParse(xmlNodePtr node,
|
|
virStorageSourcePoolDef **srcpool)
|
|
{
|
|
virStorageSourcePoolDef *source;
|
|
int ret = -1;
|
|
g_autofree char *mode = NULL;
|
|
|
|
*srcpool = NULL;
|
|
|
|
source = g_new0(virStorageSourcePoolDef, 1);
|
|
|
|
source->pool = virXMLPropString(node, "pool");
|
|
source->volume = virXMLPropString(node, "volume");
|
|
mode = virXMLPropString(node, "mode");
|
|
|
|
/* CD-ROM and Floppy allows no source */
|
|
if (!source->pool && !source->volume) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!source->pool || !source->volume) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("'pool' and 'volume' must be specified together "
|
|
"for 'pool' type source"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (mode &&
|
|
(source->mode = virStorageSourcePoolModeTypeFromString(mode)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown source mode '%s' for volume type disk"),
|
|
mode);
|
|
goto cleanup;
|
|
}
|
|
|
|
*srcpool = g_steal_pointer(&source);
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virStorageSourcePoolDefFree(source);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virStorageNetCookieDef *
|
|
virDomainStorageNetCookieParse(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autoptr(virStorageNetCookieDef) cookie = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
cookie = g_new0(virStorageNetCookieDef, 1);
|
|
|
|
if (!(cookie->name = virXPathString("string(./@name)", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s", _("missing cookie name"));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(cookie->value = virXPathString("string(.)", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, _("missing value for cookie '%s'"),
|
|
cookie->name);
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&cookie);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainStorageNetCookiesParse(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virStorageSource *src)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
ssize_t nnodes;
|
|
size_t i;
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((nnodes = virXPathNodeSet("./cookie", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
src->cookies = g_new0(virStorageNetCookieDef *, nnodes);
|
|
src->ncookies = nnodes;
|
|
|
|
for (i = 0; i < nnodes; i++) {
|
|
if (!(src->cookies[i] = virDomainStorageNetCookieParse(nodes[i], ctxt)))
|
|
return -1;
|
|
}
|
|
|
|
if (virStorageSourceNetCookiesValidate(src) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskSourceNetworkParse(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virStorageSource *src,
|
|
unsigned int flags)
|
|
{
|
|
virStorageNetProtocol protocol;
|
|
xmlNodePtr tmpnode;
|
|
|
|
if (virXMLPropEnum(node, "protocol", virStorageNetProtocolTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &protocol) < 0)
|
|
return -1;
|
|
|
|
src->protocol = protocol;
|
|
|
|
if (!(src->path = virXMLPropString(node, "name")) &&
|
|
src->protocol != VIR_STORAGE_NET_PROTOCOL_NBD) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing name for disk source"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropTristateBool(node, "tls", VIR_XML_PROP_NONE,
|
|
&src->haveTLS) < 0)
|
|
return -1;
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_STATUS) {
|
|
int value;
|
|
if (virXMLPropInt(node, "tlsFromConfig", 10, VIR_XML_PROP_NONE,
|
|
&value, 0) < 0)
|
|
return -1;
|
|
src->tlsFromConfig = !!value;
|
|
}
|
|
|
|
/* for historical reasons we store the volume and image name in one XML
|
|
* element although it complicates thing when attempting to access them. */
|
|
if (src->path &&
|
|
(src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER ||
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD)) {
|
|
char *tmp;
|
|
if (!(tmp = strchr(src->path, '/')) ||
|
|
tmp == src->path) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("can't split path '%s' into pool name and image "
|
|
"name"), src->path);
|
|
return -1;
|
|
}
|
|
|
|
src->volume = src->path;
|
|
|
|
src->path = g_strdup(tmp + 1);
|
|
|
|
tmp[0] = '\0';
|
|
}
|
|
|
|
/* snapshot currently works only for remote disks */
|
|
src->snapshot = virXPathString("string(./snapshot/@name)", ctxt);
|
|
|
|
/* config file currently only works with remote disks */
|
|
src->configFile = virXPathString("string(./config/@file)", ctxt);
|
|
|
|
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS)
|
|
src->query = virXMLPropString(node, "query");
|
|
|
|
if (virDomainStorageNetworkParseHosts(node, ctxt, &src->hosts, &src->nhosts) < 0)
|
|
return -1;
|
|
|
|
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_NFS)
|
|
virDomainStorageNetworkParseNFS(node, ctxt, src);
|
|
|
|
virStorageSourceNetworkAssignDefaultPorts(src);
|
|
|
|
virStorageSourceInitiatorParseXML(ctxt, &src->initiator);
|
|
|
|
if ((src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_FTPS) &&
|
|
(tmpnode = virXPathNode("./ssl", ctxt))) {
|
|
if (virXMLPropTristateBool(tmpnode, "verify", VIR_XML_PROP_NONE,
|
|
&src->sslverify) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if ((src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS) &&
|
|
(tmpnode = virXPathNode("./cookies", ctxt))) {
|
|
if (virDomainStorageNetCookiesParse(tmpnode, ctxt, src) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_FTP ||
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_FTPS) {
|
|
|
|
if (virXPathULongLong("string(./readahead/@size)", ctxt, &src->readahead) == -2 ||
|
|
virXPathULongLong("string(./timeout/@seconds)", ctxt, &src->timeout) == -2) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid readahead size or timeout"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskSourceNVMeParse(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virStorageSource *src)
|
|
{
|
|
g_autoptr(virStorageSourceNVMeDef) nvme = NULL;
|
|
g_autofree char *type = NULL;
|
|
g_autofree char *namespc = NULL;
|
|
xmlNodePtr address;
|
|
|
|
nvme = g_new0(virStorageSourceNVMeDef, 1);
|
|
|
|
if (!(type = virXMLPropString(node, "type"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing 'type' attribute to disk source"));
|
|
return -1;
|
|
}
|
|
|
|
if (STRNEQ(type, "pci")) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unsupported source type '%s'"),
|
|
type);
|
|
return -1;
|
|
}
|
|
|
|
if (!(namespc = virXMLPropString(node, "namespace"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing 'namespace' attribute to disk source"));
|
|
return -1;
|
|
}
|
|
|
|
if (virStrToLong_ull(namespc, NULL, 10, &nvme->namespc) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("malformed namespace '%s'"),
|
|
namespc);
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropTristateBool(node, "managed", VIR_XML_PROP_NONE,
|
|
&nvme->managed) < 0)
|
|
return -1;
|
|
|
|
if (!(address = virXPathNode("./address", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("NVMe disk source is missing address"));
|
|
return -1;
|
|
}
|
|
|
|
if (virPCIDeviceAddressParseXML(address, &nvme->pciAddr) < 0)
|
|
return -1;
|
|
|
|
src->nvme = g_steal_pointer(&nvme);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskSourceVHostUserParse(xmlNodePtr node,
|
|
virStorageSource *src,
|
|
virDomainXMLOption *xmlopt,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree char *type = virXMLPropString(node, "type");
|
|
g_autofree char *path = virXMLPropString(node, "path");
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing 'type' attribute for vhostuser disk source"));
|
|
return -1;
|
|
}
|
|
|
|
if (STRNEQ(type, "unix")) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid 'type' attribute for vhostuser disk source"));
|
|
return -1;
|
|
}
|
|
|
|
if (!path) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing 'path' attribute for vhostuser disk source"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(src->vhostuser = virDomainChrSourceDefNew(xmlopt)))
|
|
return -1;
|
|
|
|
src->vhostuser->type = virDomainChrTypeFromString(type);
|
|
src->vhostuser->data.nix.path = g_steal_pointer(&path);
|
|
|
|
if (virDomainChrSourceReconnectDefParseXML(&src->vhostuser->data.nix.reconnect,
|
|
node,
|
|
ctxt) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskSourcePRParse(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virStoragePRDef **pr)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(ctxt->node = virXPathNode("./reservations", ctxt)))
|
|
return 0;
|
|
|
|
if (!(*pr = virStoragePRDefParseXML(ctxt)))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
virStorageSource *
|
|
virDomainStorageSourceParseBase(const char *type,
|
|
const char *format,
|
|
const char *index)
|
|
{
|
|
g_autoptr(virStorageSource) src = NULL;
|
|
|
|
src = virStorageSourceNew();
|
|
src->type = VIR_STORAGE_TYPE_FILE;
|
|
|
|
if (type &&
|
|
(src->type = virStorageTypeFromString(type)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown storage source type '%s'"), type);
|
|
return NULL;
|
|
}
|
|
|
|
if (format &&
|
|
(src->format = virStorageFileFormatTypeFromString(format)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown storage source format '%s'"), format);
|
|
return NULL;
|
|
}
|
|
|
|
if (index &&
|
|
virStrToLong_uip(index, NULL, 10, &src->id) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid storage source index '%s'"), index);
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&src);
|
|
}
|
|
|
|
|
|
static virStorageSourceSlice *
|
|
virDomainStorageSourceParseSlice(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree char *offset = NULL;
|
|
g_autofree char *size = NULL;
|
|
g_autofree virStorageSourceSlice *ret = g_new0(virStorageSourceSlice, 1);
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(offset = virXPathString("string(./@offset)", ctxt)) ||
|
|
!(size = virXPathString("string(./@size)", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing offset or size attribute of slice"));
|
|
return NULL;
|
|
}
|
|
|
|
if (virStrToLong_ullp(offset, NULL, 10, &ret->offset) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("malformed value '%s' of 'offset' attribute of slice"),
|
|
offset);
|
|
return NULL;
|
|
}
|
|
|
|
if (virStrToLong_ullp(size, NULL, 10, &ret->size) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("malformed value '%s' of 'size' attribute of slice"),
|
|
size);
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&ret);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainStorageSourceParseSlices(virStorageSource *src,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
xmlNodePtr node;
|
|
|
|
if ((node = virXPathNode("./slices/slice[@type='storage']", ctxt))) {
|
|
if (!(src->sliceStorage = virDomainStorageSourceParseSlice(node, ctxt)))
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainStorageSourceParse:
|
|
* @node: XML node pointing to the source element to parse
|
|
* @ctxt: XPath context
|
|
* @src: filled with parsed data
|
|
* @flags: XML parser flags
|
|
* @xmlopt: XML parser callbacks
|
|
*
|
|
* Parses @src definition from element pointed to by @node. Note that this
|
|
* does not parse the 'type' and 'format' attributes of @src and 'type' needs
|
|
* to be set correctly prior to calling this function.
|
|
*/
|
|
int
|
|
virDomainStorageSourceParse(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virStorageSource *src,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr tmp;
|
|
|
|
ctxt->node = node;
|
|
|
|
switch ((virStorageType)src->type) {
|
|
case VIR_STORAGE_TYPE_FILE:
|
|
src->path = virXMLPropString(node, "file");
|
|
break;
|
|
case VIR_STORAGE_TYPE_BLOCK:
|
|
src->path = virXMLPropString(node, "dev");
|
|
break;
|
|
case VIR_STORAGE_TYPE_DIR:
|
|
src->path = virXMLPropString(node, "dir");
|
|
break;
|
|
case VIR_STORAGE_TYPE_NETWORK:
|
|
if (virDomainDiskSourceNetworkParse(node, ctxt, src, flags) < 0)
|
|
return -1;
|
|
break;
|
|
case VIR_STORAGE_TYPE_VOLUME:
|
|
if (virDomainDiskSourcePoolDefParse(node, &src->srcpool) < 0)
|
|
return -1;
|
|
break;
|
|
case VIR_STORAGE_TYPE_NVME:
|
|
if (virDomainDiskSourceNVMeParse(node, ctxt, src) < 0)
|
|
return -1;
|
|
break;
|
|
case VIR_STORAGE_TYPE_VHOST_USER:
|
|
if (virDomainDiskSourceVHostUserParse(node, src, xmlopt, ctxt) < 0)
|
|
return -1;
|
|
break;
|
|
case VIR_STORAGE_TYPE_NONE:
|
|
case VIR_STORAGE_TYPE_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected disk type %s"),
|
|
virStorageTypeToString(src->type));
|
|
return -1;
|
|
}
|
|
|
|
if ((tmp = virXPathNode("./auth", ctxt)) &&
|
|
!(src->auth = virStorageAuthDefParse(tmp, ctxt)))
|
|
return -1;
|
|
|
|
if ((tmp = virXPathNode("./encryption", ctxt)) &&
|
|
!(src->encryption = virStorageEncryptionParseNode(tmp, ctxt)))
|
|
return -1;
|
|
|
|
if (virDomainDiskSourcePRParse(node, ctxt, &src->pr) < 0)
|
|
return -1;
|
|
|
|
if (virDomainStorageSourceParseSlices(src, ctxt) < 0)
|
|
return -1;
|
|
|
|
if (virSecurityDeviceLabelDefParseXML(&src->seclabels, &src->nseclabels,
|
|
ctxt, flags) < 0)
|
|
return -1;
|
|
|
|
/* People sometimes pass a bogus '' source path when they mean to omit the
|
|
* source element completely (e.g. CDROM without media). This is just a
|
|
* little compatibility check to help those broken apps */
|
|
if (src->path && !*src->path)
|
|
VIR_FREE(src->path);
|
|
|
|
if ((flags & VIR_DOMAIN_DEF_PARSE_STATUS) &&
|
|
xmlopt && xmlopt->privateData.storageParse &&
|
|
(tmp = virXPathNode("./privateData", ctxt))) {
|
|
ctxt->node = tmp;
|
|
|
|
if (xmlopt->privateData.storageParse(ctxt, src) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDiskBackingStoreParse(xmlXPathContextPtr ctxt,
|
|
virStorageSource *src,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr source;
|
|
g_autoptr(virStorageSource) backingStore = NULL;
|
|
g_autofree char *type = NULL;
|
|
g_autofree char *format = NULL;
|
|
g_autofree char *idx = NULL;
|
|
|
|
if (!(ctxt->node = virXPathNode("./backingStore", ctxt)))
|
|
return 0;
|
|
|
|
/* terminator does not have a type */
|
|
if (!(type = virXMLPropString(ctxt->node, "type"))) {
|
|
src->backingStore = virStorageSourceNew();
|
|
return 0;
|
|
}
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))
|
|
idx = virXMLPropString(ctxt->node, "index");
|
|
|
|
if (!(format = virXPathString("string(./format/@type)", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing disk backing store format"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(source = virXPathNode("./source", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing disk backing store source"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(backingStore = virDomainStorageSourceParseBase(type, format, idx)))
|
|
return -1;
|
|
|
|
if (virParseScaledValue("./format/metadata_cache/max_size", NULL,
|
|
ctxt,
|
|
&backingStore->metadataCacheMaxSize,
|
|
1, ULLONG_MAX, false) < 0)
|
|
return -1;
|
|
|
|
/* backing store is always read-only */
|
|
backingStore->readonly = true;
|
|
|
|
if (virDomainStorageSourceParse(source, ctxt, backingStore, flags, xmlopt) < 0 ||
|
|
virDomainDiskBackingStoreParse(ctxt, backingStore, flags, xmlopt) < 0)
|
|
return -1;
|
|
|
|
src->backingStore = g_steal_pointer(&backingStore);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define PARSE_IOTUNE(val) \
|
|
if (virXPathULongLong("string(./iotune/" #val ")", \
|
|
ctxt, &def->blkdeviotune.val) == -2) { \
|
|
virReportError(VIR_ERR_XML_ERROR, \
|
|
_("disk iotune field '%s' must be an integer"), #val); \
|
|
return -1; \
|
|
}
|
|
|
|
static int
|
|
virDomainDiskDefIotuneParse(virDomainDiskDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
PARSE_IOTUNE(total_bytes_sec);
|
|
PARSE_IOTUNE(read_bytes_sec);
|
|
PARSE_IOTUNE(write_bytes_sec);
|
|
PARSE_IOTUNE(total_iops_sec);
|
|
PARSE_IOTUNE(read_iops_sec);
|
|
PARSE_IOTUNE(write_iops_sec);
|
|
|
|
PARSE_IOTUNE(total_bytes_sec_max);
|
|
PARSE_IOTUNE(read_bytes_sec_max);
|
|
PARSE_IOTUNE(write_bytes_sec_max);
|
|
PARSE_IOTUNE(total_iops_sec_max);
|
|
PARSE_IOTUNE(read_iops_sec_max);
|
|
PARSE_IOTUNE(write_iops_sec_max);
|
|
|
|
PARSE_IOTUNE(size_iops_sec);
|
|
|
|
PARSE_IOTUNE(total_bytes_sec_max_length);
|
|
PARSE_IOTUNE(read_bytes_sec_max_length);
|
|
PARSE_IOTUNE(write_bytes_sec_max_length);
|
|
PARSE_IOTUNE(total_iops_sec_max_length);
|
|
PARSE_IOTUNE(read_iops_sec_max_length);
|
|
PARSE_IOTUNE(write_iops_sec_max_length);
|
|
|
|
def->blkdeviotune.group_name =
|
|
virXPathString("string(./iotune/group_name)", ctxt);
|
|
|
|
if ((def->blkdeviotune.total_bytes_sec &&
|
|
def->blkdeviotune.read_bytes_sec) ||
|
|
(def->blkdeviotune.total_bytes_sec &&
|
|
def->blkdeviotune.write_bytes_sec)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("total and read/write bytes_sec "
|
|
"cannot be set at the same time"));
|
|
return -1;
|
|
}
|
|
|
|
if ((def->blkdeviotune.total_iops_sec &&
|
|
def->blkdeviotune.read_iops_sec) ||
|
|
(def->blkdeviotune.total_iops_sec &&
|
|
def->blkdeviotune.write_iops_sec)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("total and read/write iops_sec "
|
|
"cannot be set at the same time"));
|
|
return -1;
|
|
}
|
|
|
|
if ((def->blkdeviotune.total_bytes_sec_max &&
|
|
def->blkdeviotune.read_bytes_sec_max) ||
|
|
(def->blkdeviotune.total_bytes_sec_max &&
|
|
def->blkdeviotune.write_bytes_sec_max)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("total and read/write bytes_sec_max "
|
|
"cannot be set at the same time"));
|
|
return -1;
|
|
}
|
|
|
|
if ((def->blkdeviotune.total_iops_sec_max &&
|
|
def->blkdeviotune.read_iops_sec_max) ||
|
|
(def->blkdeviotune.total_iops_sec_max &&
|
|
def->blkdeviotune.write_iops_sec_max)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("total and read/write iops_sec_max "
|
|
"cannot be set at the same time"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#undef PARSE_IOTUNE
|
|
|
|
|
|
static int
|
|
virDomainDiskDefMirrorParse(virDomainDiskDef *def,
|
|
xmlNodePtr cur,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
xmlNodePtr mirrorNode;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree char *mirrorFormat = NULL;
|
|
g_autofree char *mirrorType = NULL;
|
|
g_autofree char *ready = NULL;
|
|
g_autofree char *blockJob = NULL;
|
|
g_autofree char *index = NULL;
|
|
|
|
ctxt->node = cur;
|
|
|
|
if ((blockJob = virXMLPropString(cur, "job"))) {
|
|
if ((def->mirrorJob = virDomainBlockJobTypeFromString(blockJob)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown mirror job type '%s'"), blockJob);
|
|
return -1;
|
|
}
|
|
} else {
|
|
def->mirrorJob = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY;
|
|
}
|
|
|
|
if ((mirrorType = virXMLPropString(cur, "type"))) {
|
|
mirrorFormat = virXPathString("string(./format/@type)", ctxt);
|
|
index = virXPathString("string(./source/@index)", ctxt);
|
|
} else {
|
|
if (def->mirrorJob != VIR_DOMAIN_BLOCK_JOB_TYPE_COPY) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("mirror without type only supported "
|
|
"by copy job"));
|
|
return -1;
|
|
}
|
|
mirrorFormat = virXMLPropString(cur, "format");
|
|
}
|
|
|
|
if (!(def->mirror = virDomainStorageSourceParseBase(mirrorType, mirrorFormat,
|
|
index)))
|
|
return -1;
|
|
|
|
if (mirrorType) {
|
|
if (!(mirrorNode = virXPathNode("./source", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("mirror requires source element"));
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainStorageSourceParse(mirrorNode, ctxt, def->mirror, flags,
|
|
xmlopt) < 0)
|
|
return -1;
|
|
if (virDomainDiskBackingStoreParse(ctxt, def->mirror, flags, xmlopt) < 0)
|
|
return -1;
|
|
} else {
|
|
/* For back-compat reasons, we handle a file name encoded as
|
|
* attributes, even though we prefer modern output in the style of
|
|
* backingStore */
|
|
if (!(def->mirror->path = virXMLPropString(cur, "file"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("mirror requires file name"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((ready = virXMLPropString(cur, "ready")) &&
|
|
(def->mirrorState = virDomainDiskMirrorStateTypeFromString(ready)) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown mirror ready state %s"), ready);
|
|
return -1;
|
|
}
|
|
|
|
if (virParseScaledValue("./format/metadata_cache/max_size", NULL,
|
|
ctxt,
|
|
&def->mirror->metadataCacheMaxSize,
|
|
1, ULLONG_MAX, false) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskDefGeometryParse(virDomainDiskDef *def,
|
|
xmlNodePtr cur)
|
|
{
|
|
if (virXMLPropUInt(cur, "cyls", 10, VIR_XML_PROP_NONE,
|
|
&def->geometry.cylinders) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(cur, "heads", 10, VIR_XML_PROP_NONE,
|
|
&def->geometry.heads) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(cur, "secs", 10, VIR_XML_PROP_NONE,
|
|
&def->geometry.sectors) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(cur, "trans", virDomainDiskGeometryTransTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->geometry.trans) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskDefDriverParseXML(virDomainDiskDef *def,
|
|
xmlNodePtr cur,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = cur;
|
|
|
|
def->driverName = virXMLPropString(cur, "name");
|
|
|
|
if (virXMLPropEnum(cur, "cache", virDomainDiskCacheTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->cachemode) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(cur, "error_policy",
|
|
virDomainDiskErrorPolicyTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->error_policy) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(cur, "rerror_policy",
|
|
virDomainDiskErrorPolicyTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->rerror_policy) < 0)
|
|
return -1;
|
|
|
|
if (def->rerror_policy == VIR_DOMAIN_DISK_ERROR_POLICY_ENOSPACE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Invalid disk read error policy: '%s'"),
|
|
virDomainDiskErrorPolicyTypeToString(def->rerror_policy));
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropEnum(cur, "io", virDomainDiskIoTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->iomode) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateSwitch(cur, "ioeventfd", VIR_XML_PROP_NONE,
|
|
&def->ioeventfd) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateSwitch(cur, "event_idx", VIR_XML_PROP_NONE,
|
|
&def->event_idx) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateSwitch(cur, "copy_on_read", VIR_XML_PROP_NONE,
|
|
&def->copy_on_read) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(cur, "discard", virDomainDiskDiscardTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->discard) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(cur, "iothread", 10, VIR_XML_PROP_NONZERO, &def->iothread) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(cur, "detect_zeroes",
|
|
virDomainDiskDetectZeroesTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->detect_zeroes) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(cur, "queues", 10, VIR_XML_PROP_NONE, &def->queues) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskDefDriverSourceParseXML(virStorageSource *src,
|
|
xmlNodePtr cur,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree char *tmp = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = cur;
|
|
|
|
if ((tmp = virXMLPropString(cur, "type"))) {
|
|
if (STREQ(tmp, "aio")) {
|
|
/* Xen back-compat */
|
|
src->format = VIR_STORAGE_FILE_RAW;
|
|
} else {
|
|
if ((src->format = virStorageFileFormatTypeFromString(tmp)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown driver format value '%s'"), tmp);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (virParseScaledValue("./metadata_cache/max_size", NULL,
|
|
ctxt,
|
|
&src->metadataCacheMaxSize,
|
|
1, ULLONG_MAX, false) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskDefParsePrivateData(xmlXPathContextPtr ctxt,
|
|
virDomainDiskDef *disk,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
xmlNodePtr private_node = virXPathNode("./privateData", ctxt);
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
if (!xmlopt ||
|
|
!xmlopt->privateData.diskParse ||
|
|
!private_node)
|
|
return 0;
|
|
|
|
ctxt->node = private_node;
|
|
|
|
if (xmlopt->privateData.diskParse(ctxt, disk) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virStorageSource *
|
|
virDomainDiskDefParseSourceXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(virStorageSource) src = virStorageSourceNew();
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree char *type = NULL;
|
|
xmlNodePtr tmp;
|
|
|
|
ctxt->node = node;
|
|
|
|
src->type = VIR_STORAGE_TYPE_FILE;
|
|
|
|
if ((type = virXMLPropString(node, "type")) &&
|
|
(src->type = virStorageTypeFromString(type)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown disk type '%s'"), type);
|
|
return NULL;
|
|
}
|
|
|
|
if ((tmp = virXPathNode("./source[1]", ctxt))) {
|
|
if (virDomainStorageSourceParse(tmp, ctxt, src, flags, xmlopt) < 0)
|
|
return NULL;
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)) {
|
|
g_autofree char *sourceindex = NULL;
|
|
|
|
if ((sourceindex = virXMLPropString(tmp, "index")) &&
|
|
virStrToLong_uip(sourceindex, NULL, 10, &src->id) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid disk index '%s'"), sourceindex);
|
|
return NULL;
|
|
}
|
|
}
|
|
} else {
|
|
/* Reset src->type in case when 'source' was not present */
|
|
src->type = VIR_STORAGE_TYPE_FILE;
|
|
}
|
|
|
|
if (virXPathNode("./readonly[1]", ctxt))
|
|
src->readonly = true;
|
|
|
|
if (virXPathNode("./shareable[1]", ctxt))
|
|
src->shared = true;
|
|
|
|
if ((tmp = virXPathNode("./auth", ctxt))) {
|
|
/* If we've already parsed <source> and found an <auth> child,
|
|
* then generate an error to avoid ambiguity */
|
|
if (src->auth) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("an <auth> definition already found for disk source"));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(src->auth = virStorageAuthDefParse(tmp, ctxt)))
|
|
return NULL;
|
|
}
|
|
|
|
if ((tmp = virXPathNode("./encryption", ctxt))) {
|
|
/* If we've already parsed <source> and found an <encryption> child,
|
|
* then generate an error to avoid ambiguity */
|
|
if (src->encryption) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("an <encryption> definition already found for disk source"));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(src->encryption = virStorageEncryptionParseNode(tmp, ctxt)))
|
|
return NULL;
|
|
}
|
|
|
|
if (virDomainDiskBackingStoreParse(ctxt, src, flags, xmlopt) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&src);
|
|
}
|
|
|
|
|
|
static virDomainDiskDef *
|
|
virDomainDiskDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(virDomainDiskDef) def = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr sourceNode;
|
|
xmlNodePtr targetNode;
|
|
xmlNodePtr geometryNode;
|
|
xmlNodePtr blockioNode;
|
|
xmlNodePtr driverNode;
|
|
xmlNodePtr mirrorNode;
|
|
xmlNodePtr transientNode;
|
|
g_autoptr(virStorageSource) src = NULL;
|
|
|
|
if (!(src = virDomainDiskDefParseSourceXML(xmlopt, node, ctxt, flags)))
|
|
return NULL;
|
|
|
|
if (!(def = virDomainDiskDefNewSource(xmlopt, &src)))
|
|
return NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropEnumDefault(node, "device", virDomainDiskDeviceTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->device,
|
|
VIR_DOMAIN_DISK_DEVICE_DISK) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropEnum(node, "model", virDomainDiskModelTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->model) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropEnum(node, "snapshot", virDomainSnapshotLocationTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->snapshot) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateBool(node, "rawio", VIR_XML_PROP_NONE, &def->rawio) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropEnum(node, "sgio", virDomainDeviceSGIOTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->sgio) < 0)
|
|
return NULL;
|
|
|
|
if ((sourceNode = virXPathNode("./source", ctxt))) {
|
|
if (virXMLPropEnum(sourceNode, "startupPolicy",
|
|
virDomainStartupPolicyTypeFromString,
|
|
VIR_XML_PROP_NONZERO,
|
|
&def->startupPolicy) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if ((targetNode = virXPathNode("./target", ctxt))) {
|
|
def->dst = virXMLPropString(targetNode, "dev");
|
|
|
|
if (virXMLPropEnum(targetNode, "bus",
|
|
virDomainDiskBusTypeFromString,
|
|
VIR_XML_PROP_NONZERO,
|
|
&def->bus) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropEnum(targetNode, "tray", virDomainDiskTrayTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->tray_status) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateSwitch(targetNode, "removable", VIR_XML_PROP_NONE,
|
|
&def->removable) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropUInt(targetNode, "rotation_rate", 10, VIR_XML_PROP_NONE,
|
|
&def->rotation_rate) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if ((geometryNode = virXPathNode("./geometry", ctxt))) {
|
|
if (virDomainDiskDefGeometryParse(def, geometryNode) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if ((blockioNode = virXPathNode("./blockio", ctxt))) {
|
|
if (virXMLPropUInt(blockioNode, "logical_block_size", 10, VIR_XML_PROP_NONE,
|
|
&def->blockio.logical_block_size) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropUInt(blockioNode, "physical_block_size", 10, VIR_XML_PROP_NONE,
|
|
&def->blockio.physical_block_size) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if ((driverNode = virXPathNode("./driver", ctxt))) {
|
|
if (virDomainVirtioOptionsParseXML(driverNode, &def->virtio) < 0)
|
|
return NULL;
|
|
|
|
if (virDomainDiskDefDriverParseXML(def, driverNode, ctxt) < 0)
|
|
return NULL;
|
|
|
|
if (virDomainDiskDefDriverSourceParseXML(def->src, driverNode, ctxt) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if ((mirrorNode = virXPathNode("./mirror", ctxt))) {
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)) {
|
|
if (virDomainDiskDefMirrorParse(def, mirrorNode, ctxt, flags, xmlopt) < 0)
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (virXPathNode("./auth", ctxt))
|
|
def->diskElementAuth = true;
|
|
|
|
if (virXPathNode("./encryption", ctxt))
|
|
def->diskElementEnc = true;
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_STATUS) {
|
|
xmlNodePtr diskSecretsPlacementNode;
|
|
|
|
if ((diskSecretsPlacementNode = virXPathNode("./diskSecretsPlacement", ctxt))) {
|
|
g_autofree char *secretAuth = virXMLPropString(diskSecretsPlacementNode, "auth");
|
|
g_autofree char *secretEnc = virXMLPropString(diskSecretsPlacementNode, "enc");
|
|
|
|
def->diskElementAuth = !!secretAuth;
|
|
def->diskElementEnc = !!secretEnc;
|
|
}
|
|
}
|
|
|
|
if ((transientNode = virXPathNode("./transient", ctxt))) {
|
|
def->transient = true;
|
|
|
|
if (virXMLPropTristateBool(transientNode, "shareBacking",
|
|
VIR_XML_PROP_NONE,
|
|
&def->transientShareBacking) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (virDomainDiskDefIotuneParse(def, ctxt) < 0)
|
|
return NULL;
|
|
|
|
def->domain_name = virXPathString("string(./backenddomain/@name)", ctxt);
|
|
def->serial = virXPathString("string(./serial)", ctxt);
|
|
def->wwn = virXPathString("string(./wwn)", ctxt);
|
|
def->vendor = virXPathString("string(./vendor)", ctxt);
|
|
def->product = virXPathString("string(./product)", ctxt);
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info,
|
|
flags | VIR_DOMAIN_DEF_PARSE_ALLOW_BOOT) < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_STATUS &&
|
|
virDomainDiskDefParsePrivateData(ctxt, def, xmlopt) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainParseMemory:
|
|
* @xpath: XPath to memory amount
|
|
* @units_xpath: XPath to units attribute
|
|
* @ctxt: XPath context
|
|
* @mem: scaled memory amount is stored here
|
|
* @required: whether value is required
|
|
* @capped: whether scaled value must fit within unsigned long
|
|
*
|
|
* Parse a memory element or attribute located at @xpath within
|
|
* @ctxt, and store the result into @mem, in blocks of 1024. The
|
|
* value is scaled by units located at @units_xpath (or the
|
|
* 'unit' attribute under @xpath if @units_xpath is NULL). If
|
|
* units are not present, he default scale of 1024 is used. If
|
|
* @required is set, then the value must exist; otherwise, the
|
|
* value is optional. The value must not exceed
|
|
* VIR_DOMAIN_MEMORY_PARAM_UNLIMITED once scaled; additionally,
|
|
* if @capped is true, the value must fit within an unsigned long
|
|
* (only matters on 32-bit platforms).
|
|
*
|
|
* Return 0 on success, -1 on failure after issuing error.
|
|
*/
|
|
int
|
|
virDomainParseMemory(const char *xpath,
|
|
const char *units_xpath,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned long long *mem,
|
|
bool required,
|
|
bool capped)
|
|
{
|
|
unsigned long long bytes, max;
|
|
|
|
max = virMemoryMaxValue(capped);
|
|
|
|
if (virParseScaledValue(xpath, units_xpath, ctxt,
|
|
&bytes, 1024, max, required) < 0)
|
|
return -1;
|
|
|
|
/* Yes, we really do use kibibytes for our internal sizing. */
|
|
*mem = VIR_DIV_UP(bytes, 1024);
|
|
|
|
if (*mem >= VIR_DIV_UP(max, 1024)) {
|
|
virReportError(VIR_ERR_OVERFLOW, "%s", _("size value too large"));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainParseMemoryLimit:
|
|
*
|
|
* @xpath: XPath to memory amount
|
|
* @units_xpath: XPath to units attribute
|
|
* @ctxt: XPath context
|
|
* @mem: scaled memory amount is stored here
|
|
*
|
|
* Parse a memory element or attribute located at @xpath within @ctxt, and
|
|
* store the result into @mem, in blocks of 1024. The value is scaled by
|
|
* units located at @units_xpath (or the 'unit' attribute under @xpath if
|
|
* @units_xpath is NULL). If units are not present, he default scale of 1024
|
|
* is used. The value must not exceed VIR_DOMAIN_MEMORY_PARAM_UNLIMITED
|
|
* once scaled.
|
|
*
|
|
* This helper should be used only on *_limit memory elements.
|
|
*
|
|
* Return 0 on success, -1 on failure after issuing error.
|
|
*/
|
|
static int
|
|
virDomainParseMemoryLimit(const char *xpath,
|
|
const char *units_xpath,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned long long *mem)
|
|
{
|
|
int ret;
|
|
unsigned long long bytes;
|
|
|
|
ret = virParseScaledValue(xpath, units_xpath, ctxt, &bytes, 1024,
|
|
VIR_DOMAIN_MEMORY_PARAM_UNLIMITED << 10,
|
|
false);
|
|
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
if (ret == 0)
|
|
*mem = VIR_DOMAIN_MEMORY_PARAM_UNLIMITED;
|
|
else
|
|
*mem = virMemoryLimitTruncate(VIR_DIV_UP(bytes, 1024));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasMemoryHotplug(const virDomainDef *def)
|
|
{
|
|
return def->mem.memory_slots > 0 || def->mem.max_memory > 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefGetMemoryInitial:
|
|
* @def: domain definition
|
|
*
|
|
* Returns the size of the initial amount of guest memory. The initial amount
|
|
* is the memory size excluding possible memory modules.
|
|
*/
|
|
unsigned long long
|
|
virDomainDefGetMemoryInitial(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
unsigned long long ret = def->mem.total_memory;
|
|
|
|
for (i = 0; i < def->nmems; i++)
|
|
ret -= def->mems[i]->size;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefSetMemoryTotal:
|
|
* @def: domain definition
|
|
* @size: size to set
|
|
*
|
|
* Sets the total memory size in @def. This value needs to include possible
|
|
* additional memory modules.
|
|
*/
|
|
void
|
|
virDomainDefSetMemoryTotal(virDomainDef *def,
|
|
unsigned long long size)
|
|
{
|
|
def->mem.total_memory = size;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefGetMemoryTotal:
|
|
* @def: domain definition
|
|
*
|
|
* Returns the current maximum memory size usable by the domain described by
|
|
* @def. This size includes possible additional memory devices.
|
|
*/
|
|
unsigned long long
|
|
virDomainDefGetMemoryTotal(const virDomainDef *def)
|
|
{
|
|
return def->mem.total_memory;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainControllerModelTypeFromString(const virDomainControllerDef *def,
|
|
const char *model)
|
|
{
|
|
if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI)
|
|
return virDomainControllerModelSCSITypeFromString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_USB)
|
|
return virDomainControllerModelUSBTypeFromString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI)
|
|
return virDomainControllerModelPCITypeFromString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_IDE)
|
|
return virDomainControllerModelIDETypeFromString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL)
|
|
return virDomainControllerModelVirtioSerialTypeFromString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_ISA)
|
|
return virDomainControllerModelISATypeFromString(model);
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static const char *
|
|
virDomainControllerModelTypeToString(virDomainControllerDef *def,
|
|
int model)
|
|
{
|
|
if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI)
|
|
return virDomainControllerModelSCSITypeToString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_USB)
|
|
return virDomainControllerModelUSBTypeToString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI)
|
|
return virDomainControllerModelPCITypeToString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_IDE)
|
|
return virDomainControllerModelIDETypeToString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL)
|
|
return virDomainControllerModelVirtioSerialTypeToString(model);
|
|
else if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_ISA)
|
|
return virDomainControllerModelISATypeToString(model);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainControllerDef *
|
|
virDomainControllerDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(virDomainControllerDef) def = NULL;
|
|
virDomainControllerType type = 0;
|
|
xmlNodePtr driver = NULL;
|
|
g_autofree xmlNodePtr *targetNodes = NULL;
|
|
int ntargetNodes = 0;
|
|
g_autofree xmlNodePtr *modelNodes = NULL;
|
|
int nmodelNodes = 0;
|
|
int numaNode = -1;
|
|
int ports;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int rc;
|
|
g_autofree char *idx = NULL;
|
|
g_autofree char *model = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropEnum(node, "type", virDomainControllerTypeFromString,
|
|
VIR_XML_PROP_NONE, &type) < 0)
|
|
return NULL;
|
|
|
|
if (!(def = virDomainControllerDefNew(type)))
|
|
return NULL;
|
|
|
|
if ((model = virXMLPropString(node, "model"))) {
|
|
if ((def->model = virDomainControllerModelTypeFromString(def, model)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown model type '%s'"), model);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
idx = virXMLPropString(node, "index");
|
|
if (idx) {
|
|
unsigned int idxVal;
|
|
if (virStrToLong_ui(idx, NULL, 10, &idxVal) < 0 || idxVal > INT_MAX) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Cannot parse controller index %s"), idx);
|
|
return NULL;
|
|
}
|
|
def->idx = idxVal;
|
|
}
|
|
|
|
if ((driver = virXPathNode("./driver", ctxt))) {
|
|
if (virXMLPropUInt(driver, "queues", 10, VIR_XML_PROP_NONE,
|
|
&def->queues) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropUInt(driver, "cmd_per_lun", 10, VIR_XML_PROP_NONE,
|
|
&def->cmd_per_lun) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropUInt(driver, "max_sectors", 10, VIR_XML_PROP_NONE,
|
|
&def->max_sectors) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateSwitch(driver, "ioeventfd",
|
|
VIR_XML_PROP_NONE,
|
|
&def->ioeventfd) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropUInt(driver, "iothread", 10, VIR_XML_PROP_NONE,
|
|
&def->iothread) < 0)
|
|
return NULL;
|
|
|
|
if (virDomainVirtioOptionsParseXML(driver, &def->virtio) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
nmodelNodes = virXPathNodeSet("./model", ctxt, &modelNodes);
|
|
if (nmodelNodes == 1) {
|
|
if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
|
|
if (virXMLPropEnum(modelNodes[0], "name",
|
|
virDomainControllerPCIModelNameTypeFromString,
|
|
VIR_XML_PROP_NONE,
|
|
&def->opts.pciopts.modelName) < 0)
|
|
return NULL;
|
|
}
|
|
} else if (nmodelNodes > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Multiple <model> elements in "
|
|
"controller definition not allowed"));
|
|
return NULL;
|
|
}
|
|
|
|
ntargetNodes = virXPathNodeSet("./target", ctxt, &targetNodes);
|
|
if (ntargetNodes == 1) {
|
|
if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
|
|
if (virXMLPropInt(targetNodes[0], "chassisNr", 0, VIR_XML_PROP_NONE,
|
|
&def->opts.pciopts.chassisNr,
|
|
def->opts.pciopts.chassisNr) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropInt(targetNodes[0], "chassis", 0, VIR_XML_PROP_NONE,
|
|
&def->opts.pciopts.chassis,
|
|
def->opts.pciopts.chassis) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropInt(targetNodes[0], "port", 0, VIR_XML_PROP_NONE,
|
|
&def->opts.pciopts.port,
|
|
def->opts.pciopts.port) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropInt(targetNodes[0], "busNr", 0, VIR_XML_PROP_NONE,
|
|
&def->opts.pciopts.busNr,
|
|
def->opts.pciopts.busNr) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateSwitch(targetNodes[0], "hotplug",
|
|
VIR_XML_PROP_NONE,
|
|
&def->opts.pciopts.hotplug) < 0)
|
|
return NULL;
|
|
|
|
if ((rc = virXMLPropInt(targetNodes[0], "index", 0, VIR_XML_PROP_NONE,
|
|
&def->opts.pciopts.targetIndex,
|
|
def->opts.pciopts.targetIndex)) < 0)
|
|
return NULL;
|
|
|
|
if ((rc == 1) && def->opts.pciopts.targetIndex == -1)
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid target index '%i' in PCI controller"),
|
|
def->opts.pciopts.targetIndex);
|
|
}
|
|
} else if (ntargetNodes > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Multiple <target> elements in "
|
|
"controller definition not allowed"));
|
|
return NULL;
|
|
}
|
|
|
|
/* node is parsed differently from target attributes because
|
|
* someone thought it should be a subelement instead...
|
|
*/
|
|
rc = virXPathInt("string(./target/node)", ctxt, &numaNode);
|
|
if (rc == -2 || (rc == 0 && numaNode < 0)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid NUMA node in target"));
|
|
return NULL;
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_USB &&
|
|
def->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_NONE) {
|
|
VIR_DEBUG("Ignoring device address for none model usb controller");
|
|
} else if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt,
|
|
&def->info, flags) < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if ((rc = virXMLPropInt(node, "ports", 10, VIR_XML_PROP_NONE, &ports, -1)) < 0)
|
|
return NULL;
|
|
if ((rc == 1) && ports < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Invalid ports: %i"), ports);
|
|
return NULL;
|
|
}
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL: {
|
|
if ((rc = virXMLPropInt(node, "vectors", 10, VIR_XML_PROP_NONE,
|
|
&def->opts.vioserial.vectors,
|
|
def->opts.vioserial.vectors)) < 0)
|
|
return NULL;
|
|
|
|
if ((rc == 1) && def->opts.vioserial.vectors < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Invalid vectors: %i"),
|
|
def->opts.vioserial.vectors);
|
|
return NULL;
|
|
}
|
|
|
|
def->opts.vioserial.ports = ports;
|
|
break;
|
|
}
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_USB: {
|
|
/* If the XML has a uhci1, uhci2, uhci3 controller and no
|
|
* master port was given, we should set a sensible one */
|
|
int masterPort = -1;
|
|
switch (def->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI1:
|
|
masterPort = 0;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI2:
|
|
masterPort = 2;
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI3:
|
|
masterPort = 4;
|
|
break;
|
|
}
|
|
if (masterPort != -1 &&
|
|
def->info.mastertype == VIR_DOMAIN_CONTROLLER_MASTER_NONE) {
|
|
def->info.mastertype = VIR_DOMAIN_CONTROLLER_MASTER_USB;
|
|
def->info.master.usb.startport = masterPort;
|
|
}
|
|
|
|
def->opts.usbopts.ports = ports;
|
|
break;
|
|
}
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_PCI:
|
|
switch ((virDomainControllerModelPCI) def->model) {
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT: {
|
|
unsigned long long bytes;
|
|
if ((rc = virParseScaledValue("./pcihole64", NULL,
|
|
ctxt, &bytes, 1024,
|
|
1024ULL * ULONG_MAX, false)) < 0)
|
|
return NULL;
|
|
|
|
if (rc == 1)
|
|
def->opts.pciopts.pcihole64 = true;
|
|
def->opts.pciopts.pcihole64size = VIR_DIV_UP(bytes, 1024);
|
|
}
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_TO_PCI_BRIDGE:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_UPSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_SWITCH_DOWNSTREAM_PORT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_EXPANDER_BUS:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_DEFAULT:
|
|
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_LAST:
|
|
/* Other controller models don't require extra checks */
|
|
break;
|
|
}
|
|
|
|
if (numaNode >= 0)
|
|
def->opts.pciopts.numaNode = numaNode;
|
|
|
|
break;
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS: {
|
|
if ((rc = virXMLPropInt(node, "maxGrantFrames", 10, VIR_XML_PROP_NONE,
|
|
&def->opts.xenbusopts.maxGrantFrames,
|
|
def->opts.xenbusopts.maxGrantFrames)) < 0)
|
|
return NULL;
|
|
|
|
if ((rc == 1) && def->opts.xenbusopts.maxGrantFrames < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Invalid maxGrantFrames: %i"),
|
|
def->opts.xenbusopts.maxGrantFrames);
|
|
return NULL;
|
|
}
|
|
|
|
if ((rc = virXMLPropInt(node, "maxEventChannels", 10, VIR_XML_PROP_NONE,
|
|
&def->opts.xenbusopts.maxEventChannels,
|
|
def->opts.xenbusopts.maxEventChannels)) < 0)
|
|
return NULL;
|
|
|
|
if ((rc == 1) && def->opts.xenbusopts.maxEventChannels < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Invalid maxEventChannels: %i"),
|
|
def->opts.xenbusopts.maxEventChannels);
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
|
|
void
|
|
virDomainNetGenerateMAC(virDomainXMLOption *xmlopt,
|
|
virMacAddr *mac)
|
|
{
|
|
virMacAddrGenerate(xmlopt->config.macPrefix, mac);
|
|
}
|
|
|
|
|
|
static virDomainFSDef *
|
|
virDomainFSDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainFSDef *def;
|
|
xmlNodePtr driver_node = NULL;
|
|
xmlNodePtr source_node = NULL;
|
|
g_autofree char *type = NULL;
|
|
g_autofree char *source = NULL;
|
|
g_autofree char *target = NULL;
|
|
g_autofree char *format = NULL;
|
|
g_autofree char *accessmode = NULL;
|
|
g_autofree char *usage = NULL;
|
|
g_autofree char *units = NULL;
|
|
g_autofree char *model = NULL;
|
|
g_autofree char *multidevs = NULL;
|
|
g_autofree char *fmode = NULL;
|
|
g_autofree char *dmode = NULL;
|
|
g_autofree char *sock = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(def = virDomainFSDefNew(xmlopt)))
|
|
return NULL;
|
|
|
|
type = virXMLPropString(node, "type");
|
|
if (type) {
|
|
if ((def->type = virDomainFSTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown filesystem type '%s'"), type);
|
|
goto error;
|
|
}
|
|
} else {
|
|
def->type = VIR_DOMAIN_FS_TYPE_MOUNT;
|
|
}
|
|
|
|
accessmode = virXMLPropString(node, "accessmode");
|
|
if (accessmode) {
|
|
if ((def->accessmode = virDomainFSAccessModeTypeFromString(accessmode)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown accessmode '%s'"), accessmode);
|
|
goto error;
|
|
}
|
|
} else {
|
|
def->accessmode = VIR_DOMAIN_FS_ACCESSMODE_DEFAULT;
|
|
}
|
|
|
|
fmode = virXMLPropString(node, "fmode");
|
|
if (fmode) {
|
|
if ((virStrToLong_uip(fmode, NULL, 8, &def->fmode) < 0) ||
|
|
(def->fmode > 0777)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid fmode: '%s'"), fmode);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
dmode = virXMLPropString(node, "dmode");
|
|
if (dmode) {
|
|
if ((virStrToLong_uip(dmode, NULL, 8, &def->dmode) < 0) ||
|
|
(def->dmode > 0777)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid dmode: '%s'"), dmode);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
model = virXMLPropString(node, "model");
|
|
if (model) {
|
|
if ((def->model = virDomainFSModelTypeFromString(model)) < 0 ||
|
|
def->model == VIR_DOMAIN_FS_MODEL_DEFAULT) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown model '%s'"), model);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
multidevs = virXMLPropString(node, "multidevs");
|
|
if (multidevs) {
|
|
if ((def->multidevs = virDomainFSMultidevsTypeFromString(multidevs)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown multidevs '%s'"), multidevs);
|
|
goto error;
|
|
}
|
|
} else {
|
|
def->multidevs = VIR_DOMAIN_FS_MULTIDEVS_DEFAULT;
|
|
}
|
|
|
|
if (virParseScaledValue("./space_hard_limit[1]",
|
|
NULL, ctxt, &def->space_hard_limit,
|
|
1, ULLONG_MAX, false) < 0)
|
|
goto error;
|
|
|
|
if (virParseScaledValue("./space_soft_limit[1]",
|
|
NULL, ctxt, &def->space_soft_limit,
|
|
1, ULLONG_MAX, false) < 0)
|
|
goto error;
|
|
|
|
if ((source_node = virXPathNode("./source", ctxt))) {
|
|
sock = virXMLPropString(source_node, "socket");
|
|
if (def->type == VIR_DOMAIN_FS_TYPE_MOUNT ||
|
|
def->type == VIR_DOMAIN_FS_TYPE_BIND) {
|
|
source = virXMLPropString(source_node, "dir");
|
|
} else if (def->type == VIR_DOMAIN_FS_TYPE_FILE) {
|
|
source = virXMLPropString(source_node, "file");
|
|
} else if (def->type == VIR_DOMAIN_FS_TYPE_BLOCK) {
|
|
source = virXMLPropString(source_node, "dev");
|
|
} else if (def->type == VIR_DOMAIN_FS_TYPE_TEMPLATE) {
|
|
source = virXMLPropString(source_node, "name");
|
|
} else if (def->type == VIR_DOMAIN_FS_TYPE_RAM) {
|
|
usage = virXMLPropString(source_node, "usage");
|
|
units = virXMLPropString(source_node, "units");
|
|
} else if (def->type == VIR_DOMAIN_FS_TYPE_VOLUME) {
|
|
def->src->type = VIR_STORAGE_TYPE_VOLUME;
|
|
if (virDomainDiskSourcePoolDefParse(source_node, &def->src->srcpool) < 0)
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
target = virXPathString("string(./target/@dir)", ctxt);
|
|
|
|
if (virXPathNode("./readonly", ctxt))
|
|
def->readonly = true;
|
|
|
|
if ((driver_node = virXPathNode("./driver", ctxt))) {
|
|
if (virXMLPropEnum(driver_node, "type",
|
|
virDomainFSDriverTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->fsdriver) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropEnum(driver_node, "wrpolicy",
|
|
virDomainFSWrpolicyTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->wrpolicy) < 0)
|
|
goto error;
|
|
|
|
if ((format = virXMLPropString(driver_node, "format")) &&
|
|
((def->format = virStorageFileFormatTypeFromString(format)) <= 0)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown driver format value '%s'"), format);
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainVirtioOptionsParseXML(driver_node, &def->virtio) < 0)
|
|
goto error;
|
|
}
|
|
|
|
if (def->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS) {
|
|
g_autofree char *queue_size = virXPathString("string(./driver/@queue)", ctxt);
|
|
g_autofree char *binary = virXPathString("string(./binary/@path)", ctxt);
|
|
g_autofree char *xattr = virXPathString("string(./binary/@xattr)", ctxt);
|
|
g_autofree char *cache = virXPathString("string(./binary/cache/@mode)", ctxt);
|
|
g_autofree char *sandbox = virXPathString("string(./binary/sandbox/@mode)", ctxt);
|
|
g_autofree char *posix_lock = virXPathString("string(./binary/lock/@posix)", ctxt);
|
|
g_autofree char *flock = virXPathString("string(./binary/lock/@flock)", ctxt);
|
|
int val;
|
|
|
|
if (queue_size && virStrToLong_ull(queue_size, NULL, 10, &def->queue_size) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse queue size '%s' for virtiofs"),
|
|
queue_size);
|
|
goto error;
|
|
}
|
|
|
|
if (binary)
|
|
def->binary = virFileSanitizePath(binary);
|
|
|
|
if (xattr) {
|
|
if ((val = virTristateSwitchTypeFromString(xattr)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown xattr value '%s'"), xattr);
|
|
goto error;
|
|
}
|
|
def->xattr = val;
|
|
}
|
|
|
|
if (cache) {
|
|
if ((val = virDomainFSCacheModeTypeFromString(cache)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse cache mode '%s' for virtiofs"),
|
|
cache);
|
|
goto error;
|
|
}
|
|
def->cache = val;
|
|
}
|
|
|
|
if (sandbox) {
|
|
if ((val = virDomainFSSandboxModeTypeFromString(sandbox)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse sandbox mode '%s' for virtiofs"),
|
|
sandbox);
|
|
goto error;
|
|
}
|
|
def->sandbox = val;
|
|
}
|
|
|
|
if (posix_lock) {
|
|
if ((val = virTristateSwitchTypeFromString(posix_lock)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown posix lock value '%s'"), posix_lock);
|
|
goto error;
|
|
}
|
|
def->posix_lock = val;
|
|
}
|
|
|
|
if (flock) {
|
|
if ((val = virTristateSwitchTypeFromString(flock)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown flock value '%s'"), flock);
|
|
goto error;
|
|
}
|
|
def->flock = val;
|
|
}
|
|
}
|
|
|
|
if (source == NULL && def->type != VIR_DOMAIN_FS_TYPE_RAM
|
|
&& def->type != VIR_DOMAIN_FS_TYPE_VOLUME && !sock) {
|
|
virReportError(VIR_ERR_NO_SOURCE,
|
|
target ? "%s" : NULL, target);
|
|
goto error;
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_FS_TYPE_RAM) {
|
|
if (!usage) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing 'usage' attribute for RAM filesystem"));
|
|
goto error;
|
|
}
|
|
if (virStrToLong_ull(usage, NULL, 10, &def->usage) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse usage '%s' for RAM filesystem"),
|
|
usage);
|
|
goto error;
|
|
}
|
|
if (virScaleInteger(&def->usage, units,
|
|
1024, ULLONG_MAX) < 0)
|
|
goto error;
|
|
}
|
|
|
|
def->src->path = g_steal_pointer(&source);
|
|
def->sock = g_steal_pointer(&sock);
|
|
def->dst = g_steal_pointer(&target);
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info,
|
|
flags | VIR_DOMAIN_DEF_PARSE_ALLOW_BOOT) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainFSDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
virDomainActualNetDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainNetDef *parent,
|
|
virDomainActualNetDef **def,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainActualNetDef *actual = NULL;
|
|
int ret = -1;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr bandwidth_node = NULL;
|
|
xmlNodePtr vlanNode;
|
|
xmlNodePtr virtPortNode;
|
|
g_autofree char *addrtype = NULL;
|
|
g_autofree char *macTableManager = NULL;
|
|
|
|
actual = g_new0(virDomainActualNetDef, 1);
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropEnum(node, "type", virDomainNetTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &actual->type) < 0)
|
|
goto error;
|
|
|
|
if (actual->type != VIR_DOMAIN_NET_TYPE_BRIDGE &&
|
|
actual->type != VIR_DOMAIN_NET_TYPE_DIRECT &&
|
|
actual->type != VIR_DOMAIN_NET_TYPE_HOSTDEV &&
|
|
actual->type != VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported type '%s' in interface's <actual> element"),
|
|
virDomainNetTypeToString(actual->type));
|
|
goto error;
|
|
}
|
|
|
|
if (virXMLPropTristateBool(node, "trustGuestRxFilters", VIR_XML_PROP_NONE,
|
|
&actual->trustGuestRxFilters) < 0)
|
|
goto error;
|
|
|
|
virtPortNode = virXPathNode("./virtualport", ctxt);
|
|
if (virtPortNode) {
|
|
if (actual->type == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
actual->type == VIR_DOMAIN_NET_TYPE_DIRECT ||
|
|
actual->type == VIR_DOMAIN_NET_TYPE_HOSTDEV) {
|
|
/* the virtualport in <actual> should always already
|
|
* have an instanceid/interfaceid if its required,
|
|
* so don't let the parser generate one */
|
|
if (!(actual->virtPortProfile
|
|
= virNetDevVPortProfileParse(virtPortNode,
|
|
VIR_VPORT_XML_REQUIRE_ALL_ATTRIBUTES |
|
|
VIR_VPORT_XML_REQUIRE_TYPE))) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("<virtualport> element unsupported for type='%s'"
|
|
" in interface's <actual> element"),
|
|
virDomainNetTypeToString(actual->type));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (actual->type == VIR_DOMAIN_NET_TYPE_DIRECT) {
|
|
xmlNodePtr sourceNode = virXPathNode("./source[1]", ctxt);
|
|
|
|
if (sourceNode) {
|
|
int rc;
|
|
virNetDevMacVLanMode mode;
|
|
|
|
actual->data.direct.linkdev = virXMLPropString(sourceNode, "dev");
|
|
|
|
if ((rc = virXMLPropEnum(sourceNode, "mode",
|
|
virNetDevMacVLanModeTypeFromString,
|
|
VIR_XML_PROP_NONE, &mode)) < 0)
|
|
goto error;
|
|
|
|
if (rc == 1)
|
|
actual->data.direct.mode = mode;
|
|
}
|
|
} else if (actual->type == VIR_DOMAIN_NET_TYPE_HOSTDEV) {
|
|
virDomainHostdevDef *hostdev = &actual->data.hostdev.def;
|
|
|
|
hostdev->parentnet = parent;
|
|
hostdev->info = &parent->info;
|
|
/* The helper function expects type to already be found and
|
|
* passed in as a string, since it is in a different place in
|
|
* NetDef vs HostdevDef.
|
|
*/
|
|
addrtype = virXPathString("string(./source/address/@type)", ctxt);
|
|
/* if not explicitly stated, source/vendor implies usb device */
|
|
if (!addrtype && virXPathNode("./source/vendor", ctxt))
|
|
addrtype = g_strdup("usb");
|
|
hostdev->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
|
|
if (virDomainHostdevDefParseXMLSubsys(node, ctxt, addrtype,
|
|
hostdev, flags, xmlopt) < 0) {
|
|
goto error;
|
|
}
|
|
} else if (actual->type == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
actual->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
g_autofree char *class_id = NULL;
|
|
xmlNodePtr sourceNode;
|
|
|
|
class_id = virXPathString("string(./class/@id)", ctxt);
|
|
if (class_id &&
|
|
virStrToLong_ui(class_id, NULL, 10, &actual->class_id) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse class id '%s'"),
|
|
class_id);
|
|
goto error;
|
|
}
|
|
|
|
sourceNode = virXPathNode("./source", ctxt);
|
|
if (sourceNode) {
|
|
char *brname = virXMLPropString(sourceNode, "bridge");
|
|
|
|
if (!brname && actual->type == VIR_DOMAIN_NET_TYPE_BRIDGE) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing <source> element with bridge name in "
|
|
"interface's <actual> element"));
|
|
goto error;
|
|
}
|
|
actual->data.bridge.brname = brname;
|
|
macTableManager = virXMLPropString(sourceNode, "macTableManager");
|
|
if (macTableManager &&
|
|
(actual->data.bridge.macTableManager
|
|
= virNetworkBridgeMACTableManagerTypeFromString(macTableManager)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid macTableManager setting '%s' "
|
|
"in domain interface's <actual> element"),
|
|
macTableManager);
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
bandwidth_node = virXPathNode("./bandwidth", ctxt);
|
|
if (bandwidth_node &&
|
|
virNetDevBandwidthParse(&actual->bandwidth,
|
|
NULL,
|
|
bandwidth_node,
|
|
actual->type == VIR_DOMAIN_NET_TYPE_NETWORK) < 0)
|
|
goto error;
|
|
|
|
vlanNode = virXPathNode("./vlan", ctxt);
|
|
if (vlanNode && virNetDevVlanParse(vlanNode, ctxt, &actual->vlan) < 0)
|
|
goto error;
|
|
|
|
if (virNetworkPortOptionsParseXML(ctxt, &actual->isolatedPort) < 0)
|
|
goto error;
|
|
|
|
*def = g_steal_pointer(&actual);
|
|
ret = 0;
|
|
error:
|
|
virDomainActualNetDefFree(actual);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define NET_MODEL_CHARS \
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
|
|
|
|
|
|
int
|
|
virDomainNetAppendIPAddress(virDomainNetDef *def,
|
|
const char *address,
|
|
int family,
|
|
unsigned int prefix)
|
|
{
|
|
virNetDevIPAddr *ipDef = NULL;
|
|
ipDef = g_new0(virNetDevIPAddr, 1);
|
|
|
|
if (virSocketAddrParse(&ipDef->address, address, family) < 0)
|
|
goto error;
|
|
ipDef->prefix = prefix;
|
|
|
|
if (VIR_APPEND_ELEMENT(def->guestIP.ips, def->guestIP.nips, ipDef) < 0)
|
|
goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
VIR_FREE(ipDef);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrSourceReconnectDefParseXML(virDomainChrSourceReconnectDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr cur;
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((cur = virXPathNode("./reconnect", ctxt))) {
|
|
if (virXMLPropTristateBool(cur, "enabled", VIR_XML_PROP_NONE,
|
|
&def->enabled) < 0)
|
|
return -1;
|
|
|
|
if (def->enabled == VIR_TRISTATE_BOOL_YES) {
|
|
if (virXMLPropUInt(cur, "timeout", 10, VIR_XML_PROP_REQUIRED,
|
|
&def->timeout) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainNetTeamingInfoParseXML(xmlXPathContextPtr ctxt,
|
|
virDomainNetTeamingInfo **teaming)
|
|
{
|
|
g_autofree char *typeStr = virXPathString("string(./teaming/@type)", ctxt);
|
|
g_autofree char *persistentStr = virXPathString("string(./teaming/@persistent)", ctxt);
|
|
g_autoptr(virDomainNetTeamingInfo) tmpTeaming = NULL;
|
|
int tmpType;
|
|
|
|
if (!typeStr && !persistentStr)
|
|
return 0;
|
|
|
|
tmpTeaming = g_new0(virDomainNetTeamingInfo, 1);
|
|
|
|
if ((tmpType = virDomainNetTeamingTypeFromString(typeStr)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown teaming type '%s'"),
|
|
typeStr);
|
|
return -1;
|
|
}
|
|
|
|
tmpTeaming->type = tmpType;
|
|
tmpTeaming->persistent = g_steal_pointer(&persistentStr);
|
|
*teaming = g_steal_pointer(&tmpTeaming);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virDomainNetDef *
|
|
virDomainNetDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainNetDef *def;
|
|
virDomainHostdevDef *hostdev;
|
|
xmlNodePtr source_node = NULL;
|
|
xmlNodePtr virtualport_node = NULL;
|
|
xmlNodePtr driver_node = NULL;
|
|
xmlNodePtr filterref_node = NULL;
|
|
xmlNodePtr actual_node = NULL;
|
|
xmlNodePtr vlan_node = NULL;
|
|
xmlNodePtr bandwidth_node = NULL;
|
|
xmlNodePtr tmpNode;
|
|
GHashTable *filterparams = NULL;
|
|
virDomainActualNetDef *actual = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainChrSourceReconnectDef reconnect = {0};
|
|
int rv, val;
|
|
g_autofree char *macaddr = NULL;
|
|
g_autofree char *macaddr_type = NULL;
|
|
g_autofree char *macaddr_check = NULL;
|
|
g_autofree char *network = NULL;
|
|
g_autofree char *portgroup = NULL;
|
|
g_autofree char *portid = NULL;
|
|
g_autofree char *bridge = NULL;
|
|
g_autofree char *dev = NULL;
|
|
g_autofree char *ifname = NULL;
|
|
g_autofree char *managed_tap = NULL;
|
|
g_autofree char *ifname_guest = NULL;
|
|
g_autofree char *ifname_guest_actual = NULL;
|
|
g_autofree char *script = NULL;
|
|
g_autofree char *downscript = NULL;
|
|
g_autofree char *address = NULL;
|
|
g_autofree char *port = NULL;
|
|
g_autofree char *localaddr = NULL;
|
|
g_autofree char *localport = NULL;
|
|
g_autofree char *model = NULL;
|
|
g_autofree char *backend = NULL;
|
|
g_autofree char *txmode = NULL;
|
|
g_autofree char *ioeventfd = NULL;
|
|
g_autofree char *event_idx = NULL;
|
|
g_autofree char *queues = NULL;
|
|
g_autofree char *rx_queue_size = NULL;
|
|
g_autofree char *tx_queue_size = NULL;
|
|
g_autofree char *filter = NULL;
|
|
g_autofree char *internal = NULL;
|
|
g_autofree char *mode = NULL;
|
|
g_autofree char *linkstate = NULL;
|
|
g_autofree char *addrtype = NULL;
|
|
g_autofree char *domain_name = NULL;
|
|
g_autofree char *vhostuser_mode = NULL;
|
|
g_autofree char *vhostuser_path = NULL;
|
|
g_autofree char *vhostuser_type = NULL;
|
|
g_autofree char *vhost_path = NULL;
|
|
g_autofree char *tap = NULL;
|
|
g_autofree char *vhost = NULL;
|
|
const char *prefix = xmlopt ? xmlopt->config.netPrefix : NULL;
|
|
|
|
if (!(def = virDomainNetDefNew(xmlopt)))
|
|
return NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((rv = virXMLPropEnum(node, "type", virDomainNetTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->type)) < 0)
|
|
goto error;
|
|
|
|
if (rv == 0)
|
|
def->type = VIR_DOMAIN_NET_TYPE_USER;
|
|
|
|
if (virXMLPropTristateBool(node, "trustGuestRxFilters", VIR_XML_PROP_NONE,
|
|
&def->trustGuestRxFilters) < 0)
|
|
goto error;
|
|
|
|
if ((source_node = virXPathNode("./source", ctxt))) {
|
|
xmlNodePtr tmpnode = ctxt->node;
|
|
|
|
ctxt->node = source_node;
|
|
if (virDomainNetIPInfoParseXML(_("interface host IP"), ctxt, &def->hostIP) < 0)
|
|
goto error;
|
|
ctxt->node = tmpnode;
|
|
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
network = virXMLPropString(source_node, "network");
|
|
portgroup = virXMLPropString(source_node, "portgroup");
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))
|
|
portid = virXMLPropString(source_node, "portid");
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_INTERNAL)
|
|
internal = virXMLPropString(source_node, "name");
|
|
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_BRIDGE)
|
|
bridge = virXMLPropString(source_node, "bridge");
|
|
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_DIRECT) {
|
|
dev = virXMLPropString(source_node, "dev");
|
|
mode = virXMLPropString(source_node, "mode");
|
|
}
|
|
|
|
/* This clause is only necessary because from 2010 to 2016 it was
|
|
* possible (but never documented) to configure the name of the
|
|
* guest-side interface of an openvz domain with <source dev='blah'/>.
|
|
* That was blatant misuse of <source>, so was likely (hopefully)
|
|
* never used, but just in case there was somebody using it, we
|
|
* need to generate an error. If the openvz driver is ever
|
|
* deprecated, this clause can be removed from here.
|
|
*/
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_ETHERNET &&
|
|
(dev = virXMLPropString(source_node, "dev"))) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid attempt to set <interface type='ethernet'> "
|
|
"device name with <source dev='%s'/>. "
|
|
"Use <target dev='%s'/> (for host-side) "
|
|
"or <guest dev='%s'/> (for guest-side) instead."),
|
|
dev, dev, dev);
|
|
goto error;
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_VHOSTUSER) {
|
|
vhostuser_type = virXMLPropString(source_node, "type");
|
|
vhostuser_path = virXMLPropString(source_node, "path");
|
|
vhostuser_mode = virXMLPropString(source_node, "mode");
|
|
if (virDomainChrSourceReconnectDefParseXML(&reconnect, source_node, ctxt) < 0)
|
|
goto error;
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_VDPA)
|
|
dev = virXMLPropString(source_node, "dev");
|
|
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_SERVER ||
|
|
def->type == VIR_DOMAIN_NET_TYPE_CLIENT ||
|
|
def->type == VIR_DOMAIN_NET_TYPE_MCAST ||
|
|
def->type == VIR_DOMAIN_NET_TYPE_UDP) {
|
|
|
|
address = virXMLPropString(source_node, "address");
|
|
port = virXMLPropString(source_node, "port");
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_UDP) {
|
|
xmlNodePtr tmp_node = ctxt->node;
|
|
ctxt->node = source_node;
|
|
if ((tmpNode = virXPathNode("./local", ctxt))) {
|
|
localaddr = virXMLPropString(tmpNode, "address");
|
|
localport = virXMLPropString(tmpNode, "port");
|
|
}
|
|
ctxt->node = tmp_node;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((virtualport_node = virXPathNode("./virtualport", ctxt))) {
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
if (!(def->virtPortProfile
|
|
= virNetDevVPortProfileParse(virtualport_node,
|
|
VIR_VPORT_XML_GENERATE_MISSING_DEFAULTS))) {
|
|
goto error;
|
|
}
|
|
} else if (def->type == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
def->type == VIR_DOMAIN_NET_TYPE_DIRECT ||
|
|
def->type == VIR_DOMAIN_NET_TYPE_HOSTDEV) {
|
|
if (!(def->virtPortProfile
|
|
= virNetDevVPortProfileParse(virtualport_node, VIR_VPORT_XML_GENERATE_MISSING_DEFAULTS|
|
|
VIR_VPORT_XML_REQUIRE_ALL_ATTRIBUTES|
|
|
VIR_VPORT_XML_REQUIRE_TYPE))) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("<virtualport> element unsupported for"
|
|
" <interface type='%s'>"),
|
|
virDomainNetTypeToString(def->type));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ifname = virXPathString("string(./target/@dev)", ctxt);
|
|
managed_tap = virXPathString("string(./target/@managed)", ctxt);
|
|
|
|
ifname_guest = virXPathString("string(./guest/@dev)", ctxt);
|
|
ifname_guest_actual = virXPathString("string(./guest/@actual)", ctxt);
|
|
|
|
linkstate = virXPathString("string(./link/@state)", ctxt);
|
|
script = virXPathString("string(./script/@path)", ctxt);
|
|
downscript = virXPathString("string(./downscript/@path)", ctxt);
|
|
domain_name = virXPathString("string(./backenddomain/@name)", ctxt);
|
|
model = virXPathString("string(./model/@type)", ctxt);
|
|
|
|
if ((driver_node = virXPathNode("./driver", ctxt)) &&
|
|
(virDomainVirtioOptionsParseXML(driver_node, &def->virtio) < 0))
|
|
goto error;
|
|
|
|
backend = virXMLPropString(driver_node, "name");
|
|
txmode = virXMLPropString(driver_node, "txmode");
|
|
ioeventfd = virXMLPropString(driver_node, "ioeventfd");
|
|
event_idx = virXMLPropString(driver_node, "event_idx");
|
|
queues = virXMLPropString(driver_node, "queues");
|
|
rx_queue_size = virXMLPropString(driver_node, "rx_queue_size");
|
|
tx_queue_size = virXMLPropString(driver_node, "tx_queue_size");
|
|
|
|
if ((filterref_node = virXPathNode("./filterref", ctxt))) {
|
|
filter = virXMLPropString(filterref_node, "filter");
|
|
virHashFree(filterparams);
|
|
filterparams = virNWFilterParseParamAttributes(filterref_node);
|
|
}
|
|
|
|
if ((flags & VIR_DOMAIN_DEF_PARSE_ACTUAL_NET) &&
|
|
def->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
(actual_node = virXPathNode("./actual", ctxt)) &&
|
|
(virDomainActualNetDefParseXML(actual_node, ctxt, def,
|
|
&actual, flags, xmlopt) < 0))
|
|
goto error;
|
|
|
|
if ((bandwidth_node = virXPathNode("./bandwidth", ctxt)) &&
|
|
(virNetDevBandwidthParse(&def->bandwidth, NULL, bandwidth_node,
|
|
def->type == VIR_DOMAIN_NET_TYPE_NETWORK) < 0))
|
|
goto error;
|
|
|
|
if ((vlan_node = virXPathNode("./vlan", ctxt)) &&
|
|
(virNetDevVlanParse(vlan_node, ctxt, &def->vlan) < 0))
|
|
goto error;
|
|
|
|
if ((tap = virXPathString("string(./backend/@tap)", ctxt)))
|
|
def->backend.tap = virFileSanitizePath(tap);
|
|
|
|
if ((vhost = virXPathString("string(./backend/@vhost)", ctxt)))
|
|
vhost_path = virFileSanitizePath(vhost);
|
|
|
|
if ((macaddr = virXPathString("string(./mac/@address)", ctxt))) {
|
|
if (virMacAddrParse((const char *)macaddr, &def->mac) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unable to parse mac address '%s'"),
|
|
(const char *)macaddr);
|
|
goto error;
|
|
}
|
|
if (virMacAddrIsMulticast(&def->mac)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("expected unicast mac address, found multicast '%s'"),
|
|
(const char *)macaddr);
|
|
goto error;
|
|
}
|
|
} else {
|
|
virDomainNetGenerateMAC(xmlopt, &def->mac);
|
|
def->mac_generated = true;
|
|
}
|
|
|
|
if ((macaddr_type = virXPathString("string(./mac/@type)", ctxt))) {
|
|
int tmp;
|
|
|
|
if ((tmp = virDomainNetMacTypeTypeFromString(macaddr_type)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid mac address type value: '%s'. Valid "
|
|
"values are \"generated\" and \"static\"."),
|
|
macaddr_type);
|
|
goto error;
|
|
}
|
|
def->mac_type = tmp;
|
|
}
|
|
|
|
if ((macaddr_check = virXPathString("string(./mac/@check)", ctxt))) {
|
|
int tmpCheck;
|
|
|
|
if ((tmpCheck = virTristateBoolTypeFromString(macaddr_check)) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid mac address check value: '%s'"),
|
|
macaddr_check);
|
|
goto error;
|
|
}
|
|
def->mac_check = tmpCheck;
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info,
|
|
flags | VIR_DOMAIN_DEF_PARSE_ALLOW_BOOT
|
|
| VIR_DOMAIN_DEF_PARSE_ALLOW_ROM) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (model != NULL &&
|
|
virDomainNetSetModelString(def, model) < 0)
|
|
goto error;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
if (network == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'network' attribute "
|
|
"specified with <interface type='network'/>"));
|
|
goto error;
|
|
}
|
|
if (portid &&
|
|
virUUIDParse(portid, def->data.network.portid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse port id '%s'"), portid);
|
|
goto error;
|
|
}
|
|
|
|
def->data.network.name = g_steal_pointer(&network);
|
|
def->data.network.portgroup = g_steal_pointer(&portgroup);
|
|
def->data.network.actual = g_steal_pointer(&actual);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
if (!virDomainNetIsVirtioModel(def)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Wrong or no <model> 'type' attribute "
|
|
"specified with <interface type='vhostuser'/>. "
|
|
"vhostuser requires the virtio-net* frontend"));
|
|
goto error;
|
|
}
|
|
|
|
if (STRNEQ_NULLABLE(vhostuser_type, "unix")) {
|
|
if (vhostuser_type)
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Type='%s' unsupported for"
|
|
" <interface type='vhostuser'>"),
|
|
vhostuser_type);
|
|
else
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("No <source> 'type' attribute "
|
|
"specified for <interface "
|
|
"type='vhostuser'>"));
|
|
goto error;
|
|
}
|
|
|
|
if (vhostuser_path == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'path' attribute "
|
|
"specified with <interface "
|
|
"type='vhostuser'/>"));
|
|
goto error;
|
|
}
|
|
|
|
if (vhostuser_mode == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'mode' attribute "
|
|
"specified with <interface "
|
|
"type='vhostuser'/>"));
|
|
goto error;
|
|
}
|
|
|
|
if (!(def->data.vhostuser = virDomainChrSourceDefNew(xmlopt)))
|
|
goto error;
|
|
|
|
def->data.vhostuser->type = VIR_DOMAIN_CHR_TYPE_UNIX;
|
|
def->data.vhostuser->data.nix.path = g_steal_pointer(&vhostuser_path);
|
|
|
|
if (STREQ(vhostuser_mode, "server")) {
|
|
def->data.vhostuser->data.nix.listen = true;
|
|
if (reconnect.enabled == VIR_TRISTATE_BOOL_YES) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("'reconnect' attribute unsupported "
|
|
"'server' mode for <interface type='vhostuser'>"));
|
|
goto error;
|
|
}
|
|
} else if (STREQ(vhostuser_mode, "client")) {
|
|
def->data.vhostuser->data.nix.listen = false;
|
|
def->data.vhostuser->data.nix.reconnect = reconnect;
|
|
} else {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Wrong <source> 'mode' attribute "
|
|
"specified with <interface "
|
|
"type='vhostuser'/>"));
|
|
goto error;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
if (dev == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'dev' attribute "
|
|
"specified with <interface type='vdpa'/>"));
|
|
goto error;
|
|
}
|
|
def->data.vdpa.devicepath = g_steal_pointer(&dev);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
if (bridge == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'bridge' attribute "
|
|
"specified with <interface type='bridge'/>"));
|
|
goto error;
|
|
}
|
|
def->data.bridge.brname = g_steal_pointer(&bridge);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
if (port == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'port' attribute "
|
|
"specified with socket interface"));
|
|
goto error;
|
|
}
|
|
if (virStrToLong_i(port, NULL, 10, &def->data.socket.port) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Cannot parse <source> 'port' attribute "
|
|
"with socket interface"));
|
|
goto error;
|
|
}
|
|
|
|
if (address == NULL) {
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_CLIENT ||
|
|
def->type == VIR_DOMAIN_NET_TYPE_MCAST ||
|
|
def->type == VIR_DOMAIN_NET_TYPE_UDP) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'address' attribute "
|
|
"specified with socket interface"));
|
|
goto error;
|
|
}
|
|
} else {
|
|
def->data.socket.address = g_steal_pointer(&address);
|
|
}
|
|
|
|
if (def->type != VIR_DOMAIN_NET_TYPE_UDP)
|
|
break;
|
|
|
|
if (localport == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <local> 'port' attribute "
|
|
"specified with socket interface"));
|
|
goto error;
|
|
}
|
|
if (virStrToLong_i(localport, NULL, 10, &def->data.socket.localport) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Cannot parse <local> 'port' attribute "
|
|
"with socket interface"));
|
|
goto error;
|
|
}
|
|
|
|
if (localaddr == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <local> 'address' attribute "
|
|
"specified with socket interface"));
|
|
goto error;
|
|
} else {
|
|
def->data.socket.localaddr = g_steal_pointer(&localaddr);
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
if (internal == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'name' attribute specified "
|
|
"with <interface type='internal'/>"));
|
|
goto error;
|
|
}
|
|
def->data.internal.name = g_steal_pointer(&internal);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
if (dev == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("No <source> 'dev' attribute specified "
|
|
"with <interface type='direct'/>"));
|
|
goto error;
|
|
}
|
|
|
|
if (mode != NULL) {
|
|
if ((val = virNetDevMacVLanModeTypeFromString(mode)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Unknown mode has been specified"));
|
|
goto error;
|
|
}
|
|
def->data.direct.mode = val;
|
|
} else {
|
|
def->data.direct.mode = VIR_NETDEV_MACVLAN_MODE_VEPA;
|
|
}
|
|
|
|
def->data.direct.linkdev = g_steal_pointer(&dev);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
hostdev = &def->data.hostdev.def;
|
|
hostdev->parentnet = def;
|
|
hostdev->info = &def->info;
|
|
/* The helper function expects type to already be found and
|
|
* passed in as a string, since it is in a different place in
|
|
* NetDef vs HostdevDef.
|
|
*/
|
|
addrtype = virXPathString("string(./source/address/@type)", ctxt);
|
|
/* if not explicitly stated, source/vendor implies usb device */
|
|
if (!addrtype && virXPathNode("./source/vendor", ctxt))
|
|
addrtype = g_strdup("usb");
|
|
hostdev->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
|
|
if (virDomainHostdevDefParseXMLSubsys(node, ctxt, addrtype,
|
|
hostdev, flags, xmlopt) < 0) {
|
|
goto error;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (virDomainNetIPInfoParseXML(_("guest interface"),
|
|
ctxt, &def->guestIP) < 0)
|
|
goto error;
|
|
|
|
if (managed_tap) {
|
|
bool state = false;
|
|
if (virStringParseYesNo(managed_tap, &state) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid 'managed' value '%s'"),
|
|
managed_tap);
|
|
goto error;
|
|
}
|
|
def->managed_tap = virTristateBoolFromBool(state);
|
|
}
|
|
|
|
if (def->managed_tap != VIR_TRISTATE_BOOL_NO && ifname &&
|
|
(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) &&
|
|
(STRPREFIX(ifname, VIR_NET_GENERATED_VNET_PREFIX) ||
|
|
STRPREFIX(ifname, VIR_NET_GENERATED_MACVTAP_PREFIX) ||
|
|
STRPREFIX(ifname, VIR_NET_GENERATED_MACVLAN_PREFIX) ||
|
|
(prefix && STRPREFIX(ifname, prefix)))) {
|
|
/* An auto-generated target name, blank it out */
|
|
VIR_FREE(ifname);
|
|
}
|
|
|
|
if (script != NULL)
|
|
def->script = g_steal_pointer(&script);
|
|
if (downscript != NULL)
|
|
def->downscript = g_steal_pointer(&downscript);
|
|
if (domain_name != NULL)
|
|
def->domain_name = g_steal_pointer(&domain_name);
|
|
if (ifname != NULL)
|
|
def->ifname = g_steal_pointer(&ifname);
|
|
if (ifname_guest != NULL)
|
|
def->ifname_guest = g_steal_pointer(&ifname_guest);
|
|
if (ifname_guest_actual != NULL)
|
|
def->ifname_guest_actual = g_steal_pointer(&ifname_guest_actual);
|
|
|
|
if (def->type != VIR_DOMAIN_NET_TYPE_HOSTDEV &&
|
|
virDomainNetIsVirtioModel(def)) {
|
|
if (backend != NULL) {
|
|
if ((val = virDomainNetBackendTypeFromString(backend)) < 0 ||
|
|
val == VIR_DOMAIN_NET_BACKEND_TYPE_DEFAULT) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown interface <driver name='%s'> "
|
|
"has been specified"),
|
|
backend);
|
|
goto error;
|
|
}
|
|
def->driver.virtio.name = val;
|
|
}
|
|
if (txmode != NULL) {
|
|
if ((val = virDomainNetVirtioTxModeTypeFromString(txmode)) < 0 ||
|
|
val == VIR_DOMAIN_NET_VIRTIO_TX_MODE_DEFAULT) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown interface <driver txmode='%s'> "
|
|
"has been specified"),
|
|
txmode);
|
|
goto error;
|
|
}
|
|
def->driver.virtio.txmode = val;
|
|
}
|
|
if (ioeventfd) {
|
|
if ((val = virTristateSwitchTypeFromString(ioeventfd)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown interface ioeventfd mode '%s'"),
|
|
ioeventfd);
|
|
goto error;
|
|
}
|
|
def->driver.virtio.ioeventfd = val;
|
|
}
|
|
if (event_idx) {
|
|
if ((val = virTristateSwitchTypeFromString(event_idx)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown interface event_idx mode '%s'"),
|
|
event_idx);
|
|
goto error;
|
|
}
|
|
def->driver.virtio.event_idx = val;
|
|
}
|
|
if (queues) {
|
|
unsigned int q;
|
|
if (virStrToLong_uip(queues, NULL, 10, &q) < 0) {
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
_("'queues' attribute must be positive number: %s"),
|
|
queues);
|
|
goto error;
|
|
}
|
|
if (q > 1)
|
|
def->driver.virtio.queues = q;
|
|
}
|
|
if (rx_queue_size) {
|
|
unsigned int q;
|
|
if (virStrToLong_uip(rx_queue_size, NULL, 10, &q) < 0) {
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
_("'rx_queue_size' attribute must be positive number: %s"),
|
|
rx_queue_size);
|
|
goto error;
|
|
}
|
|
def->driver.virtio.rx_queue_size = q;
|
|
}
|
|
if (tx_queue_size) {
|
|
unsigned int q;
|
|
if (virStrToLong_uip(tx_queue_size, NULL, 10, &q) < 0) {
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
_("'tx_queue_size' attribute must be positive number: %s"),
|
|
tx_queue_size);
|
|
goto error;
|
|
}
|
|
def->driver.virtio.tx_queue_size = q;
|
|
}
|
|
|
|
if ((tmpNode = virXPathNode("./driver/host", ctxt))) {
|
|
if (virXMLPropTristateSwitch(tmpNode, "csum", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.host.csum) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "gso", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.host.gso) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "tso4", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.host.tso4) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "tso6", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.host.tso6) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "ecn", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.host.ecn) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "ufo", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.host.ufo) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "mrg_rxbuf",
|
|
VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.host.mrg_rxbuf) < 0)
|
|
goto error;
|
|
}
|
|
|
|
if ((tmpNode = virXPathNode("./driver/guest", ctxt))) {
|
|
if (virXMLPropTristateSwitch(tmpNode, "csum", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.guest.csum) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "tso4", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.guest.tso4) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "tso6", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.guest.tso6) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "ecn", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.guest.ecn) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(tmpNode, "ufo", VIR_XML_PROP_NONE,
|
|
&def->driver.virtio.guest.ufo) < 0)
|
|
goto error;
|
|
}
|
|
def->backend.vhost = g_steal_pointer(&vhost_path);
|
|
}
|
|
|
|
def->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DEFAULT;
|
|
if (linkstate != NULL) {
|
|
if ((def->linkstate = virDomainNetInterfaceLinkStateTypeFromString(linkstate)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown interface link state '%s'"),
|
|
linkstate);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (filter != NULL) {
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
def->filter = g_steal_pointer(&filter);
|
|
def->filterparams = g_steal_pointer(&filterparams);
|
|
break;
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
break;
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainNetType, def->type);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (virDomainNetTeamingInfoParseXML(ctxt, &def->teaming) < 0)
|
|
goto error;
|
|
|
|
rv = virXPathULong("string(./tune/sndbuf)", ctxt, &def->tune.sndbuf);
|
|
if (rv >= 0) {
|
|
def->tune.sndbuf_specified = true;
|
|
} else if (rv == -2) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("sndbuf must be a positive integer"));
|
|
goto error;
|
|
}
|
|
|
|
if (virXPathUInt("string(./mtu/@size)", ctxt, &def->mtu) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("malformed mtu size"));
|
|
goto error;
|
|
}
|
|
|
|
node = virXPathNode("./coalesce", ctxt);
|
|
if (node) {
|
|
if (virDomainNetDefCoalesceParseXML(node, ctxt, &def->coalesce) < 0)
|
|
goto error;
|
|
}
|
|
|
|
if (virNetworkPortOptionsParseXML(ctxt, &def->isolatedPort) < 0)
|
|
goto error;
|
|
|
|
cleanup:
|
|
virDomainActualNetDefFree(actual);
|
|
virHashFree(filterparams);
|
|
return def;
|
|
|
|
error:
|
|
virDomainNetDefFree(def);
|
|
def = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
static int
|
|
virDomainChrDefaultTargetType(int devtype)
|
|
{
|
|
switch ((virDomainChrDeviceType) devtype) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("target type must be specified for %s device"),
|
|
virDomainChrDeviceTypeToString(devtype));
|
|
return -1;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
|
|
return VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_NONE;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
|
|
return VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
|
|
/* No target type yet */
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainChrTargetTypeFromString(int devtype,
|
|
const char *targetType)
|
|
{
|
|
int ret = -1;
|
|
|
|
if (!targetType)
|
|
return virDomainChrDefaultTargetType(devtype);
|
|
|
|
switch ((virDomainChrDeviceType) devtype) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
ret = virDomainChrChannelTargetTypeFromString(targetType);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
|
|
ret = virDomainChrConsoleTargetTypeFromString(targetType);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
|
|
ret = virDomainChrSerialTargetTypeFromString(targetType);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
|
|
/* No target type yet */
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
virDomainChrTargetModelFromString(int devtype,
|
|
const char *targetModel)
|
|
{
|
|
int ret = -1;
|
|
|
|
if (!targetModel)
|
|
return 0;
|
|
|
|
switch ((virDomainChrDeviceType) devtype) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
|
|
ret = virDomainChrSerialTargetModelTypeFromString(targetModel);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
|
|
/* Target model not supported yet */
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
virDomainChrDefParseTargetXML(virDomainChrDef *def,
|
|
xmlNodePtr cur,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
unsigned int port;
|
|
g_autofree char *targetType = virXMLPropString(cur, "type");
|
|
g_autofree char *targetModel = NULL;
|
|
g_autofree char *addrStr = NULL;
|
|
g_autofree char *portStr = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = cur;
|
|
|
|
if ((def->targetType =
|
|
virDomainChrTargetTypeFromString(def->deviceType,
|
|
targetType)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown target type '%s' specified for character device"),
|
|
targetType);
|
|
return -1;
|
|
}
|
|
|
|
targetModel = virXPathString("string(./model/@name)", ctxt);
|
|
|
|
if ((def->targetModel =
|
|
virDomainChrTargetModelFromString(def->deviceType,
|
|
targetModel)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown target model '%s' specified for character device"),
|
|
targetModel);
|
|
return -1;
|
|
}
|
|
|
|
switch (def->deviceType) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
switch (def->targetType) {
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD:
|
|
addrStr = virXMLPropString(cur, "address");
|
|
|
|
def->target.addr = g_new0(virSocketAddr, 1);
|
|
|
|
if (addrStr == NULL) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("guestfwd channel does not "
|
|
"define a target address"));
|
|
return -1;
|
|
}
|
|
|
|
if (virSocketAddrParse(def->target.addr, addrStr, AF_UNSPEC) < 0)
|
|
return -1;
|
|
|
|
if (def->target.addr->data.stor.ss_family != AF_INET) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("guestfwd channel only supports "
|
|
"IPv4 addresses"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropUInt(cur, "port", 10, VIR_XML_PROP_REQUIRED, &port) < 0)
|
|
return -1;
|
|
|
|
virSocketAddrSetPort(def->target.addr, port);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XEN:
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO:
|
|
def->target.name = virXMLPropString(cur, "name");
|
|
|
|
if (def->targetType == VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO &&
|
|
!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)) {
|
|
|
|
if (virXMLPropEnum(cur, "state",
|
|
virDomainChrDeviceStateTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->state) < 0)
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
portStr = virXMLPropString(cur, "port");
|
|
if (portStr == NULL) {
|
|
/* Set to negative value to indicate we should set it later */
|
|
def->target.port = -1;
|
|
break;
|
|
}
|
|
|
|
if (virStrToLong_ui(portStr, NULL, 10, &port) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid port number: %s"),
|
|
portStr);
|
|
return -1;
|
|
}
|
|
def->target.port = port;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef enum {
|
|
VIR_DOMAIN_CHR_SOURCE_MODE_CONNECT,
|
|
VIR_DOMAIN_CHR_SOURCE_MODE_BIND,
|
|
} virDomainChrSourceModeType;
|
|
|
|
|
|
static int
|
|
virDomainChrSourceModeTypeFromString(const char *str)
|
|
{
|
|
if (!str)
|
|
return -1;
|
|
|
|
if (STREQ(str, "connect"))
|
|
return VIR_DOMAIN_CHR_SOURCE_MODE_CONNECT;
|
|
if (STREQ(str, "bind"))
|
|
return VIR_DOMAIN_CHR_SOURCE_MODE_BIND;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrSourceDefParseTCP(virDomainChrSourceDef *def,
|
|
xmlNodePtr source,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainChrSourceModeType mode;
|
|
|
|
if (virXMLPropEnumDefault(source, "mode", virDomainChrSourceModeTypeFromString,
|
|
VIR_XML_PROP_NONE, &mode,
|
|
VIR_DOMAIN_CHR_SOURCE_MODE_CONNECT) < 0)
|
|
return -1;
|
|
|
|
def->data.tcp.listen = mode == VIR_DOMAIN_CHR_SOURCE_MODE_BIND;
|
|
def->data.tcp.host = virXMLPropString(source, "host");
|
|
def->data.tcp.service = virXMLPropString(source, "service");
|
|
|
|
if (virXMLPropTristateBool(source, "tls", VIR_XML_PROP_NONE,
|
|
&def->data.tcp.haveTLS) < 0)
|
|
return -1;
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_STATUS) {
|
|
int tmpVal;
|
|
|
|
if (virXMLPropInt(source, "tlsFromConfig", 10, VIR_XML_PROP_NONE,
|
|
&tmpVal, 0) < 0)
|
|
return -1;
|
|
def->data.tcp.tlsFromConfig = !!tmpVal;
|
|
}
|
|
|
|
if (virDomainChrSourceReconnectDefParseXML(&def->data.tcp.reconnect,
|
|
source,
|
|
ctxt) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrSourceDefParseUDP(virDomainChrSourceDef *def,
|
|
xmlNodePtr source)
|
|
{
|
|
virDomainChrSourceModeType mode;
|
|
|
|
if (virXMLPropEnumDefault(source, "mode", virDomainChrSourceModeTypeFromString,
|
|
VIR_XML_PROP_NONE, &mode,
|
|
VIR_DOMAIN_CHR_SOURCE_MODE_CONNECT) < 0)
|
|
return -1;
|
|
|
|
if (mode == VIR_DOMAIN_CHR_SOURCE_MODE_CONNECT &&
|
|
!def->data.udp.connectHost && !def->data.udp.connectService) {
|
|
def->data.udp.connectHost = virXMLPropString(source, "host");
|
|
def->data.udp.connectService = virXMLPropString(source, "service");
|
|
} else if (mode == VIR_DOMAIN_CHR_SOURCE_MODE_BIND &&
|
|
!def->data.udp.bindHost && !def->data.udp.bindService) {
|
|
def->data.udp.bindHost = virXMLPropString(source, "host");
|
|
def->data.udp.bindService = virXMLPropString(source, "service");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrSourceDefParseUnix(virDomainChrSourceDef *def,
|
|
xmlNodePtr source,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
virDomainChrSourceModeType mode;
|
|
|
|
if (virXMLPropEnumDefault(source, "mode", virDomainChrSourceModeTypeFromString,
|
|
VIR_XML_PROP_NONE, &mode,
|
|
VIR_DOMAIN_CHR_SOURCE_MODE_CONNECT) < 0)
|
|
return -1;
|
|
|
|
def->data.nix.listen = mode == VIR_DOMAIN_CHR_SOURCE_MODE_BIND;
|
|
def->data.nix.path = virXMLPropString(source, "path");
|
|
|
|
if (virDomainChrSourceReconnectDefParseXML(&def->data.nix.reconnect,
|
|
source,
|
|
ctxt) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrSourceDefParseFile(virDomainChrSourceDef *def,
|
|
xmlNodePtr source)
|
|
{
|
|
def->data.file.path = virXMLPropString(source, "path");
|
|
|
|
if (virXMLPropTristateSwitch(source, "append", VIR_XML_PROP_NONE,
|
|
&def->data.file.append) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrSourceDefParseProtocol(virDomainChrSourceDef *def,
|
|
xmlNodePtr protocol)
|
|
{
|
|
g_autofree char *prot = NULL;
|
|
|
|
if (def->type != VIR_DOMAIN_CHR_TYPE_TCP)
|
|
return 0;
|
|
|
|
if ((prot = virXMLPropString(protocol, "type")) &&
|
|
(def->data.tcp.protocol =
|
|
virDomainChrTcpProtocolTypeFromString(prot)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown protocol '%s'"), prot);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrSourceDefParseLog(virDomainChrSourceDef *def,
|
|
xmlNodePtr log)
|
|
{
|
|
def->logfile = virXMLPropString(log, "file");
|
|
|
|
if (virXMLPropTristateSwitch(log, "append", VIR_XML_PROP_NONE,
|
|
&def->logappend) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Parse the source half of the XML definition for a character device,
|
|
* where node is the first element of node->children of the parent
|
|
* element. def->type must already be valid.
|
|
*
|
|
* Return -1 on failure, 0 on success. */
|
|
static int
|
|
virDomainChrSourceDefParseXML(virDomainChrSourceDef *def,
|
|
xmlNodePtr cur, unsigned int flags,
|
|
virDomainChrDef *chr_def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *logs = NULL;
|
|
int nlogs = 0;
|
|
g_autofree xmlNodePtr *protocols = NULL;
|
|
int nprotocols = 0;
|
|
g_autofree xmlNodePtr *sources = NULL;
|
|
int nsources = 0;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = cur;
|
|
|
|
if ((nsources = virXPathNodeSet("./source", ctxt, &sources)) < 0) {
|
|
goto error;
|
|
} else if (nsources > 0) {
|
|
/* Parse only the first source element since only one is used
|
|
* for chardev devices, the only exception is UDP type, where
|
|
* user can specify two source elements. */
|
|
if (nsources > 1 && def->type != VIR_DOMAIN_CHR_TYPE_UDP) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one source element is allowed for "
|
|
"character device"));
|
|
goto error;
|
|
} else if (nsources > 2) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only two source elements are allowed for "
|
|
"character device"));
|
|
goto error;
|
|
}
|
|
|
|
switch ((virDomainChrType) def->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
if (virDomainChrSourceDefParseFile(def, sources[0]) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
/* PTY path is only parsed from live xml. */
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))
|
|
def->data.file.path = virXMLPropString(sources[0], "path");
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
def->data.file.path = virXMLPropString(sources[0], "path");
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
if (virDomainChrSourceDefParseUnix(def, sources[0], ctxt) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UDP:
|
|
if ((virDomainChrSourceDefParseUDP(def, sources[0]) < 0) ||
|
|
(nsources == 2 && virDomainChrSourceDefParseUDP(def, sources[1]) < 0))
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_TCP:
|
|
if (virDomainChrSourceDefParseTCP(def, sources[0], ctxt, flags) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
|
|
def->data.spiceport.channel = virXMLPropString(sources[0], "channel");
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
def->data.nmdm.master = virXMLPropString(sources[0], "master");
|
|
def->data.nmdm.slave = virXMLPropString(sources[0], "slave");
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_LAST:
|
|
case VIR_DOMAIN_CHR_TYPE_NULL:
|
|
case VIR_DOMAIN_CHR_TYPE_VC:
|
|
case VIR_DOMAIN_CHR_TYPE_STDIO:
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
|
|
break;
|
|
}
|
|
|
|
/* Check for an optional seclabel override in <source/>. */
|
|
if (chr_def) {
|
|
xmlNodePtr tmp = ctxt->node;
|
|
|
|
ctxt->node = sources[0];
|
|
if (virSecurityDeviceLabelDefParseXML(&def->seclabels, &def->nseclabels,
|
|
ctxt, flags) < 0) {
|
|
goto error;
|
|
}
|
|
ctxt->node = tmp;
|
|
}
|
|
}
|
|
|
|
if ((nlogs = virXPathNodeSet("./log", ctxt, &logs)) < 0) {
|
|
goto error;
|
|
} else if (nlogs == 1) {
|
|
if (virDomainChrSourceDefParseLog(def, logs[0]) < 0)
|
|
goto error;
|
|
} else if (nlogs > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one log element is allowed for "
|
|
"character device"));
|
|
goto error;
|
|
}
|
|
|
|
if ((nprotocols = virXPathNodeSet("./protocol", ctxt, &protocols)) < 0) {
|
|
goto error;
|
|
} else if (nprotocols == 1) {
|
|
if (virDomainChrSourceDefParseProtocol(def, protocols[0]) < 0)
|
|
goto error;
|
|
} else if (nprotocols > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one protocol element is allowed for "
|
|
"character device"));
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
virDomainChrSourceDefClear(def);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static virClass *virDomainChrSourceDefClass;
|
|
|
|
static int
|
|
virDomainChrSourceDefOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(virDomainChrSourceDef, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(virDomainChrSourceDef);
|
|
|
|
virDomainChrSourceDef *
|
|
virDomainChrSourceDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainChrSourceDef *def = NULL;
|
|
|
|
if (virDomainChrSourceDefInitialize() < 0)
|
|
return NULL;
|
|
|
|
if (!(def = virObjectNew(virDomainChrSourceDefClass)))
|
|
return NULL;
|
|
|
|
if (xmlopt && xmlopt->privateData.chrSourceNew &&
|
|
!(def->privateData = xmlopt->privateData.chrSourceNew())) {
|
|
virObjectUnref(def);
|
|
def = NULL;
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
|
|
/* Create a new character device definition and set
|
|
* default port.
|
|
*/
|
|
virDomainChrDef *
|
|
virDomainChrDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainChrDef *def = NULL;
|
|
|
|
def = g_new0(virDomainChrDef, 1);
|
|
|
|
def->target.port = -1;
|
|
|
|
if (!(def->source = virDomainChrSourceDefNew(xmlopt)))
|
|
VIR_FREE(def);
|
|
|
|
return def;
|
|
}
|
|
|
|
/* Parse the XML definition for a character device
|
|
*
|
|
* The XML we're dealing with looks like
|
|
*
|
|
* <serial type="pty">
|
|
* <source path="/dev/pts/3"/>
|
|
* <target port="1"/>
|
|
* </serial>
|
|
*
|
|
* <serial type="dev">
|
|
* <source path="/dev/ttyS0"/>
|
|
* <target port="1"/>
|
|
* </serial>
|
|
*
|
|
* <serial type="tcp">
|
|
* <source mode="connect" host="0.0.0.0" service="2445"/>
|
|
* <target port="1"/>
|
|
* </serial>
|
|
*
|
|
* <serial type="tcp">
|
|
* <source mode="bind" host="0.0.0.0" service="2445"/>
|
|
* <target port="1"/>
|
|
* <protocol type='raw'/>
|
|
* </serial>
|
|
*
|
|
* <serial type="udp">
|
|
* <source mode="bind" host="0.0.0.0" service="2445"/>
|
|
* <source mode="connect" host="0.0.0.0" service="2445"/>
|
|
* <target port="1"/>
|
|
* </serial>
|
|
*
|
|
* <serial type="unix">
|
|
* <source mode="bind" path="/tmp/foo"/>
|
|
* <target port="1"/>
|
|
* </serial>
|
|
*
|
|
* <serial type="nmdm">
|
|
* <source master="/dev/nmdm0A" slave="/dev/nmdm0B"/>
|
|
* <target port="1">
|
|
* </serial>
|
|
*
|
|
*/
|
|
static virDomainChrDef *
|
|
virDomainChrDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlXPathContextPtr ctxt,
|
|
xmlNodePtr node,
|
|
unsigned int flags)
|
|
{
|
|
xmlNodePtr target;
|
|
const char *nodeName;
|
|
virDomainChrDef *def;
|
|
g_autofree char *type = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(def = virDomainChrDefNew(xmlopt)))
|
|
return NULL;
|
|
|
|
type = virXMLPropString(node, "type");
|
|
if (type == NULL) {
|
|
def->source->type = VIR_DOMAIN_CHR_TYPE_PTY;
|
|
} else if ((def->source->type = virDomainChrTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown type presented to host for character device: %s"),
|
|
type);
|
|
goto error;
|
|
}
|
|
|
|
nodeName = (const char *) node->name;
|
|
if ((def->deviceType = virDomainChrDeviceTypeFromString(nodeName)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown character device type: %s"),
|
|
nodeName);
|
|
goto error;
|
|
}
|
|
|
|
if ((target = virXPathNode("./target", ctxt))) {
|
|
if (virDomainChrDefParseTargetXML(def, target, ctxt, flags) < 0)
|
|
goto error;
|
|
} else if ((def->targetType = virDomainChrDefaultTargetType(def->deviceType)) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainChrSourceDefParseXML(def->source, node, flags, def,
|
|
ctxt) < 0)
|
|
goto error;
|
|
|
|
if (def->source->type == VIR_DOMAIN_CHR_TYPE_SPICEVMC) {
|
|
if (def->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("spicevmc device type only supports "
|
|
"virtio"));
|
|
goto error;
|
|
} else {
|
|
def->source->data.spicevmc = VIR_DOMAIN_CHR_SPICEVMC_VDAGENT;
|
|
}
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
goto error;
|
|
|
|
if (def->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL &&
|
|
def->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB &&
|
|
def->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
def->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("usb-serial requires address of usb type"));
|
|
goto error;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainChrDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
static virDomainSmartcardDef *
|
|
virDomainSmartcardDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(virDomainSmartcardDef) def = NULL;
|
|
g_autofree char *mode = NULL;
|
|
g_autofree char *type = NULL;
|
|
g_autofree xmlNodePtr *certificates = NULL;
|
|
int n = 0;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
def = g_new0(virDomainSmartcardDef, 1);
|
|
|
|
mode = virXMLPropString(node, "mode");
|
|
if (mode == NULL) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing smartcard device mode"));
|
|
return NULL;
|
|
}
|
|
if ((def->type = virDomainSmartcardTypeFromString(mode)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown smartcard device mode: %s"),
|
|
mode);
|
|
return NULL;
|
|
}
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
|
|
break;
|
|
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
|
|
n = virXPathNodeSet("./certificate", ctxt, &certificates);
|
|
if (n != VIR_DOMAIN_SMARTCARD_NUM_CERTIFICATES) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("host-certificates mode needs "
|
|
"exactly three certificates"));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(def->data.cert.file[0] = virXMLNodeContentString(certificates[0])) ||
|
|
!(def->data.cert.file[1] = virXMLNodeContentString(certificates[1])) ||
|
|
!(def->data.cert.file[2] = virXMLNodeContentString(certificates[2])))
|
|
return NULL;
|
|
|
|
if (virXPathNode("./database", ctxt) &&
|
|
!def->data.cert.database) {
|
|
if (!(def->data.cert.database =
|
|
virXPathString("string(./database/text())", ctxt)))
|
|
return NULL;
|
|
|
|
if (*def->data.cert.database != '/') {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("expecting absolute path: %s"),
|
|
def->data.cert.database);
|
|
return NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
|
|
type = virXMLPropString(node, "type");
|
|
if (type == NULL) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("passthrough mode requires a character "
|
|
"device type attribute"));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(def->data.passthru = virDomainChrSourceDefNew(xmlopt)))
|
|
return NULL;
|
|
|
|
if ((def->data.passthru->type = virDomainChrTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown type presented to host for "
|
|
"character device: %s"), type);
|
|
return NULL;
|
|
}
|
|
|
|
if (virDomainChrSourceDefParseXML(def->data.passthru, node, flags,
|
|
NULL, ctxt) < 0)
|
|
return NULL;
|
|
|
|
if (def->data.passthru->type == VIR_DOMAIN_CHR_TYPE_SPICEVMC) {
|
|
def->data.passthru->data.spicevmc
|
|
= VIR_DOMAIN_CHR_SPICEVMC_SMARTCARD;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("unknown smartcard mode"));
|
|
return NULL;
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
/* Parse the XML definition for a TPM device
|
|
*
|
|
* The XML looks like this:
|
|
*
|
|
* <tpm model='tpm-tis'>
|
|
* <backend type='passthrough'>
|
|
* <device path='/dev/tpm0'/>
|
|
* </backend>
|
|
* </tpm>
|
|
*
|
|
* or like this:
|
|
*
|
|
* <tpm model='tpm-tis'>
|
|
* <backend type='emulator' version='2.0'/>
|
|
* </tpm>
|
|
*
|
|
* Emulator state encryption is supported with the following:
|
|
*
|
|
* <tpm model='tpm-tis'>
|
|
* <backend type='emulator' version='2.0'>
|
|
* <encryption secret='32ee7e76-2178-47a1-ab7b-269e6e348015'/>
|
|
* </backend>
|
|
* </tpm>
|
|
*
|
|
* Emulator persistent_state is supported with the following:
|
|
*
|
|
* <tpm model='tpm-tis'>
|
|
* <backend type='emulator' version='2.0' persistent_state='yes'>
|
|
* </tpm>
|
|
*/
|
|
static virDomainTPMDef *
|
|
virDomainTPMDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainTPMDef *def;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int nbackends;
|
|
g_autofree char *path = NULL;
|
|
g_autofree char *model = NULL;
|
|
g_autofree char *backend = NULL;
|
|
g_autofree char *version = NULL;
|
|
g_autofree char *secretuuid = NULL;
|
|
g_autofree char *persistent_state = NULL;
|
|
g_autofree xmlNodePtr *backends = NULL;
|
|
|
|
def = g_new0(virDomainTPMDef, 1);
|
|
|
|
model = virXMLPropString(node, "model");
|
|
if (model != NULL &&
|
|
(def->model = virDomainTPMModelTypeFromString(model)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown TPM frontend model '%s'"), model);
|
|
goto error;
|
|
}
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((nbackends = virXPathNodeSet("./backend", ctxt, &backends)) < 0)
|
|
goto error;
|
|
|
|
if (nbackends > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one TPM backend is supported"));
|
|
goto error;
|
|
}
|
|
|
|
if (nbackends == 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing TPM device backend"));
|
|
goto error;
|
|
}
|
|
|
|
if (!(backend = virXMLPropString(backends[0], "type"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing TPM device backend type"));
|
|
goto error;
|
|
}
|
|
|
|
if ((def->type = virDomainTPMBackendTypeFromString(backend)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown TPM backend type '%s'"),
|
|
backend);
|
|
goto error;
|
|
}
|
|
|
|
version = virXMLPropString(backends[0], "version");
|
|
if (!version) {
|
|
def->version = VIR_DOMAIN_TPM_VERSION_DEFAULT;
|
|
} else {
|
|
if ((def->version = virDomainTPMVersionTypeFromString(version)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unsupported TPM version '%s'"),
|
|
version);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
|
|
path = virXPathString("string(./backend/device/@path)", ctxt);
|
|
if (!path)
|
|
path = g_strdup(VIR_DOMAIN_TPM_DEFAULT_DEVICE);
|
|
def->data.passthrough.source.data.file.path = g_steal_pointer(&path);
|
|
def->data.passthrough.source.type = VIR_DOMAIN_CHR_TYPE_DEV;
|
|
break;
|
|
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
|
|
secretuuid = virXPathString("string(./backend/encryption/@secret)", ctxt);
|
|
if (secretuuid) {
|
|
if (virUUIDParse(secretuuid, def->data.emulator.secretuuid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse secret uuid '%s'"), secretuuid);
|
|
goto error;
|
|
}
|
|
def->data.emulator.hassecretuuid = true;
|
|
}
|
|
|
|
persistent_state = virXMLPropString(backends[0], "persistent_state");
|
|
if (persistent_state) {
|
|
if (virStringParseYesNo(persistent_state,
|
|
&def->data.emulator.persistent_state) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Invalid persistent_state value, either 'yes' or 'no'"));
|
|
goto error;
|
|
}
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_TPM_TYPE_LAST:
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainTPMDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
static virDomainPanicDef *
|
|
virDomainPanicDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainPanicDef *panic;
|
|
g_autofree char *model = NULL;
|
|
|
|
panic = g_new0(virDomainPanicDef, 1);
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt,
|
|
&panic->info, flags) < 0)
|
|
goto error;
|
|
|
|
model = virXMLPropString(node, "model");
|
|
if (model != NULL &&
|
|
(panic->model = virDomainPanicModelTypeFromString(model)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown panic model '%s'"), model);
|
|
goto error;
|
|
}
|
|
|
|
return panic;
|
|
|
|
error:
|
|
virDomainPanicDefFree(panic);
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse the XML definition for an input device */
|
|
static virDomainInputDef *
|
|
virDomainInputDefParseXML(virDomainXMLOption *xmlopt,
|
|
const virDomainDef *dom,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainInputDef *def;
|
|
g_autofree char *type = NULL;
|
|
g_autofree char *bus = NULL;
|
|
g_autofree char *model = NULL;
|
|
xmlNodePtr source = NULL;
|
|
|
|
def = g_new0(virDomainInputDef, 1);
|
|
|
|
ctxt->node = node;
|
|
|
|
type = virXMLPropString(node, "type");
|
|
bus = virXMLPropString(node, "bus");
|
|
model = virXMLPropString(node, "model");
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing input device type"));
|
|
goto error;
|
|
}
|
|
|
|
if ((def->type = virDomainInputTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown input device type '%s'"), type);
|
|
goto error;
|
|
}
|
|
|
|
if (model &&
|
|
((def->model = virDomainInputModelTypeFromString(model)) < 0 ||
|
|
def->model == VIR_DOMAIN_INPUT_MODEL_DEFAULT)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown input model '%s'"), model);
|
|
goto error;
|
|
}
|
|
|
|
if (bus) {
|
|
if ((def->bus = virDomainInputBusTypeFromString(bus)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown input bus type '%s'"), bus);
|
|
goto error;
|
|
}
|
|
|
|
if (dom->os.type == VIR_DOMAIN_OSTYPE_HVM) {
|
|
if (def->bus == VIR_DOMAIN_INPUT_BUS_PS2 &&
|
|
def->type != VIR_DOMAIN_INPUT_TYPE_MOUSE &&
|
|
def->type != VIR_DOMAIN_INPUT_TYPE_KBD) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("ps2 bus does not support %s input device"),
|
|
type);
|
|
goto error;
|
|
}
|
|
if (def->bus == VIR_DOMAIN_INPUT_BUS_XEN) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported input bus %s"),
|
|
bus);
|
|
goto error;
|
|
}
|
|
} else if (dom->os.type == VIR_DOMAIN_OSTYPE_XEN ||
|
|
dom->os.type == VIR_DOMAIN_OSTYPE_XENPVH) {
|
|
if (def->bus != VIR_DOMAIN_INPUT_BUS_XEN) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unsupported input bus %s"),
|
|
bus);
|
|
goto error;
|
|
}
|
|
if (def->type != VIR_DOMAIN_INPUT_TYPE_MOUSE &&
|
|
def->type != VIR_DOMAIN_INPUT_TYPE_KBD) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("xen bus does not support %s input device"),
|
|
type);
|
|
goto error;
|
|
}
|
|
} else {
|
|
if (dom->virtType == VIR_DOMAIN_VIRT_VZ ||
|
|
dom->virtType == VIR_DOMAIN_VIRT_PARALLELS) {
|
|
if (def->bus != VIR_DOMAIN_INPUT_BUS_PARALLELS) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("parallels containers don't support "
|
|
"input bus %s"),
|
|
bus);
|
|
goto error;
|
|
}
|
|
|
|
if (def->type != VIR_DOMAIN_INPUT_TYPE_MOUSE &&
|
|
def->type != VIR_DOMAIN_INPUT_TYPE_KBD) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("parallels bus does not support "
|
|
"%s input device"),
|
|
type);
|
|
goto error;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Input devices are not supported by this "
|
|
"virtualization driver."));
|
|
goto error;
|
|
}
|
|
}
|
|
} else {
|
|
if (dom->os.type == VIR_DOMAIN_OSTYPE_HVM) {
|
|
if ((def->type == VIR_DOMAIN_INPUT_TYPE_MOUSE ||
|
|
def->type == VIR_DOMAIN_INPUT_TYPE_KBD) &&
|
|
(ARCH_IS_X86(dom->os.arch) || dom->os.arch == VIR_ARCH_NONE)) {
|
|
def->bus = VIR_DOMAIN_INPUT_BUS_PS2;
|
|
} else if (ARCH_IS_S390(dom->os.arch) ||
|
|
def->type == VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH) {
|
|
def->bus = VIR_DOMAIN_INPUT_BUS_VIRTIO;
|
|
} else if (def->type == VIR_DOMAIN_INPUT_TYPE_EVDEV) {
|
|
def->bus = VIR_DOMAIN_INPUT_BUS_NONE;
|
|
} else {
|
|
def->bus = VIR_DOMAIN_INPUT_BUS_USB;
|
|
}
|
|
} else if (dom->os.type == VIR_DOMAIN_OSTYPE_XEN ||
|
|
dom->os.type == VIR_DOMAIN_OSTYPE_XENPVH) {
|
|
def->bus = VIR_DOMAIN_INPUT_BUS_XEN;
|
|
} else {
|
|
if ((dom->virtType == VIR_DOMAIN_VIRT_VZ ||
|
|
dom->virtType == VIR_DOMAIN_VIRT_PARALLELS))
|
|
def->bus = VIR_DOMAIN_INPUT_BUS_PARALLELS;
|
|
}
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
goto error;
|
|
|
|
if (def->bus == VIR_DOMAIN_INPUT_BUS_USB &&
|
|
def->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
def->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Invalid address for a USB device"));
|
|
goto error;
|
|
}
|
|
|
|
if ((source = virXPathNode("./source", ctxt))) {
|
|
g_autofree char *evdev = NULL;
|
|
|
|
if (def->type == VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH)
|
|
evdev = virXMLPropString(source, "evdev");
|
|
else if (def->type == VIR_DOMAIN_INPUT_TYPE_EVDEV)
|
|
evdev = virXMLPropString(source, "dev");
|
|
|
|
if (evdev)
|
|
def->source.evdev = virFileSanitizePath(evdev);
|
|
|
|
if (def->type == VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH ||
|
|
def->type == VIR_DOMAIN_INPUT_TYPE_EVDEV) {
|
|
if (!def->source.evdev) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing evdev path for input device"));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_INPUT_TYPE_EVDEV) {
|
|
if (virXMLPropEnum(source, "grab",
|
|
virDomainInputSourceGrabTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->source.grab) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(source, "repeat",
|
|
VIR_XML_PROP_NONE, &def->source.repeat) < 0)
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (virDomainVirtioOptionsParseXML(virXPathNode("./driver", ctxt),
|
|
&def->virtio) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainInputDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Parse the XML definition for a hub device */
|
|
static virDomainHubDef *
|
|
virDomainHubDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainHubDef *def;
|
|
g_autofree char *type = NULL;
|
|
|
|
def = g_new0(virDomainHubDef, 1);
|
|
|
|
type = virXMLPropString(node, "type");
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing hub device type"));
|
|
goto error;
|
|
}
|
|
|
|
if ((def->type = virDomainHubTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown hub device type '%s'"), type);
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainHubDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Parse the XML definition for a clock timer */
|
|
static virDomainTimerDef *
|
|
virDomainTimerDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
virDomainTimerDef *def;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr catchup;
|
|
int ret;
|
|
g_autofree char *name = NULL;
|
|
g_autofree char *present = NULL;
|
|
g_autofree char *tickpolicy = NULL;
|
|
g_autofree char *track = NULL;
|
|
g_autofree char *mode = NULL;
|
|
|
|
def = g_new0(virDomainTimerDef, 1);
|
|
|
|
ctxt->node = node;
|
|
|
|
name = virXMLPropString(node, "name");
|
|
if (name == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing timer name"));
|
|
goto error;
|
|
}
|
|
if ((def->name = virDomainTimerNameTypeFromString(name)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown timer name '%s'"), name);
|
|
goto error;
|
|
}
|
|
|
|
def->present = -1; /* unspecified */
|
|
if ((present = virXMLPropString(node, "present")) != NULL) {
|
|
bool state = false;
|
|
if (virStringParseYesNo(present, &state) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown timer present value '%s'"), present);
|
|
goto error;
|
|
}
|
|
def->present = state ? 1 : 0;
|
|
}
|
|
|
|
def->tickpolicy = -1;
|
|
tickpolicy = virXMLPropString(node, "tickpolicy");
|
|
if (tickpolicy != NULL) {
|
|
if ((def->tickpolicy = virDomainTimerTickpolicyTypeFromString(tickpolicy)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown timer tickpolicy '%s'"), tickpolicy);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
def->track = -1;
|
|
track = virXMLPropString(node, "track");
|
|
if (track != NULL) {
|
|
if ((def->track = virDomainTimerTrackTypeFromString(track)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown timer track '%s'"), track);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ret = virXPathULongLong("string(./@frequency)", ctxt, &def->frequency);
|
|
if (ret == -1) {
|
|
def->frequency = 0;
|
|
} else if (ret < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("invalid timer frequency"));
|
|
goto error;
|
|
}
|
|
|
|
def->mode = -1;
|
|
mode = virXMLPropString(node, "mode");
|
|
if (mode != NULL) {
|
|
if ((def->mode = virDomainTimerModeTypeFromString(mode)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown timer mode '%s'"), mode);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
catchup = virXPathNode("./catchup", ctxt);
|
|
if (catchup != NULL) {
|
|
ret = virXPathULong("string(./catchup/@threshold)", ctxt,
|
|
&def->catchup.threshold);
|
|
if (ret == -1) {
|
|
def->catchup.threshold = 0;
|
|
} else if (ret < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("invalid catchup threshold"));
|
|
goto error;
|
|
}
|
|
|
|
ret = virXPathULong("string(./catchup/@slew)", ctxt, &def->catchup.slew);
|
|
if (ret == -1) {
|
|
def->catchup.slew = 0;
|
|
} else if (ret < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("invalid catchup slew"));
|
|
goto error;
|
|
}
|
|
|
|
ret = virXPathULong("string(./catchup/@limit)", ctxt, &def->catchup.limit);
|
|
if (ret == -1) {
|
|
def->catchup.limit = 0;
|
|
} else if (ret < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("invalid catchup limit"));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
VIR_FREE(def);
|
|
return def;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainGraphicsAuthDefParseXML(xmlNodePtr node,
|
|
virDomainGraphicsAuthDef *def,
|
|
int type)
|
|
{
|
|
g_autofree char *validTo = NULL;
|
|
g_autofree char *connected = virXMLPropString(node, "connected");
|
|
|
|
def->passwd = virXMLPropString(node, "passwd");
|
|
|
|
if (!def->passwd)
|
|
return 0;
|
|
|
|
validTo = virXMLPropString(node, "passwdValidTo");
|
|
if (validTo) {
|
|
g_autoptr(GDateTime) then = NULL;
|
|
g_autoptr(GTimeZone) tz = g_time_zone_new_utc();
|
|
char *tmp;
|
|
int year, mon, mday, hour, min, sec;
|
|
|
|
/* Expect: YYYY-MM-DDTHH:MM:SS (%d-%d-%dT%d:%d:%d) eg 2010-11-28T14:29:01 */
|
|
if (/* year */
|
|
virStrToLong_i(validTo, &tmp, 10, &year) < 0 || *tmp != '-' ||
|
|
/* month */
|
|
virStrToLong_i(tmp+1, &tmp, 10, &mon) < 0 || *tmp != '-' ||
|
|
/* day */
|
|
virStrToLong_i(tmp+1, &tmp, 10, &mday) < 0 || *tmp != 'T' ||
|
|
/* hour */
|
|
virStrToLong_i(tmp+1, &tmp, 10, &hour) < 0 || *tmp != ':' ||
|
|
/* minute */
|
|
virStrToLong_i(tmp+1, &tmp, 10, &min) < 0 || *tmp != ':' ||
|
|
/* second */
|
|
virStrToLong_i(tmp+1, &tmp, 10, &sec) < 0 || *tmp != '\0') {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse password validity time '%s', expect YYYY-MM-DDTHH:MM:SS"),
|
|
validTo);
|
|
VIR_FREE(def->passwd);
|
|
return -1;
|
|
}
|
|
|
|
then = g_date_time_new(tz, year, mon, mday, hour, min, sec);
|
|
def->validTo = (time_t)g_date_time_to_unix(then);
|
|
def->expires = true;
|
|
}
|
|
|
|
if (connected) {
|
|
int action = virDomainGraphicsAuthConnectedTypeFromString(connected);
|
|
if (action <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown connected value %s"),
|
|
connected);
|
|
return -1;
|
|
}
|
|
|
|
/* VNC supports connected='keep' only */
|
|
if (type == VIR_DOMAIN_GRAPHICS_TYPE_VNC &&
|
|
action != VIR_DOMAIN_GRAPHICS_AUTH_CONNECTED_KEEP) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("VNC supports connected='keep' only"));
|
|
return -1;
|
|
}
|
|
|
|
def->connected = action;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainGraphicsListenDefParseXML:
|
|
* @def: listen def pointer to be filled
|
|
* @graphics: graphics def pointer
|
|
* @node: xml node of <listen/> element
|
|
* @parent: xml node of <graphics/> element
|
|
* @flags: bit-wise or of VIR_DOMAIN_DEF_PARSE_*
|
|
*
|
|
* Parses current <listen/> element from @node to @def. For backward
|
|
* compatibility the @parent element should contain node of <graphics/> element
|
|
* for the first <listen/> element in order to validate attributes from both
|
|
* elements.
|
|
*/
|
|
static int
|
|
virDomainGraphicsListenDefParseXML(virDomainGraphicsListenDef *def,
|
|
virDomainGraphicsDef *graphics,
|
|
xmlNodePtr node,
|
|
xmlNodePtr parent,
|
|
unsigned int flags)
|
|
{
|
|
int ret = -1;
|
|
const char *graphicsType = virDomainGraphicsTypeToString(graphics->type);
|
|
g_autofree char *address = virXMLPropString(node, "address");
|
|
g_autofree char *network = virXMLPropString(node, "network");
|
|
g_autofree char *socketPath = virXMLPropString(node, "socket");
|
|
g_autofree char *autoGenerated = virXMLPropString(node, "autoGenerated");
|
|
g_autofree char *addressCompat = NULL;
|
|
g_autofree char *socketCompat = NULL;
|
|
|
|
if (parent) {
|
|
addressCompat = virXMLPropString(parent, "listen");
|
|
socketCompat = virXMLPropString(parent, "socket");
|
|
}
|
|
|
|
if (virXMLPropEnum(node, "type", virDomainGraphicsListenTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &def->type) < 0)
|
|
goto error;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET:
|
|
if (graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_VNC &&
|
|
graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_SPICE) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("listen type 'socket' is not available for "
|
|
"graphics type '%s'"), graphicsType);
|
|
goto error;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE:
|
|
if (graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_SPICE &&
|
|
graphics->type != VIR_DOMAIN_GRAPHICS_TYPE_VNC) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("listen type 'none' is not available for "
|
|
"graphics type '%s'"), graphicsType);
|
|
goto error;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS:
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK:
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS) {
|
|
if (address && addressCompat && STRNEQ(address, addressCompat)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("graphics 'listen' attribute '%s' must match "
|
|
"'address' attribute of first listen element "
|
|
"(found '%s')"), addressCompat, address);
|
|
goto error;
|
|
}
|
|
|
|
if (!address)
|
|
address = g_steal_pointer(&addressCompat);
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET) {
|
|
if (socketPath && socketCompat && STRNEQ(socketPath, socketCompat)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("graphics 'socket' attribute '%s' must match "
|
|
"'socket' attribute of first listen element "
|
|
"(found '%s')"), socketCompat, socketPath);
|
|
goto error;
|
|
}
|
|
|
|
if (!socketPath)
|
|
socketPath = g_steal_pointer(&socketCompat);
|
|
}
|
|
|
|
if (address && address[0] &&
|
|
(def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS ||
|
|
(def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK &&
|
|
!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)))) {
|
|
def->address = g_steal_pointer(&address);
|
|
}
|
|
|
|
if (network && network[0]) {
|
|
if (def->type != VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("'network' attribute is valid only for listen "
|
|
"type 'network'"));
|
|
goto error;
|
|
}
|
|
def->network = g_steal_pointer(&network);
|
|
}
|
|
|
|
if (socketPath && socketPath[0]) {
|
|
if (def->type != VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("'socket' attribute is valid only for listen "
|
|
"type 'socket'"));
|
|
goto error;
|
|
}
|
|
def->socket = g_steal_pointer(&socketPath);
|
|
}
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_STATUS) {
|
|
int tmp;
|
|
if (virXMLPropInt(node, "fromConfig", 10, VIR_XML_PROP_NONE, &tmp, 0) < 0)
|
|
return -1;
|
|
def->fromConfig = tmp != 0;
|
|
}
|
|
|
|
if (autoGenerated &&
|
|
flags & VIR_DOMAIN_DEF_PARSE_STATUS) {
|
|
if (virStringParseYesNo(autoGenerated, &def->autoGenerated) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid autoGenerated value: %s"),
|
|
autoGenerated);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
error:
|
|
if (ret < 0)
|
|
virDomainGraphicsListenDefClear(def);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainGraphicsListensParseXML(virDomainGraphicsDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainGraphicsListenDef newListen = {0};
|
|
int nListens;
|
|
int ret = -1;
|
|
g_autofree xmlNodePtr *listenNodes = NULL;
|
|
g_autofree char *socketPath = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
/* parse the <listen> subelements for graphics types that support it */
|
|
nListens = virXPathNodeSet("./listen", ctxt, &listenNodes);
|
|
if (nListens < 0)
|
|
goto cleanup;
|
|
|
|
if (nListens > 0) {
|
|
size_t i;
|
|
|
|
def->listens = g_new0(virDomainGraphicsListenDef, nListens);
|
|
|
|
for (i = 0; i < nListens; i++) {
|
|
if (virDomainGraphicsListenDefParseXML(&def->listens[i], def,
|
|
listenNodes[i],
|
|
i == 0 ? node : NULL,
|
|
flags) < 0)
|
|
goto cleanup;
|
|
|
|
def->nListens++;
|
|
}
|
|
}
|
|
|
|
/* If no <listen/> element was found in XML for backward compatibility
|
|
* we should try to parse 'listen' or 'socket' attribute from <graphics/>
|
|
* element. */
|
|
if (def->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC)
|
|
socketPath = virXMLPropString(node, "socket");
|
|
|
|
if (socketPath) {
|
|
newListen.type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET;
|
|
newListen.socket = g_steal_pointer(&socketPath);
|
|
} else {
|
|
newListen.type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS;
|
|
newListen.address = virXMLPropString(node, "listen");
|
|
if (STREQ_NULLABLE(newListen.address, ""))
|
|
VIR_FREE(newListen.address);
|
|
}
|
|
|
|
/* If no <listen/> element was found add a new one created by parsing
|
|
* <graphics/> element. */
|
|
if (def->nListens == 0) {
|
|
if (VIR_APPEND_ELEMENT(def->listens, def->nListens, newListen) < 0)
|
|
goto cleanup;
|
|
} else {
|
|
virDomainGraphicsListenDef *glisten = &def->listens[0];
|
|
|
|
/* If the first <listen/> element is 'address' or 'network' and we found
|
|
* 'socket' attribute inside <graphics/> element for backward
|
|
* compatibility we need to replace the first listen by
|
|
* <listen type='socket' .../> element based on the 'socket' attribute. */
|
|
if ((glisten->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS ||
|
|
glisten->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK) &&
|
|
newListen.type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET) {
|
|
virDomainGraphicsListenDefClear(glisten);
|
|
*glisten = newListen;
|
|
memset(&newListen, 0, sizeof(newListen));
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virDomainGraphicsListenDefClear(&newListen);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainGraphicsDefParseXMLVNC(virDomainGraphicsDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autofree char *port = virXMLPropString(node, "port");
|
|
g_autofree char *websocketGenerated = virXMLPropString(node, "websocketGenerated");
|
|
g_autofree char *autoport = virXMLPropString(node, "autoport");
|
|
xmlNodePtr audioNode;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
if (virDomainGraphicsListensParseXML(def, node, ctxt, flags) < 0)
|
|
return -1;
|
|
|
|
if (port) {
|
|
if (virStrToLong_i(port, NULL, 10, &def->data.vnc.port) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse vnc port %s"), port);
|
|
return -1;
|
|
}
|
|
/* Legacy compat syntax, used -1 for auto-port */
|
|
if (def->data.vnc.port == -1) {
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)
|
|
def->data.vnc.port = 0;
|
|
def->data.vnc.autoport = true;
|
|
}
|
|
} else {
|
|
def->data.vnc.port = 0;
|
|
def->data.vnc.autoport = true;
|
|
}
|
|
|
|
if (autoport) {
|
|
ignore_value(virStringParseYesNo(autoport, &def->data.vnc.autoport));
|
|
|
|
if (def->data.vnc.autoport && flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)
|
|
def->data.vnc.port = 0;
|
|
}
|
|
|
|
if (virXMLPropInt(node, "websocket", 10, VIR_XML_PROP_NONE,
|
|
&def->data.vnc.websocket, 0) < 0)
|
|
return -1;
|
|
|
|
if (websocketGenerated)
|
|
ignore_value(virStringParseYesNo(websocketGenerated,
|
|
&def->data.vnc.websocketGenerated));
|
|
|
|
if (virXMLPropEnum(node, "sharePolicy",
|
|
virDomainGraphicsVNCSharePolicyTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->data.vnc.sharePolicy) < 0)
|
|
return -1;
|
|
|
|
if ((virXMLPropTristateBool(node, "powerControl", VIR_XML_PROP_NONE,
|
|
&def->data.vnc.powerControl)) < 0)
|
|
return -1;
|
|
|
|
def->data.vnc.keymap = virXMLPropString(node, "keymap");
|
|
|
|
ctxt->node = node;
|
|
audioNode = virXPathNode("./audio", ctxt);
|
|
if (audioNode) {
|
|
if (virXMLPropUInt(audioNode, "id", 10,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&def->data.vnc.audioId) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainGraphicsAuthDefParseXML(node, &def->data.vnc.auth,
|
|
def->type) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainGraphicsDefParseXMLSDL(virDomainGraphicsDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr glNode;
|
|
virTristateBool fullscreen;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropTristateBool(node, "fullscreen", VIR_XML_PROP_NONE,
|
|
&fullscreen) < 0)
|
|
return -1;
|
|
|
|
def->data.sdl.fullscreen = fullscreen == VIR_TRISTATE_BOOL_YES;
|
|
def->data.sdl.xauth = virXMLPropString(node, "xauth");
|
|
def->data.sdl.display = virXMLPropString(node, "display");
|
|
|
|
if ((glNode = virXPathNode("./gl", ctxt))) {
|
|
if (virXMLPropTristateBool(glNode, "enable", VIR_XML_PROP_REQUIRED,
|
|
&def->data.sdl.gl) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainGraphicsDefParseXMLRDP(virDomainGraphicsDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autofree char *port = virXMLPropString(node, "port");
|
|
g_autofree char *autoport = virXMLPropString(node, "autoport");
|
|
g_autofree char *replaceUser = virXMLPropString(node, "replaceUser");
|
|
g_autofree char *multiUser = virXMLPropString(node, "multiUser");
|
|
|
|
if (virDomainGraphicsListensParseXML(def, node, ctxt, flags) < 0)
|
|
return -1;
|
|
|
|
if (port) {
|
|
if (virStrToLong_i(port, NULL, 10, &def->data.rdp.port) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse rdp port %s"), port);
|
|
return -1;
|
|
}
|
|
/* Legacy compat syntax, used -1 for auto-port */
|
|
if (def->data.rdp.port == -1)
|
|
def->data.rdp.autoport = true;
|
|
|
|
} else {
|
|
def->data.rdp.port = 0;
|
|
def->data.rdp.autoport = true;
|
|
}
|
|
|
|
if (STREQ_NULLABLE(autoport, "yes"))
|
|
def->data.rdp.autoport = true;
|
|
|
|
if (def->data.rdp.autoport && (flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))
|
|
def->data.rdp.port = 0;
|
|
|
|
if (STREQ_NULLABLE(replaceUser, "yes"))
|
|
def->data.rdp.replaceUser = true;
|
|
|
|
if (STREQ_NULLABLE(multiUser, "yes"))
|
|
def->data.rdp.multiUser = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainGraphicsDefParseXMLDesktop(virDomainGraphicsDef *def,
|
|
xmlNodePtr node)
|
|
{
|
|
virTristateBool fullscreen;
|
|
|
|
if (virXMLPropTristateBool(node, "fullscreen", VIR_XML_PROP_NONE,
|
|
&fullscreen) < 0)
|
|
return -1;
|
|
|
|
def->data.desktop.fullscreen = fullscreen == VIR_TRISTATE_BOOL_YES;
|
|
def->data.desktop.display = virXMLPropString(node, "display");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainGraphicsDefParseXMLSpice(virDomainGraphicsDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autofree xmlNodePtr *node_list = NULL;
|
|
int n = 0;
|
|
size_t i = 0;
|
|
virTristateBool autoport;
|
|
xmlNodePtr cur;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virDomainGraphicsListensParseXML(def, node, ctxt, flags) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropInt(node, "port", 10, VIR_XML_PROP_NONE,
|
|
&def->data.spice.port, 0) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropInt(node, "tlsPort", 10, VIR_XML_PROP_NONE,
|
|
&def->data.spice.tlsPort, 0) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateBool(node, "autoport", VIR_XML_PROP_NONE,
|
|
&autoport) < 0)
|
|
return -1;
|
|
def->data.spice.autoport = autoport == VIR_TRISTATE_BOOL_YES;
|
|
|
|
def->data.spice.defaultMode = VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_ANY;
|
|
if (virXMLPropEnum(node, "defaultMode",
|
|
virDomainGraphicsSpiceChannelModeTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->data.spice.defaultMode) < 0)
|
|
return -1;
|
|
|
|
if (def->data.spice.port == -1 && def->data.spice.tlsPort == -1) {
|
|
/* Legacy compat syntax, used -1 for auto-port */
|
|
def->data.spice.autoport = true;
|
|
}
|
|
|
|
if (def->data.spice.autoport && (flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)) {
|
|
def->data.spice.port = 0;
|
|
def->data.spice.tlsPort = 0;
|
|
}
|
|
|
|
def->data.spice.keymap = virXMLPropString(node, "keymap");
|
|
|
|
if (virDomainGraphicsAuthDefParseXML(node, &def->data.spice.auth,
|
|
def->type) < 0)
|
|
return -1;
|
|
|
|
if ((n = virXPathNodeSet("./channel", ctxt, &node_list)) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainGraphicsSpiceChannelName name;
|
|
virDomainGraphicsSpiceChannelMode mode;
|
|
|
|
if (virXMLPropEnum(node_list[i], "name",
|
|
virDomainGraphicsSpiceChannelNameTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &name) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(node_list[i], "mode",
|
|
virDomainGraphicsSpiceChannelModeTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &mode) < 0)
|
|
return -1;
|
|
|
|
def->data.spice.channels[name] = mode;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./image", ctxt))) {
|
|
virDomainGraphicsSpiceImageCompression compression;
|
|
|
|
if (virXMLPropEnum(cur, "compression",
|
|
virDomainGraphicsSpiceImageCompressionTypeFromString,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&compression) < 0)
|
|
return -1;
|
|
|
|
def->data.spice.image = compression;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./jpeg", ctxt))) {
|
|
virDomainGraphicsSpiceJpegCompression compression;
|
|
|
|
if (virXMLPropEnum(cur, "compression",
|
|
virDomainGraphicsSpiceJpegCompressionTypeFromString,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&compression) < 0)
|
|
return -1;
|
|
|
|
def->data.spice.jpeg = compression;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./zlib", ctxt))) {
|
|
virDomainGraphicsSpiceZlibCompression compression;
|
|
|
|
if (virXMLPropEnum(cur, "compression",
|
|
virDomainGraphicsSpiceZlibCompressionTypeFromString,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&compression) < 0)
|
|
return -1;
|
|
|
|
def->data.spice.zlib = compression;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./playback", ctxt))) {
|
|
if (virXMLPropTristateSwitch(cur, "compression",
|
|
VIR_XML_PROP_REQUIRED,
|
|
&def->data.spice.playback) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./streaming", ctxt))) {
|
|
virDomainGraphicsSpiceStreamingMode mode;
|
|
|
|
if (virXMLPropEnum(cur, "mode",
|
|
virDomainGraphicsSpiceStreamingModeTypeFromString,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&mode) < 0)
|
|
return -1;
|
|
|
|
def->data.spice.streaming = mode;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./clipboard", ctxt))) {
|
|
if (virXMLPropTristateBool(cur, "copypaste",
|
|
VIR_XML_PROP_REQUIRED,
|
|
&def->data.spice.copypaste) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./filetransfer", ctxt))) {
|
|
if (virXMLPropTristateBool(cur, "enable",
|
|
VIR_XML_PROP_REQUIRED,
|
|
&def->data.spice.filetransfer) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./gl", ctxt))) {
|
|
def->data.spice.rendernode = virXMLPropString(cur, "rendernode");
|
|
|
|
if (virXMLPropTristateBool(cur, "enable",
|
|
VIR_XML_PROP_REQUIRED,
|
|
&def->data.spice.gl) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if ((cur = virXPathNode("./mouse", ctxt))) {
|
|
if (virXMLPropEnum(cur, "mode",
|
|
virDomainGraphicsSpiceMouseModeTypeFromString,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&def->data.spice.mousemode) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainGraphicsDefParseXMLEGLHeadless(virDomainGraphicsDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr glNode;
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((glNode = virXPathNode("./gl", ctxt)))
|
|
def->data.egl_headless.rendernode = virXMLPropString(glNode,
|
|
"rendernode");
|
|
}
|
|
|
|
|
|
virDomainGraphicsDef *
|
|
virDomainGraphicsDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainGraphicsDef *def = NULL;
|
|
|
|
def = g_new0(virDomainGraphicsDef, 1);
|
|
|
|
if (xmlopt && xmlopt->privateData.graphicsNew &&
|
|
!(def->privateData = xmlopt->privateData.graphicsNew())) {
|
|
VIR_FREE(def);
|
|
def = NULL;
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
|
|
virDomainNetDef *
|
|
virDomainNetDefNew(virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainNetDef *def = NULL;
|
|
|
|
def = g_new0(virDomainNetDef, 1);
|
|
|
|
if (xmlopt && xmlopt->privateData.networkNew &&
|
|
!(def->privateData = xmlopt->privateData.networkNew())) {
|
|
virDomainNetDefFree(def);
|
|
def = NULL;
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
|
|
/* Parse the XML definition for a graphics device */
|
|
static virDomainGraphicsDef *
|
|
virDomainGraphicsDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainGraphicsDef *def;
|
|
|
|
if (!(def = virDomainGraphicsDefNew(xmlopt)))
|
|
return NULL;
|
|
|
|
if (virXMLPropEnum(node, "type", virDomainGraphicsTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &def->type) < 0)
|
|
goto error;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
|
|
if (virDomainGraphicsDefParseXMLVNC(def, node, ctxt, flags) < 0)
|
|
goto error;
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
|
|
if (virDomainGraphicsDefParseXMLSDL(def, node, ctxt) < 0)
|
|
goto error;
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
|
|
if (virDomainGraphicsDefParseXMLRDP(def, node, ctxt, flags) < 0)
|
|
goto error;
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
|
|
if (virDomainGraphicsDefParseXMLDesktop(def, node) < 0)
|
|
goto error;
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
|
|
if (virDomainGraphicsDefParseXMLSpice(def, node, ctxt, flags) < 0)
|
|
goto error;
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
|
|
virDomainGraphicsDefParseXMLEGLHeadless(def, node, ctxt);
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainGraphicsDefFree(def);
|
|
def = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainSoundCodecDef *
|
|
virDomainSoundCodecDefParseXML(xmlNodePtr node)
|
|
{
|
|
virDomainSoundCodecDef *def;
|
|
g_autofree char *type = NULL;
|
|
|
|
def = g_new0(virDomainSoundCodecDef, 1);
|
|
|
|
type = virXMLPropString(node, "type");
|
|
if ((def->type = virDomainSoundCodecTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown codec type '%s'"), type);
|
|
goto error;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainSoundCodecDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainSoundDef *
|
|
virDomainSoundDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainSoundDef *def;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr audioNode;
|
|
|
|
def = g_new0(virDomainSoundDef, 1);
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropEnum(node, "model", virDomainSoundModelTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &def->model) < 0)
|
|
goto error;
|
|
|
|
if (virDomainSoundModelSupportsCodecs(def)) {
|
|
int ncodecs;
|
|
g_autofree xmlNodePtr *codecNodes = NULL;
|
|
|
|
/* parse the <codec> subelements for sound models that support it */
|
|
ncodecs = virXPathNodeSet("./codec", ctxt, &codecNodes);
|
|
if (ncodecs < 0)
|
|
goto error;
|
|
|
|
if (ncodecs > 0) {
|
|
size_t i;
|
|
|
|
def->codecs = g_new0(virDomainSoundCodecDef *, ncodecs);
|
|
|
|
for (i = 0; i < ncodecs; i++) {
|
|
virDomainSoundCodecDef *codec = virDomainSoundCodecDefParseXML(codecNodes[i]);
|
|
if (codec == NULL)
|
|
goto error;
|
|
|
|
codec->cad = def->ncodecs; /* that will do for now */
|
|
def->codecs[def->ncodecs++] = codec;
|
|
}
|
|
}
|
|
}
|
|
|
|
audioNode = virXPathNode("./audio", ctxt);
|
|
if (audioNode) {
|
|
if (virXMLPropUInt(audioNode, "id", 10,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&def->audioId) < 0)
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainSoundDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainSoundDefEquals(const virDomainSoundDef *a,
|
|
const virDomainSoundDef *b)
|
|
{
|
|
size_t i;
|
|
|
|
if (a->model != b->model)
|
|
return false;
|
|
|
|
if (a->ncodecs != b->ncodecs)
|
|
return false;
|
|
|
|
for (i = 0; i < a->ncodecs; i++) {
|
|
if (a->codecs[i]->type != b->codecs[i]->type)
|
|
return false;
|
|
}
|
|
|
|
if (a->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
!virDomainDeviceInfoAddressIsEqual(&a->info, &b->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
virDomainSoundDefFind(const virDomainDef *def,
|
|
const virDomainSoundDef *sound)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nsounds; i++) {
|
|
if (virDomainSoundDefEquals(sound, def->sounds[i]))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainAudioCommonParse(virDomainAudioIOCommon *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
xmlNodePtr settings;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt);
|
|
|
|
ctxt->node = node;
|
|
settings = virXPathNode("./settings", ctxt);
|
|
|
|
if (virXMLPropTristateBool(node, "mixingEngine", VIR_XML_PROP_NONE,
|
|
&def->mixingEngine) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateBool(node, "fixedSettings", VIR_XML_PROP_NONE,
|
|
&def->fixedSettings) < 0)
|
|
return -1;
|
|
|
|
if (def->fixedSettings == VIR_TRISTATE_BOOL_YES &&
|
|
def->mixingEngine != VIR_TRISTATE_BOOL_YES) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("fixed audio settings requires mixing engine"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropUInt(node, "voices", 10,
|
|
VIR_XML_PROP_NONZERO,
|
|
&def->voices) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(node, "bufferLength", 10,
|
|
VIR_XML_PROP_NONZERO,
|
|
&def->bufferLength) < 0)
|
|
return -1;
|
|
|
|
if (settings) {
|
|
if (def->fixedSettings != VIR_TRISTATE_BOOL_YES) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("audio settings specified without fixed settings flag"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropUInt(settings, "frequency", 10,
|
|
VIR_XML_PROP_NONZERO,
|
|
&def->frequency) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(settings, "channels", 10,
|
|
VIR_XML_PROP_NONZERO,
|
|
&def->channels) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(settings, "format",
|
|
virDomainAudioFormatTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->format) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainAudioALSAParse(virDomainAudioIOALSA *def,
|
|
xmlNodePtr node)
|
|
{
|
|
def->dev = virXMLPropString(node, "dev");
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainAudioCoreAudioParse(virDomainAudioIOCoreAudio *def,
|
|
xmlNodePtr node)
|
|
{
|
|
if (virXMLPropUInt(node, "bufferCount", 10, VIR_XML_PROP_NONE,
|
|
&def->bufferCount) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainAudioJackParse(virDomainAudioIOJack *def,
|
|
xmlNodePtr node)
|
|
{
|
|
def->serverName = virXMLPropString(node, "serverName");
|
|
def->clientName = virXMLPropString(node, "clientName");
|
|
def->connectPorts = virXMLPropString(node, "connectPorts");
|
|
|
|
if (virXMLPropTristateBool(node, "exactName", VIR_XML_PROP_NONE,
|
|
&def->exactName) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainAudioOSSParse(virDomainAudioIOOSS *def,
|
|
xmlNodePtr node)
|
|
{
|
|
def->dev = virXMLPropString(node, "dev");
|
|
|
|
if (virXMLPropTristateBool(node, "tryPoll", VIR_XML_PROP_NONE,
|
|
&def->tryPoll) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(node, "bufferCount", 10, VIR_XML_PROP_NONE,
|
|
&def->bufferCount) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainAudioPulseAudioParse(virDomainAudioIOPulseAudio *def,
|
|
xmlNodePtr node)
|
|
{
|
|
def->name = virXMLPropString(node, "name");
|
|
def->streamName = virXMLPropString(node, "streamName");
|
|
|
|
if (virXMLPropUInt(node, "latency", 10, VIR_XML_PROP_NONE,
|
|
&def->latency) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainAudioSDLParse(virDomainAudioIOSDL *def,
|
|
xmlNodePtr node)
|
|
{
|
|
if (virXMLPropUInt(node, "bufferCount", 10, VIR_XML_PROP_NONE,
|
|
&def->bufferCount) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virDomainAudioDef *
|
|
virDomainAudioDefParseXML(virDomainXMLOption *xmlopt G_GNUC_UNUSED,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
virDomainAudioDef *def;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr inputNode, outputNode;
|
|
|
|
def = g_new0(virDomainAudioDef, 1);
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropEnum(node, "type", virDomainAudioTypeTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &def->type) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropUInt(node, "id", 10, VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&def->id) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropUInt(node, "timerPeriod", 10, VIR_XML_PROP_NONZERO,
|
|
&def->timerPeriod) < 0)
|
|
goto error;
|
|
|
|
inputNode = virXPathNode("./input", ctxt);
|
|
outputNode = virXPathNode("./output", ctxt);
|
|
|
|
if (inputNode && virDomainAudioCommonParse(&def->input, inputNode, ctxt) < 0)
|
|
goto error;
|
|
if (outputNode && virDomainAudioCommonParse(&def->output, outputNode, ctxt) < 0)
|
|
goto error;
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_AUDIO_TYPE_NONE:
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_ALSA:
|
|
if (inputNode)
|
|
virDomainAudioALSAParse(&def->backend.alsa.input, inputNode);
|
|
if (outputNode)
|
|
virDomainAudioALSAParse(&def->backend.alsa.output, outputNode);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_COREAUDIO:
|
|
if (inputNode &&
|
|
virDomainAudioCoreAudioParse(&def->backend.coreaudio.input, inputNode) < 0)
|
|
goto error;
|
|
if (outputNode &&
|
|
virDomainAudioCoreAudioParse(&def->backend.coreaudio.output, outputNode) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_JACK:
|
|
if (inputNode &&
|
|
virDomainAudioJackParse(&def->backend.jack.input, inputNode) < 0)
|
|
goto error;
|
|
if (outputNode &&
|
|
virDomainAudioJackParse(&def->backend.jack.output, outputNode) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_OSS: {
|
|
int dspPolicySet;
|
|
|
|
if (virXMLPropTristateBool(node, "tryMMap", VIR_XML_PROP_NONE,
|
|
&def->backend.oss.tryMMap) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateBool(node, "exclusive", VIR_XML_PROP_NONE,
|
|
&def->backend.oss.exclusive) < 0)
|
|
goto error;
|
|
|
|
if ((dspPolicySet = virXMLPropInt(node, "dspPolicy", 10, VIR_XML_PROP_NONE,
|
|
&def->backend.oss.dspPolicy, 0)) < 0)
|
|
goto error;
|
|
|
|
if (dspPolicySet != 0) {
|
|
if (def->backend.oss.dspPolicy < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse 'dspPolicy' value '%i'"),
|
|
def->backend.oss.dspPolicy);
|
|
goto error;
|
|
}
|
|
def->backend.oss.dspPolicySet = true;
|
|
}
|
|
|
|
if (inputNode &&
|
|
virDomainAudioOSSParse(&def->backend.oss.input, inputNode) < 0)
|
|
goto error;
|
|
if (outputNode &&
|
|
virDomainAudioOSSParse(&def->backend.oss.output, outputNode) < 0)
|
|
goto error;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_PULSEAUDIO:
|
|
def->backend.pulseaudio.serverName = virXMLPropString(node, "serverName");
|
|
|
|
if (inputNode &&
|
|
virDomainAudioPulseAudioParse(&def->backend.pulseaudio.input, inputNode) < 0)
|
|
goto error;
|
|
if (outputNode &&
|
|
virDomainAudioPulseAudioParse(&def->backend.pulseaudio.output, outputNode) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_SDL: {
|
|
if (virXMLPropEnum(node, "driver", virDomainAudioSDLDriverTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->backend.sdl.driver) < 0)
|
|
goto error;
|
|
|
|
if (inputNode &&
|
|
virDomainAudioSDLParse(&def->backend.sdl.input, inputNode) < 0)
|
|
goto error;
|
|
if (outputNode &&
|
|
virDomainAudioSDLParse(&def->backend.sdl.output, outputNode) < 0)
|
|
goto error;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_SPICE:
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_FILE:
|
|
def->backend.file.path = virXMLPropString(node, "path");
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainAudioType, def->type);
|
|
break;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainAudioDefFree(def);
|
|
return NULL;
|
|
}
|
|
static virDomainWatchdogDef *
|
|
virDomainWatchdogDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainWatchdogDef *def;
|
|
g_autofree char *model = NULL;
|
|
g_autofree char *action = NULL;
|
|
|
|
def = g_new0(virDomainWatchdogDef, 1);
|
|
|
|
model = virXMLPropString(node, "model");
|
|
if (model == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("watchdog must contain model name"));
|
|
goto error;
|
|
}
|
|
def->model = virDomainWatchdogModelTypeFromString(model);
|
|
if (def->model < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown watchdog model '%s'"), model);
|
|
goto error;
|
|
}
|
|
|
|
action = virXMLPropString(node, "action");
|
|
if (action == NULL) {
|
|
def->action = VIR_DOMAIN_WATCHDOG_ACTION_RESET;
|
|
} else {
|
|
def->action = virDomainWatchdogActionTypeFromString(action);
|
|
if (def->action < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown watchdog action '%s'"), action);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainWatchdogDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainRNGDef *
|
|
virDomainRNGDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainRNGDef *def;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int nbackends;
|
|
g_autofree xmlNodePtr *backends = NULL;
|
|
g_autofree char *model = NULL;
|
|
g_autofree char *backend = NULL;
|
|
g_autofree char *type = NULL;
|
|
|
|
def = g_new0(virDomainRNGDef, 1);
|
|
|
|
if (!(model = virXMLPropString(node, "model"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s", _("missing RNG device model"));
|
|
goto error;
|
|
}
|
|
|
|
if ((def->model = virDomainRNGModelTypeFromString(model)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unknown RNG model '%s'"), model);
|
|
goto error;
|
|
}
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virXPathUInt("string(./rate/@bytes)", ctxt, &def->rate) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid RNG rate bytes value"));
|
|
goto error;
|
|
}
|
|
|
|
if (def->rate > 0 &&
|
|
virXPathUInt("string(./rate/@period)", ctxt, &def->period) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid RNG rate period value"));
|
|
goto error;
|
|
}
|
|
|
|
if ((nbackends = virXPathNodeSet("./backend", ctxt, &backends)) < 0)
|
|
goto error;
|
|
|
|
if (nbackends != 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one RNG backend is supported"));
|
|
goto error;
|
|
}
|
|
|
|
if (!(backend = virXMLPropString(backends[0], "model"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing RNG device backend model"));
|
|
goto error;
|
|
}
|
|
|
|
if ((def->backend = virDomainRNGBackendTypeFromString(backend)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown RNG backend model '%s'"), backend);
|
|
goto error;
|
|
}
|
|
|
|
switch ((virDomainRNGBackend) def->backend) {
|
|
case VIR_DOMAIN_RNG_BACKEND_RANDOM:
|
|
def->source.file = virXPathString("string(./backend)", ctxt);
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_EGD:
|
|
if (!(type = virXMLPropString(backends[0], "type"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing EGD backend type"));
|
|
goto error;
|
|
}
|
|
|
|
if (!(def->source.chardev = virDomainChrSourceDefNew(xmlopt)))
|
|
goto error;
|
|
|
|
def->source.chardev->type = virDomainChrTypeFromString(type);
|
|
if (def->source.chardev->type < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown backend type '%s' for egd"),
|
|
type);
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainChrSourceDefParseXML(def->source.chardev,
|
|
backends[0], flags,
|
|
NULL, ctxt) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_BUILTIN:
|
|
case VIR_DOMAIN_RNG_BACKEND_LAST:
|
|
break;
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
goto error;
|
|
|
|
if (virDomainVirtioOptionsParseXML(virXPathNode("./driver", ctxt),
|
|
&def->virtio) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainRNGDefFree(def);
|
|
def = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainMemballoonDef *
|
|
virDomainMemballoonDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainMemballoonDef *def;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr stats;
|
|
|
|
ctxt->node = node;
|
|
|
|
def = g_new0(virDomainMemballoonDef, 1);
|
|
|
|
if (virXMLPropEnum(node, "model", virDomainMemballoonModelTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &def->model) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(node, "autodeflate", VIR_XML_PROP_NONE,
|
|
&def->autodeflate) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateSwitch(node, "freePageReporting",
|
|
VIR_XML_PROP_NONE,
|
|
&def->free_page_reporting) < 0)
|
|
goto error;
|
|
|
|
if ((stats = virXPathNode("./stats", ctxt))) {
|
|
if (virXMLPropInt(stats, "period", 0, VIR_XML_PROP_NONE,
|
|
&def->period, 0) < 0)
|
|
goto error;
|
|
|
|
if (def->period < 0)
|
|
def->period = 0;
|
|
}
|
|
|
|
if (def->model == VIR_DOMAIN_MEMBALLOON_MODEL_NONE)
|
|
VIR_DEBUG("Ignoring device address for none model Memballoon");
|
|
else if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt,
|
|
&def->info, flags) < 0)
|
|
goto error;
|
|
|
|
if (virDomainVirtioOptionsParseXML(virXPathNode("./driver", ctxt),
|
|
&def->virtio) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainMemballoonDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
static virDomainNVRAMDef *
|
|
virDomainNVRAMDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainNVRAMDef *def;
|
|
|
|
def = g_new0(virDomainNVRAMDef, 1);
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainNVRAMDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
static virDomainShmemDef *
|
|
virDomainShmemDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(virDomainShmemDef) def = g_new0(virDomainShmemDef, 1);
|
|
xmlNodePtr model;
|
|
xmlNodePtr msi;
|
|
xmlNodePtr server;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((model = virXPathNode("./model", ctxt))) {
|
|
/* If there's none, we will automatically have the first one
|
|
* (as default). Unfortunately this has to be done for
|
|
* compatibility reasons. */
|
|
if (virXMLPropEnum(model, "type", virDomainShmemModelTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->model) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (!(def->name = virXMLPropString(node, "name"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("shmem element must contain 'name' attribute"));
|
|
return NULL;
|
|
}
|
|
|
|
if (def->model != VIR_DOMAIN_SHMEM_MODEL_IVSHMEM) {
|
|
if (virXMLPropEnum(node, "role", virDomainShmemRoleTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->role) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (virParseScaledValue("./size[1]", NULL, ctxt,
|
|
&def->size, 1, ULLONG_MAX, false) < 0)
|
|
return NULL;
|
|
|
|
if ((server = virXPathNode("./server[1]", ctxt))) {
|
|
g_autofree char *tmp = NULL;
|
|
|
|
def->server.enabled = true;
|
|
def->server.chr.type = VIR_DOMAIN_CHR_TYPE_UNIX;
|
|
def->server.chr.data.nix.listen = false;
|
|
if ((tmp = virXMLPropString(server, "path")))
|
|
def->server.chr.data.nix.path = virFileSanitizePath(tmp);
|
|
}
|
|
|
|
if ((msi = virXPathNode("./msi[1]", ctxt))) {
|
|
def->msi.enabled = true;
|
|
|
|
if (virXMLPropUInt(msi, "vectors", 0, VIR_XML_PROP_NONE,
|
|
&def->msi.vectors) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateSwitch(msi, "ioeventfd", VIR_XML_PROP_NONE,
|
|
&def->msi.ioeventfd) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
/* msi option is only relevant with a server */
|
|
if (def->msi.enabled && !def->server.enabled) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("msi option is only supported with a server"));
|
|
return NULL;
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
return NULL;
|
|
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
static int
|
|
virSysinfoBIOSParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virSysinfoBIOSDef **bios)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int ret = -1;
|
|
virSysinfoBIOSDef *def;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!virXMLNodeNameEqual(node, "bios")) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("XML does not contain expected 'bios' element"));
|
|
return ret;
|
|
}
|
|
|
|
def = g_new0(virSysinfoBIOSDef, 1);
|
|
|
|
def->vendor = virXPathString("string(entry[@name='vendor'])", ctxt);
|
|
def->version = virXPathString("string(entry[@name='version'])", ctxt);
|
|
def->date = virXPathString("string(entry[@name='date'])", ctxt);
|
|
def->release = virXPathString("string(entry[@name='release'])", ctxt);
|
|
if (def->date != NULL) {
|
|
char *ptr;
|
|
int month, day, year;
|
|
|
|
/* Validate just the format of the date
|
|
* Expect mm/dd/yyyy or mm/dd/yy,
|
|
* where yy must be 00->99 and would be assumed to be 19xx
|
|
* a yyyy date should be 1900 and beyond
|
|
*/
|
|
if (virStrToLong_i(def->date, &ptr, 10, &month) < 0 ||
|
|
*ptr != '/' ||
|
|
virStrToLong_i(ptr + 1, &ptr, 10, &day) < 0 ||
|
|
*ptr != '/' ||
|
|
virStrToLong_i(ptr + 1, &ptr, 10, &year) < 0 ||
|
|
*ptr != '\0' ||
|
|
(month < 1 || month > 12) ||
|
|
(day < 1 || day > 31) ||
|
|
(year < 0 || (year >= 100 && year < 1900))) {
|
|
virReportError(VIR_ERR_XML_DETAIL, "%s",
|
|
_("Invalid BIOS 'date' format"));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (!def->vendor && !def->version &&
|
|
!def->date && !def->release) {
|
|
virSysinfoBIOSDefFree(def);
|
|
def = NULL;
|
|
}
|
|
|
|
*bios = g_steal_pointer(&def);
|
|
ret = 0;
|
|
cleanup:
|
|
virSysinfoBIOSDefFree(def);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
virSysinfoSystemParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virSysinfoSystemDef **sysdef,
|
|
unsigned char *domUUID,
|
|
bool uuid_generated)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int ret = -1;
|
|
virSysinfoSystemDef *def;
|
|
g_autofree char *tmpUUID = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!virXMLNodeNameEqual(node, "system")) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("XML does not contain expected 'system' element"));
|
|
return ret;
|
|
}
|
|
|
|
def = g_new0(virSysinfoSystemDef, 1);
|
|
|
|
def->manufacturer =
|
|
virXPathString("string(entry[@name='manufacturer'])", ctxt);
|
|
def->product =
|
|
virXPathString("string(entry[@name='product'])", ctxt);
|
|
def->version =
|
|
virXPathString("string(entry[@name='version'])", ctxt);
|
|
def->serial =
|
|
virXPathString("string(entry[@name='serial'])", ctxt);
|
|
tmpUUID = virXPathString("string(entry[@name='uuid'])", ctxt);
|
|
if (tmpUUID) {
|
|
unsigned char uuidbuf[VIR_UUID_BUFLEN];
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
if (virUUIDParse(tmpUUID, uuidbuf) < 0) {
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
"%s", _("malformed <sysinfo> uuid element"));
|
|
goto cleanup;
|
|
}
|
|
if (uuid_generated) {
|
|
memcpy(domUUID, uuidbuf, VIR_UUID_BUFLEN);
|
|
} else if (memcmp(domUUID, uuidbuf, VIR_UUID_BUFLEN) != 0) {
|
|
virReportError(VIR_ERR_XML_DETAIL, "%s",
|
|
_("UUID mismatch between <uuid> and "
|
|
"<sysinfo>"));
|
|
goto cleanup;
|
|
}
|
|
/* Although we've validated the UUID as good, virUUIDParse() is
|
|
* lax with respect to allowing extraneous "-" and " ", but the
|
|
* underlying hypervisor may be less forgiving. Use virUUIDFormat()
|
|
* to validate format in xml is right. If not, then format it
|
|
* properly so that it's used correctly later.
|
|
*/
|
|
virUUIDFormat(uuidbuf, uuidstr);
|
|
def->uuid = g_strdup(uuidstr);
|
|
}
|
|
def->sku =
|
|
virXPathString("string(entry[@name='sku'])", ctxt);
|
|
def->family =
|
|
virXPathString("string(entry[@name='family'])", ctxt);
|
|
|
|
if (!def->manufacturer && !def->product && !def->version &&
|
|
!def->serial && !def->uuid && !def->sku && !def->family) {
|
|
virSysinfoSystemDefFree(def);
|
|
def = NULL;
|
|
}
|
|
|
|
*sysdef = g_steal_pointer(&def);
|
|
ret = 0;
|
|
cleanup:
|
|
virSysinfoSystemDefFree(def);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
virSysinfoBaseBoardParseXML(xmlXPathContextPtr ctxt,
|
|
virSysinfoBaseBoardDef **baseBoard,
|
|
size_t *nbaseBoard)
|
|
{
|
|
size_t i, nboards = 0;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int n;
|
|
g_autofree virSysinfoBaseBoardDef *boards = NULL;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
|
|
if ((n = virXPathNodeSet("./baseBoard", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (n)
|
|
boards = g_new0(virSysinfoBaseBoardDef, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virSysinfoBaseBoardDef *def = boards + nboards;
|
|
|
|
ctxt->node = nodes[i];
|
|
|
|
def->manufacturer =
|
|
virXPathString("string(entry[@name='manufacturer'])", ctxt);
|
|
def->product =
|
|
virXPathString("string(entry[@name='product'])", ctxt);
|
|
def->version =
|
|
virXPathString("string(entry[@name='version'])", ctxt);
|
|
def->serial =
|
|
virXPathString("string(entry[@name='serial'])", ctxt);
|
|
def->asset =
|
|
virXPathString("string(entry[@name='asset'])", ctxt);
|
|
def->location =
|
|
virXPathString("string(entry[@name='location'])", ctxt);
|
|
|
|
if (!def->manufacturer && !def->product && !def->version &&
|
|
!def->serial && !def->asset && !def->location) {
|
|
/* nada */
|
|
} else {
|
|
nboards++;
|
|
}
|
|
}
|
|
|
|
*baseBoard = g_steal_pointer(&boards);
|
|
*nbaseBoard = nboards;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virSysinfoOEMStringsParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virSysinfoOEMStringsDef **oem)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int ret = -1;
|
|
virSysinfoOEMStringsDef *def;
|
|
int nstrings;
|
|
size_t i;
|
|
g_autofree xmlNodePtr *strings = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
nstrings = virXPathNodeSet("./entry", ctxt, &strings);
|
|
if (nstrings < 0)
|
|
return -1;
|
|
if (nstrings == 0)
|
|
return 0;
|
|
|
|
def = g_new0(virSysinfoOEMStringsDef, 1);
|
|
|
|
def->values = g_new0(char *, nstrings);
|
|
|
|
def->nvalues = nstrings;
|
|
for (i = 0; i < nstrings; i++) {
|
|
if (!(def->values[i] = virXMLNodeContentString(strings[i])))
|
|
goto cleanup;
|
|
}
|
|
|
|
*oem = g_steal_pointer(&def);
|
|
ret = 0;
|
|
cleanup:
|
|
virSysinfoOEMStringsDefFree(def);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virSysinfoChassisParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virSysinfoChassisDef **chassisdef)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int ret = -1;
|
|
virSysinfoChassisDef *def;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!xmlStrEqual(node->name, BAD_CAST "chassis")) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("XML does not contain expected 'chassis' element"));
|
|
return ret;
|
|
}
|
|
|
|
def = g_new0(virSysinfoChassisDef, 1);
|
|
|
|
def->manufacturer =
|
|
virXPathString("string(entry[@name='manufacturer'])", ctxt);
|
|
def->version =
|
|
virXPathString("string(entry[@name='version'])", ctxt);
|
|
def->serial =
|
|
virXPathString("string(entry[@name='serial'])", ctxt);
|
|
def->asset =
|
|
virXPathString("string(entry[@name='asset'])", ctxt);
|
|
def->sku =
|
|
virXPathString("string(entry[@name='sku'])", ctxt);
|
|
|
|
if (!def->manufacturer && !def->version &&
|
|
!def->serial && !def->asset && !def->sku) {
|
|
virSysinfoChassisDefFree(def);
|
|
def = NULL;
|
|
}
|
|
|
|
*chassisdef = g_steal_pointer(&def);
|
|
ret = 0;
|
|
virSysinfoChassisDefFree(def);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virSysinfoParseSMBIOSDef(virSysinfoDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned char *domUUID,
|
|
bool uuid_generated)
|
|
{
|
|
xmlNodePtr tmpnode;
|
|
|
|
/* Extract BIOS related metadata */
|
|
if ((tmpnode = virXPathNode("./bios[1]", ctxt)) != NULL) {
|
|
if (virSysinfoBIOSParseXML(tmpnode, ctxt, &def->bios) < 0)
|
|
return -1;
|
|
}
|
|
|
|
/* Extract system related metadata */
|
|
if ((tmpnode = virXPathNode("./system[1]", ctxt)) != NULL) {
|
|
if (virSysinfoSystemParseXML(tmpnode, ctxt, &def->system,
|
|
domUUID, uuid_generated) < 0)
|
|
return -1;
|
|
}
|
|
|
|
/* Extract system base board metadata */
|
|
if (virSysinfoBaseBoardParseXML(ctxt, &def->baseBoard, &def->nbaseBoard) < 0)
|
|
return -1;
|
|
|
|
/* Extract chassis related metadata */
|
|
if ((tmpnode = virXPathNode("./chassis[1]", ctxt)) != NULL) {
|
|
if (virSysinfoChassisParseXML(tmpnode, ctxt, &def->chassis) < 0)
|
|
return -1;
|
|
}
|
|
|
|
/* Extract system related metadata */
|
|
if ((tmpnode = virXPathNode("./oemStrings[1]", ctxt)) != NULL) {
|
|
if (virSysinfoOEMStringsParseXML(tmpnode, ctxt, &def->oemStrings) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virSysinfoParseFWCfgDef(virSysinfoDef *def,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
int n;
|
|
size_t i;
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((n = virXPathNodeSet("./entry", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (n == 0)
|
|
return 0;
|
|
|
|
def->fw_cfgs = g_new0(virSysinfoFWCfgDef, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
g_autofree char *name = NULL;
|
|
g_autofree char *value = NULL;
|
|
g_autofree char *file = NULL;
|
|
g_autofree char *sanitizedFile = NULL;
|
|
|
|
if (!(name = virXMLPropString(nodes[i], "name"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Firmware entry is missing 'name' attribute"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(value = virXMLNodeContentString(nodes[i])))
|
|
return -1;
|
|
|
|
file = virXMLPropString(nodes[i], "file");
|
|
|
|
if (virStringIsEmpty(value))
|
|
VIR_FREE(value);
|
|
|
|
if (!value && !file) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Firmware entry must have either value or "
|
|
"'file' attribute"));
|
|
return -1;
|
|
}
|
|
|
|
if (file)
|
|
sanitizedFile = virFileSanitizePath(file);
|
|
|
|
def->fw_cfgs[i].name = g_steal_pointer(&name);
|
|
def->fw_cfgs[i].value = g_steal_pointer(&value);
|
|
def->fw_cfgs[i].file = g_steal_pointer(&sanitizedFile);
|
|
def->nfw_cfgs++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virSysinfoDef *
|
|
virSysinfoParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned char *domUUID,
|
|
bool uuid_generated)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virSysinfoDef *def;
|
|
g_autofree char *typeStr = NULL;
|
|
int type;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!virXMLNodeNameEqual(node, "sysinfo")) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("XML does not contain expected 'sysinfo' element"));
|
|
return NULL;
|
|
}
|
|
|
|
def = g_new0(virSysinfoDef, 1);
|
|
|
|
typeStr = virXMLPropString(node, "type");
|
|
if (typeStr == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("sysinfo must contain a type attribute"));
|
|
goto error;
|
|
}
|
|
if ((type = virSysinfoTypeFromString(typeStr)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown sysinfo type '%s'"), typeStr);
|
|
goto error;
|
|
}
|
|
def->type = type;
|
|
|
|
switch (def->type) {
|
|
case VIR_SYSINFO_SMBIOS:
|
|
if (virSysinfoParseSMBIOSDef(def, ctxt, domUUID, uuid_generated) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_SYSINFO_FWCFG:
|
|
if (virSysinfoParseFWCfgDef(def, node, ctxt) < 0)
|
|
goto error;
|
|
break;
|
|
|
|
case VIR_SYSINFO_LAST:
|
|
break;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virSysinfoDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int
|
|
virDomainVideoDefaultRAM(const virDomainDef *def,
|
|
const virDomainVideoType type)
|
|
{
|
|
switch (type) {
|
|
case VIR_DOMAIN_VIDEO_TYPE_VGA:
|
|
case VIR_DOMAIN_VIDEO_TYPE_CIRRUS:
|
|
case VIR_DOMAIN_VIDEO_TYPE_VMVGA:
|
|
if (def->virtType == VIR_DOMAIN_VIRT_VBOX)
|
|
return 8 * 1024;
|
|
else if (def->virtType == VIR_DOMAIN_VIRT_VMWARE)
|
|
return 4 * 1024;
|
|
else
|
|
return 16 * 1024;
|
|
break;
|
|
|
|
case VIR_DOMAIN_VIDEO_TYPE_BOCHS:
|
|
return 16 * 1024;
|
|
|
|
case VIR_DOMAIN_VIDEO_TYPE_XEN:
|
|
/* Original Xen PVFB hardcoded to 4 MB */
|
|
return 4 * 1024;
|
|
|
|
case VIR_DOMAIN_VIDEO_TYPE_QXL:
|
|
/* QEMU use 64M as the minimal video memory for qxl device */
|
|
return 64 * 1024;
|
|
|
|
case VIR_DOMAIN_VIDEO_TYPE_DEFAULT:
|
|
case VIR_DOMAIN_VIDEO_TYPE_VBOX:
|
|
case VIR_DOMAIN_VIDEO_TYPE_PARALLELS:
|
|
case VIR_DOMAIN_VIDEO_TYPE_VIRTIO:
|
|
case VIR_DOMAIN_VIDEO_TYPE_GOP:
|
|
case VIR_DOMAIN_VIDEO_TYPE_NONE:
|
|
case VIR_DOMAIN_VIDEO_TYPE_RAMFB:
|
|
case VIR_DOMAIN_VIDEO_TYPE_LAST:
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static virDomainVideoAccelDef *
|
|
virDomainVideoAccelDefParseXML(xmlNodePtr node)
|
|
{
|
|
g_autofree virDomainVideoAccelDef *def = NULL;
|
|
int val;
|
|
g_autofree char *accel2d = NULL;
|
|
g_autofree char *accel3d = NULL;
|
|
g_autofree char *rendernode = NULL;
|
|
|
|
accel3d = virXMLPropString(node, "accel3d");
|
|
accel2d = virXMLPropString(node, "accel2d");
|
|
rendernode = virXMLPropString(node, "rendernode");
|
|
|
|
if (!accel3d && !accel2d && !rendernode) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("missing values for acceleration"));
|
|
return NULL;
|
|
}
|
|
|
|
def = g_new0(virDomainVideoAccelDef, 1);
|
|
|
|
if (accel3d) {
|
|
if ((val = virTristateBoolTypeFromString(accel3d)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown accel3d value '%s'"), accel3d);
|
|
return NULL;
|
|
}
|
|
def->accel3d = val;
|
|
}
|
|
|
|
if (accel2d) {
|
|
if ((val = virTristateBoolTypeFromString(accel2d)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown accel2d value '%s'"), accel2d);
|
|
return NULL;
|
|
}
|
|
def->accel2d = val;
|
|
}
|
|
|
|
if (rendernode)
|
|
def->rendernode = virFileSanitizePath(rendernode);
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
static virDomainVideoResolutionDef *
|
|
virDomainVideoResolutionDefParseXML(xmlNodePtr node)
|
|
{
|
|
g_autofree virDomainVideoResolutionDef *def = NULL;
|
|
|
|
def = g_new0(virDomainVideoResolutionDef, 1);
|
|
|
|
if (virXMLPropUInt(node, "x", 10, VIR_XML_PROP_REQUIRED, &def->x) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropUInt(node, "y", 10, VIR_XML_PROP_REQUIRED, &def->y) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
static virDomainVideoDriverDef *
|
|
virDomainVideoDriverDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree virDomainVideoDriverDef *def = NULL;
|
|
xmlNodePtr driver = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(driver = virXPathNode("./driver", ctxt)))
|
|
return NULL;
|
|
|
|
def = g_new0(virDomainVideoDriverDef, 1);
|
|
|
|
if (virXMLPropEnum(driver, "vgaconf",
|
|
virDomainVideoVGAConfTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->vgaconf) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
static virDomainVideoDef *
|
|
virDomainVideoDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(virDomainVideoDef) def = NULL;
|
|
xmlNodePtr driver;
|
|
xmlNodePtr accel_node;
|
|
xmlNodePtr res_node;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree char *type = NULL;
|
|
g_autofree char *heads = NULL;
|
|
g_autofree char *vram = NULL;
|
|
g_autofree char *vram64 = NULL;
|
|
g_autofree char *ram = NULL;
|
|
g_autofree char *vgamem = NULL;
|
|
g_autofree char *primary = NULL;
|
|
|
|
if (!(def = virDomainVideoDefNew(xmlopt)))
|
|
return NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((primary = virXPathString("string(./model/@primary)", ctxt)) != NULL)
|
|
ignore_value(virStringParseYesNo(primary, &def->primary));
|
|
|
|
if ((accel_node = virXPathNode("./model/acceleration", ctxt)) &&
|
|
(def->accel = virDomainVideoAccelDefParseXML(accel_node)) == NULL)
|
|
return NULL;
|
|
|
|
if ((res_node = virXPathNode("./model/resolution", ctxt)) &&
|
|
(def->res = virDomainVideoResolutionDefParseXML(res_node)) == NULL)
|
|
return NULL;
|
|
|
|
if ((driver = virXPathNode("./driver", ctxt))) {
|
|
if (virXMLPropEnum(driver, "name",
|
|
virDomainVideoBackendTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->backend) < 0)
|
|
return NULL;
|
|
if (virDomainVirtioOptionsParseXML(driver, &def->virtio) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if ((type = virXPathString("string(./model/@type)", ctxt))) {
|
|
if ((def->type = virDomainVideoTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown video model '%s'"), type);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
def->type = VIR_DOMAIN_VIDEO_TYPE_DEFAULT;
|
|
}
|
|
|
|
if ((ram = virXPathString("string(./model/@ram)", ctxt))) {
|
|
if (virStrToLong_uip(ram, NULL, 10, &def->ram) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse video ram '%s'"), ram);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if ((vram = virXPathString("string(./model/@vram)", ctxt))) {
|
|
if (virStrToLong_uip(vram, NULL, 10, &def->vram) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse video vram '%s'"), vram);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if ((vram64 = virXPathString("string(./model/@vram64)", ctxt))) {
|
|
if (virStrToLong_uip(vram64, NULL, 10, &def->vram64) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse video vram64 '%s'"), vram64);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if ((vgamem = virXPathString("string(./model/@vgamem)", ctxt))) {
|
|
if (virStrToLong_uip(vgamem, NULL, 10, &def->vgamem) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cannot parse video vgamem '%s'"), vgamem);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if ((heads = virXPathString("string(./model/@heads)", ctxt))) {
|
|
if (virStrToLong_uip(heads, NULL, 10, &def->heads) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse video heads '%s'"), heads);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
|
|
return NULL;
|
|
|
|
def->driver = virDomainVideoDriverDefParseXML(node, ctxt);
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
static virDomainHostdevDef *
|
|
virDomainHostdevDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainHostdevDef *def;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree char *mode = virXMLPropString(node, "mode");
|
|
g_autofree char *type = virXMLPropString(node, "type");
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(def = virDomainHostdevDefNew()))
|
|
goto error;
|
|
|
|
if (mode) {
|
|
if ((def->mode = virDomainHostdevModeTypeFromString(mode)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown hostdev mode '%s'"), mode);
|
|
goto error;
|
|
}
|
|
} else {
|
|
def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
|
|
}
|
|
|
|
switch (def->mode) {
|
|
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
|
|
/* parse managed/mode/type, and the <source> element */
|
|
if (virDomainHostdevDefParseXMLSubsys(node, ctxt, type, def, flags, xmlopt) < 0)
|
|
goto error;
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
|
|
/* parse managed/mode/type, and the <source> element */
|
|
if (virDomainHostdevDefParseXMLCaps(node, ctxt, type, def) < 0)
|
|
goto error;
|
|
break;
|
|
default:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unexpected hostdev mode %d"), def->mode);
|
|
goto error;
|
|
}
|
|
|
|
if (def->info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, def->info,
|
|
flags | VIR_DOMAIN_DEF_PARSE_ALLOW_BOOT
|
|
| VIR_DOMAIN_DEF_PARSE_ALLOW_ROM) < 0)
|
|
goto error;
|
|
}
|
|
if (def->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) {
|
|
switch ((virDomainHostdevSubsysType) def->source.subsys.type) {
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI:
|
|
if (virXPathBoolean("boolean(./readonly)", ctxt))
|
|
def->readonly = true;
|
|
if (virXPathBoolean("boolean(./shareable)", ctxt))
|
|
def->shareable = true;
|
|
break;
|
|
|
|
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_MDEV:
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (virDomainNetTeamingInfoParseXML(ctxt, &def->teaming) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainHostdevDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainRedirdevDef *
|
|
virDomainRedirdevDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainRedirdevDef *def;
|
|
g_autofree char *bus = NULL;
|
|
g_autofree char *type = NULL;
|
|
|
|
def = g_new0(virDomainRedirdevDef, 1);
|
|
|
|
if (!(def->source = virDomainChrSourceDefNew(xmlopt)))
|
|
goto error;
|
|
|
|
bus = virXMLPropString(node, "bus");
|
|
if (bus) {
|
|
if ((def->bus = virDomainRedirdevBusTypeFromString(bus)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown redirdev bus '%s'"), bus);
|
|
goto error;
|
|
}
|
|
} else {
|
|
def->bus = VIR_DOMAIN_REDIRDEV_BUS_USB;
|
|
}
|
|
|
|
type = virXMLPropString(node, "type");
|
|
if (type) {
|
|
if ((def->source->type = virDomainChrTypeFromString(type)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown redirdev character device type '%s'"), type);
|
|
goto error;
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing type in redirdev"));
|
|
goto error;
|
|
}
|
|
|
|
/* boot gets parsed in virDomainDeviceInfoParseXML
|
|
* source gets parsed in virDomainChrSourceDefParseXML */
|
|
if (virDomainChrSourceDefParseXML(def->source, node, flags,
|
|
NULL, ctxt) < 0)
|
|
goto error;
|
|
|
|
if (def->source->type == VIR_DOMAIN_CHR_TYPE_SPICEVMC)
|
|
def->source->data.spicevmc = VIR_DOMAIN_CHR_SPICEVMC_USBREDIR;
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info,
|
|
flags | VIR_DOMAIN_DEF_PARSE_ALLOW_BOOT) < 0)
|
|
goto error;
|
|
|
|
if (def->bus == VIR_DOMAIN_REDIRDEV_BUS_USB &&
|
|
def->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
def->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Invalid address for a USB device"));
|
|
goto error;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainRedirdevDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* This is the helper function to convert USB device version from a
|
|
* format of JJ.MN to a format of 0xJJMN where JJ is the major
|
|
* version number, M is the minor version number and N is the
|
|
* sub minor version number.
|
|
* e.g. USB version 2.0 is reported as 0x0200,
|
|
* USB version 4.07 as 0x0407
|
|
*/
|
|
static int
|
|
virDomainRedirFilterUSBVersionHelper(const char *version,
|
|
virDomainRedirFilterUSBDevDef *def)
|
|
{
|
|
unsigned int major, minor;
|
|
char *s = NULL;
|
|
|
|
if ((virStrToLong_ui(version, &s, 10, &major)) < 0 ||
|
|
*s++ != '.' ||
|
|
(virStrToLong_ui(s, NULL, 10, &minor)) < 0)
|
|
goto error;
|
|
|
|
if (major >= 100 || minor >= 100)
|
|
goto error;
|
|
|
|
/* Treat JJ.M as JJ.M0, not JJ.0M */
|
|
if (strlen(s) == 1)
|
|
minor *= 10;
|
|
|
|
def->version = (major / 10) << 12 | (major % 10) << 8 |
|
|
(minor / 10) << 4 | (minor % 10) << 0;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Cannot parse USB device version %s"), version);
|
|
return -1;
|
|
}
|
|
|
|
static virDomainRedirFilterUSBDevDef *
|
|
virDomainRedirFilterUSBDevDefParseXML(xmlNodePtr node)
|
|
{
|
|
g_autofree virDomainRedirFilterUSBDevDef *def = NULL;
|
|
g_autofree char *version = NULL;
|
|
virTristateBool allow;
|
|
|
|
def = g_new0(virDomainRedirFilterUSBDevDef, 1);
|
|
|
|
if (virXMLPropInt(node, "class", 0, VIR_XML_PROP_NONE, &def->usbClass, -1) < 0)
|
|
return NULL;
|
|
|
|
if (def->usbClass != -1 && def->usbClass &~ 0xFF) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Invalid USB Class code 0x%x"), def->usbClass);
|
|
return NULL;
|
|
}
|
|
|
|
if (virXMLPropInt(node, "vendor", 0, VIR_XML_PROP_NONE, &def->vendor, -1) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropInt(node, "product", 0, VIR_XML_PROP_NONE, &def->product, -1) < 0)
|
|
return NULL;
|
|
|
|
version = virXMLPropString(node, "version");
|
|
if (version) {
|
|
if (STREQ(version, "-1"))
|
|
def->version = -1;
|
|
else if ((virDomainRedirFilterUSBVersionHelper(version, def)) < 0)
|
|
return NULL;
|
|
} else {
|
|
def->version = -1;
|
|
}
|
|
|
|
if (virXMLPropTristateBool(node, "allow", VIR_XML_PROP_REQUIRED, &allow) < 0)
|
|
return NULL;
|
|
|
|
def->allow = allow == VIR_TRISTATE_BOOL_YES;
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
static virDomainRedirFilterDef *
|
|
virDomainRedirFilterDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
int n;
|
|
size_t i;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainRedirFilterDef *def = NULL;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
|
|
def = g_new0(virDomainRedirFilterDef, 1);
|
|
|
|
ctxt->node = node;
|
|
if ((n = virXPathNodeSet("./usbdev", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n)
|
|
def->usbdevs = g_new0(virDomainRedirFilterUSBDevDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainRedirFilterUSBDevDef *usbdev =
|
|
virDomainRedirFilterUSBDevDefParseXML(nodes[i]);
|
|
|
|
if (!usbdev)
|
|
goto error;
|
|
def->usbdevs[def->nusbdevs++] = usbdev;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainRedirFilterDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
virDomainEventActionParseXML(xmlXPathContextPtr ctxt,
|
|
const char *name,
|
|
const char *xpath,
|
|
int *val,
|
|
int defaultVal,
|
|
virEventActionFromStringFunc convFunc)
|
|
{
|
|
g_autofree char *tmp = virXPathString(xpath, ctxt);
|
|
|
|
if (tmp == NULL) {
|
|
*val = defaultVal;
|
|
} else {
|
|
*val = convFunc(tmp);
|
|
if (*val < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown %s action: %s"), name, tmp);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainPMStateParseXML(xmlXPathContextPtr ctxt,
|
|
const char *xpath,
|
|
int *val)
|
|
{
|
|
g_autofree char *tmp = virXPathString(xpath, ctxt);
|
|
|
|
if (tmp) {
|
|
*val = virTristateBoolTypeFromString(tmp);
|
|
if (*val < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown PM state value %s"), tmp);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainPerfEventDefParseXML(virDomainPerfDef *perf,
|
|
xmlNodePtr node)
|
|
{
|
|
virPerfEventType name;
|
|
virTristateBool enabled;
|
|
|
|
if (virXMLPropEnum(node, "name", virPerfEventTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &name) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropTristateBool(node, "enabled", VIR_XML_PROP_REQUIRED, &enabled) < 0)
|
|
return -1;
|
|
|
|
if (perf->events[name] != VIR_TRISTATE_BOOL_ABSENT) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("perf event '%s' was already specified"),
|
|
virPerfEventTypeToString(name));
|
|
return -1;
|
|
}
|
|
|
|
perf->events[name] = enabled;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainPerfDefParseXML(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
size_t i;
|
|
int n;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
|
|
if ((n = virXPathNodeSet("./perf/event", ctxt, &nodes)) < 0)
|
|
return n;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainPerfEventDefParseXML(&def->perf, nodes[i]) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainMemorySourceDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainMemoryDef *def)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree char *nodemask = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
switch (def->model) {
|
|
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
|
|
if (virDomainParseMemory("./pagesize", "./pagesize/@unit", ctxt,
|
|
&def->pagesize, false, false) < 0)
|
|
return -1;
|
|
|
|
if ((nodemask = virXPathString("string(./nodemask)", ctxt))) {
|
|
if (virBitmapParse(nodemask, &def->sourceNodes,
|
|
VIR_DOMAIN_CPUMASK_LEN) < 0)
|
|
return -1;
|
|
|
|
if (virBitmapIsAllClear(def->sourceNodes)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Invalid value of 'nodemask': %s"), nodemask);
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
|
|
def->nvdimmPath = virXPathString("string(./path)", ctxt);
|
|
|
|
if (virDomainParseMemory("./alignsize", "./alignsize/@unit", ctxt,
|
|
&def->alignsize, false, false) < 0)
|
|
return -1;
|
|
|
|
if (virXPathBoolean("boolean(./pmem)", ctxt))
|
|
def->nvdimmPmem = true;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
|
|
def->nvdimmPath = virXPathString("string(./path)", ctxt);
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NONE:
|
|
case VIR_DOMAIN_MEMORY_MODEL_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainMemoryTargetDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainMemoryDef *def)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
int rv;
|
|
|
|
ctxt->node = node;
|
|
|
|
/* initialize to value which marks that the user didn't specify it */
|
|
def->targetNode = -1;
|
|
|
|
if ((rv = virXPathInt("string(./node)", ctxt, &def->targetNode)) == -2 ||
|
|
(rv == 0 && def->targetNode < 0)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid value of memory device node"));
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainParseMemory("./size", "./size/@unit", ctxt,
|
|
&def->size, true, false) < 0)
|
|
return -1;
|
|
|
|
if (def->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM) {
|
|
if (virDomainParseMemory("./label/size", "./label/size/@unit", ctxt,
|
|
&def->labelsize, false, false) < 0)
|
|
return -1;
|
|
|
|
if (def->labelsize && def->labelsize < 128) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("nvdimm label must be at least 128KiB"));
|
|
return -1;
|
|
}
|
|
|
|
if (def->labelsize >= def->size) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("label size must be smaller than NVDIMM size"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXPathBoolean("boolean(./readonly)", ctxt))
|
|
def->readonly = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virDomainSEVDef *
|
|
virDomainSEVDefParseXML(xmlNodePtr sevNode,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainSEVDef *def;
|
|
unsigned long policy;
|
|
g_autofree char *type = NULL;
|
|
int rc = -1;
|
|
|
|
def = g_new0(virDomainSEVDef, 1);
|
|
|
|
ctxt->node = sevNode;
|
|
|
|
if (!(type = virXMLPropString(sevNode, "type"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing launch security type"));
|
|
goto error;
|
|
}
|
|
|
|
def->sectype = virDomainLaunchSecurityTypeFromString(type);
|
|
switch ((virDomainLaunchSecurity) def->sectype) {
|
|
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
|
|
break;
|
|
case VIR_DOMAIN_LAUNCH_SECURITY_NONE:
|
|
case VIR_DOMAIN_LAUNCH_SECURITY_LAST:
|
|
default:
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unsupported launch security type '%s'"),
|
|
type);
|
|
goto error;
|
|
}
|
|
|
|
if (virXPathULongHex("string(./policy)", ctxt, &policy) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("failed to get launch security policy"));
|
|
goto error;
|
|
}
|
|
|
|
/* the following attributes are platform dependent and if missing, we can
|
|
* autofill them from domain capabilities later
|
|
*/
|
|
rc = virXPathUInt("string(./cbitpos)", ctxt, &def->cbitpos);
|
|
if (rc == 0) {
|
|
def->haveCbitpos = true;
|
|
} else if (rc == -2) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Invalid format for launch security cbitpos"));
|
|
goto error;
|
|
}
|
|
|
|
rc = virXPathUInt("string(./reducedPhysBits)", ctxt,
|
|
&def->reduced_phys_bits);
|
|
if (rc == 0) {
|
|
def->haveReducedPhysBits = true;
|
|
} else if (rc == -2) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Invalid format for launch security "
|
|
"reduced-phys-bits"));
|
|
goto error;
|
|
}
|
|
|
|
def->policy = policy;
|
|
def->dh_cert = virXPathString("string(./dhCert)", ctxt);
|
|
def->session = virXPathString("string(./session)", ctxt);
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainSEVDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainMemoryDef *
|
|
virDomainMemoryDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr memdevNode,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr node;
|
|
virDomainMemoryDef *def;
|
|
g_autofree char *tmp = NULL;
|
|
|
|
def = g_new0(virDomainMemoryDef, 1);
|
|
|
|
ctxt->node = memdevNode;
|
|
|
|
if (virXMLPropEnum(memdevNode, "model", virDomainMemoryModelTypeFromString,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&def->model) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropEnum(memdevNode, "access", virDomainMemoryAccessTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->access) < 0)
|
|
goto error;
|
|
|
|
if (virXMLPropTristateBool(memdevNode, "discard", VIR_XML_PROP_NONE,
|
|
&def->discard) < 0)
|
|
goto error;
|
|
|
|
/* Extract NVDIMM UUID. */
|
|
if (def->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM &&
|
|
(tmp = virXPathString("string(./uuid[1])", ctxt))) {
|
|
def->uuid = g_new0(unsigned char, VIR_UUID_BUFLEN);
|
|
|
|
if (virUUIDParse(tmp, def->uuid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("malformed uuid element"));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* source */
|
|
if ((node = virXPathNode("./source", ctxt)) &&
|
|
virDomainMemorySourceDefParseXML(node, ctxt, def) < 0)
|
|
goto error;
|
|
|
|
/* target */
|
|
if (!(node = virXPathNode("./target", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing <target> element for <memory> device"));
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainMemoryTargetDefParseXML(node, ctxt, def) < 0)
|
|
goto error;
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, memdevNode, ctxt,
|
|
&def->info, flags) < 0)
|
|
goto error;
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainMemoryDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainIOMMUDef *
|
|
virDomainIOMMUDefParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr driver;
|
|
g_autofree virDomainIOMMUDef *iommu = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
iommu = g_new0(virDomainIOMMUDef, 1);
|
|
|
|
if (virXMLPropEnum(node, "model", virDomainIOMMUModelTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &iommu->model) < 0)
|
|
return NULL;
|
|
|
|
if ((driver = virXPathNode("./driver", ctxt))) {
|
|
if (virXMLPropTristateSwitch(driver, "intremap", VIR_XML_PROP_NONE,
|
|
&iommu->intremap) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateSwitch(driver, "caching_mode", VIR_XML_PROP_NONE,
|
|
&iommu->caching_mode) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateSwitch(driver, "iotlb", VIR_XML_PROP_NONE,
|
|
&iommu->iotlb) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateSwitch(driver, "eim", VIR_XML_PROP_NONE,
|
|
&iommu->eim) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropUInt(driver, "aw_bits", 10, VIR_XML_PROP_NONE,
|
|
&iommu->aw_bits) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&iommu);
|
|
}
|
|
|
|
|
|
static virDomainVsockDef *
|
|
virDomainVsockDefParseXML(virDomainXMLOption *xmlopt,
|
|
xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
xmlNodePtr cid;
|
|
g_autoptr(virDomainVsockDef) vsock = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (!(vsock = virDomainVsockDefNew(xmlopt)))
|
|
return NULL;
|
|
|
|
if (virXMLPropEnum(node, "model", virDomainVsockModelTypeFromString,
|
|
VIR_XML_PROP_NONE, &vsock->model) < 0)
|
|
return NULL;
|
|
|
|
if ((cid = virXPathNode("./cid", ctxt))) {
|
|
if (virXMLPropUInt(cid, "address", 10,
|
|
VIR_XML_PROP_NONZERO,
|
|
&vsock->guest_cid) < 0)
|
|
return NULL;
|
|
|
|
if (virXMLPropTristateBool(cid, "auto", VIR_XML_PROP_NONE,
|
|
&vsock->auto_cid) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &vsock->info, flags) < 0)
|
|
return NULL;
|
|
|
|
if (virDomainVirtioOptionsParseXML(virXPathNode("./driver", ctxt),
|
|
&vsock->virtio) < 0)
|
|
return NULL;
|
|
|
|
|
|
return g_steal_pointer(&vsock);
|
|
}
|
|
|
|
virDomainDeviceDef *
|
|
virDomainDeviceDefParse(const char *xmlStr,
|
|
const virDomainDef *def,
|
|
virDomainXMLOption *xmlopt,
|
|
void *parseOpaque,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(xmlDoc) xml = NULL;
|
|
xmlNodePtr node;
|
|
g_autoptr(xmlXPathContext) ctxt = NULL;
|
|
g_autoptr(virDomainDeviceDef) dev = NULL;
|
|
|
|
if (!(xml = virXMLParseStringCtxt(xmlStr, _("(device_definition)"), &ctxt)))
|
|
return NULL;
|
|
|
|
node = ctxt->node;
|
|
|
|
dev = g_new0(virDomainDeviceDef, 1);
|
|
|
|
if ((dev->type = virDomainDeviceTypeFromString((const char *) node->name)) < 0) {
|
|
/* Some crazy mapping of serial, parallel, console and channel to
|
|
* VIR_DOMAIN_DEVICE_CHR. */
|
|
if (virXMLNodeNameEqual(node, "channel") ||
|
|
virXMLNodeNameEqual(node, "console") ||
|
|
virXMLNodeNameEqual(node, "parallel") ||
|
|
virXMLNodeNameEqual(node, "serial")) {
|
|
dev->type = VIR_DOMAIN_DEVICE_CHR;
|
|
} else {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown device type '%s'"),
|
|
node->name);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
switch ((virDomainDeviceType) dev->type) {
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
if (!(dev->data.disk = virDomainDiskDefParseXML(xmlopt, node, ctxt,
|
|
flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
if (!(dev->data.lease = virDomainLeaseDefParseXML(node, ctxt)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
if (!(dev->data.fs = virDomainFSDefParseXML(xmlopt, node, ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
if (!(dev->data.net = virDomainNetDefParseXML(xmlopt, node, ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
if (!(dev->data.input = virDomainInputDefParseXML(xmlopt, def, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
if (!(dev->data.sound = virDomainSoundDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_AUDIO:
|
|
if (!(dev->data.audio = virDomainAudioDefParseXML(xmlopt, node, ctxt)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
if (!(dev->data.watchdog = virDomainWatchdogDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
if (!(dev->data.video = virDomainVideoDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
if (!(dev->data.hostdev = virDomainHostdevDefParseXML(xmlopt, node,
|
|
ctxt,
|
|
flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
if (!(dev->data.controller = virDomainControllerDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
if (!(dev->data.graphics = virDomainGraphicsDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
if (!(dev->data.hub = virDomainHubDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
if (!(dev->data.redirdev = virDomainRedirdevDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
if (!(dev->data.rng = virDomainRNGDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
if (!(dev->data.chr = virDomainChrDefParseXML(xmlopt,
|
|
ctxt,
|
|
node,
|
|
flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
if (!(dev->data.smartcard = virDomainSmartcardDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_MEMBALLOON:
|
|
if (!(dev->data.memballoon = virDomainMemballoonDefParseXML(xmlopt,
|
|
node,
|
|
ctxt,
|
|
flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NVRAM:
|
|
if (!(dev->data.nvram = virDomainNVRAMDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SHMEM:
|
|
if (!(dev->data.shmem = virDomainShmemDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_TPM:
|
|
if (!(dev->data.tpm = virDomainTPMDefParseXML(xmlopt, node, ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_PANIC:
|
|
if (!(dev->data.panic = virDomainPanicDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
if (!(dev->data.memory = virDomainMemoryDefParseXML(xmlopt, node,
|
|
ctxt, flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
if (!(dev->data.iommu = virDomainIOMMUDefParseXML(node, ctxt)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
if (!(dev->data.vsock = virDomainVsockDefParseXML(xmlopt, node, ctxt,
|
|
flags)))
|
|
return NULL;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
case VIR_DOMAIN_DEVICE_LAST:
|
|
break;
|
|
}
|
|
|
|
/* callback to fill driver specific device aspects */
|
|
if (virDomainDeviceDefPostParseOne(dev, def, flags,
|
|
xmlopt, parseOpaque) < 0)
|
|
return NULL;
|
|
|
|
/* validate the configuration */
|
|
if (virDomainDeviceDefValidate(dev, def, flags, xmlopt, parseOpaque) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&dev);
|
|
}
|
|
|
|
|
|
virDomainDiskDef *
|
|
virDomainDiskDefParse(const char *xmlStr,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(xmlDoc) xml = NULL;
|
|
g_autoptr(xmlXPathContext) ctxt = NULL;
|
|
|
|
if (!(xml = virXMLParseStringCtxtRoot(xmlStr, _("(disk_definition)"), "disk", &ctxt)))
|
|
return NULL;
|
|
|
|
return virDomainDiskDefParseXML(xmlopt, ctxt->node, ctxt, flags);
|
|
}
|
|
|
|
|
|
virStorageSource *
|
|
virDomainDiskDefParseSource(const char *xmlStr,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(xmlDoc) xml = NULL;
|
|
g_autoptr(xmlXPathContext) ctxt = NULL;
|
|
g_autoptr(virStorageSource) src = NULL;
|
|
xmlNodePtr driverNode;
|
|
|
|
if (!(xml = virXMLParseStringCtxtRoot(xmlStr, _("(disk_definition)"), "disk", &ctxt)))
|
|
return NULL;
|
|
|
|
if (!(src = virDomainDiskDefParseSourceXML(xmlopt, ctxt->node, ctxt, flags)))
|
|
return NULL;
|
|
|
|
if ((driverNode = virXPathNode("./driver", ctxt))) {
|
|
if (virDomainDiskDefDriverSourceParseXML(src, driverNode, ctxt) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (virStorageSourceIsEmpty(src)) {
|
|
virReportError(VIR_ERR_NO_SOURCE, NULL);
|
|
return NULL;
|
|
}
|
|
|
|
if (virDomainDiskDefValidateSource(src) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&src);
|
|
}
|
|
|
|
|
|
static const char *
|
|
virDomainChrTargetTypeToString(int deviceType,
|
|
int targetType)
|
|
{
|
|
const char *type = NULL;
|
|
|
|
switch (deviceType) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
type = virDomainChrChannelTargetTypeToString(targetType);
|
|
break;
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
|
|
type = virDomainChrConsoleTargetTypeToString(targetType);
|
|
break;
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
|
|
type = virDomainChrSerialTargetTypeToString(targetType);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
int
|
|
virDomainHostdevInsert(virDomainDef *def, virDomainHostdevDef *hostdev)
|
|
{
|
|
return VIR_APPEND_ELEMENT(def->hostdevs, def->nhostdevs, hostdev);
|
|
}
|
|
|
|
virDomainHostdevDef *
|
|
virDomainHostdevRemove(virDomainDef *def, size_t i)
|
|
{
|
|
virDomainHostdevDef *hostdev = def->hostdevs[i];
|
|
|
|
VIR_DELETE_ELEMENT(def->hostdevs, i, def->nhostdevs);
|
|
return hostdev;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevMatchSubsysUSB(virDomainHostdevDef *first,
|
|
virDomainHostdevDef *second)
|
|
{
|
|
virDomainHostdevSubsysUSB *first_usbsrc = &first->source.subsys.u.usb;
|
|
virDomainHostdevSubsysUSB *second_usbsrc = &second->source.subsys.u.usb;
|
|
|
|
if (first_usbsrc->bus && first_usbsrc->device) {
|
|
/* specified by bus location on host */
|
|
if (first_usbsrc->bus == second_usbsrc->bus &&
|
|
first_usbsrc->device == second_usbsrc->device)
|
|
return 1;
|
|
} else {
|
|
/* specified by product & vendor id */
|
|
if (first_usbsrc->product == second_usbsrc->product &&
|
|
first_usbsrc->vendor == second_usbsrc->vendor)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevMatchSubsysPCI(virDomainHostdevDef *first,
|
|
virDomainHostdevDef *second)
|
|
{
|
|
virDomainHostdevSubsysPCI *first_pcisrc = &first->source.subsys.u.pci;
|
|
virDomainHostdevSubsysPCI *second_pcisrc = &second->source.subsys.u.pci;
|
|
|
|
if (first_pcisrc->addr.domain == second_pcisrc->addr.domain &&
|
|
first_pcisrc->addr.bus == second_pcisrc->addr.bus &&
|
|
first_pcisrc->addr.slot == second_pcisrc->addr.slot &&
|
|
first_pcisrc->addr.function == second_pcisrc->addr.function)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevMatchSubsysSCSIHost(virDomainHostdevDef *first,
|
|
virDomainHostdevDef *second)
|
|
{
|
|
virDomainHostdevSubsysSCSIHost *first_scsihostsrc =
|
|
&first->source.subsys.u.scsi.u.host;
|
|
virDomainHostdevSubsysSCSIHost *second_scsihostsrc =
|
|
&second->source.subsys.u.scsi.u.host;
|
|
|
|
if (STREQ(first_scsihostsrc->adapter, second_scsihostsrc->adapter) &&
|
|
first_scsihostsrc->bus == second_scsihostsrc->bus &&
|
|
first_scsihostsrc->target == second_scsihostsrc->target &&
|
|
first_scsihostsrc->unit == second_scsihostsrc->unit)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevMatchSubsysSCSIiSCSI(virDomainHostdevDef *first,
|
|
virDomainHostdevDef *second)
|
|
{
|
|
virDomainHostdevSubsysSCSIiSCSI *first_iscsisrc =
|
|
&first->source.subsys.u.scsi.u.iscsi;
|
|
virDomainHostdevSubsysSCSIiSCSI *second_iscsisrc =
|
|
&second->source.subsys.u.scsi.u.iscsi;
|
|
|
|
if (STREQ(first_iscsisrc->src->hosts[0].name, second_iscsisrc->src->hosts[0].name) &&
|
|
first_iscsisrc->src->hosts[0].port == second_iscsisrc->src->hosts[0].port &&
|
|
STREQ(first_iscsisrc->src->path, second_iscsisrc->src->path))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevMatchSubsysMediatedDev(virDomainHostdevDef *a,
|
|
virDomainHostdevDef *b)
|
|
{
|
|
virDomainHostdevSubsysMediatedDev *src_a = &a->source.subsys.u.mdev;
|
|
virDomainHostdevSubsysMediatedDev *src_b = &b->source.subsys.u.mdev;
|
|
|
|
if (STREQ(src_a->uuidstr, src_b->uuidstr))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevMatchSubsys(virDomainHostdevDef *a,
|
|
virDomainHostdevDef *b)
|
|
{
|
|
if (a->source.subsys.type != b->source.subsys.type)
|
|
return 0;
|
|
|
|
switch ((virDomainHostdevSubsysType) a->source.subsys.type) {
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
|
|
return virDomainHostdevMatchSubsysPCI(a, b);
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
|
|
return virDomainHostdevMatchSubsysUSB(a, b);
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI:
|
|
if (a->source.subsys.u.scsi.protocol !=
|
|
b->source.subsys.u.scsi.protocol)
|
|
return 0;
|
|
if (a->source.subsys.u.scsi.protocol ==
|
|
VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI)
|
|
return virDomainHostdevMatchSubsysSCSIiSCSI(a, b);
|
|
else
|
|
return virDomainHostdevMatchSubsysSCSIHost(a, b);
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST:
|
|
if (a->source.subsys.u.scsi_host.protocol !=
|
|
b->source.subsys.u.scsi_host.protocol)
|
|
return 0;
|
|
if (STREQ(a->source.subsys.u.scsi_host.wwpn,
|
|
b->source.subsys.u.scsi_host.wwpn))
|
|
return 1;
|
|
else
|
|
return 0;
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV:
|
|
return virDomainHostdevMatchSubsysMediatedDev(a, b);
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevMatchCapsStorage(virDomainHostdevDef *a,
|
|
virDomainHostdevDef *b)
|
|
{
|
|
return STREQ_NULLABLE(a->source.caps.u.storage.block,
|
|
b->source.caps.u.storage.block);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevMatchCapsMisc(virDomainHostdevDef *a,
|
|
virDomainHostdevDef *b)
|
|
{
|
|
return STREQ_NULLABLE(a->source.caps.u.misc.chardev,
|
|
b->source.caps.u.misc.chardev);
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevMatchCapsNet(virDomainHostdevDef *a,
|
|
virDomainHostdevDef *b)
|
|
{
|
|
return STREQ_NULLABLE(a->source.caps.u.net.ifname,
|
|
b->source.caps.u.net.ifname);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevMatchCaps(virDomainHostdevDef *a,
|
|
virDomainHostdevDef *b)
|
|
{
|
|
if (a->source.caps.type != b->source.caps.type)
|
|
return 0;
|
|
|
|
switch (a->source.caps.type) {
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_STORAGE:
|
|
return virDomainHostdevMatchCapsStorage(a, b);
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_MISC:
|
|
return virDomainHostdevMatchCapsMisc(a, b);
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET:
|
|
return virDomainHostdevMatchCapsNet(a, b);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevMatch(virDomainHostdevDef *a,
|
|
virDomainHostdevDef *b)
|
|
{
|
|
if (a->mode != b->mode)
|
|
return 0;
|
|
|
|
switch (a->mode) {
|
|
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
|
|
return virDomainHostdevMatchSubsys(a, b);
|
|
case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
|
|
return virDomainHostdevMatchCaps(a, b);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Find an entry in hostdevs that matches the source spec in
|
|
* @match. return pointer to the entry in @found (if found is
|
|
* non-NULL). Returns index (within hostdevs) of matched entry, or -1
|
|
* if no match was found.
|
|
*/
|
|
int
|
|
virDomainHostdevFind(virDomainDef *def,
|
|
virDomainHostdevDef *match,
|
|
virDomainHostdevDef **found)
|
|
{
|
|
virDomainHostdevDef *local_found;
|
|
size_t i;
|
|
|
|
if (!found)
|
|
found = &local_found;
|
|
*found = NULL;
|
|
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
if (virDomainHostdevMatch(match, def->hostdevs[i])) {
|
|
*found = def->hostdevs[i];
|
|
break;
|
|
}
|
|
}
|
|
return *found ? i : -1;
|
|
}
|
|
|
|
static bool
|
|
virDomainDiskControllerMatch(int controller_type, int disk_bus)
|
|
{
|
|
if (controller_type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI &&
|
|
disk_bus == VIR_DOMAIN_DISK_BUS_SCSI)
|
|
return true;
|
|
|
|
if (controller_type == VIR_DOMAIN_CONTROLLER_TYPE_FDC &&
|
|
disk_bus == VIR_DOMAIN_DISK_BUS_FDC)
|
|
return true;
|
|
|
|
if (controller_type == VIR_DOMAIN_CONTROLLER_TYPE_IDE &&
|
|
disk_bus == VIR_DOMAIN_DISK_BUS_IDE)
|
|
return true;
|
|
|
|
if (controller_type == VIR_DOMAIN_CONTROLLER_TYPE_SATA &&
|
|
disk_bus == VIR_DOMAIN_DISK_BUS_SATA)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDiskIndexByAddress(virDomainDef *def,
|
|
virPCIDeviceAddress *pci_address,
|
|
virDomainDeviceCCWAddress *ccw_addr,
|
|
unsigned int bus, unsigned int target,
|
|
unsigned int unit)
|
|
{
|
|
virDomainDiskDef *vdisk;
|
|
virDomainControllerDef *controller = NULL;
|
|
size_t i;
|
|
int cidx;
|
|
|
|
if ((cidx = virDomainControllerFindByPCIAddress(def, pci_address)) >= 0)
|
|
controller = def->controllers[cidx];
|
|
|
|
if (!controller && ccw_addr) {
|
|
cidx = virDomainControllerFindByCCWAddress(def, ccw_addr);
|
|
if (cidx >= 0)
|
|
controller = def->controllers[cidx];
|
|
}
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
vdisk = def->disks[i];
|
|
if (vdisk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI &&
|
|
virPCIDeviceAddressEqual(&vdisk->info.addr.pci, pci_address))
|
|
return i;
|
|
if (vdisk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW &&
|
|
ccw_addr &&
|
|
virDomainDeviceCCWAddressEqual(&vdisk->info.addr.ccw, ccw_addr)) {
|
|
return i;
|
|
}
|
|
if (vdisk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE) {
|
|
virDomainDeviceDriveAddress *drive = &vdisk->info.addr.drive;
|
|
if (controller &&
|
|
virDomainDiskControllerMatch(controller->type, vdisk->bus) &&
|
|
drive->controller == controller->idx &&
|
|
drive->bus == bus && drive->target == target &&
|
|
drive->unit == unit)
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
virDomainDiskDef *
|
|
virDomainDiskByAddress(virDomainDef *def,
|
|
virPCIDeviceAddress *pci_address,
|
|
virDomainDeviceCCWAddress *ccw_addr,
|
|
unsigned int bus,
|
|
unsigned int target,
|
|
unsigned int unit)
|
|
{
|
|
int idx = virDomainDiskIndexByAddress(def, pci_address, ccw_addr,
|
|
bus, target, unit);
|
|
return idx < 0 ? NULL : def->disks[idx];
|
|
}
|
|
|
|
int
|
|
virDomainDiskIndexByName(virDomainDef *def, const char *name,
|
|
bool allow_ambiguous)
|
|
{
|
|
virDomainDiskDef *vdisk;
|
|
size_t i;
|
|
int candidate = -1;
|
|
|
|
/* We prefer the <target dev='name'/> name (it's shorter, required
|
|
* for all disks, and should be unambiguous), but also support
|
|
* <source file='name'/> (if unambiguous). Assume dst if there is
|
|
* no leading slash, source name otherwise. */
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
vdisk = def->disks[i];
|
|
if (*name != '/') {
|
|
if (STREQ(vdisk->dst, name))
|
|
return i;
|
|
} else if (STREQ_NULLABLE(virDomainDiskGetSource(vdisk), name)) {
|
|
if (allow_ambiguous)
|
|
return i;
|
|
if (candidate >= 0)
|
|
return -1;
|
|
candidate = i;
|
|
}
|
|
}
|
|
return candidate;
|
|
}
|
|
|
|
virDomainDiskDef *
|
|
virDomainDiskByName(virDomainDef *def,
|
|
const char *name,
|
|
bool allow_ambiguous)
|
|
{
|
|
int idx = virDomainDiskIndexByName(def, name, allow_ambiguous);
|
|
|
|
if (idx < 0)
|
|
return NULL;
|
|
|
|
return def->disks[idx];
|
|
}
|
|
|
|
|
|
virDomainDiskDef *
|
|
virDomainDiskByTarget(virDomainDef *def,
|
|
const char *dst)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
if (STREQ(def->disks[i]->dst, dst))
|
|
return def->disks[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void virDomainDiskInsert(virDomainDef *def,
|
|
virDomainDiskDef *disk)
|
|
{
|
|
int idx;
|
|
/* Tentatively plan to insert disk at the end. */
|
|
int insertAt = -1;
|
|
|
|
/* Then work backwards looking for disks on
|
|
* the same bus. If we find a disk with a drive
|
|
* index greater than the new one, insert at
|
|
* that position
|
|
*/
|
|
for (idx = (def->ndisks - 1); idx >= 0; idx--) {
|
|
/* If bus matches and current disk is after
|
|
* new disk, then new disk should go here */
|
|
if (def->disks[idx]->bus == disk->bus &&
|
|
(virDiskNameToIndex(def->disks[idx]->dst) >
|
|
virDiskNameToIndex(disk->dst))) {
|
|
insertAt = idx;
|
|
} else if (def->disks[idx]->bus == disk->bus &&
|
|
insertAt == -1) {
|
|
/* Last disk with match bus is before the
|
|
* new disk, then put new disk just after
|
|
*/
|
|
insertAt = idx + 1;
|
|
}
|
|
}
|
|
|
|
ignore_value(VIR_INSERT_ELEMENT(def->disks, insertAt, def->ndisks, disk));
|
|
}
|
|
|
|
|
|
virDomainDiskDef *
|
|
virDomainDiskRemove(virDomainDef *def, size_t i)
|
|
{
|
|
virDomainDiskDef *disk = def->disks[i];
|
|
|
|
VIR_DELETE_ELEMENT(def->disks, i, def->ndisks);
|
|
return disk;
|
|
}
|
|
|
|
virDomainDiskDef *
|
|
virDomainDiskRemoveByName(virDomainDef *def, const char *name)
|
|
{
|
|
int idx = virDomainDiskIndexByName(def, name, false);
|
|
if (idx < 0)
|
|
return NULL;
|
|
return virDomainDiskRemove(def, idx);
|
|
}
|
|
|
|
int virDomainNetInsert(virDomainDef *def, virDomainNetDef *net)
|
|
{
|
|
/* hostdev net devices must also exist in the hostdevs array */
|
|
if (net->type == VIR_DOMAIN_NET_TYPE_HOSTDEV &&
|
|
virDomainHostdevInsert(def, &net->data.hostdev.def) < 0)
|
|
return -1;
|
|
|
|
if (VIR_APPEND_ELEMENT(def->nets, def->nnets, net) < 0) {
|
|
/* virDomainHostdevInsert just appends new hostdevs, so we are sure
|
|
* that the hostdev we've added a few lines above is at the end of
|
|
* array. Although, devices are indexed from zero ... */
|
|
virDomainHostdevRemove(def, def->nhostdevs - 1);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* virDomainNetFindIdx:
|
|
* @def: domain definition
|
|
* @net: interface definition
|
|
*
|
|
* Lookup domain's network interface based on passed @net
|
|
* definition. If @net's MAC address was auto generated,
|
|
* the MAC comparison is ignored.
|
|
*
|
|
* Return: index of match if unique match found,
|
|
* -1 otherwise and an error is logged.
|
|
*/
|
|
int
|
|
virDomainNetFindIdx(virDomainDef *def, virDomainNetDef *net)
|
|
{
|
|
size_t i;
|
|
int matchidx = -1;
|
|
char mac[VIR_MAC_STRING_BUFLEN];
|
|
bool MACAddrSpecified = !net->mac_generated;
|
|
bool PCIAddrSpecified = virDomainDeviceAddressIsValid(&net->info,
|
|
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI);
|
|
bool CCWAddrSpecified = virDomainDeviceAddressIsValid(&net->info,
|
|
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW);
|
|
g_autofree char *addr = NULL;
|
|
const char *macAddr = _("(<null>)");
|
|
const char *alias = _("(<null>)");
|
|
|
|
if (MACAddrSpecified)
|
|
macAddr = virMacAddrFormat(&net->mac, mac);
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
if (MACAddrSpecified &&
|
|
virMacAddrCmp(&def->nets[i]->mac, &net->mac) != 0)
|
|
continue;
|
|
|
|
if (PCIAddrSpecified &&
|
|
!virPCIDeviceAddressEqual(&def->nets[i]->info.addr.pci,
|
|
&net->info.addr.pci))
|
|
continue;
|
|
|
|
if (CCWAddrSpecified &&
|
|
!virDomainDeviceCCWAddressEqual(&def->nets[i]->info.addr.ccw,
|
|
&net->info.addr.ccw))
|
|
continue;
|
|
|
|
if (net->info.alias &&
|
|
STRNEQ_NULLABLE(def->nets[i]->info.alias, net->info.alias)) {
|
|
continue;
|
|
}
|
|
|
|
if (matchidx >= 0) {
|
|
/* there were multiple matches on mac address, and no
|
|
* qualifying guest-side PCI/CCW address was given, so we must
|
|
* fail (NB: a USB address isn't adequate, since it may
|
|
* specify only vendor and product ID, and there may be
|
|
* multiples of those.
|
|
*/
|
|
if (MACAddrSpecified) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("multiple devices matching MAC address %s found"),
|
|
macAddr);
|
|
} else {
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
_("multiple matching devices found"));
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
matchidx = i;
|
|
}
|
|
|
|
if (matchidx >= 0)
|
|
return matchidx;
|
|
|
|
if (net->info.alias)
|
|
alias = net->info.alias;
|
|
|
|
if (CCWAddrSpecified)
|
|
addr = virDomainCCWAddressAsString(&net->info.addr.ccw);
|
|
else if (PCIAddrSpecified)
|
|
addr = virPCIDeviceAddressAsString(&net->info.addr.pci);
|
|
else
|
|
addr = g_strdup(_("(<null>)"));
|
|
|
|
virReportError(VIR_ERR_DEVICE_MISSING,
|
|
_("no device found at address '%s' matching MAC address"
|
|
" '%s' and alias '%s'"),
|
|
addr, macAddr, alias);
|
|
return -1;
|
|
}
|
|
|
|
bool
|
|
virDomainHasNet(virDomainDef *def, virDomainNetDef *net)
|
|
{
|
|
size_t i;
|
|
bool PCIAddrSpecified = virDomainDeviceAddressIsValid(&net->info,
|
|
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI);
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
if (virMacAddrCmp(&def->nets[i]->mac, &net->mac))
|
|
continue;
|
|
|
|
if (PCIAddrSpecified) {
|
|
if (virPCIDeviceAddressEqual(&def->nets[i]->info.addr.pci,
|
|
&net->info.addr.pci))
|
|
return true;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
virDomainNetRemoveHostdev(virDomainDef *def,
|
|
virDomainNetDef *net)
|
|
{
|
|
/* hostdev net devices are normally in the hostdevs array, but
|
|
* might have already been removed by the time we get here */
|
|
virDomainHostdevDef *hostdev = virDomainNetGetActualHostdev(net);
|
|
size_t i;
|
|
|
|
if (hostdev) {
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
if (def->hostdevs[i] == hostdev) {
|
|
virDomainHostdevRemove(def, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
virDomainNetDef *
|
|
virDomainNetRemove(virDomainDef *def, size_t i)
|
|
{
|
|
virDomainNetDef *net = def->nets[i];
|
|
|
|
virDomainNetRemoveHostdev(def, net);
|
|
VIR_DELETE_ELEMENT(def->nets, i, def->nnets);
|
|
return net;
|
|
}
|
|
|
|
|
|
virDomainNetDef *
|
|
virDomainNetRemoveByObj(virDomainDef *def, virDomainNetDef *net)
|
|
{
|
|
size_t i;
|
|
|
|
/* the device might have been added to hostdevs but not nets */
|
|
virDomainNetRemoveHostdev(def, net);
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
if (def->nets[i] == net) {
|
|
VIR_DELETE_ELEMENT(def->nets, i, def->nnets);
|
|
break;
|
|
}
|
|
}
|
|
return net;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainNetUpdate(virDomainDef *def,
|
|
size_t netidx,
|
|
virDomainNetDef *newnet)
|
|
{
|
|
size_t hostdevidx;
|
|
virDomainNetDef *oldnet = def->nets[netidx];
|
|
virDomainHostdevDef *oldhostdev = virDomainNetGetActualHostdev(oldnet);
|
|
virDomainHostdevDef *newhostdev = virDomainNetGetActualHostdev(newnet);
|
|
|
|
/*
|
|
* if newnet or oldnet has a valid hostdev*, we need to update the
|
|
* hostdevs list
|
|
*/
|
|
if (oldhostdev) {
|
|
for (hostdevidx = 0; hostdevidx < def->nhostdevs; hostdevidx++) {
|
|
if (def->hostdevs[hostdevidx] == oldhostdev)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (oldhostdev && hostdevidx < def->nhostdevs) {
|
|
if (newhostdev) {
|
|
/* update existing entry in def->hostdevs */
|
|
def->hostdevs[hostdevidx] = newhostdev;
|
|
} else {
|
|
/* delete oldhostdev from def->hostdevs */
|
|
virDomainHostdevRemove(def, hostdevidx);
|
|
}
|
|
} else if (newhostdev) {
|
|
/* add newhostdev to end of def->hostdevs */
|
|
if (VIR_APPEND_ELEMENT(def->hostdevs, def->nhostdevs, newhostdev) < 0)
|
|
return -1;
|
|
}
|
|
|
|
def->nets[netidx] = newnet;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainNetDHCPInterfaces(virDomainDef *def,
|
|
virDomainInterfacePtr **ifaces)
|
|
{
|
|
g_autoptr(virConnect) conn = NULL;
|
|
virDomainInterfacePtr *ifaces_ret = NULL;
|
|
size_t ifaces_count = 0;
|
|
size_t i;
|
|
|
|
if (!(conn = virGetConnectNetwork()))
|
|
return -1;
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
g_autoptr(virNetwork) network = NULL;
|
|
char macaddr[VIR_MAC_STRING_BUFLEN];
|
|
virNetworkDHCPLeasePtr *leases = NULL;
|
|
int n_leases = 0;
|
|
virDomainInterfacePtr iface = NULL;
|
|
size_t j;
|
|
|
|
if (def->nets[i]->type != VIR_DOMAIN_NET_TYPE_NETWORK)
|
|
continue;
|
|
|
|
virMacAddrFormat(&(def->nets[i]->mac), macaddr);
|
|
|
|
network = virNetworkLookupByName(conn,
|
|
def->nets[i]->data.network.name);
|
|
if (!network)
|
|
goto error;
|
|
|
|
if ((n_leases = virNetworkGetDHCPLeases(network, macaddr,
|
|
&leases, 0)) < 0)
|
|
goto error;
|
|
|
|
if (n_leases) {
|
|
ifaces_ret = g_renew(virDomainInterfacePtr, ifaces_ret, ifaces_count + 1);
|
|
ifaces_ret[ifaces_count] = g_new0(virDomainInterface, 1);
|
|
iface = ifaces_ret[ifaces_count];
|
|
ifaces_count++;
|
|
|
|
/* Assuming each lease corresponds to a separate IP */
|
|
iface->naddrs = n_leases;
|
|
iface->addrs = g_new0(virDomainIPAddress, iface->naddrs);
|
|
iface->name = g_strdup(def->nets[i]->ifname);
|
|
iface->hwaddr = g_strdup(macaddr);
|
|
}
|
|
|
|
for (j = 0; j < n_leases; j++) {
|
|
virNetworkDHCPLeasePtr lease = leases[j];
|
|
virDomainIPAddressPtr ip_addr = &iface->addrs[j];
|
|
|
|
ip_addr->addr = g_strdup(lease->ipaddr);
|
|
ip_addr->type = lease->type;
|
|
ip_addr->prefix = lease->prefix;
|
|
|
|
virNetworkDHCPLeaseFree(lease);
|
|
}
|
|
|
|
VIR_FREE(leases);
|
|
}
|
|
|
|
*ifaces = g_steal_pointer(&ifaces_ret);
|
|
return ifaces_count;
|
|
|
|
error:
|
|
if (ifaces_ret) {
|
|
for (i = 0; i < ifaces_count; i++)
|
|
virDomainInterfaceFree(ifaces_ret[i]);
|
|
}
|
|
VIR_FREE(ifaces_ret);
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainNetARPInterfaces(virDomainDef *def,
|
|
virDomainInterfacePtr **ifaces)
|
|
{
|
|
size_t i, j;
|
|
size_t ifaces_count = 0;
|
|
int ret = -1;
|
|
char macaddr[VIR_MAC_STRING_BUFLEN];
|
|
virDomainInterfacePtr *ifaces_ret = NULL;
|
|
virDomainInterfacePtr iface = NULL;
|
|
virArpTable *table;
|
|
|
|
table = virArpTableGet();
|
|
if (!table)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
virMacAddrFormat(&(def->nets[i]->mac), macaddr);
|
|
for (j = 0; j < table->n; j++) {
|
|
virArpTableEntry entry = table->t[j];
|
|
|
|
if (STREQ(entry.mac, macaddr)) {
|
|
iface = g_new0(virDomainInterface, 1);
|
|
|
|
iface->name = g_strdup(def->nets[i]->ifname);
|
|
|
|
iface->hwaddr = g_strdup(macaddr);
|
|
|
|
iface->addrs = g_new0(virDomainIPAddress, 1);
|
|
iface->naddrs = 1;
|
|
|
|
iface->addrs->addr = g_strdup(entry.ipaddr);
|
|
|
|
if (VIR_APPEND_ELEMENT(ifaces_ret, ifaces_count, iface) < 0)
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
*ifaces = g_steal_pointer(&ifaces_ret);
|
|
ret = ifaces_count;
|
|
|
|
cleanup:
|
|
virArpTableFree(table);
|
|
virDomainInterfaceFree(iface);
|
|
|
|
if (ifaces_ret) {
|
|
for (i = 0; i < ifaces_count; i++)
|
|
virDomainInterfaceFree(ifaces_ret[i]);
|
|
}
|
|
VIR_FREE(ifaces_ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void virDomainControllerInsert(virDomainDef *def,
|
|
virDomainControllerDef *controller)
|
|
{
|
|
def->controllers = g_renew(virDomainControllerDef *, def->controllers, def->ncontrollers + 1);
|
|
virDomainControllerInsertPreAlloced(def, controller);
|
|
}
|
|
|
|
void virDomainControllerInsertPreAlloced(virDomainDef *def,
|
|
virDomainControllerDef *controller)
|
|
{
|
|
int idx;
|
|
/* Tentatively plan to insert controller at the end. */
|
|
int insertAt = -1;
|
|
virDomainControllerDef *current = NULL;
|
|
|
|
/* Then work backwards looking for controllers of
|
|
* the same type. If we find a controller with a
|
|
* index greater than the new one, insert at
|
|
* that position
|
|
*/
|
|
for (idx = (def->ncontrollers - 1); idx >= 0; idx--) {
|
|
current = def->controllers[idx];
|
|
if (current->type == controller->type) {
|
|
if (controller->idx == -1) {
|
|
/* If the new controller doesn't have an index set
|
|
* yet, put it just past this controller, which until
|
|
* now was the last controller of this type.
|
|
*/
|
|
insertAt = idx + 1;
|
|
break;
|
|
}
|
|
if (current->idx > controller->idx) {
|
|
/* If bus matches and current controller is after
|
|
* new controller, then new controller should go here
|
|
* */
|
|
insertAt = idx;
|
|
} else if (controller->info.mastertype == VIR_DOMAIN_CONTROLLER_MASTER_NONE &&
|
|
current->info.mastertype != VIR_DOMAIN_CONTROLLER_MASTER_NONE &&
|
|
current->idx == controller->idx) {
|
|
/* If bus matches and index matches and new controller is
|
|
* master and current isn't a master, then new controller
|
|
* should go here to be placed before its companion
|
|
*/
|
|
insertAt = idx;
|
|
} else if (insertAt == -1) {
|
|
/* Last controller with match bus is before the
|
|
* new controller, then put new controller just after
|
|
*/
|
|
insertAt = idx + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* VIR_INSERT_ELEMENT_INPLACE will never return an error here. */
|
|
ignore_value(VIR_INSERT_ELEMENT_INPLACE(def->controllers, insertAt,
|
|
def->ncontrollers, controller));
|
|
}
|
|
|
|
int
|
|
virDomainControllerFind(const virDomainDef *def,
|
|
int type,
|
|
int idx)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
if ((def->controllers[i]->type == type) &&
|
|
(def->controllers[i]->idx == idx)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainControllerFindUnusedIndex(virDomainDef const *def, int type)
|
|
{
|
|
int idx = 0;
|
|
|
|
while (virDomainControllerFind(def, type, idx) >= 0)
|
|
idx++;
|
|
|
|
return idx;
|
|
}
|
|
|
|
|
|
const char *
|
|
virDomainControllerAliasFind(const virDomainDef *def,
|
|
int type,
|
|
int idx)
|
|
{
|
|
int contIndex;
|
|
const char *contTypeStr = virDomainControllerTypeToString(type);
|
|
|
|
if (!contTypeStr) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown controller type %d"),
|
|
type);
|
|
return NULL;
|
|
}
|
|
|
|
contIndex = virDomainControllerFind(def, type, idx);
|
|
if (contIndex < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Could not find %s controller with index %d "
|
|
"required for device"),
|
|
contTypeStr, idx);
|
|
return NULL;
|
|
}
|
|
if (!def->controllers[contIndex]->info.alias) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Device alias was not set for %s controller "
|
|
"with index %d "),
|
|
contTypeStr, idx);
|
|
return NULL;
|
|
}
|
|
return def->controllers[contIndex]->info.alias;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainControllerFindByType(virDomainDef *def,
|
|
int type)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
if (def->controllers[i]->type == type)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virDomainControllerFindByCCWAddress(virDomainDef *def,
|
|
virDomainDeviceCCWAddress *addr)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
virDomainDeviceInfo *info = &def->controllers[i]->info;
|
|
|
|
if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW &&
|
|
virDomainDeviceCCWAddressEqual(&info->addr.ccw, addr))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virDomainControllerFindByPCIAddress(virDomainDef *def,
|
|
virPCIDeviceAddress *addr)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
virDomainDeviceInfo *info = &def->controllers[i]->info;
|
|
|
|
if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI &&
|
|
virPCIDeviceAddressEqual(&info->addr.pci, addr))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
virDomainControllerDef *
|
|
virDomainControllerRemove(virDomainDef *def, size_t i)
|
|
{
|
|
virDomainControllerDef *controller = def->controllers[i];
|
|
|
|
VIR_DELETE_ELEMENT(def->controllers, i, def->ncontrollers);
|
|
return controller;
|
|
}
|
|
|
|
int virDomainLeaseIndex(virDomainDef *def,
|
|
virDomainLeaseDef *lease)
|
|
{
|
|
virDomainLeaseDef *vlease;
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nleases; i++) {
|
|
vlease = def->leases[i];
|
|
/* Either both must have lockspaces present which match.. */
|
|
if (vlease->lockspace && lease->lockspace) {
|
|
if (STRNEQ(vlease->lockspace, lease->lockspace))
|
|
continue;
|
|
/* ...or neither must have a lockspace present */
|
|
} else if (vlease->lockspace || lease->lockspace) {
|
|
continue;
|
|
}
|
|
|
|
if (STREQ(vlease->key, lease->key))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
void virDomainLeaseInsertPreAlloc(virDomainDef *def)
|
|
{
|
|
VIR_EXPAND_N(def->leases, def->nleases, 1);
|
|
}
|
|
|
|
void virDomainLeaseInsert(virDomainDef *def, virDomainLeaseDef *lease)
|
|
{
|
|
virDomainLeaseInsertPreAlloc(def);
|
|
virDomainLeaseInsertPreAlloced(def, lease);
|
|
}
|
|
|
|
|
|
void virDomainLeaseInsertPreAlloced(virDomainDef *def,
|
|
virDomainLeaseDef *lease)
|
|
{
|
|
if (lease == NULL)
|
|
VIR_SHRINK_N(def->leases, def->nleases, 1);
|
|
else
|
|
def->leases[def->nleases-1] = lease;
|
|
}
|
|
|
|
|
|
virDomainLeaseDef *
|
|
virDomainLeaseRemoveAt(virDomainDef *def, size_t i)
|
|
{
|
|
virDomainLeaseDef *lease = def->leases[i];
|
|
|
|
VIR_DELETE_ELEMENT(def->leases, i, def->nleases);
|
|
return lease;
|
|
}
|
|
|
|
|
|
virDomainLeaseDef *
|
|
virDomainLeaseRemove(virDomainDef *def,
|
|
virDomainLeaseDef *lease)
|
|
{
|
|
int idx = virDomainLeaseIndex(def, lease);
|
|
if (idx < 0)
|
|
return NULL;
|
|
return virDomainLeaseRemoveAt(def, idx);
|
|
}
|
|
|
|
bool
|
|
virDomainChrEquals(virDomainChrDef *src,
|
|
virDomainChrDef *tgt)
|
|
{
|
|
if (!src || !tgt)
|
|
return src == tgt;
|
|
|
|
if (src->deviceType != tgt->deviceType ||
|
|
!virDomainChrSourceDefIsEqual(src->source, tgt->source))
|
|
return false;
|
|
|
|
switch ((virDomainChrDeviceType) src->deviceType) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
if (src->targetType != tgt->targetType)
|
|
return false;
|
|
switch ((virDomainChrChannelTargetType) src->targetType) {
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XEN:
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO:
|
|
return STREQ_NULLABLE(src->target.name, tgt->target.name);
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD:
|
|
if (!src->target.addr || !tgt->target.addr)
|
|
return src->target.addr == tgt->target.addr;
|
|
return memcmp(src->target.addr, tgt->target.addr,
|
|
sizeof(*src->target.addr)) == 0;
|
|
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_NONE:
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_LAST:
|
|
/* shouldn't happen */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
|
|
if (src->targetType != tgt->targetType)
|
|
return false;
|
|
|
|
G_GNUC_FALLTHROUGH;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
|
|
return src->target.port == tgt->target.port;
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
|
|
/* shouldn't happen */
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virDomainChrDef *
|
|
virDomainChrFind(virDomainDef *def,
|
|
virDomainChrDef *target)
|
|
{
|
|
virDomainChrDef *chr;
|
|
const virDomainChrDef **arrPtr;
|
|
size_t i, cnt;
|
|
|
|
virDomainChrGetDomainPtrs(def, target->deviceType, &arrPtr, &cnt);
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
/* Cast away const */
|
|
chr = (virDomainChrDef *) arrPtr[i];
|
|
if (virDomainChrEquals(chr, target))
|
|
return chr;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Return the address within vmdef to be modified when working with a
|
|
* chrdefptr of the given type. */
|
|
static int G_GNUC_WARN_UNUSED_RESULT
|
|
virDomainChrGetDomainPtrsInternal(virDomainDef *vmdef,
|
|
virDomainChrDeviceType type,
|
|
virDomainChrDef ****arrPtr,
|
|
size_t **cntPtr)
|
|
{
|
|
switch (type) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
|
|
*arrPtr = &vmdef->parallels;
|
|
*cntPtr = &vmdef->nparallels;
|
|
return 0;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
|
|
*arrPtr = &vmdef->serials;
|
|
*cntPtr = &vmdef->nserials;
|
|
return 0;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
|
|
*arrPtr = &vmdef->consoles;
|
|
*cntPtr = &vmdef->nconsoles;
|
|
return 0;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
|
|
*arrPtr = &vmdef->channels;
|
|
*cntPtr = &vmdef->nchannels;
|
|
return 0;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown char device type: %d"), type);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* Return the array within vmdef that can contain a chrdefptr of the
|
|
* given type. */
|
|
void
|
|
virDomainChrGetDomainPtrs(const virDomainDef *vmdef,
|
|
virDomainChrDeviceType type,
|
|
const virDomainChrDef ***arrPtr,
|
|
size_t *cntPtr)
|
|
{
|
|
virDomainChrDef ***arrVar = NULL;
|
|
size_t *cntVar = NULL;
|
|
|
|
/* Cast away const; we add it back in the final assignment. */
|
|
if (virDomainChrGetDomainPtrsInternal((virDomainDef *) vmdef, type,
|
|
&arrVar, &cntVar) < 0) {
|
|
*arrPtr = NULL;
|
|
*cntPtr = 0;
|
|
} else {
|
|
*arrPtr = (const virDomainChrDef **) *arrVar;
|
|
*cntPtr = *cntVar;
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
virDomainChrPreAlloc(virDomainDef *vmdef,
|
|
virDomainChrDef *chr)
|
|
{
|
|
virDomainChrDef ***arrPtr = NULL;
|
|
size_t *cntPtr = NULL;
|
|
|
|
if (virDomainChrGetDomainPtrsInternal(vmdef, chr->deviceType,
|
|
&arrPtr, &cntPtr) < 0)
|
|
return -1;
|
|
|
|
VIR_REALLOC_N(*arrPtr, *cntPtr + 1);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
virDomainChrInsertPreAlloced(virDomainDef *vmdef,
|
|
virDomainChrDef *chr)
|
|
{
|
|
virDomainChrDef ***arrPtr = NULL;
|
|
size_t *cntPtr = NULL;
|
|
|
|
if (virDomainChrGetDomainPtrsInternal(vmdef, chr->deviceType,
|
|
&arrPtr, &cntPtr) < 0)
|
|
return;
|
|
|
|
VIR_APPEND_ELEMENT_INPLACE(*arrPtr, *cntPtr, chr);
|
|
}
|
|
|
|
virDomainChrDef *
|
|
virDomainChrRemove(virDomainDef *vmdef,
|
|
virDomainChrDef *chr)
|
|
{
|
|
virDomainChrDef *ret = NULL;
|
|
virDomainChrDef ***arrPtr = NULL;
|
|
size_t i, *cntPtr = NULL;
|
|
|
|
if (virDomainChrGetDomainPtrsInternal(vmdef, chr->deviceType,
|
|
&arrPtr, &cntPtr) < 0)
|
|
return NULL;
|
|
|
|
for (i = 0; i < *cntPtr; i++) {
|
|
ret = (*arrPtr)[i];
|
|
|
|
if (virDomainChrEquals(ret, chr))
|
|
break;
|
|
}
|
|
|
|
if (i == *cntPtr)
|
|
return NULL;
|
|
|
|
VIR_DELETE_ELEMENT(*arrPtr, i, *cntPtr);
|
|
return ret;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
virDomainRNGFind(virDomainDef *def,
|
|
virDomainRNGDef *rng)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nrngs; i++) {
|
|
virDomainRNGDef *tmp = def->rngs[i];
|
|
|
|
if (rng->model != tmp->model || rng->backend != tmp->backend)
|
|
continue;
|
|
|
|
if (rng->rate != tmp->rate || rng->period != tmp->period)
|
|
continue;
|
|
|
|
switch ((virDomainRNGBackend) rng->backend) {
|
|
case VIR_DOMAIN_RNG_BACKEND_RANDOM:
|
|
if (STRNEQ_NULLABLE(rng->source.file, tmp->source.file))
|
|
continue;
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_EGD:
|
|
if (!virDomainChrSourceDefIsEqual(rng->source.chardev,
|
|
tmp->source.chardev))
|
|
continue;
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_BUILTIN:
|
|
case VIR_DOMAIN_RNG_BACKEND_LAST:
|
|
break;
|
|
}
|
|
|
|
if (rng->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
!virDomainDeviceInfoAddressIsEqual(&rng->info, &tmp->info))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
if (i < def->nrngs)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
virDomainRNGDef *
|
|
virDomainRNGRemove(virDomainDef *def,
|
|
size_t idx)
|
|
{
|
|
virDomainRNGDef *ret = def->rngs[idx];
|
|
|
|
VIR_DELETE_ELEMENT(def->rngs, idx, def->nrngs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainMemoryFindByDefInternal(virDomainDef *def,
|
|
virDomainMemoryDef *mem,
|
|
bool allowAddressFallback)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nmems; i++) {
|
|
virDomainMemoryDef *tmp = def->mems[i];
|
|
|
|
/* address, if present */
|
|
if (allowAddressFallback) {
|
|
if (tmp->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
|
|
continue;
|
|
} else {
|
|
if (mem->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
!virDomainDeviceInfoAddressIsEqual(&tmp->info, &mem->info))
|
|
continue;
|
|
}
|
|
|
|
/* alias, if present */
|
|
if (mem->info.alias &&
|
|
STRNEQ_NULLABLE(tmp->info.alias, mem->info.alias))
|
|
continue;
|
|
|
|
/* target info -> always present */
|
|
if (tmp->model != mem->model ||
|
|
tmp->targetNode != mem->targetNode ||
|
|
tmp->size != mem->size)
|
|
continue;
|
|
|
|
switch (mem->model) {
|
|
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
|
|
/* source stuff -> match with device */
|
|
if (tmp->pagesize != mem->pagesize)
|
|
continue;
|
|
|
|
if (!virBitmapEqual(tmp->sourceNodes, mem->sourceNodes))
|
|
continue;
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
|
|
if (STRNEQ(tmp->nvdimmPath, mem->nvdimmPath))
|
|
continue;
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
|
|
if (STRNEQ(tmp->nvdimmPath, mem->nvdimmPath))
|
|
continue;
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NONE:
|
|
case VIR_DOMAIN_MEMORY_MODEL_LAST:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (i == def->nmems)
|
|
return -1;
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainMemoryFindByDef(virDomainDef *def,
|
|
virDomainMemoryDef *mem)
|
|
{
|
|
return virDomainMemoryFindByDefInternal(def, mem, false);
|
|
}
|
|
|
|
|
|
int
|
|
virDomainMemoryFindInactiveByDef(virDomainDef *def,
|
|
virDomainMemoryDef *mem)
|
|
{
|
|
int ret;
|
|
|
|
if ((ret = virDomainMemoryFindByDefInternal(def, mem, false)) < 0)
|
|
ret = virDomainMemoryFindByDefInternal(def, mem, true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainMemoryInsert:
|
|
*
|
|
* Inserts a memory device definition into the domain definition. This helper
|
|
* should be used only in hot/cold-plug cases as it's blindly modifying the
|
|
* total memory size.
|
|
*/
|
|
int
|
|
virDomainMemoryInsert(virDomainDef *def,
|
|
virDomainMemoryDef *mem)
|
|
{
|
|
unsigned long long memory = virDomainDefGetMemoryTotal(def);
|
|
int id = def->nmems;
|
|
|
|
if (mem->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
virDomainDefHasDeviceAddress(def, &mem->info)) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("Domain already contains a device with the same "
|
|
"address"));
|
|
return -1;
|
|
}
|
|
|
|
if (VIR_APPEND_ELEMENT_COPY(def->mems, def->nmems, mem) < 0)
|
|
return -1;
|
|
|
|
virDomainDefSetMemoryTotal(def, memory + mem->size);
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainMemoryRemove:
|
|
*
|
|
* Removes a memory device definition from the domain definition. This helper
|
|
* should be used only in hot/cold-plug cases as it's blindly modifying the
|
|
* total memory size.
|
|
*/
|
|
virDomainMemoryDef *
|
|
virDomainMemoryRemove(virDomainDef *def,
|
|
int idx)
|
|
{
|
|
unsigned long long memory = virDomainDefGetMemoryTotal(def);
|
|
virDomainMemoryDef *ret = def->mems[idx];
|
|
|
|
VIR_DELETE_ELEMENT(def->mems, idx, def->nmems);
|
|
|
|
/* fix total memory size of the domain */
|
|
virDomainDefSetMemoryTotal(def, memory - ret->size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
virDomainRedirdevDefFind(virDomainDef *def,
|
|
virDomainRedirdevDef *redirdev)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nredirdevs; i++) {
|
|
virDomainRedirdevDef *tmp = def->redirdevs[i];
|
|
|
|
if (redirdev->bus != tmp->bus)
|
|
continue;
|
|
|
|
if (!virDomainChrSourceDefIsEqual(redirdev->source, tmp->source))
|
|
continue;
|
|
|
|
if (redirdev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
!virDomainDeviceInfoAddressIsEqual(&redirdev->info, &tmp->info))
|
|
continue;
|
|
|
|
if (redirdev->info.alias &&
|
|
STRNEQ_NULLABLE(redirdev->info.alias, tmp->info.alias))
|
|
continue;
|
|
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
virDomainRedirdevDef *
|
|
virDomainRedirdevDefRemove(virDomainDef *def, size_t idx)
|
|
{
|
|
virDomainRedirdevDef *ret = def->redirdevs[idx];
|
|
|
|
VIR_DELETE_ELEMENT(def->redirdevs, idx, def->nredirdevs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainShmemDefInsert(virDomainDef *def,
|
|
virDomainShmemDef *shmem)
|
|
{
|
|
return VIR_APPEND_ELEMENT(def->shmems, def->nshmems, shmem);
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainShmemDefEquals(virDomainShmemDef *src,
|
|
virDomainShmemDef *dst)
|
|
{
|
|
if (STRNEQ_NULLABLE(src->name, dst->name))
|
|
return false;
|
|
|
|
if (src->size != dst->size)
|
|
return false;
|
|
|
|
if (src->model != dst->model)
|
|
return false;
|
|
|
|
if (src->role != dst->role)
|
|
return false;
|
|
|
|
if (src->server.enabled != dst->server.enabled)
|
|
return false;
|
|
|
|
if (src->server.enabled) {
|
|
if (STRNEQ_NULLABLE(src->server.chr.data.nix.path,
|
|
dst->server.chr.data.nix.path))
|
|
return false;
|
|
}
|
|
|
|
if (src->msi.enabled != dst->msi.enabled)
|
|
return false;
|
|
|
|
if (src->msi.enabled) {
|
|
if (src->msi.vectors != dst->msi.vectors)
|
|
return false;
|
|
if (src->msi.ioeventfd != dst->msi.ioeventfd)
|
|
return false;
|
|
}
|
|
|
|
if (src->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
!virDomainDeviceInfoAddressIsEqual(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
virDomainShmemDefFind(virDomainDef *def,
|
|
virDomainShmemDef *shmem)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nshmems; i++) {
|
|
if (virDomainShmemDefEquals(shmem, def->shmems[i]))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
virDomainShmemDef *
|
|
virDomainShmemDefRemove(virDomainDef *def,
|
|
size_t idx)
|
|
{
|
|
virDomainShmemDef *ret = def->shmems[idx];
|
|
|
|
VIR_DELETE_ELEMENT(def->shmems, idx, def->nshmems);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainInputDefEquals(const virDomainInputDef *a,
|
|
const virDomainInputDef *b)
|
|
{
|
|
if (a->type != b->type)
|
|
return false;
|
|
|
|
if (a->bus != b->bus)
|
|
return false;
|
|
|
|
if (a->type == VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH &&
|
|
STRNEQ_NULLABLE(a->source.evdev, b->source.evdev))
|
|
return false;
|
|
|
|
if (a->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
!virDomainDeviceInfoAddressIsEqual(&a->info, &b->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
ssize_t
|
|
virDomainInputDefFind(const virDomainDef *def,
|
|
const virDomainInputDef *input)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ninputs; i++) {
|
|
if (virDomainInputDefEquals(input, def->inputs[i]))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainVsockDefEquals(const virDomainVsockDef *a,
|
|
const virDomainVsockDef *b)
|
|
{
|
|
if (a->model != b->model)
|
|
return false;
|
|
|
|
if (a->auto_cid != b->auto_cid)
|
|
return false;
|
|
|
|
if (a->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
|
|
!virDomainDeviceInfoAddressIsEqual(&a->info, &b->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
char *
|
|
virDomainDefGetDefaultEmulator(virDomainDef *def,
|
|
virCaps *caps)
|
|
{
|
|
char *retemu;
|
|
g_autofree virCapsDomainData *capsdata = NULL;
|
|
|
|
if (!(capsdata = virCapabilitiesDomainDataLookup(caps, def->os.type,
|
|
def->os.arch, def->virtType, NULL, NULL)))
|
|
return NULL;
|
|
|
|
retemu = g_strdup(capsdata->emulator);
|
|
|
|
return retemu;
|
|
}
|
|
|
|
static int
|
|
virDomainDefParseBootXML(xmlXPathContextPtr ctxt,
|
|
virDomainDef *def)
|
|
{
|
|
xmlNodePtr node;
|
|
size_t i;
|
|
int n;
|
|
g_autofree char *tmp = NULL;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
|
|
/* analysis of the boot devices */
|
|
if ((n = virXPathNodeSet("./os/boot", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < n && i < VIR_DOMAIN_BOOT_LAST; i++) {
|
|
int val;
|
|
g_autofree char *dev = virXMLPropString(nodes[i], "dev");
|
|
if (!dev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing boot device"));
|
|
return -1;
|
|
}
|
|
if ((val = virDomainBootTypeFromString(dev)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown boot device '%s'"),
|
|
dev);
|
|
return -1;
|
|
}
|
|
def->os.bootDevs[def->os.nBootDevs++] = val;
|
|
}
|
|
|
|
if ((node = virXPathNode("./os/bootmenu[1]", ctxt))) {
|
|
tmp = virXMLPropString(node, "enable");
|
|
if (tmp) {
|
|
def->os.bootmenu = virTristateBoolTypeFromString(tmp);
|
|
if (def->os.bootmenu <= 0) {
|
|
/* In order not to break misconfigured machines, this
|
|
* should not emit an error, but rather set the bootmenu
|
|
* to disabled */
|
|
VIR_WARN("disabling bootmenu due to unknown option '%s'",
|
|
tmp);
|
|
def->os.bootmenu = VIR_TRISTATE_BOOL_NO;
|
|
}
|
|
VIR_FREE(tmp);
|
|
}
|
|
|
|
tmp = virXMLPropString(node, "timeout");
|
|
if (tmp && def->os.bootmenu == VIR_TRISTATE_BOOL_YES) {
|
|
if (virStrToLong_uip(tmp, NULL, 0, &def->os.bm_timeout) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid value for boot menu timeout"));
|
|
return -1;
|
|
}
|
|
def->os.bm_timeout_set = true;
|
|
}
|
|
VIR_FREE(tmp);
|
|
}
|
|
|
|
if ((node = virXPathNode("./os/bios[1]", ctxt))) {
|
|
tmp = virXMLPropString(node, "useserial");
|
|
if (tmp) {
|
|
bool state = false;
|
|
ignore_value(virStringParseYesNo(tmp, &state));
|
|
def->os.bios.useserial = virTristateBoolFromBool(state);
|
|
VIR_FREE(tmp);
|
|
}
|
|
|
|
tmp = virXMLPropString(node, "rebootTimeout");
|
|
if (tmp) {
|
|
/* that was really just for the check if it is there */
|
|
|
|
if (virStrToLong_i(tmp, NULL, 0, &def->os.bios.rt_delay) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid value for rebootTimeout"));
|
|
return -1;
|
|
}
|
|
def->os.bios.rt_set = true;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int virDomainIdMapEntrySort(const void *a, const void *b)
|
|
{
|
|
const virDomainIdMapEntry *entrya = a;
|
|
const virDomainIdMapEntry *entryb = b;
|
|
|
|
if (entrya->start > entryb->start)
|
|
return 1;
|
|
else if (entrya->start < entryb->start)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* Parse the XML definition for user namespace id map.
|
|
*
|
|
* idmap has the form of
|
|
*
|
|
* <uid start='0' target='1000' count='10'/>
|
|
* <gid start='0' target='1000' count='10'/>
|
|
*/
|
|
static virDomainIdMapEntry *
|
|
virDomainIdmapDefParseXML(xmlXPathContextPtr ctxt,
|
|
xmlNodePtr *node,
|
|
size_t num)
|
|
{
|
|
size_t i;
|
|
virDomainIdMapEntry *idmap = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
|
|
idmap = g_new0(virDomainIdMapEntry, num);
|
|
|
|
for (i = 0; i < num; i++) {
|
|
ctxt->node = node[i];
|
|
if (virXPathUInt("string(./@start)", ctxt, &idmap[i].start) < 0 ||
|
|
virXPathUInt("string(./@target)", ctxt, &idmap[i].target) < 0 ||
|
|
virXPathUInt("string(./@count)", ctxt, &idmap[i].count) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("invalid idmap start/target/count settings"));
|
|
VIR_FREE(idmap);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
qsort(idmap, num, sizeof(idmap[0]), virDomainIdMapEntrySort);
|
|
|
|
return idmap;
|
|
}
|
|
|
|
/* Parse the XML definition for an IOThread ID
|
|
*
|
|
* Format is :
|
|
*
|
|
* <iothreads>4</iothreads>
|
|
* <iothreadids>
|
|
* <iothread id='1'/>
|
|
* <iothread id='3'/>
|
|
* <iothread id='5'/>
|
|
* <iothread id='7'/>
|
|
* </iothreadids>
|
|
*/
|
|
static virDomainIOThreadIDDef *
|
|
virDomainIOThreadIDDefParseXML(xmlNodePtr node)
|
|
{
|
|
g_autoptr(virDomainIOThreadIDDef) iothrid = g_new0(virDomainIOThreadIDDef, 1);
|
|
|
|
if (virXMLPropUInt(node, "id", 10,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&iothrid->iothread_id) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&iothrid);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseIOThreads(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
size_t i;
|
|
int n = 0;
|
|
unsigned int iothreads = 0;
|
|
g_autofree char *tmp = NULL;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
|
|
tmp = virXPathString("string(./iothreads[1])", ctxt);
|
|
if (tmp && virStrToLong_uip(tmp, NULL, 10, &iothreads) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid iothreads count '%s'"), tmp);
|
|
return -1;
|
|
}
|
|
|
|
/* Extract any iothread id's defined */
|
|
if ((n = virXPathNodeSet("./iothreadids/iothread", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (n > iothreads)
|
|
iothreads = n;
|
|
|
|
if (n)
|
|
def->iothreadids = g_new0(virDomainIOThreadIDDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainIOThreadIDDef *iothrid = NULL;
|
|
if (!(iothrid = virDomainIOThreadIDDefParseXML(nodes[i])))
|
|
return -1;
|
|
|
|
if (virDomainIOThreadIDFind(def, iothrid->iothread_id)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("duplicate iothread id '%u' found"),
|
|
iothrid->iothread_id);
|
|
virDomainIOThreadIDDefFree(iothrid);
|
|
return -1;
|
|
}
|
|
def->iothreadids[def->niothreadids++] = iothrid;
|
|
}
|
|
|
|
return virDomainIOThreadIDDefArrayInit(def, iothreads);
|
|
}
|
|
|
|
|
|
/* Parse the XML definition for a vcpupin
|
|
*
|
|
* vcpupin has the form of
|
|
* <vcpupin vcpu='0' cpuset='0'/>
|
|
*/
|
|
static int
|
|
virDomainVcpuPinDefParseXML(virDomainDef *def,
|
|
xmlNodePtr node)
|
|
{
|
|
virDomainVcpuDef *vcpu;
|
|
unsigned int vcpuid;
|
|
g_autofree char *tmp = NULL;
|
|
|
|
if (virXMLPropUInt(node, "vcpu", 10, VIR_XML_PROP_REQUIRED, &vcpuid) < 0)
|
|
return -1;
|
|
|
|
if (!(vcpu = virDomainDefGetVcpu(def, vcpuid))) {
|
|
VIR_WARN("Ignoring vcpupin for missing vcpus");
|
|
return 0;
|
|
}
|
|
|
|
if (!(tmp = virXMLPropString(node, "cpuset"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing cpuset for vcpupin"));
|
|
return -1;
|
|
}
|
|
|
|
if (vcpu->cpumask) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("duplicate vcpupin for vcpu '%d'"), vcpuid);
|
|
return -1;
|
|
}
|
|
|
|
if (virBitmapParse(tmp, &vcpu->cpumask, VIR_DOMAIN_CPUMASK_LEN) < 0)
|
|
return -1;
|
|
|
|
if (virBitmapIsAllClear(vcpu->cpumask)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Invalid value of 'cpuset': %s"), tmp);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Parse the XML definition for a iothreadpin
|
|
* and an iothreadspin has the form
|
|
* <iothreadpin iothread='1' cpuset='2'/>
|
|
*/
|
|
static int
|
|
virDomainIOThreadPinDefParseXML(xmlNodePtr node,
|
|
virDomainDef *def)
|
|
{
|
|
virDomainIOThreadIDDef *iothrid;
|
|
unsigned int iothreadid;
|
|
g_autofree char *tmp = NULL;
|
|
g_autoptr(virBitmap) cpumask = NULL;
|
|
|
|
if (virXMLPropUInt(node, "iothread", 10,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
&iothreadid) < 0)
|
|
return -1;
|
|
|
|
if (!(iothrid = virDomainIOThreadIDFind(def, iothreadid))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Cannot find 'iothread' : %u"),
|
|
iothreadid);
|
|
return -1;
|
|
}
|
|
|
|
if (!(tmp = virXMLPropString(node, "cpuset"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing cpuset for iothreadpin"));
|
|
return -1;
|
|
}
|
|
|
|
if (virBitmapParse(tmp, &cpumask, VIR_DOMAIN_CPUMASK_LEN) < 0)
|
|
return -1;
|
|
|
|
if (virBitmapIsAllClear(cpumask)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Invalid value of 'cpuset': %s"),
|
|
tmp);
|
|
return -1;
|
|
}
|
|
|
|
if (iothrid->cpumask) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("duplicate iothreadpin for same iothread '%u'"),
|
|
iothreadid);
|
|
return -1;
|
|
}
|
|
|
|
iothrid->cpumask = g_steal_pointer(&cpumask);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Parse the XML definition for emulatorpin.
|
|
* emulatorpin has the form of
|
|
* <emulatorpin cpuset='0'/>
|
|
*/
|
|
static virBitmap *
|
|
virDomainEmulatorPinDefParseXML(xmlNodePtr node)
|
|
{
|
|
g_autofree char *tmp = NULL;
|
|
g_autoptr(virBitmap) def = NULL;
|
|
|
|
if (!(tmp = virXMLPropString(node, "cpuset"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing cpuset for emulatorpin"));
|
|
return NULL;
|
|
}
|
|
|
|
if (virBitmapParse(tmp, &def, VIR_DOMAIN_CPUMASK_LEN) < 0)
|
|
return NULL;
|
|
|
|
if (virBitmapIsAllClear(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Invalid value of 'cpuset': %s"), tmp);
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
|
|
virDomainControllerDef *
|
|
virDomainDefAddController(virDomainDef *def, int type, int idx, int model)
|
|
{
|
|
virDomainControllerDef *cont;
|
|
|
|
if (!(cont = virDomainControllerDefNew(type)))
|
|
return NULL;
|
|
|
|
if (idx < 0)
|
|
idx = virDomainControllerFindUnusedIndex(def, type);
|
|
|
|
cont->idx = idx;
|
|
cont->model = model;
|
|
|
|
if (VIR_APPEND_ELEMENT_COPY(def->controllers, def->ncontrollers, cont) < 0) {
|
|
VIR_FREE(cont);
|
|
return NULL;
|
|
}
|
|
|
|
return cont;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefAddUSBController:
|
|
* @def: the domain
|
|
* @idx: index for new controller (or -1 for "lowest unused index")
|
|
* @model: VIR_DOMAIN_CONTROLLER_MODEL_USB_* or -1
|
|
*
|
|
* Add a USB controller of the specified model (or default model for
|
|
* current machinetype if model == -1). If model is ich9-usb-ehci,
|
|
* also add companion uhci1, uhci2, and uhci3 controllers at the same
|
|
* index.
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
int
|
|
virDomainDefAddUSBController(virDomainDef *def, int idx, int model)
|
|
{
|
|
virDomainControllerDef *cont; /* this is a *copy* of the virDomainControllerDef */
|
|
|
|
cont = virDomainDefAddController(def, VIR_DOMAIN_CONTROLLER_TYPE_USB,
|
|
idx, model);
|
|
if (!cont)
|
|
return -1;
|
|
|
|
if (model != VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_EHCI1)
|
|
return 0;
|
|
|
|
/* When the initial controller is ich9-usb-ehci, also add the
|
|
* companion controllers
|
|
*/
|
|
|
|
idx = cont->idx; /* in case original request was "-1" */
|
|
|
|
if (!(cont = virDomainDefAddController(def, VIR_DOMAIN_CONTROLLER_TYPE_USB,
|
|
idx, VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI1)))
|
|
return -1;
|
|
cont->info.mastertype = VIR_DOMAIN_CONTROLLER_MASTER_USB;
|
|
cont->info.master.usb.startport = 0;
|
|
|
|
if (!(cont = virDomainDefAddController(def, VIR_DOMAIN_CONTROLLER_TYPE_USB,
|
|
idx, VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI2)))
|
|
return -1;
|
|
cont->info.mastertype = VIR_DOMAIN_CONTROLLER_MASTER_USB;
|
|
cont->info.master.usb.startport = 2;
|
|
|
|
if (!(cont = virDomainDefAddController(def, VIR_DOMAIN_CONTROLLER_TYPE_USB,
|
|
idx, VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI3)))
|
|
return -1;
|
|
cont->info.mastertype = VIR_DOMAIN_CONTROLLER_MASTER_USB;
|
|
cont->info.master.usb.startport = 4;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDefMaybeAddController(virDomainDef *def,
|
|
int type,
|
|
int idx,
|
|
int model)
|
|
{
|
|
/* skip if a specific index was given and it is already
|
|
* in use for that type of controller
|
|
*/
|
|
if (idx >= 0 && virDomainControllerFind(def, type, idx) >= 0)
|
|
return 0;
|
|
|
|
if (virDomainDefAddController(def, type, idx, model))
|
|
return 1;
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDefMaybeAddInput(virDomainDef *def,
|
|
int type,
|
|
int bus)
|
|
{
|
|
size_t i;
|
|
virDomainInputDef *input;
|
|
|
|
for (i = 0; i < def->ninputs; i++) {
|
|
if (def->inputs[i]->type == type &&
|
|
def->inputs[i]->bus == bus)
|
|
return 0;
|
|
}
|
|
|
|
input = g_new0(virDomainInputDef, 1);
|
|
|
|
input->type = type;
|
|
input->bus = bus;
|
|
|
|
if (VIR_APPEND_ELEMENT(def->inputs, def->ninputs, input) < 0) {
|
|
VIR_FREE(input);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHugepagesParseXML(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainHugePage *hugepage)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
g_autofree char *nodeset = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virDomainParseMemory("./@size", "./@unit", ctxt,
|
|
&hugepage->size, true, false) < 0)
|
|
return -1;
|
|
|
|
if (!hugepage->size) {
|
|
virReportError(VIR_ERR_XML_DETAIL, "%s",
|
|
_("hugepage size can't be zero"));
|
|
return -1;
|
|
}
|
|
|
|
if ((nodeset = virXMLPropString(node, "nodeset"))) {
|
|
if (virBitmapParse(nodeset, &hugepage->nodemask,
|
|
VIR_DOMAIN_CPUMASK_LEN) < 0)
|
|
return -1;
|
|
|
|
if (virBitmapIsAllClear(hugepage->nodemask)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Invalid value of 'nodeset': %s"), nodeset);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virDomainResourceDef *
|
|
virDomainResourceDefParse(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainResourceDef *def = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
def = g_new0(virDomainResourceDef, 1);
|
|
|
|
/* Find out what type of virtualization to use */
|
|
if (!(def->partition = virXPathString("string(./partition)", ctxt))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing resource partition attribute"));
|
|
goto error;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainResourceDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainFeaturesHyperVDefParse(virDomainDef *def,
|
|
xmlNodePtr node)
|
|
{
|
|
def->features[VIR_DOMAIN_FEATURE_HYPERV] = VIR_TRISTATE_SWITCH_ON;
|
|
|
|
node = xmlFirstElementChild(node);
|
|
while (node != NULL) {
|
|
int feature;
|
|
virTristateSwitch value;
|
|
xmlNodePtr child;
|
|
|
|
feature = virDomainHypervTypeFromString((const char *)node->name);
|
|
if (feature < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported HyperV Enlightenment feature: %s"),
|
|
node->name);
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropTristateSwitch(node, "state", VIR_XML_PROP_REQUIRED,
|
|
&value) < 0)
|
|
return -1;
|
|
|
|
def->hyperv_features[feature] = value;
|
|
|
|
switch ((virDomainHyperv) feature) {
|
|
case VIR_DOMAIN_HYPERV_RELAXED:
|
|
case VIR_DOMAIN_HYPERV_VAPIC:
|
|
case VIR_DOMAIN_HYPERV_VPINDEX:
|
|
case VIR_DOMAIN_HYPERV_RUNTIME:
|
|
case VIR_DOMAIN_HYPERV_SYNIC:
|
|
case VIR_DOMAIN_HYPERV_RESET:
|
|
case VIR_DOMAIN_HYPERV_FREQUENCIES:
|
|
case VIR_DOMAIN_HYPERV_REENLIGHTENMENT:
|
|
case VIR_DOMAIN_HYPERV_TLBFLUSH:
|
|
case VIR_DOMAIN_HYPERV_IPI:
|
|
case VIR_DOMAIN_HYPERV_EVMCS:
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_STIMER:
|
|
if (value != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
child = xmlFirstElementChild(node);
|
|
while (child) {
|
|
if (STRNEQ((const char *)child->name, "direct")) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported Hyper-V stimer feature: %s"),
|
|
child->name);
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropTristateSwitch(child, "state", VIR_XML_PROP_REQUIRED,
|
|
&def->hyperv_stimer_direct) < 0)
|
|
return -1;
|
|
|
|
child = xmlNextElementSibling(child);
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_SPINLOCKS:
|
|
if (value != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
if (virXMLPropUInt(node, "retries", 0, VIR_XML_PROP_REQUIRED,
|
|
&def->hyperv_spinlocks) < 0)
|
|
return -1;
|
|
|
|
if (def->hyperv_spinlocks < 0xFFF) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("HyperV spinlock retry count must be at least 4095"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_VENDOR_ID:
|
|
if (value != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
if (!(def->hyperv_vendor_id = virXMLPropString(node, "value"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing 'value' attribute for HyperV feature 'vendor_id'"));
|
|
return -1;
|
|
}
|
|
|
|
if (strlen(def->hyperv_vendor_id) > VIR_DOMAIN_HYPERV_VENDOR_ID_MAX) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("HyperV vendor_id value must not be more than %d characters."),
|
|
VIR_DOMAIN_HYPERV_VENDOR_ID_MAX);
|
|
return -1;
|
|
}
|
|
|
|
/* ensure that the string can be passed to qemu */
|
|
if (strchr(def->hyperv_vendor_id, ',')) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("HyperV vendor_id value is invalid"));
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_LAST:
|
|
break;
|
|
}
|
|
|
|
node = xmlNextElementSibling(node);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainFeaturesKVMDefParse(virDomainDef *def,
|
|
xmlNodePtr node)
|
|
{
|
|
def->features[VIR_DOMAIN_FEATURE_KVM] = VIR_TRISTATE_SWITCH_ON;
|
|
|
|
node = xmlFirstElementChild(node);
|
|
while (node) {
|
|
int feature;
|
|
virTristateSwitch value;
|
|
|
|
feature = virDomainKVMTypeFromString((const char *)node->name);
|
|
if (feature < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported KVM feature: %s"),
|
|
node->name);
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropTristateSwitch(node, "state", VIR_XML_PROP_REQUIRED,
|
|
&value) < 0)
|
|
return -1;
|
|
|
|
def->kvm_features[feature] = value;
|
|
|
|
node = xmlNextElementSibling(node);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainFeaturesXENDefParse(virDomainDef *def,
|
|
xmlNodePtr node)
|
|
{
|
|
def->features[VIR_DOMAIN_FEATURE_XEN] = VIR_TRISTATE_SWITCH_ON;
|
|
|
|
if (def->features[VIR_DOMAIN_FEATURE_XEN] == VIR_TRISTATE_SWITCH_ON) {
|
|
int feature;
|
|
virTristateSwitch value;
|
|
|
|
node = xmlFirstElementChild(node);
|
|
while (node) {
|
|
feature = virDomainXenTypeFromString((const char *)node->name);
|
|
if (feature < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unsupported Xen feature: %s"),
|
|
node->name);
|
|
return -1;
|
|
}
|
|
|
|
if (virXMLPropTristateSwitch(node, "state",
|
|
VIR_XML_PROP_REQUIRED, &value) < 0)
|
|
return -1;
|
|
|
|
def->xen_features[feature] = value;
|
|
|
|
switch ((virDomainXen) feature) {
|
|
case VIR_DOMAIN_XEN_E820_HOST:
|
|
break;
|
|
|
|
case VIR_DOMAIN_XEN_PASSTHROUGH:
|
|
if (value != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
if (virXMLPropEnum(node, "mode",
|
|
virDomainXenPassthroughModeTypeFromString,
|
|
VIR_XML_PROP_NONZERO,
|
|
&def->xen_passthrough_mode) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_XEN_LAST:
|
|
break;
|
|
}
|
|
|
|
node = xmlNextElementSibling(node);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainFeaturesDefParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
size_t i;
|
|
int n;
|
|
|
|
if ((n = virXPathNodeSet("./features/*", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
g_autofree char *tmp = NULL;
|
|
int val = virDomainFeatureTypeFromString((const char *)nodes[i]->name);
|
|
if (val < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unexpected feature '%s'"), nodes[i]->name);
|
|
return -1;
|
|
}
|
|
|
|
switch ((virDomainFeature) val) {
|
|
case VIR_DOMAIN_FEATURE_APIC:
|
|
if ((tmp = virXPathString("string(./features/apic/@eoi)", ctxt))) {
|
|
int eoi;
|
|
if ((eoi = virTristateSwitchTypeFromString(tmp)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown value for attribute eoi: '%s'"),
|
|
tmp);
|
|
return -1;
|
|
}
|
|
def->apic_eoi = eoi;
|
|
}
|
|
G_GNUC_FALLTHROUGH;
|
|
case VIR_DOMAIN_FEATURE_ACPI:
|
|
case VIR_DOMAIN_FEATURE_PAE:
|
|
case VIR_DOMAIN_FEATURE_VIRIDIAN:
|
|
case VIR_DOMAIN_FEATURE_PRIVNET:
|
|
case VIR_DOMAIN_FEATURE_MSRS:
|
|
def->features[val] = VIR_TRISTATE_SWITCH_ON;
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_HYPERV:
|
|
if (virDomainFeaturesHyperVDefParse(def, nodes[i]) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_KVM:
|
|
if (virDomainFeaturesKVMDefParse(def, nodes[i]) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_XEN:
|
|
if (virDomainFeaturesXENDefParse(def, nodes[i]) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_CAPABILITIES: {
|
|
virDomainCapabilitiesPolicy policy;
|
|
|
|
if (virXMLPropEnumDefault(nodes[i], "policy",
|
|
virDomainCapabilitiesPolicyTypeFromString,
|
|
VIR_XML_PROP_NONE, &policy,
|
|
VIR_DOMAIN_CAPABILITIES_POLICY_DEFAULT) < 0)
|
|
return -1;
|
|
|
|
def->features[val] = policy;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_FEATURE_VMCOREINFO:
|
|
case VIR_DOMAIN_FEATURE_HAP:
|
|
case VIR_DOMAIN_FEATURE_PMU:
|
|
case VIR_DOMAIN_FEATURE_PVSPINLOCK:
|
|
case VIR_DOMAIN_FEATURE_VMPORT:
|
|
case VIR_DOMAIN_FEATURE_SMM: {
|
|
virTristateSwitch state;
|
|
|
|
if (virXMLPropTristateSwitch(nodes[i], "state",
|
|
VIR_XML_PROP_NONE, &state) < 0)
|
|
return -1;
|
|
|
|
if (state == VIR_TRISTATE_SWITCH_ABSENT)
|
|
state = VIR_TRISTATE_SWITCH_ON;
|
|
|
|
def->features[val] = state;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_FEATURE_GIC:
|
|
if (virXMLPropEnum(nodes[i], "version", virGICVersionTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->gic_version) < 0)
|
|
return -1;
|
|
|
|
def->features[val] = VIR_TRISTATE_SWITCH_ON;
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_IOAPIC: {
|
|
virDomainIOAPIC driver;
|
|
|
|
if (virXMLPropEnumDefault(nodes[i], "driver", virDomainIOAPICTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &driver,
|
|
VIR_DOMAIN_IOAPIC_NONE) < 0)
|
|
return -1;
|
|
|
|
def->features[val] = driver;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_FEATURE_HPT:
|
|
if (virXMLPropEnum(nodes[i], "resizing",
|
|
virDomainHPTResizingTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &def->hpt_resizing) < 0)
|
|
return -1;
|
|
|
|
if (virParseScaledValue("./features/hpt/maxpagesize",
|
|
NULL,
|
|
ctxt,
|
|
&def->hpt_maxpagesize,
|
|
1024,
|
|
ULLONG_MAX,
|
|
false) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s",
|
|
_("Unable to parse HPT maxpagesize setting"));
|
|
return -1;
|
|
}
|
|
def->hpt_maxpagesize = VIR_DIV_UP(def->hpt_maxpagesize, 1024);
|
|
|
|
if (def->hpt_resizing != VIR_DOMAIN_HPT_RESIZING_NONE ||
|
|
def->hpt_maxpagesize > 0) {
|
|
def->features[val] = VIR_TRISTATE_SWITCH_ON;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_CFPC: {
|
|
virDomainCFPC value;
|
|
|
|
if (virXMLPropEnumDefault(nodes[i], "value", virDomainCFPCTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &value,
|
|
VIR_DOMAIN_CFPC_NONE) < 0)
|
|
return -1;
|
|
|
|
def->features[val] = value;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_FEATURE_SBBC: {
|
|
virDomainSBBC value;
|
|
|
|
if (virXMLPropEnumDefault(nodes[i], "value", virDomainSBBCTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &value,
|
|
VIR_DOMAIN_SBBC_NONE) < 0)
|
|
return -1;
|
|
|
|
def->features[val] = value;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_FEATURE_IBS: {
|
|
virDomainIBS value;
|
|
|
|
if (virXMLPropEnumDefault(nodes[i], "value", virDomainIBSTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &value,
|
|
VIR_DOMAIN_IBS_NONE) < 0)
|
|
return -1;
|
|
|
|
def->features[val] = value;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_FEATURE_HTM:
|
|
case VIR_DOMAIN_FEATURE_NESTED_HV:
|
|
case VIR_DOMAIN_FEATURE_CCF_ASSIST: {
|
|
virTristateSwitch state;
|
|
|
|
if (virXMLPropTristateSwitch(nodes[i], "state",
|
|
VIR_XML_PROP_REQUIRED, &state) < 0)
|
|
return -1;
|
|
|
|
def->features[val] = state;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_FEATURE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if (def->features[VIR_DOMAIN_FEATURE_SMM] == VIR_TRISTATE_SWITCH_ON) {
|
|
int rv = virParseScaledValue("string(./features/smm/tseg)",
|
|
"string(./features/smm/tseg/@unit)",
|
|
ctxt,
|
|
&def->tseg_size,
|
|
1024 * 1024, /* Defaults to mebibytes */
|
|
ULLONG_MAX,
|
|
false);
|
|
if (rv < 0)
|
|
return -1;
|
|
def->tseg_specified = rv;
|
|
}
|
|
|
|
if (def->features[VIR_DOMAIN_FEATURE_MSRS] == VIR_TRISTATE_SWITCH_ON) {
|
|
virDomainMsrsUnknown unknown;
|
|
xmlNodePtr node = NULL;
|
|
if ((node = virXPathNode("./features/msrs", ctxt)) == NULL)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(node, "unknown", virDomainMsrsUnknownTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &unknown) < 0)
|
|
return -1;
|
|
|
|
def->msrs_features[VIR_DOMAIN_MSRS_UNKNOWN] = unknown;
|
|
}
|
|
|
|
if ((n = virXPathNodeSet("./features/capabilities/*", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virTristateSwitch state;
|
|
int val = virDomainProcessCapsFeatureTypeFromString((const char *)nodes[i]->name);
|
|
if (val < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unexpected capability feature '%s'"), nodes[i]->name);
|
|
return -1;
|
|
}
|
|
|
|
|
|
if (virXMLPropTristateSwitch(nodes[i], "state", VIR_XML_PROP_NONE,
|
|
&state) < 0)
|
|
return -1;
|
|
|
|
if (state == VIR_TRISTATE_SWITCH_ABSENT)
|
|
state = VIR_TRISTATE_SWITCH_ON;
|
|
|
|
def->caps_features[val] = state;
|
|
}
|
|
VIR_FREE(nodes);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefMaybeAddHostdevSCSIcontroller(virDomainDef *def)
|
|
{
|
|
/* Look for any hostdev scsi dev */
|
|
size_t i;
|
|
int maxController = -1;
|
|
virDomainHostdevDef *hostdev;
|
|
int newModel = -1;
|
|
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
hostdev = def->hostdevs[i];
|
|
if (virHostdevIsSCSIDevice(hostdev) &&
|
|
(int)hostdev->info->addr.drive.controller > maxController) {
|
|
virDomainControllerDef *cont;
|
|
|
|
maxController = hostdev->info->addr.drive.controller;
|
|
/* We may be creating a new controller because this one is full.
|
|
* So let's grab the model from it and update the model we're
|
|
* going to add as long as this one isn't undefined. The premise
|
|
* being keeping the same controller model for all SCSI hostdevs. */
|
|
cont = virDomainDeviceFindSCSIController(def, &hostdev->info->addr.drive);
|
|
if (cont && cont->model != -1)
|
|
newModel = cont->model;
|
|
}
|
|
}
|
|
|
|
if (maxController == -1)
|
|
return 0;
|
|
|
|
for (i = 0; i <= maxController; i++) {
|
|
if (virDomainDefMaybeAddController(def, VIR_DOMAIN_CONTROLLER_TYPE_SCSI,
|
|
i, newModel) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainLoaderDefParseXML(xmlNodePtr node,
|
|
virDomainLoaderDef *loader,
|
|
bool fwAutoSelect)
|
|
{
|
|
if (!fwAutoSelect) {
|
|
if (virXMLPropTristateBool(node, "readonly", VIR_XML_PROP_NONE,
|
|
&loader->readonly) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(node, "type", virDomainLoaderTypeFromString,
|
|
VIR_XML_PROP_NONZERO, &loader->type) < 0)
|
|
return -1;
|
|
|
|
if (!(loader->path = virXMLNodeContentString(node)))
|
|
return -1;
|
|
|
|
if (STREQ(loader->path, ""))
|
|
VIR_FREE(loader->path);
|
|
}
|
|
|
|
if (virXMLPropTristateBool(node, "secure", VIR_XML_PROP_NONE,
|
|
&loader->secure) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainSchedulerParseCommonAttrs(xmlNodePtr node,
|
|
virProcessSchedPolicy *policy,
|
|
int *priority)
|
|
{
|
|
if (virXMLPropEnum(node, "scheduler", virProcessSchedPolicyTypeFromString,
|
|
VIR_XML_PROP_REQUIRED | VIR_XML_PROP_NONZERO,
|
|
policy) < 0)
|
|
return -1;
|
|
|
|
if (*policy == VIR_PROC_POLICY_FIFO || *policy == VIR_PROC_POLICY_RR) {
|
|
if (virXMLPropInt(node, "priority", 10, VIR_XML_PROP_REQUIRED,
|
|
priority, 0) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainEmulatorSchedParse(xmlNodePtr node,
|
|
virDomainDef *def)
|
|
{
|
|
g_autofree virDomainThreadSchedParam *sched = NULL;
|
|
|
|
sched = g_new0(virDomainThreadSchedParam, 1);
|
|
|
|
if (virDomainSchedulerParseCommonAttrs(node,
|
|
&sched->policy,
|
|
&sched->priority) < 0)
|
|
return -1;
|
|
|
|
def->cputune.emulatorsched = g_steal_pointer(&sched);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virBitmap *
|
|
virDomainSchedulerParse(xmlNodePtr node,
|
|
const char *elementName,
|
|
const char *attributeName,
|
|
virProcessSchedPolicy *policy,
|
|
int *priority)
|
|
{
|
|
virBitmap *ret = NULL;
|
|
g_autofree char *tmp = NULL;
|
|
|
|
if (!(tmp = virXMLPropString(node, attributeName))) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Missing attribute '%s' in element '%s'"),
|
|
attributeName, elementName);
|
|
goto error;
|
|
}
|
|
|
|
if (virBitmapParse(tmp, &ret, VIR_DOMAIN_CPUMASK_LEN) < 0)
|
|
goto error;
|
|
|
|
if (virBitmapIsAllClear(ret)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("'%s' scheduler bitmap '%s' is empty"),
|
|
attributeName, tmp);
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainSchedulerParseCommonAttrs(node, policy, priority) < 0)
|
|
goto error;
|
|
|
|
return ret;
|
|
|
|
error:
|
|
virBitmapFree(ret);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainThreadSchedParseHelper(xmlNodePtr node,
|
|
const char *elementName,
|
|
const char *attributeName,
|
|
virDomainThreadSchedParam *(*func)(virDomainDef *, unsigned int),
|
|
virDomainDef *def)
|
|
{
|
|
ssize_t next = -1;
|
|
virDomainThreadSchedParam *sched = NULL;
|
|
virProcessSchedPolicy policy = 0;
|
|
int priority = 0;
|
|
g_autoptr(virBitmap) map = NULL;
|
|
|
|
if (!(map = virDomainSchedulerParse(node, elementName, attributeName,
|
|
&policy, &priority)))
|
|
return -1;
|
|
|
|
while ((next = virBitmapNextSetBit(map, next)) > -1) {
|
|
if (!(sched = func(def, next)))
|
|
return -1;
|
|
|
|
if (sched->policy != VIR_PROC_POLICY_NONE) {
|
|
virReportError(VIR_ERR_XML_DETAIL,
|
|
_("'%s' attributes '%s' must not overlap"),
|
|
elementName, attributeName);
|
|
return -1;
|
|
}
|
|
|
|
sched->policy = policy;
|
|
sched->priority = priority;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainVcpuThreadSchedParse(xmlNodePtr node,
|
|
virDomainDef *def)
|
|
{
|
|
return virDomainThreadSchedParseHelper(node,
|
|
"vcpusched",
|
|
"vcpus",
|
|
virDomainDefGetVcpuSched,
|
|
def);
|
|
}
|
|
|
|
|
|
static virDomainThreadSchedParam *
|
|
virDomainDefGetIOThreadSched(virDomainDef *def,
|
|
unsigned int iothread)
|
|
{
|
|
virDomainIOThreadIDDef *iothrinfo;
|
|
|
|
if (!(iothrinfo = virDomainIOThreadIDFind(def, iothread))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Cannot find 'iothread' : %u"),
|
|
iothread);
|
|
return NULL;
|
|
}
|
|
|
|
return &iothrinfo->sched;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainIOThreadSchedParse(xmlNodePtr node,
|
|
virDomainDef *def)
|
|
{
|
|
return virDomainThreadSchedParseHelper(node,
|
|
"iothreadsched",
|
|
"iothreads",
|
|
virDomainDefGetIOThreadSched,
|
|
def);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainVcpuParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
int n;
|
|
xmlNodePtr vcpuNode;
|
|
size_t i;
|
|
unsigned int maxvcpus;
|
|
unsigned int vcpus;
|
|
g_autofree char *tmp = NULL;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
int rc;
|
|
|
|
vcpus = maxvcpus = 1;
|
|
|
|
if ((vcpuNode = virXPathNode("./vcpu[1]", ctxt))) {
|
|
if (!(tmp = virXMLNodeContentString(vcpuNode)))
|
|
return -1;
|
|
|
|
if (virStrToLong_ui(tmp, NULL, 10, &maxvcpus) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("maximum vcpus count must be an integer"));
|
|
return -1;
|
|
}
|
|
VIR_FREE(tmp);
|
|
|
|
if ((rc = virXMLPropUInt(vcpuNode, "current", 10, VIR_XML_PROP_NONE, &vcpus)) < 0) {
|
|
return -1;
|
|
} else if (rc == 0) {
|
|
vcpus = maxvcpus;
|
|
}
|
|
|
|
if (virXMLPropEnumDefault(vcpuNode, "placement",
|
|
virDomainCpuPlacementModeTypeFromString,
|
|
VIR_XML_PROP_NONE, &def->placement_mode,
|
|
VIR_DOMAIN_CPU_PLACEMENT_MODE_STATIC) < 0)
|
|
return -1;
|
|
|
|
if (def->placement_mode != VIR_DOMAIN_CPU_PLACEMENT_MODE_AUTO) {
|
|
tmp = virXMLPropString(vcpuNode, "cpuset");
|
|
if (tmp) {
|
|
if (virBitmapParse(tmp, &def->cpumask, VIR_DOMAIN_CPUMASK_LEN) < 0)
|
|
return -1;
|
|
|
|
if (virBitmapIsAllClear(def->cpumask)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Invalid value of 'cpuset': %s"), tmp);
|
|
return -1;
|
|
}
|
|
|
|
VIR_FREE(tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (virDomainDefSetVcpusMax(def, maxvcpus, xmlopt) < 0)
|
|
return -1;
|
|
|
|
if ((n = virXPathNodeSet("./vcpus/vcpu", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (n) {
|
|
/* if individual vcpu states are provided take them as master */
|
|
def->individualvcpus = true;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainVcpuDef *vcpu;
|
|
virTristateBool state;
|
|
unsigned int id;
|
|
|
|
if (virXMLPropUInt(nodes[i], "id", 10, VIR_XML_PROP_REQUIRED, &id) < 0)
|
|
return -1;
|
|
|
|
if (id >= def->maxvcpus) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("vcpu id '%u' is out of range of maximum "
|
|
"vcpu count"), id);
|
|
return -1;
|
|
}
|
|
|
|
vcpu = virDomainDefGetVcpu(def, id);
|
|
|
|
if (virXMLPropTristateBool(nodes[i], "enabled",
|
|
VIR_XML_PROP_REQUIRED, &state) < 0)
|
|
return -1;
|
|
|
|
vcpu->online = state == VIR_TRISTATE_BOOL_YES;
|
|
|
|
if (virXMLPropTristateBool(nodes[i], "hotpluggable",
|
|
VIR_XML_PROP_NONE,
|
|
&vcpu->hotpluggable) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(nodes[i], "order", 10, VIR_XML_PROP_NONE,
|
|
&vcpu->order) < 0)
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (virDomainDefSetVcpus(def, vcpus) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseBootInitOptions(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
char *name = NULL;
|
|
size_t i;
|
|
int n;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
|
|
def->os.init = virXPathString("string(./os/init[1])", ctxt);
|
|
def->os.cmdline = virXPathString("string(./os/cmdline[1])", ctxt);
|
|
def->os.initdir = virXPathString("string(./os/initdir[1])", ctxt);
|
|
def->os.inituser = virXPathString("string(./os/inituser[1])", ctxt);
|
|
def->os.initgroup = virXPathString("string(./os/initgroup[1])", ctxt);
|
|
|
|
if ((n = virXPathNodeSet("./os/initarg", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
def->os.initargv = g_new0(char *, n+1);
|
|
for (i = 0; i < n; i++) {
|
|
if (!nodes[i]->children ||
|
|
!nodes[i]->children->content) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("No data supplied for <initarg> element"));
|
|
return -1;
|
|
}
|
|
def->os.initargv[i] = g_strdup((const char *)nodes[i]->children->content);
|
|
}
|
|
def->os.initargv[n] = NULL;
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./os/initenv", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
def->os.initenv = g_new0(virDomainOSEnv *, n + 1);
|
|
for (i = 0; i < n; i++) {
|
|
if (!(name = virXMLPropString(nodes[i], "name"))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("No name supplied for <initenv> element"));
|
|
return -1;
|
|
}
|
|
|
|
if (!nodes[i]->children ||
|
|
!nodes[i]->children->content) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("No value supplied for <initenv name='%s'> element"),
|
|
name);
|
|
return -1;
|
|
}
|
|
|
|
def->os.initenv[i] = g_new0(virDomainOSEnv, 1);
|
|
def->os.initenv[i]->name = name;
|
|
def->os.initenv[i]->value = g_strdup((const char *)nodes[i]->children->content);
|
|
}
|
|
def->os.initenv[n] = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainDefParseBootKernelOptions(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
def->os.kernel = virXPathString("string(./os/kernel[1])", ctxt);
|
|
def->os.initrd = virXPathString("string(./os/initrd[1])", ctxt);
|
|
def->os.cmdline = virXPathString("string(./os/cmdline[1])", ctxt);
|
|
def->os.dtb = virXPathString("string(./os/dtb[1])", ctxt);
|
|
def->os.root = virXPathString("string(./os/root[1])", ctxt);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseBootFirmwareOptions(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree char *firmware = virXPathString("string(./os/@firmware)", ctxt);
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree int *features = NULL;
|
|
int fw = 0;
|
|
int n = 0;
|
|
size_t i;
|
|
|
|
if (!firmware)
|
|
return 0;
|
|
|
|
fw = virDomainOsDefFirmwareTypeFromString(firmware);
|
|
|
|
if (fw <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown firmware value %s"),
|
|
firmware);
|
|
return -1;
|
|
}
|
|
|
|
def->os.firmware = fw;
|
|
|
|
if ((n = virXPathNodeSet("./os/firmware/feature", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (n > 0)
|
|
features = g_new0(int, VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_LAST);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
g_autofree char *name = virXMLPropString(nodes[i], "name");
|
|
g_autofree char *enabled = virXMLPropString(nodes[i], "enabled");
|
|
int feature = virDomainOsDefFirmwareFeatureTypeFromString(name);
|
|
int val = virTristateBoolTypeFromString(enabled);
|
|
|
|
if (feature < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid firmware feature name '%s'"),
|
|
name);
|
|
return -1;
|
|
}
|
|
|
|
if (val < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid firmware feature enabled value '%s'"),
|
|
enabled);
|
|
return -1;
|
|
}
|
|
|
|
features[feature] = val;
|
|
}
|
|
|
|
def->os.firmwareFeatures = g_steal_pointer(&features);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseBootLoaderOptions(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
xmlNodePtr loader_node = virXPathNode("./os/loader[1]", ctxt);
|
|
const bool fwAutoSelect = def->os.firmware != VIR_DOMAIN_OS_DEF_FIRMWARE_NONE;
|
|
|
|
if (!loader_node)
|
|
return 0;
|
|
|
|
def->os.loader = g_new0(virDomainLoaderDef, 1);
|
|
|
|
if (virDomainLoaderDefParseXML(loader_node,
|
|
def->os.loader,
|
|
fwAutoSelect) < 0)
|
|
return -1;
|
|
|
|
def->os.loader->nvram = virXPathString("string(./os/nvram[1])", ctxt);
|
|
if (!fwAutoSelect)
|
|
def->os.loader->templt = virXPathString("string(./os/nvram[1]/@template)", ctxt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseBootAcpiOptions(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
int n;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree char *tmp = NULL;
|
|
|
|
if ((n = virXPathNodeSet("./os/acpi/table", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Only one acpi table is supported"));
|
|
return -1;
|
|
}
|
|
|
|
if (n == 1) {
|
|
tmp = virXMLPropString(nodes[0], "type");
|
|
|
|
if (!tmp) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing acpi table type"));
|
|
return -1;
|
|
}
|
|
|
|
if (STREQ_NULLABLE(tmp, "slic")) {
|
|
VIR_FREE(tmp);
|
|
if (!(tmp = virXMLNodeContentString(nodes[0])))
|
|
return -1;
|
|
|
|
def->os.slic_table = virFileSanitizePath(tmp);
|
|
} else {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Unknown acpi table type: %s"),
|
|
tmp);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseBootOptions(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
/*
|
|
* Booting options for different OS types....
|
|
*
|
|
* - A bootloader (and optional kernel+initrd) (xen)
|
|
* - A kernel + initrd (xen)
|
|
* - A boot device (and optional kernel+initrd) (hvm)
|
|
* - An init script (exe)
|
|
*/
|
|
|
|
switch ((virDomainOSType) def->os.type) {
|
|
case VIR_DOMAIN_OSTYPE_HVM:
|
|
virDomainDefParseBootKernelOptions(def, ctxt);
|
|
|
|
if (virDomainDefParseBootFirmwareOptions(def, ctxt) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefParseBootLoaderOptions(def, ctxt) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefParseBootAcpiOptions(def, ctxt) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefParseBootXML(ctxt, def) < 0)
|
|
return -1;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_OSTYPE_XEN:
|
|
case VIR_DOMAIN_OSTYPE_XENPVH:
|
|
case VIR_DOMAIN_OSTYPE_UML:
|
|
virDomainDefParseBootKernelOptions(def, ctxt);
|
|
|
|
if (virDomainDefParseBootLoaderOptions(def, ctxt) < 0)
|
|
return -1;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_OSTYPE_EXE:
|
|
if (virDomainDefParseBootInitOptions(def, ctxt) < 0)
|
|
return -1;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_OSTYPE_LINUX:
|
|
case VIR_DOMAIN_OSTYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainResctrlParseVcpus(virDomainDef *def,
|
|
xmlNodePtr node,
|
|
virBitmap **vcpus)
|
|
{
|
|
g_autofree char *vcpus_str = NULL;
|
|
|
|
vcpus_str = virXMLPropString(node, "vcpus");
|
|
if (!vcpus_str) {
|
|
virReportError(VIR_ERR_XML_ERROR, _("Missing %s attribute 'vcpus'"),
|
|
node->name);
|
|
return -1;
|
|
}
|
|
if (virBitmapParse(vcpus_str, vcpus, VIR_DOMAIN_CPUMASK_LEN) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid %s attribute 'vcpus' value '%s'"),
|
|
node->name, vcpus_str);
|
|
return -1;
|
|
}
|
|
|
|
/* We need to limit the bitmap to number of vCPUs. If there's nothing left,
|
|
* then we can just clean up and return 0 immediately */
|
|
virBitmapShrink(*vcpus, def->maxvcpus);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainResctrlVcpuMatch(virDomainDef *def,
|
|
virBitmap *vcpus,
|
|
virDomainResctrlDef **resctrl)
|
|
{
|
|
ssize_t i = 0;
|
|
|
|
for (i = 0; i < def->nresctrls; i++) {
|
|
/* vcpus group has been created, directly use the existing one.
|
|
* Just updating memory allocation information of that group
|
|
*/
|
|
if (virBitmapEqual(def->resctrls[i]->vcpus, vcpus)) {
|
|
*resctrl = def->resctrls[i];
|
|
break;
|
|
}
|
|
if (virBitmapOverlaps(def->resctrls[i]->vcpus, vcpus)) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Overlapping vcpus in resctrls"));
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainCachetuneDefParseCache(xmlXPathContextPtr ctxt,
|
|
xmlNodePtr node,
|
|
virResctrlAlloc *alloc)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
unsigned int level;
|
|
unsigned int cache;
|
|
virCacheType type;
|
|
unsigned long long size;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropUInt(node, "id", 10, VIR_XML_PROP_REQUIRED, &cache) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(node, "level", 10, VIR_XML_PROP_REQUIRED, &level) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropEnum(node, "type", virCacheTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &type) < 0)
|
|
return -1;
|
|
|
|
if (virParseScaledValue("./@size", "./@unit",
|
|
ctxt, &size, 1024,
|
|
ULLONG_MAX, true) < 0)
|
|
return -1;
|
|
|
|
if (virResctrlAllocSetCacheSize(alloc, level, type, cache, size) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Checking if the monitor's vcpus and tag is conflicted with existing
|
|
* allocation and monitors.
|
|
*
|
|
* Returns 1 if @monitor->vcpus equals to @resctrl->vcpus, then the monitor
|
|
* will share the underlying resctrl group with @resctrl->alloc. Returns -1
|
|
* if any conflict found. Returns 0 if no conflict and @monitor->vcpus is
|
|
* not equal to @resctrl->vcpus.
|
|
*/
|
|
static int
|
|
virDomainResctrlValidateMonitor(virDomainResctrlDef *resctrl,
|
|
virDomainResctrlMonDef *monitor)
|
|
{
|
|
size_t i = 0;
|
|
int vcpu = -1;
|
|
bool vcpus_overlap_any = false;
|
|
bool vcpus_equal_to_resctrl = false;
|
|
bool vcpus_overlap_no_resctrl = false;
|
|
bool default_alloc_monitor = virResctrlAllocIsEmpty(resctrl->alloc);
|
|
|
|
if (virBitmapIsAllClear(monitor->vcpus)) {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("vcpus is empty"));
|
|
return -1;
|
|
}
|
|
|
|
while ((vcpu = virBitmapNextSetBit(monitor->vcpus, vcpu)) >= 0) {
|
|
if (!virBitmapIsBitSet(resctrl->vcpus, vcpu)) {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("Monitor vcpus conflicts with allocation"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
vcpus_equal_to_resctrl = virBitmapEqual(monitor->vcpus, resctrl->vcpus);
|
|
|
|
for (i = 0; i < resctrl->nmonitors; i++) {
|
|
if (virBitmapEqual(monitor->vcpus, resctrl->monitors[i]->vcpus)) {
|
|
if (monitor->tag != resctrl->monitors[i]->tag) {
|
|
continue;
|
|
} else {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("Identical vcpus found in same type monitors"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (virBitmapOverlaps(monitor->vcpus, resctrl->monitors[i]->vcpus))
|
|
vcpus_overlap_any = true;
|
|
|
|
if (vcpus_equal_to_resctrl ||
|
|
virBitmapEqual(resctrl->monitors[i]->vcpus, resctrl->vcpus))
|
|
continue;
|
|
|
|
if (virBitmapOverlaps(monitor->vcpus, resctrl->monitors[i]->vcpus))
|
|
vcpus_overlap_no_resctrl = true;
|
|
}
|
|
|
|
if (vcpus_overlap_no_resctrl ||
|
|
(default_alloc_monitor && vcpus_overlap_any)) {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("vcpus overlaps in resctrl groups"));
|
|
return -1;
|
|
}
|
|
|
|
if (vcpus_equal_to_resctrl && !default_alloc_monitor)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define VIR_DOMAIN_RESCTRL_MONITOR_CACHELEVEL 3
|
|
|
|
static int
|
|
virDomainResctrlMonDefParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
xmlNodePtr node,
|
|
virResctrlMonitorType tag,
|
|
virDomainResctrlDef *resctrl)
|
|
{
|
|
virDomainResctrlMonDef *domresmon = NULL;
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
unsigned int level = 0;
|
|
size_t i = 0;
|
|
int n = 0;
|
|
int rv = -1;
|
|
int ret = -1;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree char *tmp = NULL;
|
|
g_autofree char *id = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if ((n = virXPathNodeSet("./monitor", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Cannot extract monitor nodes"));
|
|
goto cleanup;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
domresmon = g_new0(virDomainResctrlMonDef, 1);
|
|
|
|
domresmon->tag = tag;
|
|
|
|
domresmon->instance = virResctrlMonitorNew();
|
|
if (!domresmon->instance) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Could not create monitor"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (tag == VIR_RESCTRL_MONITOR_TYPE_CACHE) {
|
|
if (virXMLPropUInt(nodes[i], "level", 10, VIR_XML_PROP_REQUIRED,
|
|
&level) < 0)
|
|
goto cleanup;
|
|
|
|
if (level != VIR_DOMAIN_RESCTRL_MONITOR_CACHELEVEL) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("Invalid monitor cache level '%d'"),
|
|
level);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (virDomainResctrlParseVcpus(def, nodes[i], &domresmon->vcpus) < 0)
|
|
goto cleanup;
|
|
|
|
rv = virDomainResctrlValidateMonitor(resctrl, domresmon);
|
|
if (rv < 0)
|
|
goto cleanup;
|
|
|
|
/* If monitor's vcpu list is identical to the vcpu list of the
|
|
* associated allocation, set monitor's id to the same value
|
|
* as the allocation. */
|
|
if (rv == 1) {
|
|
const char *alloc_id = virResctrlAllocGetID(resctrl->alloc);
|
|
|
|
id = g_strdup(alloc_id);
|
|
} else {
|
|
if (!(tmp = virBitmapFormat(domresmon->vcpus)))
|
|
goto cleanup;
|
|
|
|
id = g_strdup_printf("vcpus_%s", tmp);
|
|
}
|
|
|
|
virResctrlMonitorSetAlloc(domresmon->instance, resctrl->alloc);
|
|
|
|
if (virResctrlMonitorSetID(domresmon->instance, id) < 0)
|
|
goto cleanup;
|
|
|
|
if (VIR_APPEND_ELEMENT(resctrl->monitors,
|
|
resctrl->nmonitors,
|
|
domresmon) < 0)
|
|
goto cleanup;
|
|
|
|
VIR_FREE(id);
|
|
VIR_FREE(tmp);
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virDomainResctrlMonDefFree(domresmon);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virDomainResctrlDef *
|
|
virDomainResctrlNew(xmlNodePtr node,
|
|
virResctrlAlloc *alloc,
|
|
virBitmap *vcpus,
|
|
unsigned int flags)
|
|
{
|
|
virDomainResctrlDef *resctrl = NULL;
|
|
g_autofree char *vcpus_str = NULL;
|
|
g_autofree char *alloc_id = NULL;
|
|
|
|
/* We need to format it back because we need to be consistent in the naming
|
|
* even when users specify some "sub-optimal" string there. */
|
|
vcpus_str = virBitmapFormat(vcpus);
|
|
if (!vcpus_str)
|
|
return NULL;
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))
|
|
alloc_id = virXMLPropString(node, "id");
|
|
|
|
if (!alloc_id) {
|
|
/* The number of allocations is limited and the directory structure is flat,
|
|
* not hierarchical, so we need to have all same allocations in one
|
|
* directory, so it's nice to have it named appropriately. For now it's
|
|
* 'vcpus_...' but it's designed in order for it to be changeable in the
|
|
* future (it's part of the status XML). */
|
|
alloc_id = g_strdup_printf("vcpus_%s", vcpus_str);
|
|
}
|
|
|
|
if (virResctrlAllocSetID(alloc, alloc_id) < 0)
|
|
return NULL;
|
|
|
|
resctrl = g_new0(virDomainResctrlDef, 1);
|
|
resctrl->vcpus = virBitmapNewCopy(vcpus);
|
|
resctrl->alloc = virObjectRef(alloc);
|
|
|
|
return resctrl;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainCachetuneDefParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
xmlNodePtr node,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainResctrlDef *resctrl = NULL;
|
|
ssize_t i = 0;
|
|
int n;
|
|
int ret = -1;
|
|
g_autoptr(virBitmap) vcpus = NULL;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autoptr(virResctrlAlloc) alloc = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virDomainResctrlParseVcpus(def, node, &vcpus) < 0)
|
|
return -1;
|
|
|
|
if (virBitmapIsAllClear(vcpus))
|
|
return 0;
|
|
|
|
if ((n = virXPathNodeSet("./cache", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Cannot extract cache nodes under cachetune"));
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainResctrlVcpuMatch(def, vcpus, &resctrl) < 0)
|
|
return -1;
|
|
|
|
if (resctrl) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Identical vcpus in cachetunes found"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(alloc = virResctrlAllocNew()))
|
|
return -1;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainCachetuneDefParseCache(ctxt, nodes[i], alloc) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!(resctrl = virDomainResctrlNew(node, alloc, vcpus, flags)))
|
|
return -1;
|
|
|
|
if (virDomainResctrlMonDefParse(def, ctxt, node,
|
|
VIR_RESCTRL_MONITOR_TYPE_CACHE,
|
|
resctrl) < 0)
|
|
goto cleanup;
|
|
|
|
/* If no <cache> element or <monitor> element in <cachetune>, do not
|
|
* append any resctrl element */
|
|
if (!resctrl->nmonitors && n == 0) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (VIR_APPEND_ELEMENT(def->resctrls, def->nresctrls, resctrl) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virDomainResctrlDefFree(resctrl);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseIDs(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
unsigned int flags,
|
|
bool *uuid_generated)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree char *tmp = NULL;
|
|
long id = -1;
|
|
int n;
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))
|
|
if (virXPathLong("string(./@id)", ctxt, &id) < 0)
|
|
id = -1;
|
|
def->id = (int)id;
|
|
|
|
/* Extract domain name */
|
|
if (!(def->name = virXPathString("string(./name[1])", ctxt))) {
|
|
virReportError(VIR_ERR_NO_NAME, NULL);
|
|
goto error;
|
|
}
|
|
|
|
/* Extract domain uuid. If both uuid and sysinfo/system/entry/uuid
|
|
* exist, they must match; and if only the latter exists, it can
|
|
* also serve as the uuid. */
|
|
tmp = virXPathString("string(./uuid[1])", ctxt);
|
|
if (!tmp) {
|
|
if (virUUIDGenerate(def->uuid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Failed to generate UUID"));
|
|
goto error;
|
|
}
|
|
*uuid_generated = true;
|
|
} else {
|
|
if (virUUIDParse(tmp, def->uuid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("malformed uuid element"));
|
|
goto error;
|
|
}
|
|
VIR_FREE(tmp);
|
|
}
|
|
|
|
/* Extract domain genid - a genid can either be provided or generated */
|
|
if ((n = virXPathNodeSet("./genid", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n > 0) {
|
|
if (n != 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("element 'genid' can only appear once"));
|
|
goto error;
|
|
}
|
|
def->genidRequested = true;
|
|
if (!(tmp = virXPathString("string(./genid)", ctxt))) {
|
|
if (virUUIDGenerate(def->genid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Failed to generate genid"));
|
|
goto error;
|
|
}
|
|
def->genidGenerated = true;
|
|
} else {
|
|
if (virUUIDParse(tmp, def->genid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("malformed genid element"));
|
|
goto error;
|
|
}
|
|
VIR_FREE(tmp);
|
|
}
|
|
}
|
|
VIR_FREE(nodes);
|
|
return 0;
|
|
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseCaps(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
g_autofree char *virttype = NULL;
|
|
g_autofree char *arch = NULL;
|
|
g_autofree char *ostype = NULL;
|
|
|
|
virttype = virXPathString("string(./@type)", ctxt);
|
|
ostype = virXPathString("string(./os/type[1])", ctxt);
|
|
arch = virXPathString("string(./os/type[1]/@arch)", ctxt);
|
|
|
|
def->os.bootloader = virXPathString("string(./bootloader)", ctxt);
|
|
def->os.bootloaderArgs = virXPathString("string(./bootloader_args)", ctxt);
|
|
def->os.machine = virXPathString("string(./os/type[1]/@machine)", ctxt);
|
|
def->emulator = virXPathString("string(./devices/emulator[1])", ctxt);
|
|
|
|
if (!virttype) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("missing domain type attribute"));
|
|
return -1;
|
|
}
|
|
if ((def->virtType = virDomainVirtTypeFromString(virttype)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("invalid domain type %s"), virttype);
|
|
return -1;
|
|
}
|
|
|
|
if (!ostype) {
|
|
if (def->os.bootloader) {
|
|
def->os.type = VIR_DOMAIN_OSTYPE_XEN;
|
|
} else {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("an os <type> must be specified"));
|
|
return -1;
|
|
}
|
|
} else {
|
|
if ((def->os.type = virDomainOSTypeFromString(ostype)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown OS type '%s'"), ostype);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* HACK: For xen driver we previously used bogus 'linux' as the
|
|
* os type for paravirt, whereas capabilities declare it to
|
|
* be 'xen'. So we accept the former and convert
|
|
*/
|
|
if (def->os.type == VIR_DOMAIN_OSTYPE_LINUX &&
|
|
def->virtType == VIR_DOMAIN_VIRT_XEN) {
|
|
def->os.type = VIR_DOMAIN_OSTYPE_XEN;
|
|
}
|
|
|
|
if (arch && !(def->os.arch = virArchFromString(arch))) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unknown architecture %s"), arch);
|
|
return -1;
|
|
}
|
|
|
|
if (def->os.arch == VIR_ARCH_NONE) {
|
|
if (xmlopt && xmlopt->config.defArch != VIR_ARCH_NONE)
|
|
def->os.arch = xmlopt->config.defArch;
|
|
else
|
|
def->os.arch = virArchFromHost();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefParseMemory(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree char *tmp = NULL;
|
|
xmlNodePtr node = NULL;
|
|
size_t i;
|
|
int n;
|
|
|
|
/* Extract domain memory */
|
|
if (virDomainParseMemory("./memory[1]", NULL, ctxt,
|
|
&def->mem.total_memory, false, true) < 0)
|
|
goto error;
|
|
|
|
if (virDomainParseMemory("./currentMemory[1]", NULL, ctxt,
|
|
&def->mem.cur_balloon, false, true) < 0)
|
|
goto error;
|
|
|
|
if (virDomainParseMemory("./maxMemory[1]", NULL, ctxt,
|
|
&def->mem.max_memory, false, false) < 0)
|
|
goto error;
|
|
|
|
if (virXPathUInt("string(./maxMemory[1]/@slots)", ctxt, &def->mem.memory_slots) == -2) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Failed to parse memory slot count"));
|
|
goto error;
|
|
}
|
|
|
|
/* and info about it */
|
|
if ((tmp = virXPathString("string(./memory[1]/@dumpCore)", ctxt)) &&
|
|
(def->mem.dump_core = virTristateSwitchTypeFromString(tmp)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Invalid memory core dump attribute value '%s'"), tmp);
|
|
goto error;
|
|
}
|
|
VIR_FREE(tmp);
|
|
|
|
tmp = virXPathString("string(./memoryBacking/source/@type)", ctxt);
|
|
if (tmp) {
|
|
if ((def->mem.source = virDomainMemorySourceTypeFromString(tmp)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown memoryBacking/source/type '%s'"), tmp);
|
|
goto error;
|
|
}
|
|
VIR_FREE(tmp);
|
|
}
|
|
|
|
tmp = virXPathString("string(./memoryBacking/access/@mode)", ctxt);
|
|
if (tmp) {
|
|
if ((def->mem.access = virDomainMemoryAccessTypeFromString(tmp)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown memoryBacking/access/mode '%s'"), tmp);
|
|
goto error;
|
|
}
|
|
VIR_FREE(tmp);
|
|
}
|
|
|
|
tmp = virXPathString("string(./memoryBacking/allocation/@mode)", ctxt);
|
|
if (tmp) {
|
|
if ((def->mem.allocation = virDomainMemoryAllocationTypeFromString(tmp)) <= 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown memoryBacking/allocation/mode '%s'"), tmp);
|
|
goto error;
|
|
}
|
|
VIR_FREE(tmp);
|
|
}
|
|
|
|
if (virXPathNode("./memoryBacking/hugepages", ctxt)) {
|
|
/* hugepages will be used */
|
|
if ((n = virXPathNodeSet("./memoryBacking/hugepages/page", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot extract hugepages nodes"));
|
|
goto error;
|
|
}
|
|
|
|
if (n) {
|
|
def->mem.hugepages = g_new0(virDomainHugePage, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainHugepagesParseXML(nodes[i], ctxt,
|
|
&def->mem.hugepages[i]) < 0)
|
|
goto error;
|
|
def->mem.nhugepages++;
|
|
}
|
|
|
|
VIR_FREE(nodes);
|
|
} else {
|
|
/* no hugepage pages */
|
|
def->mem.hugepages = g_new0(virDomainHugePage, 1);
|
|
def->mem.nhugepages = 1;
|
|
}
|
|
}
|
|
|
|
if ((node = virXPathNode("./memoryBacking/nosharepages", ctxt)))
|
|
def->mem.nosharepages = true;
|
|
|
|
if (virXPathBoolean("boolean(./memoryBacking/locked)", ctxt))
|
|
def->mem.locked = true;
|
|
|
|
if (virXPathBoolean("boolean(./memoryBacking/discard)", ctxt))
|
|
def->mem.discard = VIR_TRISTATE_BOOL_YES;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainMemorytuneDefParseMemory(xmlXPathContextPtr ctxt,
|
|
xmlNodePtr node,
|
|
virResctrlAlloc *alloc)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
unsigned int id;
|
|
unsigned int bandwidth;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virXMLPropUInt(node, "id", 10, VIR_XML_PROP_REQUIRED, &id) < 0)
|
|
return -1;
|
|
|
|
if (virXMLPropUInt(node, "bandwidth", 10, VIR_XML_PROP_REQUIRED,
|
|
&bandwidth) < 0)
|
|
return -1;
|
|
|
|
if (virResctrlAllocSetMemoryBandwidth(alloc, id, bandwidth) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainMemorytuneDefParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
xmlNodePtr node,
|
|
unsigned int flags)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virDomainResctrlDef *resctrl = NULL;
|
|
virDomainResctrlDef *newresctrl = NULL;
|
|
g_autoptr(virBitmap) vcpus = NULL;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autoptr(virResctrlAlloc) alloc = NULL;
|
|
ssize_t i = 0;
|
|
size_t nmons = 0;
|
|
size_t ret = -1;
|
|
|
|
int n;
|
|
|
|
ctxt->node = node;
|
|
|
|
if (virDomainResctrlParseVcpus(def, node, &vcpus) < 0)
|
|
return -1;
|
|
|
|
if (virBitmapIsAllClear(vcpus))
|
|
return 0;
|
|
|
|
if ((n = virXPathNodeSet("./node", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Cannot extract memory nodes under memorytune"));
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainResctrlVcpuMatch(def, vcpus, &resctrl) < 0)
|
|
return -1;
|
|
|
|
if (resctrl) {
|
|
alloc = virObjectRef(resctrl->alloc);
|
|
} else {
|
|
if (!(alloc = virResctrlAllocNew()))
|
|
return -1;
|
|
}
|
|
|
|
/* First, parse <memorytune/node> element if any <node> element exists */
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainMemorytuneDefParseMemory(ctxt, nodes[i], alloc) < 0)
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* If this is a new allocation, format ID and append to resctrl, otherwise
|
|
* just update the existing alloc information, which is done in above
|
|
* virDomainMemorytuneDefParseMemory */
|
|
if (!resctrl) {
|
|
if (!(newresctrl = virDomainResctrlNew(node, alloc, vcpus, flags)))
|
|
return -1;
|
|
|
|
resctrl = newresctrl;
|
|
}
|
|
|
|
/* Next, parse <memorytune/monitor> element */
|
|
nmons = resctrl->nmonitors;
|
|
if (virDomainResctrlMonDefParse(def, ctxt, node,
|
|
VIR_RESCTRL_MONITOR_TYPE_MEMBW,
|
|
resctrl) < 0)
|
|
goto cleanup;
|
|
|
|
nmons = resctrl->nmonitors - nmons;
|
|
/* Now @nmons contains the new <monitor> element number found in current
|
|
* <memorytune> element, and @n holds the number of new <node> element,
|
|
* only append the new @newresctrl object to domain if any of them is
|
|
* not zero. */
|
|
if (newresctrl && (nmons || n)) {
|
|
if (VIR_APPEND_ELEMENT(def->resctrls, def->nresctrls, newresctrl) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virDomainResctrlDefFree(newresctrl);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefTunablesParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
size_t i;
|
|
int n;
|
|
|
|
/* Extract blkio cgroup tunables */
|
|
if (virXPathUInt("string(./blkiotune/weight)", ctxt,
|
|
&def->blkio.weight) < 0)
|
|
def->blkio.weight = 0;
|
|
|
|
if ((n = virXPathNodeSet("./blkiotune/device", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("cannot extract blkiotune nodes"));
|
|
return -1;
|
|
}
|
|
if (n)
|
|
def->blkio.devices = g_new0(virBlkioDevice, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainBlkioDeviceParseXML(nodes[i], ctxt,
|
|
&def->blkio.devices[i]) < 0)
|
|
return -1;
|
|
def->blkio.ndevices++;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* Extract other memory tunables */
|
|
if (virDomainParseMemoryLimit("./memtune/hard_limit[1]", NULL, ctxt,
|
|
&def->mem.hard_limit) < 0)
|
|
return -1;
|
|
|
|
if (virDomainParseMemoryLimit("./memtune/soft_limit[1]", NULL, ctxt,
|
|
&def->mem.soft_limit) < 0)
|
|
return -1;
|
|
|
|
if (virDomainParseMemory("./memtune/min_guarantee[1]", NULL, ctxt,
|
|
&def->mem.min_guarantee, false, false) < 0)
|
|
return -1;
|
|
|
|
if (virDomainParseMemoryLimit("./memtune/swap_hard_limit[1]", NULL, ctxt,
|
|
&def->mem.swap_hard_limit) < 0)
|
|
return -1;
|
|
|
|
if (virDomainVcpuParse(def, ctxt, xmlopt) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefParseIOThreads(def, ctxt) < 0)
|
|
return -1;
|
|
|
|
/* Extract cpu tunables. */
|
|
if ((n = virXPathULongLong("string(./cputune/shares[1])", ctxt,
|
|
&def->cputune.shares)) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune shares value"));
|
|
return -1;
|
|
} else if (n == 0) {
|
|
def->cputune.sharesSpecified = true;
|
|
}
|
|
|
|
if (virXPathULongLong("string(./cputune/period[1])", ctxt,
|
|
&def->cputune.period) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune period value"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXPathLongLong("string(./cputune/quota[1])", ctxt,
|
|
&def->cputune.quota) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune quota value"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXPathULongLong("string(./cputune/global_period[1])", ctxt,
|
|
&def->cputune.global_period) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune global period value"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXPathLongLong("string(./cputune/global_quota[1])", ctxt,
|
|
&def->cputune.global_quota) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune global quota value"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXPathULongLong("string(./cputune/emulator_period[1])", ctxt,
|
|
&def->cputune.emulator_period) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune emulator period value"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXPathLongLong("string(./cputune/emulator_quota[1])", ctxt,
|
|
&def->cputune.emulator_quota) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune emulator quota value"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
if (virXPathULongLong("string(./cputune/iothread_period[1])", ctxt,
|
|
&def->cputune.iothread_period) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune iothread period value"));
|
|
return -1;
|
|
}
|
|
|
|
if (virXPathLongLong("string(./cputune/iothread_quota[1])", ctxt,
|
|
&def->cputune.iothread_quota) < -1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("can't parse cputune iothread quota value"));
|
|
return -1;
|
|
}
|
|
|
|
if ((n = virXPathNodeSet("./cputune/vcpupin", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainVcpuPinDefParseXML(def, nodes[i]))
|
|
return -1;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./cputune/emulatorpin", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot extract emulatorpin nodes"));
|
|
return -1;
|
|
}
|
|
|
|
if (n) {
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one emulatorpin is supported"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(def->cputune.emulatorpin = virDomainEmulatorPinDefParseXML(nodes[0])))
|
|
return -1;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
|
|
if ((n = virXPathNodeSet("./cputune/iothreadpin", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot extract iothreadpin nodes"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainIOThreadPinDefParseXML(nodes[i], def) < 0)
|
|
return -1;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./cputune/vcpusched", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot extract vcpusched nodes"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainVcpuThreadSchedParse(nodes[i], def) < 0)
|
|
return -1;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./cputune/iothreadsched", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot extract iothreadsched nodes"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainIOThreadSchedParse(nodes[i], def) < 0)
|
|
return -1;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./cputune/emulatorsched", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot extract emulatorsched nodes"));
|
|
return -1;
|
|
}
|
|
|
|
if (n) {
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one emulatorsched is supported"));
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainEmulatorSchedParse(nodes[0], def) < 0)
|
|
return -1;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./cputune/cachetune", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot extract cachetune nodes"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainCachetuneDefParse(def, ctxt, nodes[i], flags) < 0)
|
|
return -1;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./cputune/memorytune", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("cannot extract memorytune nodes"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (virDomainMemorytuneDefParse(def, ctxt, nodes[i], flags) < 0)
|
|
return -1;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefLifecycleParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
if (virDomainEventActionParseXML(ctxt, "on_reboot",
|
|
"string(./on_reboot[1])",
|
|
&def->onReboot,
|
|
VIR_DOMAIN_LIFECYCLE_ACTION_RESTART,
|
|
virDomainLifecycleActionTypeFromString) < 0)
|
|
goto error;
|
|
|
|
if (virDomainEventActionParseXML(ctxt, "on_poweroff",
|
|
"string(./on_poweroff[1])",
|
|
&def->onPoweroff,
|
|
VIR_DOMAIN_LIFECYCLE_ACTION_DESTROY,
|
|
virDomainLifecycleActionTypeFromString) < 0)
|
|
goto error;
|
|
|
|
if (virDomainEventActionParseXML(ctxt, "on_crash",
|
|
"string(./on_crash[1])",
|
|
&def->onCrash,
|
|
VIR_DOMAIN_LIFECYCLE_ACTION_DESTROY,
|
|
virDomainLifecycleActionTypeFromString) < 0)
|
|
goto error;
|
|
|
|
if (virDomainEventActionParseXML(ctxt, "on_lockfailure",
|
|
"string(./on_lockfailure[1])",
|
|
&def->onLockFailure,
|
|
VIR_DOMAIN_LOCK_FAILURE_DEFAULT,
|
|
virDomainLockFailureTypeFromString) < 0)
|
|
goto error;
|
|
|
|
if (virDomainPMStateParseXML(ctxt,
|
|
"string(./pm/suspend-to-mem/@enabled)",
|
|
&def->pm.s3) < 0)
|
|
goto error;
|
|
|
|
if (virDomainPMStateParseXML(ctxt,
|
|
"string(./pm/suspend-to-disk/@enabled)",
|
|
&def->pm.s4) < 0)
|
|
goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefClockParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
size_t i;
|
|
int n;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree char *tmp = NULL;
|
|
|
|
if ((tmp = virXPathString("string(./clock/@offset)", ctxt)) &&
|
|
(def->clock.offset = virDomainClockOffsetTypeFromString(tmp)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown clock offset '%s'"), tmp);
|
|
goto error;
|
|
}
|
|
VIR_FREE(tmp);
|
|
|
|
switch (def->clock.offset) {
|
|
case VIR_DOMAIN_CLOCK_OFFSET_LOCALTIME:
|
|
case VIR_DOMAIN_CLOCK_OFFSET_UTC:
|
|
tmp = virXPathString("string(./clock/@adjustment)", ctxt);
|
|
if (tmp) {
|
|
if (STREQ(tmp, "reset")) {
|
|
def->clock.data.utc_reset = true;
|
|
} else {
|
|
if (virStrToLong_ll(tmp, NULL, 10,
|
|
&def->clock.data.variable.adjustment) < 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unknown clock adjustment '%s'"),
|
|
tmp);
|
|
goto error;
|
|
}
|
|
switch (def->clock.offset) {
|
|
case VIR_DOMAIN_CLOCK_OFFSET_LOCALTIME:
|
|
def->clock.data.variable.basis = VIR_DOMAIN_CLOCK_BASIS_LOCALTIME;
|
|
break;
|
|
case VIR_DOMAIN_CLOCK_OFFSET_UTC:
|
|
def->clock.data.variable.basis = VIR_DOMAIN_CLOCK_BASIS_UTC;
|
|
break;
|
|
}
|
|
def->clock.offset = VIR_DOMAIN_CLOCK_OFFSET_VARIABLE;
|
|
}
|
|
VIR_FREE(tmp);
|
|
} else {
|
|
def->clock.data.utc_reset = false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CLOCK_OFFSET_VARIABLE:
|
|
if (virXPathLongLong("number(./clock/@adjustment)", ctxt,
|
|
&def->clock.data.variable.adjustment) < 0)
|
|
def->clock.data.variable.adjustment = 0;
|
|
if (virXPathLongLong("number(./clock/@adjustment0)", ctxt,
|
|
&def->clock.data.variable.adjustment0) < 0)
|
|
def->clock.data.variable.adjustment0 = 0;
|
|
tmp = virXPathString("string(./clock/@basis)", ctxt);
|
|
if (tmp) {
|
|
if ((def->clock.data.variable.basis = virDomainClockBasisTypeFromString(tmp)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown clock basis '%s'"), tmp);
|
|
goto error;
|
|
}
|
|
VIR_FREE(tmp);
|
|
} else {
|
|
def->clock.data.variable.basis = VIR_DOMAIN_CLOCK_BASIS_UTC;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE:
|
|
def->clock.data.timezone = virXPathString("string(./clock/@timezone)", ctxt);
|
|
if (!def->clock.data.timezone) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing 'timezone' attribute for clock with offset='timezone'"));
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ((n = virXPathNodeSet("./clock/timer", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n)
|
|
def->clock.timers = g_new0(virDomainTimerDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainTimerDef *timer = virDomainTimerDefParseXML(nodes[i], ctxt);
|
|
|
|
if (!timer)
|
|
goto error;
|
|
|
|
def->clock.timers[def->clock.ntimers++] = timer;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
virDomainDefControllersParse(virDomainDef *def,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags,
|
|
bool *usb_none)
|
|
{
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
bool usb_other = false;
|
|
bool usb_master = false;
|
|
size_t i;
|
|
int n;
|
|
|
|
if ((n = virXPathNodeSet("./devices/controller", ctxt, &nodes)) < 0)
|
|
return -1;
|
|
|
|
if (n)
|
|
def->controllers = g_new0(virDomainControllerDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
g_autoptr(virDomainControllerDef) controller = NULL;
|
|
|
|
controller = virDomainControllerDefParseXML(xmlopt, nodes[i],
|
|
ctxt, flags);
|
|
|
|
if (!controller)
|
|
return -1;
|
|
|
|
/* sanitize handling of "none" usb controller */
|
|
if (controller->type == VIR_DOMAIN_CONTROLLER_TYPE_USB) {
|
|
if (controller->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_NONE) {
|
|
if (usb_other || *usb_none) {
|
|
virReportError(VIR_ERR_XML_DETAIL, "%s",
|
|
_("Can't add another USB controller: "
|
|
"USB is disabled for this domain"));
|
|
return -1;
|
|
}
|
|
*usb_none = true;
|
|
} else {
|
|
if (*usb_none) {
|
|
virReportError(VIR_ERR_XML_DETAIL, "%s",
|
|
_("Can't add another USB controller: "
|
|
"USB is disabled for this domain"));
|
|
return -1;
|
|
}
|
|
usb_other = true;
|
|
}
|
|
|
|
if (controller->info.mastertype == VIR_DOMAIN_CONTROLLER_MASTER_NONE)
|
|
usb_master = true;
|
|
}
|
|
|
|
virDomainControllerInsertPreAlloced(def, g_steal_pointer(&controller));
|
|
}
|
|
|
|
if (usb_other && !usb_master) {
|
|
virReportError(VIR_ERR_XML_DETAIL, "%s",
|
|
_("No master USB controller specified"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static virDomainDef *
|
|
virDomainDefParseXML(xmlDocPtr xml,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
xmlNodePtr node = NULL;
|
|
size_t i, j;
|
|
int n;
|
|
virDomainDef *def;
|
|
bool uuid_generated = false;
|
|
bool usb_none = false;
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
g_autofree char *tmp = NULL;
|
|
|
|
if (flags & VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA) {
|
|
g_autofree char *schema = NULL;
|
|
|
|
schema = virFileFindResource("domain.rng",
|
|
abs_top_srcdir "/docs/schemas",
|
|
PKGDATADIR "/schemas");
|
|
if (!schema)
|
|
return NULL;
|
|
if (virXMLValidateAgainstSchema(schema, xml) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (!(def = virDomainDefNew()))
|
|
return NULL;
|
|
|
|
if (virDomainDefParseIDs(def, ctxt, flags, &uuid_generated) < 0)
|
|
goto error;
|
|
|
|
if (virDomainDefParseCaps(def, ctxt, xmlopt) < 0)
|
|
goto error;
|
|
|
|
/* Extract short description of domain (title) */
|
|
def->title = virXPathString("string(./title[1])", ctxt);
|
|
if (def->title && strchr(def->title, '\n')) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Domain title can't contain newlines"));
|
|
goto error;
|
|
}
|
|
|
|
/* Extract documentation if present */
|
|
def->description = virXPathString("string(./description[1])", ctxt);
|
|
|
|
/* analysis of security label, done early even though we format it
|
|
* late, so devices can refer to this for defaults */
|
|
if (!(flags & VIR_DOMAIN_DEF_PARSE_SKIP_SECLABEL)) {
|
|
if (virSecurityLabelDefsParseXML(def, ctxt, xmlopt, flags) == -1)
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainDefParseMemory(def, ctxt) < 0)
|
|
goto error;
|
|
|
|
if (virDomainDefTunablesParse(def, ctxt, xmlopt, flags) < 0)
|
|
goto error;
|
|
|
|
if (virCPUDefParseXML(ctxt, "./cpu[1]", VIR_CPU_TYPE_GUEST, &def->cpu,
|
|
false) < 0)
|
|
goto error;
|
|
|
|
if (virDomainNumaDefParseXML(def->numa, ctxt) < 0)
|
|
goto error;
|
|
|
|
if (virDomainNumaGetCPUCountTotal(def->numa) > virDomainDefGetVcpusMax(def)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Number of CPUs in <numa> exceeds the"
|
|
" <vcpu> count"));
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainNumaGetMaxCPUID(def->numa) >= virDomainDefGetVcpusMax(def)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("CPU IDs in <numa> exceed the <vcpu> count"));
|
|
goto error;
|
|
}
|
|
|
|
if (virDomainNumatuneParseXML(def->numa,
|
|
def->placement_mode ==
|
|
VIR_DOMAIN_CPU_PLACEMENT_MODE_STATIC,
|
|
ctxt) < 0)
|
|
goto error;
|
|
|
|
if (virDomainNumatuneHasPlacementAuto(def->numa) &&
|
|
!def->cpumask && !virDomainDefHasVcpuPin(def) &&
|
|
!def->cputune.emulatorpin &&
|
|
!virDomainIOThreadIDArrayHasPin(def))
|
|
def->placement_mode = VIR_DOMAIN_CPU_PLACEMENT_MODE_AUTO;
|
|
|
|
if ((n = virXPathNodeSet("./resource", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("cannot extract resource nodes"));
|
|
goto error;
|
|
}
|
|
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only one resource element is supported"));
|
|
goto error;
|
|
}
|
|
|
|
if (n &&
|
|
!(def->resource = virDomainResourceDefParse(nodes[0], ctxt)))
|
|
goto error;
|
|
VIR_FREE(nodes);
|
|
|
|
if (virDomainFeaturesDefParse(def, ctxt) < 0)
|
|
goto error;
|
|
|
|
if (virDomainDefLifecycleParse(def, ctxt) < 0)
|
|
goto error;
|
|
|
|
if (virDomainPerfDefParseXML(def, ctxt) < 0)
|
|
goto error;
|
|
|
|
if (virDomainDefClockParse(def, ctxt) < 0)
|
|
goto error;
|
|
|
|
if (virDomainDefParseBootOptions(def, ctxt) < 0)
|
|
goto error;
|
|
|
|
/* analysis of the disk devices */
|
|
if ((n = virXPathNodeSet("./devices/disk", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainDiskDef *disk = virDomainDiskDefParseXML(xmlopt,
|
|
nodes[i],
|
|
ctxt,
|
|
flags);
|
|
if (!disk)
|
|
goto error;
|
|
|
|
virDomainDiskInsert(def, disk);
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if (virDomainDefControllersParse(def, ctxt, xmlopt, flags, &usb_none) < 0)
|
|
goto error;
|
|
|
|
/* analysis of the resource leases */
|
|
if ((n = virXPathNodeSet("./devices/lease", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("cannot extract device leases"));
|
|
goto error;
|
|
}
|
|
if (n)
|
|
def->leases = g_new0(virDomainLeaseDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainLeaseDef *lease = virDomainLeaseDefParseXML(nodes[i], ctxt);
|
|
if (!lease)
|
|
goto error;
|
|
|
|
def->leases[def->nleases++] = lease;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the filesystems */
|
|
if ((n = virXPathNodeSet("./devices/filesystem", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->fss = g_new0(virDomainFSDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainFSDef *fs = virDomainFSDefParseXML(xmlopt,
|
|
nodes[i],
|
|
ctxt,
|
|
flags);
|
|
if (!fs)
|
|
goto error;
|
|
|
|
def->fss[def->nfss++] = fs;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the network devices */
|
|
if ((n = virXPathNodeSet("./devices/interface", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->nets = g_new0(virDomainNetDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainNetDef *net = virDomainNetDefParseXML(xmlopt,
|
|
nodes[i],
|
|
ctxt,
|
|
flags);
|
|
if (!net)
|
|
goto error;
|
|
|
|
def->nets[def->nnets++] = net;
|
|
|
|
/* <interface type='hostdev'> (and <interface type='net'>
|
|
* where the actual network type is already known to be
|
|
* hostdev) must also be in the hostdevs array.
|
|
*/
|
|
if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_HOSTDEV &&
|
|
virDomainHostdevInsert(def, virDomainNetGetActualHostdev(net)) < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
|
|
/* analysis of the smartcard devices */
|
|
if ((n = virXPathNodeSet("./devices/smartcard", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->smartcards = g_new0(virDomainSmartcardDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainSmartcardDef *card = virDomainSmartcardDefParseXML(xmlopt,
|
|
nodes[i],
|
|
ctxt,
|
|
flags);
|
|
if (!card)
|
|
goto error;
|
|
|
|
def->smartcards[def->nsmartcards++] = card;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
|
|
/* analysis of the character devices */
|
|
if ((n = virXPathNodeSet("./devices/parallel", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->parallels = g_new0(virDomainChrDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainChrDef *chr = virDomainChrDefParseXML(xmlopt,
|
|
ctxt,
|
|
nodes[i],
|
|
flags);
|
|
if (!chr)
|
|
goto error;
|
|
|
|
if (chr->target.port == -1) {
|
|
int maxport = -1;
|
|
for (j = 0; j < i; j++) {
|
|
if (def->parallels[j]->target.port > maxport)
|
|
maxport = def->parallels[j]->target.port;
|
|
}
|
|
chr->target.port = maxport + 1;
|
|
}
|
|
def->parallels[def->nparallels++] = chr;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./devices/serial", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n)
|
|
def->serials = g_new0(virDomainChrDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainChrDef *chr = virDomainChrDefParseXML(xmlopt,
|
|
ctxt,
|
|
nodes[i],
|
|
flags);
|
|
if (!chr)
|
|
goto error;
|
|
|
|
if (chr->target.port == -1) {
|
|
int maxport = -1;
|
|
for (j = 0; j < i; j++) {
|
|
if (def->serials[j]->target.port > maxport)
|
|
maxport = def->serials[j]->target.port;
|
|
}
|
|
chr->target.port = maxport + 1;
|
|
}
|
|
def->serials[def->nserials++] = chr;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./devices/console", ctxt, &nodes)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("cannot extract console devices"));
|
|
goto error;
|
|
}
|
|
if (n)
|
|
def->consoles = g_new0(virDomainChrDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainChrDef *chr = virDomainChrDefParseXML(xmlopt,
|
|
ctxt,
|
|
nodes[i],
|
|
flags);
|
|
if (!chr)
|
|
goto error;
|
|
|
|
chr->target.port = i;
|
|
def->consoles[def->nconsoles++] = chr;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./devices/channel", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->channels = g_new0(virDomainChrDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainChrDef *chr = virDomainChrDefParseXML(xmlopt,
|
|
ctxt,
|
|
nodes[i],
|
|
flags);
|
|
if (!chr)
|
|
goto error;
|
|
|
|
def->channels[def->nchannels++] = chr;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
|
|
/* analysis of the input devices */
|
|
if ((n = virXPathNodeSet("./devices/input", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->inputs = g_new0(virDomainInputDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainInputDef *input = virDomainInputDefParseXML(xmlopt,
|
|
def,
|
|
nodes[i],
|
|
ctxt,
|
|
flags);
|
|
if (!input)
|
|
goto error;
|
|
|
|
/* Check if USB bus is required */
|
|
if (input->bus == VIR_DOMAIN_INPUT_BUS_USB && usb_none) {
|
|
virDomainInputDefFree(input);
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Can't add USB input device. "
|
|
"USB bus is disabled"));
|
|
goto error;
|
|
}
|
|
|
|
def->inputs[def->ninputs++] = input;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the graphics devices */
|
|
if ((n = virXPathNodeSet("./devices/graphics", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->graphics = g_new0(virDomainGraphicsDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainGraphicsDef *graphics = virDomainGraphicsDefParseXML(xmlopt,
|
|
nodes[i],
|
|
ctxt,
|
|
flags);
|
|
if (!graphics)
|
|
goto error;
|
|
|
|
def->graphics[def->ngraphics++] = graphics;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the sound devices */
|
|
if ((n = virXPathNodeSet("./devices/sound", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->sounds = g_new0(virDomainSoundDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainSoundDef *sound = virDomainSoundDefParseXML(xmlopt,
|
|
nodes[i],
|
|
ctxt,
|
|
flags);
|
|
if (!sound)
|
|
goto error;
|
|
|
|
def->sounds[def->nsounds++] = sound;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the audio devices */
|
|
if ((n = virXPathNodeSet("./devices/audio", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->audios = g_new0(virDomainAudioDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainAudioDef *audio = virDomainAudioDefParseXML(xmlopt,
|
|
nodes[i],
|
|
ctxt);
|
|
if (!audio)
|
|
goto error;
|
|
|
|
def->audios[def->naudios++] = audio;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the video devices */
|
|
if ((n = virXPathNodeSet("./devices/video", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->videos = g_new0(virDomainVideoDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
g_autoptr(virDomainVideoDef) video = NULL;
|
|
ssize_t insertAt = -1;
|
|
|
|
if (!(video = virDomainVideoDefParseXML(xmlopt, nodes[i],
|
|
ctxt, flags)))
|
|
goto error;
|
|
|
|
if (video->primary) {
|
|
insertAt = 0;
|
|
}
|
|
|
|
if (VIR_INSERT_ELEMENT_INPLACE(def->videos,
|
|
insertAt,
|
|
def->nvideos,
|
|
video) < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the host devices */
|
|
if ((n = virXPathNodeSet("./devices/hostdev", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n > 0)
|
|
VIR_REALLOC_N(def->hostdevs, def->nhostdevs + n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainHostdevDef *hostdev;
|
|
|
|
hostdev = virDomainHostdevDefParseXML(xmlopt, nodes[i], ctxt,
|
|
flags);
|
|
if (!hostdev)
|
|
goto error;
|
|
|
|
if (hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB &&
|
|
usb_none) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Can't add host USB device: "
|
|
"USB is disabled in this host"));
|
|
virDomainHostdevDefFree(hostdev);
|
|
goto error;
|
|
}
|
|
|
|
def->hostdevs[def->nhostdevs++] = hostdev;
|
|
|
|
/* For a domain definition, we need to check if the controller
|
|
* for this hostdev exists yet and if not add it. This cannot be
|
|
* done during virDomainHostdevAssignAddress (as part of device
|
|
* post processing) because that will result in the failure to
|
|
* load the controller during hostdev hotplug.
|
|
*/
|
|
if (virDomainDefMaybeAddHostdevSCSIcontroller(def) < 0)
|
|
goto error;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the watchdog devices */
|
|
def->watchdog = NULL;
|
|
if ((n = virXPathNodeSet("./devices/watchdog", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("only a single watchdog device is supported"));
|
|
goto error;
|
|
}
|
|
if (n > 0) {
|
|
virDomainWatchdogDef *watchdog;
|
|
|
|
watchdog = virDomainWatchdogDefParseXML(xmlopt, nodes[0], ctxt, flags);
|
|
if (!watchdog)
|
|
goto error;
|
|
|
|
def->watchdog = watchdog;
|
|
VIR_FREE(nodes);
|
|
}
|
|
|
|
/* analysis of the memballoon devices */
|
|
def->memballoon = NULL;
|
|
if ((n = virXPathNodeSet("./devices/memballoon", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("only a single memory balloon device is supported"));
|
|
goto error;
|
|
}
|
|
if (n > 0) {
|
|
virDomainMemballoonDef *memballoon;
|
|
|
|
memballoon = virDomainMemballoonDefParseXML(xmlopt, nodes[0], ctxt, flags);
|
|
if (!memballoon)
|
|
goto error;
|
|
|
|
def->memballoon = memballoon;
|
|
VIR_FREE(nodes);
|
|
}
|
|
|
|
/* Parse the RNG devices */
|
|
if ((n = virXPathNodeSet("./devices/rng", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->rngs = g_new0(virDomainRNGDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainRNGDef *rng = virDomainRNGDefParseXML(xmlopt, nodes[i],
|
|
ctxt, flags);
|
|
if (!rng)
|
|
goto error;
|
|
|
|
def->rngs[def->nrngs++] = rng;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* Parse the TPM devices */
|
|
if ((n = virXPathNodeSet("./devices/tpm", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n > 2) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("a maximum of two TPM devices is supported, one of "
|
|
"them being a TPM Proxy device"));
|
|
goto error;
|
|
}
|
|
|
|
if (n)
|
|
def->tpms = g_new0(virDomainTPMDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainTPMDef *tpm = virDomainTPMDefParseXML(xmlopt, nodes[i],
|
|
ctxt, flags);
|
|
if (!tpm)
|
|
goto error;
|
|
|
|
def->tpms[def->ntpms++] = tpm;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./devices/nvram", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("only a single nvram device is supported"));
|
|
goto error;
|
|
} else if (n == 1) {
|
|
virDomainNVRAMDef *nvram =
|
|
virDomainNVRAMDefParseXML(xmlopt, nodes[0], ctxt, flags);
|
|
if (!nvram)
|
|
goto error;
|
|
def->nvram = nvram;
|
|
VIR_FREE(nodes);
|
|
}
|
|
|
|
/* analysis of the hub devices */
|
|
if ((n = virXPathNodeSet("./devices/hub", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->hubs = g_new0(virDomainHubDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainHubDef *hub;
|
|
|
|
hub = virDomainHubDefParseXML(xmlopt, nodes[i], ctxt, flags);
|
|
if (!hub)
|
|
goto error;
|
|
|
|
if (hub->type == VIR_DOMAIN_HUB_TYPE_USB && usb_none) {
|
|
virDomainHubDefFree(hub);
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Can't add USB hub: "
|
|
"USB is disabled for this domain"));
|
|
goto error;
|
|
}
|
|
|
|
def->hubs[def->nhubs++] = hub;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the redirected devices */
|
|
if ((n = virXPathNodeSet("./devices/redirdev", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->redirdevs = g_new0(virDomainRedirdevDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainRedirdevDef *redirdev =
|
|
virDomainRedirdevDefParseXML(xmlopt, nodes[i], ctxt, flags);
|
|
if (!redirdev)
|
|
goto error;
|
|
|
|
def->redirdevs[def->nredirdevs++] = redirdev;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the redirection filter rules */
|
|
if ((n = virXPathNodeSet("./devices/redirfilter", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("only one set of redirection filter rule is supported"));
|
|
goto error;
|
|
}
|
|
|
|
if (n) {
|
|
virDomainRedirFilterDef *redirfilter =
|
|
virDomainRedirFilterDefParseXML(nodes[0], ctxt);
|
|
if (!redirfilter)
|
|
goto error;
|
|
|
|
def->redirfilter = redirfilter;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the panic devices */
|
|
if ((n = virXPathNodeSet("./devices/panic", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->panics = g_new0(virDomainPanicDef *, n);
|
|
for (i = 0; i < n; i++) {
|
|
virDomainPanicDef *panic;
|
|
|
|
panic = virDomainPanicDefParseXML(xmlopt, nodes[i], ctxt, flags);
|
|
if (!panic)
|
|
goto error;
|
|
|
|
def->panics[def->npanics++] = panic;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the shmem devices */
|
|
if ((n = virXPathNodeSet("./devices/shmem", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->shmems = g_new0(virDomainShmemDef *, n);
|
|
|
|
node = ctxt->node;
|
|
for (i = 0; i < n; i++) {
|
|
virDomainShmemDef *shmem;
|
|
ctxt->node = nodes[i];
|
|
shmem = virDomainShmemDefParseXML(xmlopt, nodes[i], ctxt, flags);
|
|
if (!shmem)
|
|
goto error;
|
|
|
|
def->shmems[def->nshmems++] = shmem;
|
|
}
|
|
ctxt->node = node;
|
|
VIR_FREE(nodes);
|
|
|
|
/* Check for SEV feature */
|
|
if ((node = virXPathNode("./launchSecurity", ctxt)) != NULL) {
|
|
def->sev = virDomainSEVDefParseXML(node, ctxt);
|
|
if (!def->sev)
|
|
goto error;
|
|
}
|
|
|
|
/* analysis of memory devices */
|
|
if ((n = virXPathNodeSet("./devices/memory", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
if (n)
|
|
def->mems = g_new0(virDomainMemoryDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virDomainMemoryDef *mem = virDomainMemoryDefParseXML(xmlopt,
|
|
nodes[i],
|
|
ctxt,
|
|
flags);
|
|
if (!mem)
|
|
goto error;
|
|
|
|
def->mems[def->nmems++] = mem;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./devices/iommu", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only a single IOMMU device is supported"));
|
|
goto error;
|
|
}
|
|
|
|
if (n > 0) {
|
|
if (!(def->iommu = virDomainIOMMUDefParseXML(nodes[0], ctxt)))
|
|
goto error;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./devices/vsock", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n > 1) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("only a single vsock device is supported"));
|
|
goto error;
|
|
}
|
|
|
|
if (n > 0) {
|
|
if (!(def->vsock = virDomainVsockDefParseXML(xmlopt, nodes[0],
|
|
ctxt, flags)))
|
|
goto error;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
/* analysis of the user namespace mapping */
|
|
if ((n = virXPathNodeSet("./idmap/uid", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n) {
|
|
def->idmap.uidmap = virDomainIdmapDefParseXML(ctxt, nodes, n);
|
|
if (!def->idmap.uidmap)
|
|
goto error;
|
|
|
|
def->idmap.nuidmap = n;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./idmap/gid", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
if (n) {
|
|
def->idmap.gidmap = virDomainIdmapDefParseXML(ctxt, nodes, n);
|
|
if (!def->idmap.gidmap)
|
|
goto error;
|
|
|
|
def->idmap.ngidmap = n;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((n = virXPathNodeSet("./sysinfo", ctxt, &nodes)) < 0)
|
|
goto error;
|
|
|
|
def->sysinfo = g_new0(virSysinfoDef *, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virSysinfoDef *sysinfo = virSysinfoParseXML(nodes[i], ctxt,
|
|
def->uuid, uuid_generated);
|
|
|
|
if (!sysinfo)
|
|
goto error;
|
|
|
|
def->sysinfo[def->nsysinfo++] = sysinfo;
|
|
}
|
|
VIR_FREE(nodes);
|
|
|
|
if ((tmp = virXPathString("string(./os/smbios/@mode)", ctxt))) {
|
|
int mode;
|
|
|
|
if ((mode = virDomainSmbiosModeTypeFromString(tmp)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown smbios mode '%s'"), tmp);
|
|
goto error;
|
|
}
|
|
def->os.smbios_mode = mode;
|
|
}
|
|
|
|
if (virDomainKeyWrapDefParseXML(def, ctxt) < 0)
|
|
goto error;
|
|
|
|
/* Extract custom metadata */
|
|
if ((node = virXPathNode("./metadata[1]", ctxt)) != NULL)
|
|
def->metadata = xmlCopyNode(node, 1);
|
|
|
|
/* we have to make a copy of all of the callback pointers here since
|
|
* we won't have the virCaps structure available during free
|
|
*/
|
|
def->ns = xmlopt->ns;
|
|
|
|
if (def->ns.parse) {
|
|
if (virXMLNamespaceRegister(ctxt, &def->ns) < 0)
|
|
goto error;
|
|
if ((def->ns.parse)(ctxt, &def->namespaceData) < 0)
|
|
goto error;
|
|
}
|
|
|
|
return def;
|
|
|
|
error:
|
|
virDomainDefFree(def);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virDomainObj *
|
|
virDomainObjParseXML(xmlDocPtr xml,
|
|
xmlXPathContextPtr ctxt,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
long val;
|
|
xmlNodePtr config;
|
|
xmlNodePtr oldnode;
|
|
g_autoptr(virDomainObj) obj = NULL;
|
|
size_t i;
|
|
int n;
|
|
virDomainState state;
|
|
int reason = 0;
|
|
void *parseOpaque = NULL;
|
|
g_autofree char *tmp = NULL;
|
|
g_autofree xmlNodePtr *taintNodes = NULL;
|
|
g_autofree xmlNodePtr *depNodes = NULL;
|
|
|
|
if (!(obj = virDomainObjNew(xmlopt)))
|
|
return NULL;
|
|
|
|
if (!(config = virXPathNode("./domain", ctxt))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("no domain config"));
|
|
return NULL;
|
|
}
|
|
|
|
oldnode = ctxt->node;
|
|
ctxt->node = config;
|
|
obj->def = virDomainDefParseXML(xml, ctxt, xmlopt, flags);
|
|
ctxt->node = oldnode;
|
|
if (!obj->def)
|
|
return NULL;
|
|
|
|
if (virXMLPropEnum(ctxt->node, "state", virDomainStateTypeFromString,
|
|
VIR_XML_PROP_REQUIRED, &state) < 0)
|
|
return NULL;
|
|
|
|
if ((tmp = virXMLPropString(ctxt->node, "reason"))) {
|
|
if ((reason = virDomainStateReasonFromString(state, tmp)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("invalid domain state reason '%s'"), tmp);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
virDomainObjSetState(obj, state, reason);
|
|
|
|
if (virXPathLong("string(./@pid)", ctxt, &val) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("invalid pid"));
|
|
return NULL;
|
|
}
|
|
obj->pid = (pid_t)val;
|
|
|
|
if ((n = virXPathNodeSet("./taint", ctxt, &taintNodes)) < 0)
|
|
return NULL;
|
|
for (i = 0; i < n; i++) {
|
|
int rc;
|
|
virDomainTaintFlags taint;
|
|
|
|
if ((rc = virXMLPropEnum(taintNodes[i], "flag",
|
|
virDomainTaintTypeFromString,
|
|
VIR_XML_PROP_NONE, &taint)) < 0)
|
|
return NULL;
|
|
|
|
if (rc == 1)
|
|
virDomainObjTaint(obj, taint);
|
|
}
|
|
|
|
if ((n = virXPathNodeSet("./deprecation", ctxt, &depNodes)) < 0)
|
|
return NULL;
|
|
for (i = 0; i < n; i++) {
|
|
g_autofree char *str = virXMLNodeContentString(depNodes[i]);
|
|
virDomainObjDeprecation(obj, str);
|
|
}
|
|
|
|
if (xmlopt->privateData.parse &&
|
|
xmlopt->privateData.parse(ctxt, obj, &xmlopt->config) < 0)
|
|
return NULL;
|
|
|
|
if (xmlopt->privateData.getParseOpaque)
|
|
parseOpaque = xmlopt->privateData.getParseOpaque(obj);
|
|
|
|
/* callback to fill driver specific domain aspects */
|
|
if (virDomainDefPostParse(obj->def, flags, xmlopt, parseOpaque) < 0)
|
|
return NULL;
|
|
|
|
/* validate configuration */
|
|
if (virDomainDefValidate(obj->def, flags, xmlopt, parseOpaque) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&obj);
|
|
}
|
|
|
|
|
|
static virDomainDef *
|
|
virDomainDefParse(const char *xmlStr,
|
|
const char *filename,
|
|
virDomainXMLOption *xmlopt,
|
|
void *parseOpaque,
|
|
unsigned int flags)
|
|
{
|
|
xmlDocPtr xml = NULL;
|
|
virDomainDef *def = NULL;
|
|
int keepBlanksDefault = xmlKeepBlanksDefault(0);
|
|
xmlNodePtr root;
|
|
if (!(xml = virXMLParse(filename, xmlStr, _("(domain_definition)"))))
|
|
goto cleanup;
|
|
|
|
root = xmlDocGetRootElement(xml);
|
|
if (!virXMLNodeNameEqual(root, "domain")) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unexpected root element <%s>, "
|
|
"expecting <domain>"),
|
|
root->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
def = virDomainDefParseNode(xml, root, xmlopt, parseOpaque, flags);
|
|
|
|
cleanup:
|
|
xmlFreeDoc(xml);
|
|
xmlKeepBlanksDefault(keepBlanksDefault);
|
|
return def;
|
|
}
|
|
|
|
virDomainDef *
|
|
virDomainDefParseString(const char *xmlStr,
|
|
virDomainXMLOption *xmlopt,
|
|
void *parseOpaque,
|
|
unsigned int flags)
|
|
{
|
|
return virDomainDefParse(xmlStr, NULL, xmlopt, parseOpaque, flags);
|
|
}
|
|
|
|
virDomainDef *
|
|
virDomainDefParseFile(const char *filename,
|
|
virDomainXMLOption *xmlopt,
|
|
void *parseOpaque,
|
|
unsigned int flags)
|
|
{
|
|
return virDomainDefParse(NULL, filename, xmlopt, parseOpaque, flags);
|
|
}
|
|
|
|
|
|
virDomainDef *
|
|
virDomainDefParseNode(xmlDocPtr xml,
|
|
xmlNodePtr root,
|
|
virDomainXMLOption *xmlopt,
|
|
void *parseOpaque,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(xmlXPathContext) ctxt = NULL;
|
|
g_autoptr(virDomainDef) def = NULL;
|
|
|
|
if (!(ctxt = virXMLXPathContextNew(xml)))
|
|
return NULL;
|
|
|
|
ctxt->node = root;
|
|
|
|
if (!(def = virDomainDefParseXML(xml, ctxt, xmlopt, flags)))
|
|
return NULL;
|
|
|
|
/* callback to fill driver specific domain aspects */
|
|
if (virDomainDefPostParse(def, flags, xmlopt, parseOpaque) < 0)
|
|
return NULL;
|
|
|
|
/* validate configuration */
|
|
if (virDomainDefValidate(def, flags, xmlopt, parseOpaque) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
|
|
virDomainObj *
|
|
virDomainObjParseNode(xmlDocPtr xml,
|
|
xmlNodePtr root,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
g_autoptr(xmlXPathContext) ctxt = NULL;
|
|
|
|
if (!virXMLNodeNameEqual(root, "domstatus")) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unexpected root element <%s>, "
|
|
"expecting <domstatus>"),
|
|
root->name);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(ctxt = virXMLXPathContextNew(xml)))
|
|
return NULL;
|
|
|
|
ctxt->node = root;
|
|
return virDomainObjParseXML(xml, ctxt, xmlopt, flags);
|
|
}
|
|
|
|
|
|
virDomainObj *
|
|
virDomainObjParseFile(const char *filename,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
xmlDocPtr xml;
|
|
virDomainObj *obj = NULL;
|
|
int keepBlanksDefault = xmlKeepBlanksDefault(0);
|
|
|
|
if ((xml = virXMLParseFile(filename))) {
|
|
obj = virDomainObjParseNode(xml, xmlDocGetRootElement(xml),
|
|
xmlopt, flags);
|
|
xmlFreeDoc(xml);
|
|
}
|
|
|
|
xmlKeepBlanksDefault(keepBlanksDefault);
|
|
return obj;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainTimerDefCheckABIStability(virDomainTimerDef *src,
|
|
virDomainTimerDef *dst)
|
|
{
|
|
if (src->name != dst->name) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target timer %s does not match source %s"),
|
|
virDomainTimerNameTypeToString(dst->name),
|
|
virDomainTimerNameTypeToString(src->name));
|
|
return false;
|
|
}
|
|
|
|
if (src->present != dst->present) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target timer presence %d does not match source %d"),
|
|
dst->present, src->present);
|
|
return false;
|
|
}
|
|
|
|
if (src->name == VIR_DOMAIN_TIMER_NAME_TSC) {
|
|
if (src->frequency != dst->frequency) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target TSC frequency %llu does not match source %llu"),
|
|
dst->frequency, src->frequency);
|
|
return false;
|
|
}
|
|
|
|
if (src->mode != dst->mode) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target TSC mode %s does not match source %s"),
|
|
virDomainTimerModeTypeToString(dst->mode),
|
|
virDomainTimerModeTypeToString(src->mode));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainDeviceInfoCheckABIStability(virDomainDeviceInfo *src,
|
|
virDomainDeviceInfo *dst)
|
|
{
|
|
if (src->type != dst->type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device address type %s does not match source %s"),
|
|
virDomainDeviceAddressTypeToString(dst->type),
|
|
virDomainDeviceAddressTypeToString(src->type));
|
|
return false;
|
|
}
|
|
|
|
switch ((virDomainDeviceAddressType) src->type) {
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI:
|
|
if (src->addr.pci.domain != dst->addr.pci.domain ||
|
|
src->addr.pci.bus != dst->addr.pci.bus ||
|
|
src->addr.pci.slot != dst->addr.pci.slot ||
|
|
src->addr.pci.function != dst->addr.pci.function) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device PCI address " VIR_PCI_DEVICE_ADDRESS_FMT
|
|
"does not match source " VIR_PCI_DEVICE_ADDRESS_FMT),
|
|
dst->addr.pci.domain, dst->addr.pci.bus,
|
|
dst->addr.pci.slot, dst->addr.pci.function,
|
|
src->addr.pci.domain, src->addr.pci.bus,
|
|
src->addr.pci.slot, src->addr.pci.function);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE:
|
|
if (src->addr.drive.controller != dst->addr.drive.controller ||
|
|
src->addr.drive.bus != dst->addr.drive.bus ||
|
|
src->addr.drive.unit != dst->addr.drive.unit) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device drive address %d:%d:%d "
|
|
"does not match source %d:%d:%d"),
|
|
dst->addr.drive.controller, dst->addr.drive.bus,
|
|
dst->addr.drive.unit,
|
|
src->addr.drive.controller, src->addr.drive.bus,
|
|
src->addr.drive.unit);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL:
|
|
if (src->addr.vioserial.controller != dst->addr.vioserial.controller ||
|
|
src->addr.vioserial.bus != dst->addr.vioserial.bus ||
|
|
src->addr.vioserial.port != dst->addr.vioserial.port) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device virtio serial address %d:%d:%d "
|
|
"does not match source %d:%d:%d"),
|
|
dst->addr.vioserial.controller, dst->addr.vioserial.bus,
|
|
dst->addr.vioserial.port,
|
|
src->addr.vioserial.controller, src->addr.vioserial.bus,
|
|
src->addr.vioserial.port);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID:
|
|
if (src->addr.ccid.controller != dst->addr.ccid.controller ||
|
|
src->addr.ccid.slot != dst->addr.ccid.slot) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device ccid address %d:%d "
|
|
"does not match source %d:%d"),
|
|
dst->addr.ccid.controller,
|
|
dst->addr.ccid.slot,
|
|
src->addr.ccid.controller,
|
|
src->addr.ccid.slot);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_ISA:
|
|
if (src->addr.isa.iobase != dst->addr.isa.iobase ||
|
|
src->addr.isa.irq != dst->addr.isa.irq) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device isa address %d:%d "
|
|
"does not match source %d:%d"),
|
|
dst->addr.isa.iobase,
|
|
dst->addr.isa.irq,
|
|
src->addr.isa.iobase,
|
|
src->addr.isa.irq);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DIMM:
|
|
if (src->addr.dimm.slot != dst->addr.dimm.slot) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device dimm slot %u does not match "
|
|
"source %u"),
|
|
dst->addr.dimm.slot,
|
|
src->addr.dimm.slot);
|
|
return false;
|
|
}
|
|
|
|
if (src->addr.dimm.base != dst->addr.dimm.base) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device dimm base address '%llx' does "
|
|
"not match source '%llx'"),
|
|
dst->addr.dimm.base,
|
|
src->addr.dimm.base);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_UNASSIGNED:
|
|
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (src->acpiIndex != dst->acpiIndex) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target ACPI index '%u' does not match source '%u'"),
|
|
dst->acpiIndex, src->acpiIndex);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainVirtioOptionsCheckABIStability(virDomainVirtioOptions *src,
|
|
virDomainVirtioOptions *dst)
|
|
{
|
|
if (!src && !dst)
|
|
return true;
|
|
|
|
if (!src || !dst) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target device virtio options don't match the source"));
|
|
return false;
|
|
}
|
|
|
|
if (src->iommu != dst->iommu) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device iommu option '%s' does not "
|
|
"match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->iommu),
|
|
virTristateSwitchTypeToString(src->iommu));
|
|
return false;
|
|
}
|
|
if (src->ats != dst->ats) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device ats option '%s' does not "
|
|
"match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->ats),
|
|
virTristateSwitchTypeToString(src->ats));
|
|
return false;
|
|
}
|
|
if (src->packed != dst->packed) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target device packed option '%s' does not "
|
|
"match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->packed),
|
|
virTristateSwitchTypeToString(src->packed));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainDiskDefCheckABIStability(virDomainDiskDef *src,
|
|
virDomainDiskDef *dst)
|
|
{
|
|
if (src->device != dst->device) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target disk device %s does not match source %s"),
|
|
virDomainDiskDeviceTypeToString(dst->device),
|
|
virDomainDiskDeviceTypeToString(src->device));
|
|
return false;
|
|
}
|
|
|
|
if (src->bus != dst->bus) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target disk bus %s does not match source %s"),
|
|
virDomainDiskBusTypeToString(dst->bus),
|
|
virDomainDiskBusTypeToString(src->bus));
|
|
return false;
|
|
}
|
|
|
|
if (STRNEQ(src->dst, dst->dst)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target disk %s does not match source %s"),
|
|
dst->dst, src->dst);
|
|
return false;
|
|
}
|
|
|
|
if (STRNEQ_NULLABLE(src->serial, dst->serial)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target disk serial %s does not match source %s"),
|
|
NULLSTR(dst->serial), NULLSTR(src->serial));
|
|
return false;
|
|
}
|
|
|
|
if (STRNEQ_NULLABLE(src->wwn, dst->wwn)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target disk wwn '%s' does not match source '%s'"),
|
|
NULLSTR(dst->wwn), NULLSTR(src->wwn));
|
|
return false;
|
|
|
|
}
|
|
|
|
if (src->src->readonly != dst->src->readonly) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target disk access mode does not match source"));
|
|
return false;
|
|
}
|
|
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target disk model %s does not match source %s"),
|
|
virDomainDiskModelTypeToString(dst->model),
|
|
virDomainDiskModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (src->rotation_rate != dst->rotation_rate) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target disk rotation rate %u RPM does not match source %u RPM"),
|
|
dst->rotation_rate, src->rotation_rate);
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainControllerDefCheckABIStability(virDomainControllerDef *src,
|
|
virDomainControllerDef *dst)
|
|
{
|
|
if (src->type != dst->type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target controller type %s does not match source %s"),
|
|
virDomainControllerTypeToString(dst->type),
|
|
virDomainControllerTypeToString(src->type));
|
|
return false;
|
|
}
|
|
|
|
if (src->idx != dst->idx) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target controller index %d does not match source %d"),
|
|
dst->idx, src->idx);
|
|
return false;
|
|
}
|
|
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target controller model %d does not match source %d"),
|
|
dst->model, src->model);
|
|
return false;
|
|
}
|
|
|
|
if (src->type == VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL) {
|
|
if (src->opts.vioserial.ports != dst->opts.vioserial.ports) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target controller ports %d does not match source %d"),
|
|
dst->opts.vioserial.ports, src->opts.vioserial.ports);
|
|
return false;
|
|
}
|
|
|
|
if (src->opts.vioserial.vectors != dst->opts.vioserial.vectors) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target controller vectors %d does not match source %d"),
|
|
dst->opts.vioserial.vectors, src->opts.vioserial.vectors);
|
|
return false;
|
|
}
|
|
} else if (src->type == VIR_DOMAIN_CONTROLLER_TYPE_USB) {
|
|
if (src->opts.usbopts.ports != dst->opts.usbopts.ports) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target controller ports %d does not match source %d"),
|
|
dst->opts.usbopts.ports, src->opts.usbopts.ports);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainFsDefCheckABIStability(virDomainFSDef *src,
|
|
virDomainFSDef *dst)
|
|
{
|
|
if (STRNEQ_NULLABLE(src->dst, dst->dst)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target filesystem guest target %s does not match source %s"),
|
|
dst->dst, src->dst);
|
|
return false;
|
|
}
|
|
|
|
if (src->readonly != dst->readonly) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target filesystem access mode does not match source"));
|
|
return false;
|
|
}
|
|
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target filesystem model does not match source"));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainNetDefCheckABIStability(virDomainNetDef *src,
|
|
virDomainNetDef *dst)
|
|
{
|
|
char srcmac[VIR_MAC_STRING_BUFLEN];
|
|
char dstmac[VIR_MAC_STRING_BUFLEN];
|
|
|
|
if (virMacAddrCmp(&src->mac, &dst->mac) != 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target network card mac %s"
|
|
" does not match source %s"),
|
|
virMacAddrFormat(&dst->mac, dstmac),
|
|
virMacAddrFormat(&src->mac, srcmac));
|
|
return false;
|
|
}
|
|
|
|
if (STRNEQ_NULLABLE(src->modelstr, dst->modelstr)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target network card model %s does not match source %s"),
|
|
NULLSTR(dst->modelstr), NULLSTR(src->modelstr));
|
|
return false;
|
|
}
|
|
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target network card model %s does not match source %s"),
|
|
virDomainNetModelTypeToString(dst->model),
|
|
virDomainNetModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (src->mtu != dst->mtu) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target network card MTU %d does not match source %d"),
|
|
dst->mtu, src->mtu);
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainInputDefCheckABIStability(virDomainInputDef *src,
|
|
virDomainInputDef *dst)
|
|
{
|
|
if (src->type != dst->type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target input device type %s does not match source %s"),
|
|
virDomainInputTypeToString(dst->type),
|
|
virDomainInputTypeToString(src->type));
|
|
return false;
|
|
}
|
|
|
|
if (src->bus != dst->bus) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target input device bus %s does not match source %s"),
|
|
virDomainInputBusTypeToString(dst->bus),
|
|
virDomainInputBusTypeToString(src->bus));
|
|
return false;
|
|
}
|
|
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target input model %s does not match source %s"),
|
|
virDomainInputBusTypeToString(dst->model),
|
|
virDomainInputBusTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainSoundDefCheckABIStability(virDomainSoundDef *src,
|
|
virDomainSoundDef *dst)
|
|
{
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target sound card model %s does not match source %s"),
|
|
virDomainSoundModelTypeToString(dst->model),
|
|
virDomainSoundModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainVideoDefCheckABIStability(virDomainVideoDef *src,
|
|
virDomainVideoDef *dst)
|
|
{
|
|
if (src->type != dst->type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target video card model %s does not match source %s"),
|
|
virDomainVideoTypeToString(dst->type),
|
|
virDomainVideoTypeToString(src->type));
|
|
return false;
|
|
}
|
|
|
|
if (src->ram != dst->ram) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target video card ram %u does not match source %u"),
|
|
dst->ram, src->ram);
|
|
return false;
|
|
}
|
|
|
|
if (src->vram != dst->vram) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target video card vram %u does not match source %u"),
|
|
dst->vram, src->vram);
|
|
return false;
|
|
}
|
|
|
|
if (src->vram64 != dst->vram64) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target video card vram64 %u does not match source %u"),
|
|
dst->vram64, src->vram64);
|
|
return false;
|
|
}
|
|
|
|
if (src->vgamem != dst->vgamem) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target video card vgamem %u does not match source %u"),
|
|
dst->vgamem, src->vgamem);
|
|
return false;
|
|
}
|
|
|
|
if (src->heads != dst->heads) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target video card heads %u does not match source %u"),
|
|
dst->heads, src->heads);
|
|
return false;
|
|
}
|
|
|
|
if ((src->accel && !dst->accel) ||
|
|
(!src->accel && dst->accel)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target video card acceleration does not match source"));
|
|
return false;
|
|
}
|
|
|
|
if (src->accel) {
|
|
if (src->accel->accel2d != dst->accel->accel2d) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target video card 2d accel %u does not match source %u"),
|
|
dst->accel->accel2d, src->accel->accel2d);
|
|
return false;
|
|
}
|
|
|
|
if (src->accel->accel3d != dst->accel->accel3d) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target video card 3d accel %u does not match source %u"),
|
|
dst->accel->accel3d, src->accel->accel3d);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainHostdevDefCheckABIStability(virDomainHostdevDef *src,
|
|
virDomainHostdevDef *dst)
|
|
{
|
|
if (src->mode != dst->mode) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target host device mode %s does not match source %s"),
|
|
virDomainHostdevModeTypeToString(dst->mode),
|
|
virDomainHostdevModeTypeToString(src->mode));
|
|
return false;
|
|
}
|
|
|
|
if (src->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
|
|
src->source.subsys.type != dst->source.subsys.type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target host device subsystem %s does not match source %s"),
|
|
virDomainHostdevSubsysTypeToString(dst->source.subsys.type),
|
|
virDomainHostdevSubsysTypeToString(src->source.subsys.type));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(src->info, dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainSmartcardDefCheckABIStability(virDomainSmartcardDef *src,
|
|
virDomainSmartcardDef *dst)
|
|
{
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainSerialDefCheckABIStability(virDomainChrDef *src,
|
|
virDomainChrDef *dst)
|
|
{
|
|
if (src->targetType != dst->targetType) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target serial type %s does not match source %s"),
|
|
virDomainChrSerialTargetTypeToString(dst->targetType),
|
|
virDomainChrSerialTargetTypeToString(src->targetType));
|
|
return false;
|
|
}
|
|
|
|
if (src->target.port != dst->target.port) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target serial port %d does not match source %d"),
|
|
dst->target.port, src->target.port);
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainParallelDefCheckABIStability(virDomainChrDef *src,
|
|
virDomainChrDef *dst)
|
|
{
|
|
if (src->target.port != dst->target.port) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target parallel port %d does not match source %d"),
|
|
dst->target.port, src->target.port);
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainChannelDefCheckABIStability(virDomainChrDef *src,
|
|
virDomainChrDef *dst)
|
|
{
|
|
if (src->targetType != dst->targetType) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target channel type %s does not match source %s"),
|
|
virDomainChrChannelTargetTypeToString(dst->targetType),
|
|
virDomainChrChannelTargetTypeToString(src->targetType));
|
|
return false;
|
|
}
|
|
|
|
switch (src->targetType) {
|
|
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XEN:
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO:
|
|
if (STRNEQ_NULLABLE(src->target.name, dst->target.name)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target channel name %s does not match source %s"),
|
|
NULLSTR(dst->target.name), NULLSTR(src->target.name));
|
|
return false;
|
|
}
|
|
if (src->source->type != dst->source->type &&
|
|
(src->source->type == VIR_DOMAIN_CHR_TYPE_SPICEVMC ||
|
|
dst->source->type == VIR_DOMAIN_CHR_TYPE_SPICEVMC) &&
|
|
!src->target.name) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Changing device type to/from spicevmc would"
|
|
" change default target channel name"));
|
|
return false;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD:
|
|
if (memcmp(src->target.addr, dst->target.addr,
|
|
sizeof(*src->target.addr)) != 0) {
|
|
g_autofree char *saddr = virSocketAddrFormatFull(src->target.addr, true, ":");
|
|
g_autofree char *daddr = virSocketAddrFormatFull(dst->target.addr, true, ":");
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target channel addr %s does not match source %s"),
|
|
NULLSTR(daddr), NULLSTR(saddr));
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainConsoleDefCheckABIStability(virDomainChrDef *src,
|
|
virDomainChrDef *dst)
|
|
{
|
|
if (src->targetType != dst->targetType) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target console type %s does not match source %s"),
|
|
virDomainChrConsoleTargetTypeToString(dst->targetType),
|
|
virDomainChrConsoleTargetTypeToString(src->targetType));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainWatchdogDefCheckABIStability(virDomainWatchdogDef *src,
|
|
virDomainWatchdogDef *dst)
|
|
{
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target watchdog model %s does not match source %s"),
|
|
virDomainWatchdogModelTypeToString(dst->model),
|
|
virDomainWatchdogModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainMemballoonDefCheckABIStability(virDomainMemballoonDef *src,
|
|
virDomainMemballoonDef *dst)
|
|
{
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target balloon model %s does not match source %s"),
|
|
virDomainMemballoonModelTypeToString(dst->model),
|
|
virDomainMemballoonModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (src->autodeflate != dst->autodeflate) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target balloon autodeflate attribute value "
|
|
"'%s' does not match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->autodeflate),
|
|
virTristateSwitchTypeToString(src->autodeflate));
|
|
return false;
|
|
}
|
|
|
|
if (src->free_page_reporting != dst->free_page_reporting) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target balloon freePageReporting attribute value "
|
|
"'%s' does not match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->free_page_reporting),
|
|
virTristateSwitchTypeToString(src->free_page_reporting));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainRNGDefCheckABIStability(virDomainRNGDef *src,
|
|
virDomainRNGDef *dst)
|
|
{
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target RNG model '%s' does not match source '%s'"),
|
|
virDomainRNGModelTypeToString(dst->model),
|
|
virDomainRNGModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainHubDefCheckABIStability(virDomainHubDef *src,
|
|
virDomainHubDef *dst)
|
|
{
|
|
if (src->type != dst->type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target hub device type %s does not match source %s"),
|
|
virDomainHubTypeToString(dst->type),
|
|
virDomainHubTypeToString(src->type));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainRedirdevDefCheckABIStability(virDomainRedirdevDef *src,
|
|
virDomainRedirdevDef *dst)
|
|
{
|
|
if (src->bus != dst->bus) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target redirected device bus %s does not match "
|
|
"source %s"),
|
|
virDomainRedirdevBusTypeToString(dst->bus),
|
|
virDomainRedirdevBusTypeToString(src->bus));
|
|
return false;
|
|
}
|
|
|
|
switch ((virDomainRedirdevBus) src->bus) {
|
|
case VIR_DOMAIN_REDIRDEV_BUS_USB:
|
|
if (src->source->type != dst->source->type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target redirected device source type %s does "
|
|
"not match source device source type %s"),
|
|
virDomainChrTypeToString(dst->source->type),
|
|
virDomainChrTypeToString(src->source->type));
|
|
return false;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_REDIRDEV_BUS_LAST:
|
|
break;
|
|
}
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainRedirFilterDefCheckABIStability(virDomainRedirFilterDef *src,
|
|
virDomainRedirFilterDef *dst)
|
|
{
|
|
size_t i;
|
|
|
|
if (src->nusbdevs != dst->nusbdevs) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target USB redirection filter rule "
|
|
"count %zu does not match source %zu"),
|
|
dst->nusbdevs, src->nusbdevs);
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < src->nusbdevs; i++) {
|
|
virDomainRedirFilterUSBDevDef *srcUSBDev = src->usbdevs[i];
|
|
virDomainRedirFilterUSBDevDef *dstUSBDev = dst->usbdevs[i];
|
|
if (srcUSBDev->usbClass != dstUSBDev->usbClass) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("Target USB Class code does not match source"));
|
|
return false;
|
|
}
|
|
|
|
if (srcUSBDev->vendor != dstUSBDev->vendor) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("Target USB vendor ID does not match source"));
|
|
return false;
|
|
}
|
|
|
|
if (srcUSBDev->product != dstUSBDev->product) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("Target USB product ID does not match source"));
|
|
return false;
|
|
}
|
|
|
|
if (srcUSBDev->version != dstUSBDev->version) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
"%s", _("Target USB version does not match source"));
|
|
return false;
|
|
}
|
|
|
|
if (srcUSBDev->allow != dstUSBDev->allow) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target USB allow '%s' does not match source '%s'"),
|
|
dstUSBDev->allow ? "yes" : "no",
|
|
srcUSBDev->allow ? "yes" : "no");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainDefFeaturesCheckABIStability(virDomainDef *src,
|
|
virDomainDef *dst)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < VIR_DOMAIN_FEATURE_LAST; i++) {
|
|
const char *featureName = virDomainFeatureTypeToString(i);
|
|
|
|
switch ((virDomainFeature) i) {
|
|
case VIR_DOMAIN_FEATURE_ACPI:
|
|
case VIR_DOMAIN_FEATURE_PAE:
|
|
case VIR_DOMAIN_FEATURE_HAP:
|
|
case VIR_DOMAIN_FEATURE_VIRIDIAN:
|
|
case VIR_DOMAIN_FEATURE_PRIVNET:
|
|
case VIR_DOMAIN_FEATURE_HYPERV:
|
|
case VIR_DOMAIN_FEATURE_KVM:
|
|
case VIR_DOMAIN_FEATURE_XEN:
|
|
case VIR_DOMAIN_FEATURE_PVSPINLOCK:
|
|
case VIR_DOMAIN_FEATURE_PMU:
|
|
case VIR_DOMAIN_FEATURE_VMPORT:
|
|
case VIR_DOMAIN_FEATURE_SMM:
|
|
case VIR_DOMAIN_FEATURE_VMCOREINFO:
|
|
case VIR_DOMAIN_FEATURE_HTM:
|
|
case VIR_DOMAIN_FEATURE_NESTED_HV:
|
|
case VIR_DOMAIN_FEATURE_CCF_ASSIST:
|
|
if (src->features[i] != dst->features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s', destination: '%s'"),
|
|
featureName,
|
|
virTristateSwitchTypeToString(src->features[i]),
|
|
virTristateSwitchTypeToString(dst->features[i]));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_CAPABILITIES:
|
|
if (src->features[i] != dst->features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s=%s', destination: '%s=%s'"),
|
|
featureName,
|
|
"policy",
|
|
virDomainCapabilitiesPolicyTypeToString(src->features[i]),
|
|
"policy",
|
|
virDomainCapabilitiesPolicyTypeToString(dst->features[i]));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_GIC:
|
|
if (src->features[i] != dst->features[i] ||
|
|
src->gic_version != dst->gic_version) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s,%s=%s', destination: '%s,%s=%s'"),
|
|
featureName,
|
|
virTristateSwitchTypeToString(src->features[i]),
|
|
"version", virGICVersionTypeToString(src->gic_version),
|
|
virTristateSwitchTypeToString(dst->features[i]),
|
|
"version", virGICVersionTypeToString(dst->gic_version));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_HPT:
|
|
if (src->features[i] != dst->features[i] ||
|
|
src->hpt_resizing != dst->hpt_resizing ||
|
|
src->hpt_maxpagesize != dst->hpt_maxpagesize) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s,%s=%s,%s=%llu', destination: '%s,%s=%s,%s=%llu'"),
|
|
featureName,
|
|
virTristateSwitchTypeToString(src->features[i]),
|
|
"resizing", virDomainHPTResizingTypeToString(src->hpt_resizing),
|
|
"maxpagesize", src->hpt_maxpagesize,
|
|
virTristateSwitchTypeToString(dst->features[i]),
|
|
"resizing", virDomainHPTResizingTypeToString(dst->hpt_resizing),
|
|
"maxpagesize", dst->hpt_maxpagesize);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_APIC:
|
|
if (src->features[i] != dst->features[i] ||
|
|
src->apic_eoi != dst->apic_eoi) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s,%s=%s', destination: '%s,%s=%s'"),
|
|
featureName,
|
|
virTristateSwitchTypeToString(src->features[i]),
|
|
"eoi", virTristateSwitchTypeToString(src->apic_eoi),
|
|
virTristateSwitchTypeToString(dst->features[i]),
|
|
"eoi", virTristateSwitchTypeToString(dst->apic_eoi));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_IOAPIC:
|
|
if (src->features[i] != dst->features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s=%s', destination: '%s=%s'"),
|
|
featureName,
|
|
"driver", virDomainIOAPICTypeToString(src->features[i]),
|
|
"driver", virDomainIOAPICTypeToString(dst->features[i]));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_CFPC:
|
|
if (src->features[i] != dst->features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s=%s', destination: '%s=%s'"),
|
|
featureName,
|
|
"value", virDomainCFPCTypeToString(src->features[i]),
|
|
"value", virDomainCFPCTypeToString(dst->features[i]));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_SBBC:
|
|
if (src->features[i] != dst->features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s=%s', destination: '%s=%s'"),
|
|
featureName,
|
|
"value", virDomainSBBCTypeToString(src->features[i]),
|
|
"value", virDomainSBBCTypeToString(dst->features[i]));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_IBS:
|
|
if (src->features[i] != dst->features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of feature '%s' differs: "
|
|
"source: '%s=%s', destination: '%s=%s'"),
|
|
featureName,
|
|
"value", virDomainIBSTypeToString(src->features[i]),
|
|
"value", virDomainIBSTypeToString(dst->features[i]));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_MSRS:
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* hyperv */
|
|
if (src->features[VIR_DOMAIN_FEATURE_HYPERV] == VIR_TRISTATE_SWITCH_ON) {
|
|
for (i = 0; i < VIR_DOMAIN_HYPERV_LAST; i++) {
|
|
switch ((virDomainHyperv) i) {
|
|
case VIR_DOMAIN_HYPERV_RELAXED:
|
|
case VIR_DOMAIN_HYPERV_VAPIC:
|
|
case VIR_DOMAIN_HYPERV_VPINDEX:
|
|
case VIR_DOMAIN_HYPERV_RUNTIME:
|
|
case VIR_DOMAIN_HYPERV_SYNIC:
|
|
case VIR_DOMAIN_HYPERV_STIMER:
|
|
case VIR_DOMAIN_HYPERV_RESET:
|
|
case VIR_DOMAIN_HYPERV_FREQUENCIES:
|
|
case VIR_DOMAIN_HYPERV_REENLIGHTENMENT:
|
|
case VIR_DOMAIN_HYPERV_TLBFLUSH:
|
|
case VIR_DOMAIN_HYPERV_IPI:
|
|
case VIR_DOMAIN_HYPERV_EVMCS:
|
|
if (src->hyperv_features[i] != dst->hyperv_features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of HyperV enlightenment "
|
|
"feature '%s' differs: "
|
|
"source: '%s', destination: '%s'"),
|
|
virDomainHypervTypeToString(i),
|
|
virTristateSwitchTypeToString(src->hyperv_features[i]),
|
|
virTristateSwitchTypeToString(dst->hyperv_features[i]));
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_SPINLOCKS:
|
|
/* spinlock count matters! */
|
|
if (src->hyperv_spinlocks != dst->hyperv_spinlocks) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("HyperV spinlock retry count differs: "
|
|
"source: '%u', destination: '%u'"),
|
|
src->hyperv_spinlocks,
|
|
dst->hyperv_spinlocks);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_VENDOR_ID:
|
|
if (STRNEQ_NULLABLE(src->hyperv_vendor_id, dst->hyperv_vendor_id)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("HyperV vendor_id differs: "
|
|
"source: '%s', destination: '%s'"),
|
|
src->hyperv_vendor_id,
|
|
dst->hyperv_vendor_id);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_LAST:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (src->hyperv_features[VIR_DOMAIN_HYPERV_STIMER] == VIR_TRISTATE_SWITCH_ON) {
|
|
if (src->hyperv_stimer_direct != dst->hyperv_stimer_direct) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of HyperV stimer direct feature differs: "
|
|
"source: '%s', destination: '%s'"),
|
|
virTristateSwitchTypeToString(src->hyperv_stimer_direct),
|
|
virTristateSwitchTypeToString(dst->hyperv_stimer_direct));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* xen */
|
|
if (src->features[VIR_DOMAIN_FEATURE_XEN] == VIR_TRISTATE_SWITCH_ON) {
|
|
for (i = 0; i < VIR_DOMAIN_XEN_LAST; i++) {
|
|
if (src->xen_features[i] != dst->xen_features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of Xen feature '%s' differs: "
|
|
"source: '%s', destination: '%s'"),
|
|
virDomainXenTypeToString(i),
|
|
virTristateSwitchTypeToString(src->xen_features[i]),
|
|
virTristateSwitchTypeToString(dst->xen_features[i]));
|
|
return false;
|
|
}
|
|
switch ((virDomainXen) i) {
|
|
case VIR_DOMAIN_XEN_E820_HOST:
|
|
break;
|
|
|
|
case VIR_DOMAIN_XEN_PASSTHROUGH:
|
|
if (src->xen_passthrough_mode != dst->xen_passthrough_mode) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("'mode' of Xen passthrough feature differs: "
|
|
"source: '%s', destination: '%s'"),
|
|
virDomainXenPassthroughModeTypeToString(src->xen_passthrough_mode),
|
|
virDomainXenPassthroughModeTypeToString(dst->xen_passthrough_mode));
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_XEN_LAST:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* kvm */
|
|
if (src->features[VIR_DOMAIN_FEATURE_KVM] == VIR_TRISTATE_SWITCH_ON) {
|
|
for (i = 0; i < VIR_DOMAIN_KVM_LAST; i++) {
|
|
switch ((virDomainKVM) i) {
|
|
case VIR_DOMAIN_KVM_HIDDEN:
|
|
case VIR_DOMAIN_KVM_DEDICATED:
|
|
case VIR_DOMAIN_KVM_POLLCONTROL:
|
|
if (src->kvm_features[i] != dst->kvm_features[i]) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of KVM feature '%s' differs: "
|
|
"source: '%s', destination: '%s'"),
|
|
virDomainKVMTypeToString(i),
|
|
virTristateSwitchTypeToString(src->kvm_features[i]),
|
|
virTristateSwitchTypeToString(dst->kvm_features[i]));
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_KVM_LAST:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* smm */
|
|
if (src->features[VIR_DOMAIN_FEATURE_SMM] == VIR_TRISTATE_SWITCH_ON) {
|
|
if (src->tseg_specified != dst->tseg_specified) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("SMM TSEG differs: source: %s, destination: '%s'"),
|
|
src->tseg_specified ? _("specified") : _("not specified"),
|
|
dst->tseg_specified ? _("specified") : _("not specified"));
|
|
return false;
|
|
}
|
|
|
|
if (src->tseg_specified &&
|
|
src->tseg_size != dst->tseg_size) {
|
|
const char *unit_src, *unit_dst;
|
|
unsigned long long short_size_src = virFormatIntPretty(src->tseg_size,
|
|
&unit_src);
|
|
unsigned long long short_size_dst = virFormatIntPretty(dst->tseg_size,
|
|
&unit_dst);
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Size of SMM TSEG size differs: "
|
|
"source: '%llu %s', destination: '%llu %s'"),
|
|
short_size_src, unit_src,
|
|
short_size_dst, unit_dst);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
virDomainPanicDefCheckABIStability(virDomainPanicDef *src,
|
|
virDomainPanicDef *dst)
|
|
{
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target panic model '%s' does not match source '%s'"),
|
|
virDomainPanicModelTypeToString(dst->model),
|
|
virDomainPanicModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
return virDomainDeviceInfoCheckABIStability(&src->info, &dst->info);
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainShmemDefCheckABIStability(virDomainShmemDef *src,
|
|
virDomainShmemDef *dst)
|
|
{
|
|
if (src->role != dst->role) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target shared memory role '%s' does not match "
|
|
"source role '%s'"),
|
|
virDomainShmemRoleTypeToString(dst->role),
|
|
virDomainShmemRoleTypeToString(src->role));
|
|
return false;
|
|
}
|
|
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target shared memory model '%s' does not match "
|
|
"source model '%s'"),
|
|
virDomainShmemModelTypeToString(dst->model),
|
|
virDomainShmemModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (src->size != dst->size) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target shared memory size '%llu' does not match "
|
|
"source size '%llu'"), dst->size, src->size);
|
|
return false;
|
|
}
|
|
|
|
if (src->server.enabled != dst->server.enabled) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target shared memory server usage doesn't match "
|
|
"source"));
|
|
return false;
|
|
}
|
|
|
|
if (src->msi.vectors != dst->msi.vectors ||
|
|
src->msi.enabled != dst->msi.enabled ||
|
|
src->msi.ioeventfd != dst->msi.ioeventfd) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target shared memory MSI configuration doesn't match "
|
|
"source"));
|
|
return false;
|
|
}
|
|
|
|
return virDomainDeviceInfoCheckABIStability(&src->info, &dst->info);
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainTPMDefCheckABIStability(virDomainTPMDef *src,
|
|
virDomainTPMDef *dst)
|
|
{
|
|
if (src->type != dst->type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target TPM device type doesn't match source"));
|
|
return false;
|
|
}
|
|
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target TPM device model doesn't match source"));
|
|
return false;
|
|
}
|
|
|
|
if (src->version != dst->version) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target TPM version doesn't match source"));
|
|
return false;
|
|
}
|
|
|
|
return virDomainDeviceInfoCheckABIStability(&src->info, &dst->info);
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainMemtuneCheckABIStability(const virDomainDef *src,
|
|
const virDomainDef *dst,
|
|
unsigned int flags)
|
|
{
|
|
if (virDomainDefGetMemoryInitial(src) != virDomainDefGetMemoryInitial(dst)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain max memory %lld "
|
|
"does not match source %lld"),
|
|
virDomainDefGetMemoryInitial(dst),
|
|
virDomainDefGetMemoryInitial(src));
|
|
return false;
|
|
}
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_ABI_CHECK_SKIP_VOLATILE) &&
|
|
src->mem.cur_balloon != dst->mem.cur_balloon) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain current memory %lld "
|
|
"does not match source %lld"),
|
|
dst->mem.cur_balloon,
|
|
src->mem.cur_balloon);
|
|
return false;
|
|
}
|
|
|
|
if (src->mem.max_memory != dst->mem.max_memory) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target maximum memory size '%llu' "
|
|
"doesn't match source '%llu'"),
|
|
dst->mem.max_memory,
|
|
src->mem.max_memory);
|
|
return false;
|
|
}
|
|
|
|
if (src->mem.memory_slots != dst->mem.memory_slots) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain memory slots "
|
|
"count '%u' doesn't match source '%u'"),
|
|
dst->mem.memory_slots,
|
|
src->mem.memory_slots);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainMemoryDefCheckABIStability(virDomainMemoryDef *src,
|
|
virDomainMemoryDef *dst)
|
|
{
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target memory device model '%s' "
|
|
"doesn't match source model '%s'"),
|
|
virDomainMemoryModelTypeToString(dst->model),
|
|
virDomainMemoryModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (src->targetNode != dst->targetNode) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target memory device targetNode '%d' "
|
|
"doesn't match source targetNode '%d'"),
|
|
dst->targetNode, src->targetNode);
|
|
return false;
|
|
}
|
|
|
|
if (src->size != dst->size) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target memory device size '%llu' doesn't match "
|
|
"source memory device size '%llu'"),
|
|
dst->size, src->size);
|
|
return false;
|
|
}
|
|
|
|
if (src->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM) {
|
|
if (src->labelsize != dst->labelsize) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target NVDIMM label size '%llu' doesn't match "
|
|
"source NVDIMM label size '%llu'"),
|
|
src->labelsize, dst->labelsize);
|
|
return false;
|
|
}
|
|
|
|
if (src->alignsize != dst->alignsize) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target NVDIMM alignment '%llu' doesn't match "
|
|
"source NVDIMM alignment '%llu'"),
|
|
src->alignsize, dst->alignsize);
|
|
return false;
|
|
}
|
|
|
|
if (src->nvdimmPmem != dst->nvdimmPmem) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target NVDIMM pmem flag doesn't match "
|
|
"source NVDIMM pmem flag"));
|
|
return false;
|
|
}
|
|
|
|
if (src->readonly != dst->readonly) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target NVDIMM readonly flag doesn't match "
|
|
"source NVDIMM readonly flag"));
|
|
return false;
|
|
}
|
|
|
|
if ((src->uuid || dst->uuid) &&
|
|
!(src->uuid && dst->uuid &&
|
|
memcmp(src->uuid, dst->uuid, VIR_UUID_BUFLEN) == 0)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target NVDIMM UUID doesn't match source NVDIMM"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return virDomainDeviceInfoCheckABIStability(&src->info, &dst->info);
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainIOMMUDefCheckABIStability(virDomainIOMMUDef *src,
|
|
virDomainIOMMUDef *dst)
|
|
{
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain IOMMU device model '%s' "
|
|
"does not match source '%s'"),
|
|
virDomainIOMMUModelTypeToString(dst->model),
|
|
virDomainIOMMUModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
if (src->intremap != dst->intremap) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain IOMMU device intremap value '%s' "
|
|
"does not match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->intremap),
|
|
virTristateSwitchTypeToString(src->intremap));
|
|
return false;
|
|
}
|
|
if (src->caching_mode != dst->caching_mode) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain IOMMU device caching mode '%s' "
|
|
"does not match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->caching_mode),
|
|
virTristateSwitchTypeToString(src->caching_mode));
|
|
return false;
|
|
}
|
|
if (src->eim != dst->eim) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain IOMMU device eim value '%s' "
|
|
"does not match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->eim),
|
|
virTristateSwitchTypeToString(src->eim));
|
|
return false;
|
|
}
|
|
if (src->iotlb != dst->iotlb) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain IOMMU device iotlb value '%s' "
|
|
"does not match source '%s'"),
|
|
virTristateSwitchTypeToString(dst->iotlb),
|
|
virTristateSwitchTypeToString(src->iotlb));
|
|
return false;
|
|
}
|
|
if (src->aw_bits != dst->aw_bits) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain IOMMU device aw_bits value '%d' "
|
|
"does not match source '%d'"),
|
|
dst->aw_bits, src->aw_bits);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainVsockDefCheckABIStability(virDomainVsockDef *src,
|
|
virDomainVsockDef *dst)
|
|
{
|
|
if (src->model != dst->model) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain vsock device model '%s' "
|
|
"does not match source '%s'"),
|
|
virDomainVsockModelTypeToString(dst->model),
|
|
virDomainVsockModelTypeToString(src->model));
|
|
return false;
|
|
}
|
|
|
|
if (!virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
|
|
return false;
|
|
|
|
if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainDefVcpuCheckAbiStability(virDomainDef *src,
|
|
virDomainDef *dst)
|
|
{
|
|
size_t i;
|
|
|
|
if (src->maxvcpus != dst->maxvcpus) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain vCPU max %zu does not match source %zu"),
|
|
dst->maxvcpus, src->maxvcpus);
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < src->maxvcpus; i++) {
|
|
virDomainVcpuDef *svcpu = src->vcpus[i];
|
|
virDomainVcpuDef *dvcpu = dst->vcpus[i];
|
|
|
|
if (svcpu->online != dvcpu->online) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("State of vCPU '%zu' differs between source and "
|
|
"destination definitions"), i);
|
|
return false;
|
|
}
|
|
|
|
if (svcpu->order != dvcpu->order) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("vcpu enable order of vCPU '%zu' differs between "
|
|
"source and destination definitions"), i);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* This compares two configurations and looks for any differences
|
|
* which will affect the guest ABI. This is primarily to allow
|
|
* validation of custom XML config passed in during migration
|
|
*/
|
|
bool
|
|
virDomainDefCheckABIStabilityFlags(virDomainDef *src,
|
|
virDomainDef *dst,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
size_t i;
|
|
virErrorPtr err;
|
|
g_autofree char *strSrc = NULL;
|
|
g_autofree char *strDst = NULL;
|
|
|
|
if (src->virtType != dst->virtType) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain virt type %s does not match source %s"),
|
|
virDomainVirtTypeToString(dst->virtType),
|
|
virDomainVirtTypeToString(src->virtType));
|
|
goto error;
|
|
}
|
|
|
|
if (memcmp(src->uuid, dst->uuid, VIR_UUID_BUFLEN) != 0) {
|
|
char uuidsrc[VIR_UUID_STRING_BUFLEN];
|
|
char uuiddst[VIR_UUID_STRING_BUFLEN];
|
|
virUUIDFormat(src->uuid, uuidsrc);
|
|
virUUIDFormat(dst->uuid, uuiddst);
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain uuid %s does not match source %s"),
|
|
uuiddst, uuidsrc);
|
|
goto error;
|
|
}
|
|
|
|
if (src->genidRequested != dst->genidRequested) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target domain requested genid does not match source"));
|
|
goto error;
|
|
}
|
|
|
|
if (src->genidRequested &&
|
|
memcmp(src->genid, dst->genid, VIR_UUID_BUFLEN) != 0) {
|
|
char guidsrc[VIR_UUID_STRING_BUFLEN];
|
|
char guiddst[VIR_UUID_STRING_BUFLEN];
|
|
|
|
virUUIDFormat(src->genid, guidsrc);
|
|
virUUIDFormat(dst->genid, guiddst);
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain genid %s does not match source %s"),
|
|
guiddst, guidsrc);
|
|
goto error;
|
|
}
|
|
|
|
/* Not strictly ABI related, but we want to make sure domains
|
|
* don't get silently re-named through the backdoor when passing
|
|
* custom XML into various APIs, since this would create havoc
|
|
*/
|
|
if (STRNEQ_NULLABLE(src->name, dst->name)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain name '%s' does not match source '%s'"),
|
|
dst->name, src->name);
|
|
goto error;
|
|
}
|
|
|
|
if (!virDomainMemtuneCheckABIStability(src, dst, flags))
|
|
goto error;
|
|
|
|
if (!virDomainNumaCheckABIStability(src->numa, dst->numa))
|
|
goto error;
|
|
|
|
if (!virDomainDefVcpuCheckAbiStability(src, dst))
|
|
goto error;
|
|
|
|
if (src->os.type != dst->os.type) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain OS type %s does not match source %s"),
|
|
virDomainOSTypeToString(dst->os.type),
|
|
virDomainOSTypeToString(src->os.type));
|
|
goto error;
|
|
}
|
|
if (src->os.arch != dst->os.arch) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain architecture %s does not match source %s"),
|
|
virArchToString(dst->os.arch),
|
|
virArchToString(src->os.arch));
|
|
goto error;
|
|
}
|
|
if (STRNEQ_NULLABLE(src->os.machine, dst->os.machine)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain machine type %s does not match source %s"),
|
|
dst->os.machine, src->os.machine);
|
|
goto error;
|
|
}
|
|
|
|
if (src->os.smbios_mode != dst->os.smbios_mode) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain SMBIOS mode %s does not match source %s"),
|
|
virDomainSmbiosModeTypeToString(dst->os.smbios_mode),
|
|
virDomainSmbiosModeTypeToString(src->os.smbios_mode));
|
|
goto error;
|
|
}
|
|
|
|
if (!virDomainDefFeaturesCheckABIStability(src, dst))
|
|
goto error;
|
|
|
|
if (src->clock.ntimers != dst->clock.ntimers) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target domain timers do not match source"));
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->clock.ntimers; i++) {
|
|
if (!virDomainTimerDefCheckABIStability(src->clock.timers[i],
|
|
dst->clock.timers[i]))
|
|
goto error;
|
|
}
|
|
|
|
if (!virCPUDefIsEqual(src->cpu, dst->cpu, true))
|
|
goto error;
|
|
|
|
if (src->nsysinfo != dst->nsysinfo) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target domain count of sysinfo does not match source"));
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nsysinfo; i++) {
|
|
if (!virSysinfoIsEqual(src->sysinfo[i], dst->sysinfo[i]))
|
|
goto error;
|
|
}
|
|
|
|
if (src->ndisks != dst->ndisks) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain disk count %zu does not match source %zu"),
|
|
dst->ndisks, src->ndisks);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->ndisks; i++)
|
|
if (!virDomainDiskDefCheckABIStability(src->disks[i], dst->disks[i]))
|
|
goto error;
|
|
|
|
if (src->ncontrollers != dst->ncontrollers) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain controller count %zu "
|
|
"does not match source %zu"),
|
|
dst->ncontrollers, src->ncontrollers);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->ncontrollers; i++)
|
|
if (!virDomainControllerDefCheckABIStability(src->controllers[i],
|
|
dst->controllers[i]))
|
|
goto error;
|
|
|
|
if (src->nfss != dst->nfss) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain filesystem count %zu "
|
|
"does not match source %zu"),
|
|
dst->nfss, src->nfss);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nfss; i++)
|
|
if (!virDomainFsDefCheckABIStability(src->fss[i], dst->fss[i]))
|
|
goto error;
|
|
|
|
if (src->nnets != dst->nnets) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain net card count %zu "
|
|
"does not match source %zu"),
|
|
dst->nnets, src->nnets);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nnets; i++)
|
|
if (!virDomainNetDefCheckABIStability(src->nets[i], dst->nets[i]))
|
|
goto error;
|
|
|
|
if (src->ninputs != dst->ninputs) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain input device count %zu "
|
|
"does not match source %zu"),
|
|
dst->ninputs, src->ninputs);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->ninputs; i++)
|
|
if (!virDomainInputDefCheckABIStability(src->inputs[i], dst->inputs[i]))
|
|
goto error;
|
|
|
|
if (src->nsounds != dst->nsounds) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain sound card count %zu "
|
|
"does not match source %zu"),
|
|
dst->nsounds, src->nsounds);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nsounds; i++)
|
|
if (!virDomainSoundDefCheckABIStability(src->sounds[i], dst->sounds[i]))
|
|
goto error;
|
|
|
|
if (src->nvideos != dst->nvideos) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain video card count %zu "
|
|
"does not match source %zu"),
|
|
dst->nvideos, src->nvideos);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nvideos; i++)
|
|
if (!virDomainVideoDefCheckABIStability(src->videos[i], dst->videos[i]))
|
|
goto error;
|
|
|
|
if (src->nhostdevs != dst->nhostdevs) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain host device count %zu "
|
|
"does not match source %zu"),
|
|
dst->nhostdevs, src->nhostdevs);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nhostdevs; i++)
|
|
if (!virDomainHostdevDefCheckABIStability(src->hostdevs[i],
|
|
dst->hostdevs[i]))
|
|
goto error;
|
|
|
|
if (src->nsmartcards != dst->nsmartcards) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain smartcard count %zu "
|
|
"does not match source %zu"),
|
|
dst->nsmartcards, src->nsmartcards);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nsmartcards; i++)
|
|
if (!virDomainSmartcardDefCheckABIStability(src->smartcards[i],
|
|
dst->smartcards[i]))
|
|
goto error;
|
|
|
|
if (src->nserials != dst->nserials) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain serial port count %zu "
|
|
"does not match source %zu"),
|
|
dst->nserials, src->nserials);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nserials; i++)
|
|
if (!virDomainSerialDefCheckABIStability(src->serials[i],
|
|
dst->serials[i]))
|
|
goto error;
|
|
|
|
if (src->nparallels != dst->nparallels) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain parallel port count %zu "
|
|
"does not match source %zu"),
|
|
dst->nparallels, src->nparallels);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nparallels; i++)
|
|
if (!virDomainParallelDefCheckABIStability(src->parallels[i],
|
|
dst->parallels[i]))
|
|
goto error;
|
|
|
|
if (src->nchannels != dst->nchannels) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain channel count %zu "
|
|
"does not match source %zu"),
|
|
dst->nchannels, src->nchannels);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nchannels; i++)
|
|
if (!virDomainChannelDefCheckABIStability(src->channels[i],
|
|
dst->channels[i]))
|
|
goto error;
|
|
|
|
if (src->nconsoles != dst->nconsoles) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain console count %zu "
|
|
"does not match source %zu"),
|
|
dst->nconsoles, src->nconsoles);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nconsoles; i++)
|
|
if (!virDomainConsoleDefCheckABIStability(src->consoles[i],
|
|
dst->consoles[i]))
|
|
goto error;
|
|
|
|
if (src->nhubs != dst->nhubs) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain hub device count %zu "
|
|
"does not match source %zu"),
|
|
dst->nhubs, src->nhubs);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nhubs; i++)
|
|
if (!virDomainHubDefCheckABIStability(src->hubs[i], dst->hubs[i]))
|
|
goto error;
|
|
|
|
if (src->nredirdevs != dst->nredirdevs) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain redirected devices count %zu "
|
|
"does not match source %zu"),
|
|
dst->nredirdevs, src->nredirdevs);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nredirdevs; i++) {
|
|
if (!virDomainRedirdevDefCheckABIStability(src->redirdevs[i],
|
|
dst->redirdevs[i]))
|
|
goto error;
|
|
}
|
|
|
|
if ((!src->redirfilter && dst->redirfilter) ||
|
|
(src->redirfilter && !dst->redirfilter)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain USB redirection filter count %d "
|
|
"does not match source %d"),
|
|
dst->redirfilter ? 1 : 0, src->redirfilter ? 1 : 0);
|
|
goto error;
|
|
}
|
|
|
|
if (src->redirfilter &&
|
|
!virDomainRedirFilterDefCheckABIStability(src->redirfilter,
|
|
dst->redirfilter))
|
|
goto error;
|
|
|
|
if ((!src->watchdog && dst->watchdog) ||
|
|
(src->watchdog && !dst->watchdog)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain watchdog count %d "
|
|
"does not match source %d"),
|
|
dst->watchdog ? 1 : 0, src->watchdog ? 1 : 0);
|
|
goto error;
|
|
}
|
|
|
|
if (src->watchdog &&
|
|
!virDomainWatchdogDefCheckABIStability(src->watchdog, dst->watchdog))
|
|
goto error;
|
|
|
|
if ((!src->memballoon && dst->memballoon) ||
|
|
(src->memballoon && !dst->memballoon)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain memory balloon count %d "
|
|
"does not match source %d"),
|
|
dst->memballoon ? 1 : 0, src->memballoon ? 1 : 0);
|
|
goto error;
|
|
}
|
|
|
|
if (src->memballoon &&
|
|
!virDomainMemballoonDefCheckABIStability(src->memballoon,
|
|
dst->memballoon))
|
|
goto error;
|
|
|
|
if (src->nrngs != dst->nrngs) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain RNG device count %zu "
|
|
"does not match source %zu"), dst->nrngs, src->nrngs);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nrngs; i++)
|
|
if (!virDomainRNGDefCheckABIStability(src->rngs[i], dst->rngs[i]))
|
|
goto error;
|
|
|
|
if (src->npanics != dst->npanics) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain panic device count %zu "
|
|
"does not match source %zu"), dst->npanics, src->npanics);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->npanics; i++) {
|
|
if (!virDomainPanicDefCheckABIStability(src->panics[i], dst->panics[i]))
|
|
goto error;
|
|
}
|
|
|
|
if (src->nshmems != dst->nshmems) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain shared memory device count %zu "
|
|
"does not match source %zu"), dst->nshmems, src->nshmems);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nshmems; i++) {
|
|
if (!virDomainShmemDefCheckABIStability(src->shmems[i], dst->shmems[i]))
|
|
goto error;
|
|
}
|
|
|
|
if (src->ntpms != dst->ntpms) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain TPM device count %zu "
|
|
"does not match source %zu"),
|
|
dst->ntpms, src->ntpms);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->ntpms; i++) {
|
|
if (!virDomainTPMDefCheckABIStability(src->tpms[i], dst->tpms[i]))
|
|
goto error;
|
|
}
|
|
|
|
if (src->nmems != dst->nmems) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Target domain memory device count %zu "
|
|
"does not match source %zu"), dst->nmems, src->nmems);
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < src->nmems; i++) {
|
|
if (!virDomainMemoryDefCheckABIStability(src->mems[i], dst->mems[i]))
|
|
goto error;
|
|
}
|
|
|
|
if (!!src->iommu != !!dst->iommu) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target domain IOMMU device count "
|
|
"does not match source"));
|
|
goto error;
|
|
}
|
|
|
|
if (src->iommu &&
|
|
!virDomainIOMMUDefCheckABIStability(src->iommu, dst->iommu))
|
|
goto error;
|
|
|
|
if (!!src->vsock != !!dst->vsock) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Target domain vsock device count "
|
|
"does not match source"));
|
|
goto error;
|
|
}
|
|
|
|
if (src->vsock &&
|
|
!virDomainVsockDefCheckABIStability(src->vsock, dst->vsock))
|
|
goto error;
|
|
|
|
if (xmlopt && xmlopt->abi.domain &&
|
|
!xmlopt->abi.domain(src, dst))
|
|
goto error;
|
|
|
|
/* Coverity is not very happy with this - all dead_error_condition */
|
|
#if !STATIC_ANALYSIS
|
|
/* This switch statement is here to trigger compiler warning when adding
|
|
* a new device type. When you are adding a new field to the switch you
|
|
* also have to add an check above. Otherwise the switch statement has no
|
|
* real function here and should be optimized out by the compiler. */
|
|
i = VIR_DOMAIN_DEVICE_LAST;
|
|
switch ((virDomainDeviceType) i) {
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
case VIR_DOMAIN_DEVICE_MEMBALLOON:
|
|
case VIR_DOMAIN_DEVICE_NVRAM:
|
|
case VIR_DOMAIN_DEVICE_LAST:
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
case VIR_DOMAIN_DEVICE_TPM:
|
|
case VIR_DOMAIN_DEVICE_PANIC:
|
|
case VIR_DOMAIN_DEVICE_SHMEM:
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
case VIR_DOMAIN_DEVICE_AUDIO:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
|
|
error:
|
|
virErrorPreserveLast(&err);
|
|
|
|
strSrc = virDomainDefFormat(src, xmlopt, 0);
|
|
strDst = virDomainDefFormat(dst, xmlopt, 0);
|
|
VIR_DEBUG("XMLs that failed stability check were: src=\"%s\", dst=\"%s\"",
|
|
NULLSTR(strSrc), NULLSTR(strDst));
|
|
|
|
virErrorRestore(&err);
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefCheckABIStability(virDomainDef *src,
|
|
virDomainDef *dst,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
return virDomainDefCheckABIStabilityFlags(src, dst, xmlopt, 0);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefAddDiskControllersForType(virDomainDef *def,
|
|
int controllerType,
|
|
int diskBus)
|
|
{
|
|
size_t i;
|
|
int maxController = -1;
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
if (def->disks[i]->bus != diskBus)
|
|
continue;
|
|
|
|
if (def->disks[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
|
|
continue;
|
|
|
|
if ((int)def->disks[i]->info.addr.drive.controller > maxController)
|
|
maxController = def->disks[i]->info.addr.drive.controller;
|
|
}
|
|
|
|
if (maxController == -1)
|
|
return 0;
|
|
|
|
for (i = 0; i <= maxController; i++) {
|
|
if (virDomainDefMaybeAddController(def, controllerType, i, -1) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefMaybeAddVirtioSerialController(virDomainDef *def)
|
|
{
|
|
/* Look for any virtio serial or virtio console devs */
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nchannels; i++) {
|
|
virDomainChrDef *channel = def->channels[i];
|
|
|
|
if (channel->targetType == VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO) {
|
|
int idx = 0;
|
|
if (channel->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL)
|
|
idx = channel->info.addr.vioserial.controller;
|
|
|
|
if (virDomainDefMaybeAddController(def,
|
|
VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL, idx, -1) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < def->nconsoles; i++) {
|
|
virDomainChrDef *console = def->consoles[i];
|
|
|
|
if (console->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_VIRTIO) {
|
|
int idx = 0;
|
|
if (console->info.type ==
|
|
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL)
|
|
idx = console->info.addr.vioserial.controller;
|
|
|
|
if (virDomainDefMaybeAddController(def,
|
|
VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL, idx, -1) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefMaybeAddSmartcardController(virDomainDef *def)
|
|
{
|
|
/* Look for any smartcard devs */
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nsmartcards; i++) {
|
|
virDomainSmartcardDef *smartcard = def->smartcards[i];
|
|
int idx = 0;
|
|
|
|
if (smartcard->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID) {
|
|
idx = smartcard->info.addr.ccid.controller;
|
|
} else if (smartcard->info.type
|
|
== VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
|
|
size_t j;
|
|
int max = -1;
|
|
|
|
for (j = 0; j < def->nsmartcards; j++) {
|
|
virDomainDeviceInfo *info = &def->smartcards[j]->info;
|
|
if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID &&
|
|
info->addr.ccid.controller == 0 &&
|
|
(int) info->addr.ccid.slot > max)
|
|
max = info->addr.ccid.slot;
|
|
}
|
|
smartcard->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID;
|
|
smartcard->info.addr.ccid.controller = 0;
|
|
smartcard->info.addr.ccid.slot = max + 1;
|
|
}
|
|
|
|
if (virDomainDefMaybeAddController(def,
|
|
VIR_DOMAIN_CONTROLLER_TYPE_CCID,
|
|
idx, -1) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Based on the declared <address/> info for any devices,
|
|
* add necessary drive controllers which are not already present
|
|
* in the XML. This is for compat with existing apps which will
|
|
* not know/care about <controller> info in the XML
|
|
*/
|
|
static int
|
|
virDomainDefAddImplicitControllers(virDomainDef *def)
|
|
{
|
|
if (virDomainDefAddDiskControllersForType(def,
|
|
VIR_DOMAIN_CONTROLLER_TYPE_SCSI,
|
|
VIR_DOMAIN_DISK_BUS_SCSI) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefAddDiskControllersForType(def,
|
|
VIR_DOMAIN_CONTROLLER_TYPE_FDC,
|
|
VIR_DOMAIN_DISK_BUS_FDC) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefAddDiskControllersForType(def,
|
|
VIR_DOMAIN_CONTROLLER_TYPE_IDE,
|
|
VIR_DOMAIN_DISK_BUS_IDE) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefAddDiskControllersForType(def,
|
|
VIR_DOMAIN_CONTROLLER_TYPE_SATA,
|
|
VIR_DOMAIN_DISK_BUS_SATA) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefMaybeAddVirtioSerialController(def) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefMaybeAddSmartcardController(def) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefMaybeAddHostdevSCSIcontroller(def) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainDefAddImplicitVideo(virDomainDef *def, virDomainXMLOption *xmlopt)
|
|
{
|
|
g_autoptr(virDomainVideoDef) video = NULL;
|
|
|
|
/* For backwards compatibility, if no <video> tag is set but there
|
|
* is a <graphics> tag, then we add a single video tag */
|
|
if (def->ngraphics == 0 || def->nvideos > 0)
|
|
return 0;
|
|
|
|
if (!(video = virDomainVideoDefNew(xmlopt)))
|
|
return -1;
|
|
video->type = VIR_DOMAIN_VIDEO_TYPE_DEFAULT;
|
|
if (VIR_APPEND_ELEMENT(def->videos, def->nvideos, video) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virDomainDefAddImplicitDevices(virDomainDef *def, virDomainXMLOption *xmlopt)
|
|
{
|
|
if (virDomainDefAddConsoleCompat(def) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefAddImplicitControllers(def) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefAddImplicitVideo(def, xmlopt) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
virDomainIOThreadIDDef *
|
|
virDomainIOThreadIDFind(const virDomainDef *def,
|
|
unsigned int iothread_id)
|
|
{
|
|
size_t i;
|
|
|
|
if (!def->iothreadids || !def->niothreadids)
|
|
return NULL;
|
|
|
|
for (i = 0; i < def->niothreadids; i++) {
|
|
if (iothread_id == def->iothreadids[i]->iothread_id)
|
|
return def->iothreadids[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
virDomainIOThreadIDDef *
|
|
virDomainIOThreadIDAdd(virDomainDef *def,
|
|
unsigned int iothread_id)
|
|
{
|
|
virDomainIOThreadIDDef *iothrid = NULL;
|
|
|
|
iothrid = g_new0(virDomainIOThreadIDDef, 1);
|
|
|
|
iothrid->iothread_id = iothread_id;
|
|
|
|
if (VIR_APPEND_ELEMENT_COPY(def->iothreadids, def->niothreadids,
|
|
iothrid) < 0)
|
|
goto error;
|
|
|
|
return iothrid;
|
|
|
|
error:
|
|
virDomainIOThreadIDDefFree(iothrid);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainIOThreadIDDel(virDomainDef *def,
|
|
unsigned int iothread_id)
|
|
{
|
|
size_t i, j;
|
|
|
|
for (i = 0; i < def->niothreadids; i++) {
|
|
if (def->iothreadids[i]->iothread_id == iothread_id) {
|
|
/* If we were sequential and removed a threadid in the
|
|
* beginning or middle of the list, then unconditionally
|
|
* clear the autofill flag so we don't lose these
|
|
* definitions for XML formatting.
|
|
*/
|
|
for (j = i + 1; j < def->niothreadids; j++)
|
|
def->iothreadids[j]->autofill = false;
|
|
|
|
virDomainIOThreadIDDefFree(def->iothreadids[i]);
|
|
VIR_DELETE_ELEMENT(def->iothreadids, i, def->niothreadids);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainEventActionDefFormat(virBuffer *buf,
|
|
int type,
|
|
const char *name,
|
|
virEventActionToStringFunc convFunc)
|
|
{
|
|
const char *typeStr = convFunc(type);
|
|
if (!typeStr) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected %s action: %d"), name, type);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<%s>%s</%s>\n", name, typeStr, name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virSecurityLabelDefFormat(virBuffer *buf,
|
|
virSecurityLabelDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *sectype = virDomainSeclabelTypeToString(def->type);
|
|
|
|
if (!sectype)
|
|
return;
|
|
|
|
if (def->type == VIR_DOMAIN_SECLABEL_DEFAULT)
|
|
return;
|
|
|
|
/* libvirt versions prior to 0.10.0 support just a single seclabel element
|
|
* in the XML, and that would typically be filled with type=selinux.
|
|
* Don't format it in the MIGRATABLE case, for backwards compatibility
|
|
*/
|
|
if ((STREQ_NULLABLE(def->model, "dac") ||
|
|
STREQ_NULLABLE(def->model, "none")) && def->implicit &&
|
|
(flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE))
|
|
return;
|
|
|
|
virBufferAsprintf(buf, "<seclabel type='%s'",
|
|
sectype);
|
|
|
|
virBufferEscapeString(buf, " model='%s'", def->model);
|
|
|
|
if (def->type == VIR_DOMAIN_SECLABEL_NONE) {
|
|
virBufferAddLit(buf, "/>\n");
|
|
return;
|
|
}
|
|
|
|
virBufferAsprintf(buf, " relabel='%s'",
|
|
def->relabel ? "yes" : "no");
|
|
|
|
if (def->label || def->imagelabel || def->baselabel) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferEscapeString(buf, "<label>%s</label>\n",
|
|
def->label);
|
|
if (def->relabel)
|
|
virBufferEscapeString(buf, "<imagelabel>%s</imagelabel>\n",
|
|
def->imagelabel);
|
|
if (def->type == VIR_DOMAIN_SECLABEL_DYNAMIC)
|
|
virBufferEscapeString(buf, "<baselabel>%s</baselabel>\n",
|
|
def->baselabel);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</seclabel>\n");
|
|
} else {
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
virSecurityDeviceLabelDefFormat(virBuffer *buf,
|
|
virSecurityDeviceLabelDef *def,
|
|
unsigned int flags)
|
|
{
|
|
/* For offline output, skip elements that allow labels but have no
|
|
* label specified (possible if labelskip was ignored on input). */
|
|
if ((flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE) && !def->label && def->relabel)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "<seclabel");
|
|
|
|
if (def->model)
|
|
virBufferEscapeString(buf, " model='%s'", def->model);
|
|
|
|
if (def->labelskip)
|
|
virBufferAddLit(buf, " labelskip='yes'");
|
|
else
|
|
virBufferAsprintf(buf, " relabel='%s'", def->relabel ? "yes" : "no");
|
|
|
|
if (def->label) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferEscapeString(buf, "<label>%s</label>\n",
|
|
def->label);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</seclabel>\n");
|
|
} else {
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainLeaseDefFormat(virBuffer *buf,
|
|
virDomainLeaseDef *def)
|
|
{
|
|
virBufferAddLit(buf, "<lease>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferEscapeString(buf, "<lockspace>%s</lockspace>\n", def->lockspace);
|
|
virBufferEscapeString(buf, "<key>%s</key>\n", def->key);
|
|
virBufferEscapeString(buf, "<target path='%s'", def->path);
|
|
if (def->offset)
|
|
virBufferAsprintf(buf, " offset='%llu'", def->offset);
|
|
virBufferAddLit(buf, "/>\n");
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</lease>\n");
|
|
}
|
|
|
|
static void
|
|
virDomainDiskGeometryDefFormat(virBuffer *buf,
|
|
virDomainDiskDef *def)
|
|
{
|
|
const char *trans =
|
|
virDomainDiskGeometryTransTypeToString(def->geometry.trans);
|
|
|
|
if (def->geometry.cylinders > 0 &&
|
|
def->geometry.heads > 0 &&
|
|
def->geometry.sectors > 0) {
|
|
virBufferAsprintf(buf,
|
|
"<geometry cyls='%u' heads='%u' secs='%u'",
|
|
def->geometry.cylinders,
|
|
def->geometry.heads,
|
|
def->geometry.sectors);
|
|
|
|
if (def->geometry.trans != VIR_DOMAIN_DISK_TRANS_DEFAULT)
|
|
virBufferEscapeString(buf, " trans='%s'", trans);
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
virDomainDiskBlockIoDefFormat(virBuffer *buf,
|
|
virDomainDiskDef *def)
|
|
{
|
|
if (def->blockio.logical_block_size > 0 ||
|
|
def->blockio.physical_block_size > 0) {
|
|
virBufferAddLit(buf, "<blockio");
|
|
if (def->blockio.logical_block_size > 0) {
|
|
virBufferAsprintf(buf,
|
|
" logical_block_size='%u'",
|
|
def->blockio.logical_block_size);
|
|
}
|
|
if (def->blockio.physical_block_size > 0) {
|
|
virBufferAsprintf(buf,
|
|
" physical_block_size='%u'",
|
|
def->blockio.physical_block_size);
|
|
}
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainSourceDefFormatSeclabel(virBuffer *buf,
|
|
size_t nseclabels,
|
|
virSecurityDeviceLabelDef **seclabels,
|
|
unsigned int flags)
|
|
{
|
|
size_t n;
|
|
|
|
for (n = 0; n < nseclabels; n++)
|
|
virSecurityDeviceLabelDefFormat(buf, seclabels[n], flags);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainDiskSourceFormatNetworkCookies(virBuffer *buf,
|
|
virStorageSource *src,
|
|
unsigned int flags)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
size_t i;
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_FORMAT_SECURE))
|
|
return;
|
|
|
|
for (i = 0; i < src->ncookies; i++) {
|
|
virBufferEscapeString(&childBuf, "<cookie name='%s'>", src->cookies[i]->name);
|
|
virBufferEscapeString(&childBuf, "%s</cookie>\n", src->cookies[i]->value);
|
|
}
|
|
|
|
virXMLFormatElement(buf, "cookies", NULL, &childBuf);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainDiskSourceFormatNetwork(virBuffer *attrBuf,
|
|
virBuffer *childBuf,
|
|
virStorageSource *src,
|
|
unsigned int flags)
|
|
{
|
|
size_t n;
|
|
g_autofree char *path = NULL;
|
|
|
|
virBufferAsprintf(attrBuf, " protocol='%s'",
|
|
virStorageNetProtocolTypeToString(src->protocol));
|
|
|
|
if (src->volume)
|
|
path = g_strdup_printf("%s/%s", src->volume, src->path);
|
|
|
|
virBufferEscapeString(attrBuf, " name='%s'", path ? path : src->path);
|
|
virBufferEscapeString(attrBuf, " query='%s'", src->query);
|
|
|
|
if (src->haveTLS != VIR_TRISTATE_BOOL_ABSENT &&
|
|
!(flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE &&
|
|
src->tlsFromConfig))
|
|
virBufferAsprintf(attrBuf, " tls='%s'",
|
|
virTristateBoolTypeToString(src->haveTLS));
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_STATUS)
|
|
virBufferAsprintf(attrBuf, " tlsFromConfig='%d'", src->tlsFromConfig);
|
|
|
|
for (n = 0; n < src->nhosts; n++) {
|
|
virBufferAddLit(childBuf, "<host");
|
|
virBufferEscapeString(childBuf, " name='%s'", src->hosts[n].name);
|
|
|
|
if (src->hosts[n].port)
|
|
virBufferAsprintf(childBuf, " port='%u'", src->hosts[n].port);
|
|
|
|
if (src->hosts[n].transport)
|
|
virBufferAsprintf(childBuf, " transport='%s'",
|
|
virStorageNetHostTransportTypeToString(src->hosts[n].transport));
|
|
|
|
virBufferEscapeString(childBuf, " socket='%s'", src->hosts[n].socket);
|
|
virBufferAddLit(childBuf, "/>\n");
|
|
}
|
|
|
|
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_NFS &&
|
|
(src->nfs_user || src->nfs_group)) {
|
|
virBufferAddLit(childBuf, "<identity");
|
|
|
|
virBufferEscapeString(childBuf, " user='%s'", src->nfs_user);
|
|
virBufferEscapeString(childBuf, " group='%s'", src->nfs_group);
|
|
|
|
virBufferAddLit(childBuf, "/>\n");
|
|
}
|
|
|
|
|
|
virBufferEscapeString(childBuf, "<snapshot name='%s'/>\n", src->snapshot);
|
|
virBufferEscapeString(childBuf, "<config file='%s'/>\n", src->configFile);
|
|
|
|
virStorageSourceInitiatorFormatXML(&src->initiator, childBuf);
|
|
|
|
if (src->sslverify != VIR_TRISTATE_BOOL_ABSENT) {
|
|
virBufferAsprintf(childBuf, "<ssl verify='%s'/>\n",
|
|
virTristateBoolTypeToString(src->sslverify));
|
|
}
|
|
|
|
virDomainDiskSourceFormatNetworkCookies(childBuf, src, flags);
|
|
|
|
if (src->readahead)
|
|
virBufferAsprintf(childBuf, "<readahead size='%llu'/>\n", src->readahead);
|
|
|
|
if (src->timeout)
|
|
virBufferAsprintf(childBuf, "<timeout seconds='%llu'/>\n", src->timeout);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainDiskSourceNVMeFormat(virBuffer *attrBuf,
|
|
virBuffer *childBuf,
|
|
const virStorageSourceNVMeDef *nvme)
|
|
{
|
|
virBufferAddLit(attrBuf, " type='pci'");
|
|
if (nvme->managed != VIR_TRISTATE_BOOL_ABSENT)
|
|
virBufferAsprintf(attrBuf, " managed='%s'",
|
|
virTristateBoolTypeToString(nvme->managed));
|
|
virBufferAsprintf(attrBuf, " namespace='%llu'", nvme->namespc);
|
|
virPCIDeviceAddressFormat(childBuf, nvme->pciAddr, false);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainChrSourceReconnectDefFormat(virBuffer *buf,
|
|
virDomainChrSourceReconnectDef *def);
|
|
|
|
|
|
static void
|
|
virDomainDiskSourceVhostuserFormat(virBuffer *attrBuf,
|
|
virBuffer *childBuf,
|
|
virDomainChrSourceDef *vhostuser)
|
|
{
|
|
virBufferAddLit(attrBuf, " type='unix'");
|
|
virBufferAsprintf(attrBuf, " path='%s'", vhostuser->data.nix.path);
|
|
|
|
virDomainChrSourceReconnectDefFormat(childBuf, &vhostuser->data.nix.reconnect);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskSourceFormatPrivateData(virBuffer *buf,
|
|
virStorageSource *src,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_FORMAT_STATUS) ||
|
|
!xmlopt || !xmlopt->privateData.storageFormat)
|
|
return 0;
|
|
|
|
if (xmlopt->privateData.storageFormat(src, &childBuf) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, "privateData", NULL, &childBuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainDiskSourceFormatSlice(virBuffer *buf,
|
|
const char *slicetype,
|
|
virStorageSourceSlice *slice)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (!slice)
|
|
return;
|
|
|
|
virBufferAsprintf(&attrBuf, " type='%s'", slicetype);
|
|
virBufferAsprintf(&attrBuf, " offset='%llu'", slice->offset);
|
|
virBufferAsprintf(&attrBuf, " size='%llu'", slice->size);
|
|
|
|
virXMLFormatElement(buf, "slice", &attrBuf, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainDiskSourceFormatSlices(virBuffer *buf,
|
|
virStorageSource *src)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
virDomainDiskSourceFormatSlice(&childBuf, "storage", src->sliceStorage);
|
|
|
|
virXMLFormatElement(buf, "slices", NULL, &childBuf);
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDiskSourceFormat:
|
|
* @buf: output buffer
|
|
* @src: storage source definition to format
|
|
* @element: name to use for the top-level element (often "source")
|
|
* @policy: startup policy attribute value, if necessary
|
|
* @attrIndex: the 'index' attribute of <source> is formatted if true
|
|
* @flags: XML formatter flags
|
|
* @skipAuth: Skip formatting of <auth>
|
|
* @skipEnc: Skip formatting of <encryption>
|
|
* regardless of the original definition state
|
|
* @xmlopt: XML formatter callbacks
|
|
*
|
|
* Formats @src into a <source> element. Note that this doesn't format the
|
|
* 'type' and 'format' properties of @src.
|
|
*/
|
|
int
|
|
virDomainDiskSourceFormat(virBuffer *buf,
|
|
virStorageSource *src,
|
|
const char *element,
|
|
int policy,
|
|
bool attrIndex,
|
|
unsigned int flags,
|
|
bool skipAuth,
|
|
bool skipEnc,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
switch ((virStorageType)src->type) {
|
|
case VIR_STORAGE_TYPE_FILE:
|
|
virBufferEscapeString(&attrBuf, " file='%s'", src->path);
|
|
break;
|
|
|
|
case VIR_STORAGE_TYPE_BLOCK:
|
|
virBufferEscapeString(&attrBuf, " dev='%s'", src->path);
|
|
break;
|
|
|
|
case VIR_STORAGE_TYPE_DIR:
|
|
virBufferEscapeString(&attrBuf, " dir='%s'", src->path);
|
|
break;
|
|
|
|
case VIR_STORAGE_TYPE_NETWORK:
|
|
virDomainDiskSourceFormatNetwork(&attrBuf, &childBuf, src, flags);
|
|
break;
|
|
|
|
case VIR_STORAGE_TYPE_VOLUME:
|
|
if (src->srcpool) {
|
|
virBufferEscapeString(&attrBuf, " pool='%s'", src->srcpool->pool);
|
|
virBufferEscapeString(&attrBuf, " volume='%s'",
|
|
src->srcpool->volume);
|
|
if (src->srcpool->mode)
|
|
virBufferAsprintf(&attrBuf, " mode='%s'",
|
|
virStorageSourcePoolModeTypeToString(src->srcpool->mode));
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_STORAGE_TYPE_NVME:
|
|
virDomainDiskSourceNVMeFormat(&attrBuf, &childBuf, src->nvme);
|
|
break;
|
|
|
|
case VIR_STORAGE_TYPE_VHOST_USER:
|
|
virDomainDiskSourceVhostuserFormat(&attrBuf, &childBuf, src->vhostuser);
|
|
break;
|
|
|
|
case VIR_STORAGE_TYPE_NONE:
|
|
case VIR_STORAGE_TYPE_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected disk type %d"), src->type);
|
|
return -1;
|
|
}
|
|
|
|
virDomainDiskSourceFormatSlices(&childBuf, src);
|
|
|
|
if (src->type != VIR_STORAGE_TYPE_NETWORK)
|
|
virDomainSourceDefFormatSeclabel(&childBuf, src->nseclabels,
|
|
src->seclabels, flags);
|
|
|
|
/* Storage Source formatting will not carry through the blunder
|
|
* that disk source formatting had at one time to format the
|
|
* <auth> for a volume source type. The <auth> information is
|
|
* kept in the storage pool and would be overwritten anyway.
|
|
* So avoid formatting it for volumes. */
|
|
if (src->auth && !skipAuth && src->type != VIR_STORAGE_TYPE_VOLUME)
|
|
virStorageAuthDefFormat(&childBuf, src->auth);
|
|
|
|
if (src->encryption && !skipEnc &&
|
|
virStorageEncryptionFormat(&childBuf, src->encryption) < 0)
|
|
return -1;
|
|
|
|
if (src->pr)
|
|
virStoragePRDefFormat(&childBuf, src->pr,
|
|
flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE);
|
|
if (policy && src->type != VIR_STORAGE_TYPE_NETWORK)
|
|
virBufferEscapeString(&attrBuf, " startupPolicy='%s'",
|
|
virDomainStartupPolicyTypeToString(policy));
|
|
|
|
if (attrIndex && src->id != 0)
|
|
virBufferAsprintf(&attrBuf, " index='%u'", src->id);
|
|
|
|
if (virDomainDiskSourceFormatPrivateData(&childBuf, src, flags, xmlopt) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, element, &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDiskBackingStoreFormat(virBuffer *buf,
|
|
virStorageSource *src,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) formatAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) formatChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf);
|
|
bool inactive = flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE;
|
|
virStorageSource *backingStore = src->backingStore;
|
|
|
|
if (!backingStore)
|
|
return 0;
|
|
|
|
/* don't write detected backing chain members to inactive xml */
|
|
if (inactive && backingStore->detected)
|
|
return 0;
|
|
|
|
if (backingStore->type == VIR_STORAGE_TYPE_NONE) {
|
|
virBufferAddLit(buf, "<backingStore/>\n");
|
|
return 0;
|
|
}
|
|
|
|
if (backingStore->format <= 0 || backingStore->format >= VIR_STORAGE_FILE_LAST) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected disk backing store format %d"),
|
|
backingStore->format);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(&attrBuf, " type='%s'",
|
|
virStorageTypeToString(backingStore->type));
|
|
if (backingStore->id != 0)
|
|
virBufferAsprintf(&attrBuf, " index='%u'", backingStore->id);
|
|
|
|
virBufferAsprintf(&formatAttrBuf, " type='%s'",
|
|
virStorageFileFormatTypeToString(backingStore->format));
|
|
|
|
if (backingStore->metadataCacheMaxSize > 0) {
|
|
g_auto(virBuffer) metadataCacheChildBuf = VIR_BUFFER_INIT_CHILD(&formatChildBuf);
|
|
|
|
virBufferAsprintf(&metadataCacheChildBuf,
|
|
"<max_size unit='bytes'>%llu</max_size>\n",
|
|
backingStore->metadataCacheMaxSize);
|
|
|
|
virXMLFormatElement(&formatChildBuf, "metadata_cache", NULL, &metadataCacheChildBuf);
|
|
}
|
|
|
|
virXMLFormatElement(&childBuf, "format", &formatAttrBuf, &formatChildBuf);
|
|
|
|
|
|
if (virDomainDiskSourceFormat(&childBuf, backingStore, "source", 0, false,
|
|
flags, false, false, xmlopt) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDiskBackingStoreFormat(&childBuf, backingStore, xmlopt, flags) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, "backingStore", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define FORMAT_IOTUNE(val) \
|
|
if (disk->blkdeviotune.val) { \
|
|
virBufferAsprintf(&childBuf, "<" #val ">%llu</" #val ">\n", \
|
|
disk->blkdeviotune.val); \
|
|
}
|
|
|
|
static void
|
|
virDomainDiskDefFormatIotune(virBuffer *buf,
|
|
virDomainDiskDef *disk)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
FORMAT_IOTUNE(total_bytes_sec);
|
|
FORMAT_IOTUNE(read_bytes_sec);
|
|
FORMAT_IOTUNE(write_bytes_sec);
|
|
FORMAT_IOTUNE(total_iops_sec);
|
|
FORMAT_IOTUNE(read_iops_sec);
|
|
FORMAT_IOTUNE(write_iops_sec);
|
|
|
|
FORMAT_IOTUNE(total_bytes_sec_max);
|
|
FORMAT_IOTUNE(read_bytes_sec_max);
|
|
FORMAT_IOTUNE(write_bytes_sec_max);
|
|
FORMAT_IOTUNE(total_iops_sec_max);
|
|
FORMAT_IOTUNE(read_iops_sec_max);
|
|
FORMAT_IOTUNE(write_iops_sec_max);
|
|
|
|
if (disk->blkdeviotune.size_iops_sec) {
|
|
virBufferAsprintf(&childBuf, "<size_iops_sec>%llu</size_iops_sec>\n",
|
|
disk->blkdeviotune.size_iops_sec);
|
|
}
|
|
|
|
if (disk->blkdeviotune.group_name) {
|
|
virBufferEscapeString(&childBuf, "<group_name>%s</group_name>\n",
|
|
disk->blkdeviotune.group_name);
|
|
}
|
|
|
|
FORMAT_IOTUNE(total_bytes_sec_max_length);
|
|
FORMAT_IOTUNE(read_bytes_sec_max_length);
|
|
FORMAT_IOTUNE(write_bytes_sec_max_length);
|
|
FORMAT_IOTUNE(total_iops_sec_max_length);
|
|
FORMAT_IOTUNE(read_iops_sec_max_length);
|
|
FORMAT_IOTUNE(write_iops_sec_max_length);
|
|
|
|
virXMLFormatElement(buf, "iotune", NULL, &childBuf);
|
|
}
|
|
|
|
#undef FORMAT_IOTUNE
|
|
|
|
|
|
static void
|
|
virDomainDiskDefFormatDriver(virBuffer *buf,
|
|
virDomainDiskDef *disk)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
virBufferEscapeString(&attrBuf, " name='%s'", virDomainDiskGetDriver(disk));
|
|
|
|
if (disk->src->format > 0)
|
|
virBufferAsprintf(&attrBuf, " type='%s'",
|
|
virStorageFileFormatTypeToString(disk->src->format));
|
|
|
|
if (disk->cachemode)
|
|
virBufferAsprintf(&attrBuf, " cache='%s'",
|
|
virDomainDiskCacheTypeToString(disk->cachemode));
|
|
|
|
if (disk->error_policy)
|
|
virBufferAsprintf(&attrBuf, " error_policy='%s'",
|
|
virDomainDiskErrorPolicyTypeToString(disk->error_policy));
|
|
|
|
if (disk->rerror_policy)
|
|
virBufferAsprintf(&attrBuf, " rerror_policy='%s'",
|
|
virDomainDiskErrorPolicyTypeToString(disk->rerror_policy));
|
|
|
|
if (disk->iomode)
|
|
virBufferAsprintf(&attrBuf, " io='%s'",
|
|
virDomainDiskIoTypeToString(disk->iomode));
|
|
|
|
if (disk->ioeventfd)
|
|
virBufferAsprintf(&attrBuf, " ioeventfd='%s'",
|
|
virTristateSwitchTypeToString(disk->ioeventfd));
|
|
|
|
if (disk->event_idx)
|
|
virBufferAsprintf(&attrBuf, " event_idx='%s'",
|
|
virTristateSwitchTypeToString(disk->event_idx));
|
|
|
|
if (disk->copy_on_read)
|
|
virBufferAsprintf(&attrBuf, " copy_on_read='%s'",
|
|
virTristateSwitchTypeToString(disk->copy_on_read));
|
|
|
|
if (disk->discard)
|
|
virBufferAsprintf(&attrBuf, " discard='%s'",
|
|
virDomainDiskDiscardTypeToString(disk->discard));
|
|
|
|
if (disk->iothread)
|
|
virBufferAsprintf(&attrBuf, " iothread='%u'", disk->iothread);
|
|
|
|
if (disk->detect_zeroes)
|
|
virBufferAsprintf(&attrBuf, " detect_zeroes='%s'",
|
|
virDomainDiskDetectZeroesTypeToString(disk->detect_zeroes));
|
|
|
|
if (disk->queues)
|
|
virBufferAsprintf(&attrBuf, " queues='%u'", disk->queues);
|
|
|
|
virDomainVirtioOptionsFormat(&attrBuf, disk->virtio);
|
|
|
|
if (disk->src->metadataCacheMaxSize > 0) {
|
|
g_auto(virBuffer) metadataCacheChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf);
|
|
|
|
virBufferAsprintf(&metadataCacheChildBuf,
|
|
"<max_size unit='bytes'>%llu</max_size>\n",
|
|
disk->src->metadataCacheMaxSize);
|
|
|
|
virXMLFormatElement(&childBuf, "metadata_cache", NULL, &metadataCacheChildBuf);
|
|
}
|
|
|
|
virXMLFormatElement(buf, "driver", &attrBuf, &childBuf);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskDefFormatMirror(virBuffer *buf,
|
|
virDomainDiskDef *disk,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) formatAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) formatChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf);
|
|
const char *formatStr = NULL;
|
|
|
|
/* For now, mirroring is currently output-only: we only output it
|
|
* for live domains, therefore we ignore it on input except for
|
|
* the internal parse on libvirtd restart. We prefer to output
|
|
* the new style similar to backingStore, but for back-compat on
|
|
* blockcopy files we also have to output old style attributes.
|
|
* The parser accepts either style across libvirtd upgrades. */
|
|
|
|
if (!disk->mirror ||
|
|
(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE))
|
|
return 0;
|
|
|
|
if (disk->mirror->format)
|
|
formatStr = virStorageFileFormatTypeToString(disk->mirror->format);
|
|
virBufferAsprintf(&attrBuf, " type='%s'",
|
|
virStorageTypeToString(disk->mirror->type));
|
|
if (disk->mirror->type == VIR_STORAGE_TYPE_FILE &&
|
|
disk->mirrorJob == VIR_DOMAIN_BLOCK_JOB_TYPE_COPY) {
|
|
virBufferEscapeString(&attrBuf, " file='%s'", disk->mirror->path);
|
|
virBufferEscapeString(&attrBuf, " format='%s'", formatStr);
|
|
}
|
|
virBufferEscapeString(&attrBuf, " job='%s'",
|
|
virDomainBlockJobTypeToString(disk->mirrorJob));
|
|
if (disk->mirrorState)
|
|
virBufferEscapeString(&attrBuf, " ready='%s'",
|
|
virDomainDiskMirrorStateTypeToString(disk->mirrorState));
|
|
|
|
virBufferEscapeString(&formatAttrBuf, " type='%s'", formatStr);
|
|
if (disk->mirror->metadataCacheMaxSize > 0) {
|
|
g_auto(virBuffer) metadataCacheChildBuf = VIR_BUFFER_INIT_CHILD(&formatChildBuf);
|
|
|
|
virBufferAsprintf(&metadataCacheChildBuf,
|
|
"<max_size unit='bytes'>%llu</max_size>\n",
|
|
disk->mirror->metadataCacheMaxSize);
|
|
|
|
virXMLFormatElement(&formatChildBuf, "metadata_cache", NULL, &metadataCacheChildBuf);
|
|
}
|
|
|
|
virXMLFormatElement(&childBuf, "format", &formatAttrBuf, &formatChildBuf);
|
|
|
|
if (virDomainDiskSourceFormat(&childBuf, disk->mirror, "source", 0, true,
|
|
flags, false, false, xmlopt) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDiskBackingStoreFormat(&childBuf, disk->mirror, xmlopt, flags) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, "mirror", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskDefFormatPrivateData(virBuffer *buf,
|
|
virDomainDiskDef *disk,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_FORMAT_STATUS) ||
|
|
!xmlopt ||
|
|
!xmlopt->privateData.diskFormat)
|
|
return 0;
|
|
|
|
if (xmlopt->privateData.diskFormat(disk, &childBuf) < 0)
|
|
return -1;
|
|
|
|
virXMLFormatElement(buf, "privateData", NULL, &childBuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskDefFormat(virBuffer *buf,
|
|
virDomainDiskDef *def,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
const char *type = virStorageTypeToString(def->src->type);
|
|
const char *device = virDomainDiskDeviceTypeToString(def->device);
|
|
const char *bus = virDomainDiskBusTypeToString(def->bus);
|
|
const char *sgio = virDomainDeviceSGIOTypeToString(def->sgio);
|
|
|
|
if (!type || !def->src->type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected disk type %d"), def->src->type);
|
|
return -1;
|
|
}
|
|
if (!device) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected disk device %d"), def->device);
|
|
return -1;
|
|
}
|
|
if (!bus) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected disk bus %d"), def->bus);
|
|
return -1;
|
|
}
|
|
if (!sgio) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unexpected disk sgio mode '%d'"), def->sgio);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf,
|
|
"<disk type='%s' device='%s'",
|
|
type, device);
|
|
|
|
if (def->model) {
|
|
virBufferAsprintf(buf, " model='%s'",
|
|
virDomainDiskModelTypeToString(def->model));
|
|
}
|
|
|
|
if (def->rawio) {
|
|
virBufferAsprintf(buf, " rawio='%s'",
|
|
virTristateBoolTypeToString(def->rawio));
|
|
}
|
|
|
|
if (def->sgio)
|
|
virBufferAsprintf(buf, " sgio='%s'", sgio);
|
|
|
|
if (def->snapshot &&
|
|
!(def->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE &&
|
|
def->src->readonly))
|
|
virBufferAsprintf(buf, " snapshot='%s'",
|
|
virDomainSnapshotLocationTypeToString(def->snapshot));
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
virDomainDiskDefFormatDriver(buf, def);
|
|
|
|
/* Format as child of <disk> if defined there; otherwise,
|
|
* if defined as child of <source>, then format later */
|
|
if (def->src->auth && def->diskElementAuth)
|
|
virStorageAuthDefFormat(buf, def->src->auth);
|
|
|
|
if (virDomainDiskSourceFormat(buf, def->src, "source", def->startupPolicy,
|
|
true, flags,
|
|
def->diskElementAuth, def->diskElementEnc,
|
|
xmlopt) < 0)
|
|
return -1;
|
|
|
|
/* Don't format backingStore to inactive XMLs until the code for
|
|
* persistent storage of backing chains is ready. */
|
|
if (virDomainDiskBackingStoreFormat(buf, def->src, xmlopt, flags) < 0)
|
|
return -1;
|
|
|
|
virBufferEscapeString(buf, "<backenddomain name='%s'/>\n", def->domain_name);
|
|
|
|
virDomainDiskGeometryDefFormat(buf, def);
|
|
virDomainDiskBlockIoDefFormat(buf, def);
|
|
|
|
if (virDomainDiskDefFormatMirror(buf, def, flags, xmlopt) < 0)
|
|
return -1;
|
|
|
|
virBufferAsprintf(buf, "<target dev='%s' bus='%s'",
|
|
def->dst, bus);
|
|
if ((def->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY ||
|
|
def->device == VIR_DOMAIN_DISK_DEVICE_CDROM) &&
|
|
def->tray_status != VIR_DOMAIN_DISK_TRAY_CLOSED)
|
|
virBufferAsprintf(buf, " tray='%s'",
|
|
virDomainDiskTrayTypeToString(def->tray_status));
|
|
if (def->bus == VIR_DOMAIN_DISK_BUS_USB &&
|
|
def->removable != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(buf, " removable='%s'",
|
|
virTristateSwitchTypeToString(def->removable));
|
|
}
|
|
if (def->rotation_rate)
|
|
virBufferAsprintf(buf, " rotation_rate='%u'", def->rotation_rate);
|
|
virBufferAddLit(buf, "/>\n");
|
|
|
|
virDomainDiskDefFormatIotune(buf, def);
|
|
|
|
if (def->src->readonly)
|
|
virBufferAddLit(buf, "<readonly/>\n");
|
|
if (def->src->shared)
|
|
virBufferAddLit(buf, "<shareable/>\n");
|
|
if (def->transient) {
|
|
virBufferAddLit(buf, "<transient");
|
|
if (def->transientShareBacking == VIR_TRISTATE_BOOL_YES)
|
|
virBufferAddLit(buf, " shareBacking='yes'");
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
virBufferEscapeString(buf, "<serial>%s</serial>\n", def->serial);
|
|
virBufferEscapeString(buf, "<wwn>%s</wwn>\n", def->wwn);
|
|
virBufferEscapeString(buf, "<vendor>%s</vendor>\n", def->vendor);
|
|
virBufferEscapeString(buf, "<product>%s</product>\n", def->product);
|
|
|
|
/* If originally found as a child of <disk>, then format thusly;
|
|
* otherwise, will be formatted as child of <source> */
|
|
if (def->src->encryption && def->diskElementEnc &&
|
|
virStorageEncryptionFormat(buf, def->src->encryption) < 0)
|
|
return -1;
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags | VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT);
|
|
|
|
if (virDomainDiskDefFormatPrivateData(buf, def, flags, xmlopt) < 0)
|
|
return -1;
|
|
|
|
/* format diskElementAuth and diskElementEnc into status XML to preserve
|
|
* formatting */
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_STATUS) {
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (def->diskElementAuth)
|
|
virBufferAddLit(&attrBuf, " auth='true'");
|
|
if (def->diskElementEnc)
|
|
virBufferAddLit(&attrBuf, " enc='true'");
|
|
|
|
virXMLFormatElement(buf, "diskSecretsPlacement", &attrBuf, NULL);
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</disk>\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainControllerDriverFormat(virBuffer *buf,
|
|
virDomainControllerDef *def)
|
|
{
|
|
g_auto(virBuffer) driverBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (def->queues)
|
|
virBufferAsprintf(&driverBuf, " queues='%u'", def->queues);
|
|
|
|
if (def->cmd_per_lun)
|
|
virBufferAsprintf(&driverBuf, " cmd_per_lun='%u'", def->cmd_per_lun);
|
|
|
|
if (def->max_sectors)
|
|
virBufferAsprintf(&driverBuf, " max_sectors='%u'", def->max_sectors);
|
|
|
|
if (def->ioeventfd) {
|
|
virBufferAsprintf(&driverBuf, " ioeventfd='%s'",
|
|
virTristateSwitchTypeToString(def->ioeventfd));
|
|
}
|
|
|
|
if (def->iothread)
|
|
virBufferAsprintf(&driverBuf, " iothread='%u'", def->iothread);
|
|
|
|
virDomainVirtioOptionsFormat(&driverBuf, def->virtio);
|
|
|
|
virXMLFormatElement(buf, "driver", &driverBuf, NULL);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainControllerDefFormat(virBuffer *buf,
|
|
virDomainControllerDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *type = virDomainControllerTypeToString(def->type);
|
|
const char *model = NULL;
|
|
const char *modelName = NULL;
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected controller type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
if (def->model != -1) {
|
|
model = virDomainControllerModelTypeToString(def, def->model);
|
|
|
|
if (!model) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected model type %d"), def->model);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
virBufferAsprintf(&attrBuf,
|
|
" type='%s' index='%d'",
|
|
type, def->idx);
|
|
|
|
if (model)
|
|
virBufferEscapeString(&attrBuf, " model='%s'", model);
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL:
|
|
if (def->opts.vioserial.ports != -1) {
|
|
virBufferAsprintf(&attrBuf, " ports='%d'",
|
|
def->opts.vioserial.ports);
|
|
}
|
|
if (def->opts.vioserial.vectors != -1) {
|
|
virBufferAsprintf(&attrBuf, " vectors='%d'",
|
|
def->opts.vioserial.vectors);
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_USB:
|
|
if (def->opts.usbopts.ports != -1) {
|
|
virBufferAsprintf(&attrBuf, " ports='%d'",
|
|
def->opts.usbopts.ports);
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS:
|
|
if (def->opts.xenbusopts.maxGrantFrames != -1) {
|
|
virBufferAsprintf(&attrBuf, " maxGrantFrames='%d'",
|
|
def->opts.xenbusopts.maxGrantFrames);
|
|
}
|
|
if (def->opts.xenbusopts.maxEventChannels != -1) {
|
|
virBufferAsprintf(&attrBuf, " maxEventChannels='%d'",
|
|
def->opts.xenbusopts.maxEventChannels);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
|
|
bool formatModelName = true;
|
|
|
|
if (def->opts.pciopts.modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_NONE)
|
|
formatModelName = false;
|
|
|
|
/* Historically, libvirt didn't support specifying a model name for
|
|
* pci-root controllers; starting from 3.6.0, however, pSeries guests
|
|
* use pci-root controllers with model name spapr-pci-host-bridge to
|
|
* represent all PHBs, including the default one.
|
|
*
|
|
* In order to allow migration of pSeries guests from older libvirt
|
|
* versions and back, we don't format the model name in the migratable
|
|
* XML if it's spapr-pci-host-bridge, thus making "no model name" and
|
|
* "spapr-pci-host-bridge model name" basically equivalent.
|
|
*
|
|
* The spapr-pci-host-bridge device is specific to pSeries.
|
|
*/
|
|
if (def->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT &&
|
|
def->opts.pciopts.modelName == VIR_DOMAIN_CONTROLLER_PCI_MODEL_NAME_SPAPR_PCI_HOST_BRIDGE &&
|
|
flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE) {
|
|
formatModelName = false;
|
|
}
|
|
|
|
if (formatModelName) {
|
|
modelName = virDomainControllerPCIModelNameTypeToString(def->opts.pciopts.modelName);
|
|
if (!modelName) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected model name value %d"),
|
|
def->opts.pciopts.modelName);
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(&childBuf, "<model name='%s'/>\n", modelName);
|
|
}
|
|
|
|
if (def->opts.pciopts.chassisNr != -1 ||
|
|
def->opts.pciopts.chassis != -1 ||
|
|
def->opts.pciopts.port != -1 ||
|
|
def->opts.pciopts.busNr != -1 ||
|
|
def->opts.pciopts.targetIndex != -1 ||
|
|
def->opts.pciopts.numaNode != -1 ||
|
|
def->opts.pciopts.hotplug != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAddLit(&childBuf, "<target");
|
|
if (def->opts.pciopts.chassisNr != -1)
|
|
virBufferAsprintf(&childBuf, " chassisNr='%d'",
|
|
def->opts.pciopts.chassisNr);
|
|
if (def->opts.pciopts.chassis != -1)
|
|
virBufferAsprintf(&childBuf, " chassis='%d'",
|
|
def->opts.pciopts.chassis);
|
|
if (def->opts.pciopts.port != -1)
|
|
virBufferAsprintf(&childBuf, " port='0x%x'",
|
|
def->opts.pciopts.port);
|
|
if (def->opts.pciopts.busNr != -1)
|
|
virBufferAsprintf(&childBuf, " busNr='%d'",
|
|
def->opts.pciopts.busNr);
|
|
if (def->opts.pciopts.targetIndex != -1)
|
|
virBufferAsprintf(&childBuf, " index='%d'",
|
|
def->opts.pciopts.targetIndex);
|
|
if (def->opts.pciopts.hotplug != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(&childBuf, " hotplug='%s'",
|
|
virTristateSwitchTypeToString(def->opts.pciopts.hotplug));
|
|
}
|
|
if (def->opts.pciopts.numaNode == -1) {
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
} else {
|
|
virBufferAddLit(&childBuf, ">\n");
|
|
virBufferAdjustIndent(&childBuf, 2);
|
|
virBufferAsprintf(&childBuf, "<node>%d</node>\n",
|
|
def->opts.pciopts.numaNode);
|
|
virBufferAdjustIndent(&childBuf, -2);
|
|
virBufferAddLit(&childBuf, "</target>\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
virDomainControllerDriverFormat(&childBuf, def);
|
|
|
|
virDomainDeviceInfoFormat(&childBuf, &def->info, flags);
|
|
|
|
if (def->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI &&
|
|
def->opts.pciopts.pcihole64) {
|
|
virBufferAsprintf(&childBuf, "<pcihole64 unit='KiB'>%lu</"
|
|
"pcihole64>\n", def->opts.pciopts.pcihole64size);
|
|
}
|
|
|
|
virXMLFormatElement(buf, "controller", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainFSIndexByName(virDomainDef *def, const char *name)
|
|
{
|
|
virDomainFSDef *fs;
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nfss; i++) {
|
|
fs = def->fss[i];
|
|
if (STREQ(fs->dst, name))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainFSDefFormat(virBuffer *buf,
|
|
virDomainFSDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *type = virDomainFSTypeToString(def->type);
|
|
const char *accessmode = virDomainFSAccessModeTypeToString(def->accessmode);
|
|
const char *fsdriver = virDomainFSDriverTypeToString(def->fsdriver);
|
|
const char *wrpolicy = virDomainFSWrpolicyTypeToString(def->wrpolicy);
|
|
const char *multidevs = virDomainFSMultidevsTypeToString(def->multidevs);
|
|
const char *src = def->src->path;
|
|
g_auto(virBuffer) driverAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) driverBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) binaryAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) binaryBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected filesystem type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
if (!accessmode) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected accessmode %d"), def->accessmode);
|
|
return -1;
|
|
}
|
|
|
|
if (!multidevs) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected multidevs %d"), def->multidevs);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<filesystem type='%s'", type);
|
|
if (def->accessmode != VIR_DOMAIN_FS_ACCESSMODE_DEFAULT)
|
|
virBufferAsprintf(buf, " accessmode='%s'", accessmode);
|
|
if (def->model) {
|
|
virBufferAsprintf(buf, " model='%s'",
|
|
virDomainFSModelTypeToString(def->model));
|
|
}
|
|
if (def->multidevs)
|
|
virBufferAsprintf(buf, " multidevs='%s'", multidevs);
|
|
|
|
if (def->fmode)
|
|
virBufferAsprintf(buf, " fmode='%04o'", def->fmode);
|
|
|
|
if (def->dmode)
|
|
virBufferAsprintf(buf, " dmode='%04o'", def->dmode);
|
|
|
|
virBufferAddLit(buf, ">\n");
|
|
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAdjustIndent(&driverBuf, 2);
|
|
virBufferAdjustIndent(&binaryBuf, 2);
|
|
if (def->fsdriver) {
|
|
virBufferAsprintf(&driverAttrBuf, " type='%s'", fsdriver);
|
|
|
|
if (def->format)
|
|
virBufferAsprintf(&driverAttrBuf, " format='%s'",
|
|
virStorageFileFormatTypeToString(def->format));
|
|
|
|
/* Don't generate anything if wrpolicy is set to default */
|
|
if (def->wrpolicy)
|
|
virBufferAsprintf(&driverAttrBuf, " wrpolicy='%s'", wrpolicy);
|
|
|
|
if (def->queue_size)
|
|
virBufferAsprintf(&driverAttrBuf, " queue='%llu'", def->queue_size);
|
|
|
|
}
|
|
|
|
if (def->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS) {
|
|
g_auto(virBuffer) lockAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
virBufferEscapeString(&binaryAttrBuf, " path='%s'", def->binary);
|
|
|
|
if (def->xattr != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(&binaryAttrBuf, " xattr='%s'",
|
|
virTristateSwitchTypeToString(def->xattr));
|
|
}
|
|
|
|
if (def->cache != VIR_DOMAIN_FS_CACHE_MODE_DEFAULT) {
|
|
virBufferAsprintf(&binaryBuf, "<cache mode='%s'/>\n",
|
|
virDomainFSCacheModeTypeToString(def->cache));
|
|
}
|
|
|
|
if (def->sandbox != VIR_DOMAIN_FS_SANDBOX_MODE_DEFAULT) {
|
|
virBufferAsprintf(&binaryBuf, "<sandbox mode='%s'/>\n",
|
|
virDomainFSSandboxModeTypeToString(def->sandbox));
|
|
}
|
|
|
|
if (def->posix_lock != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(&lockAttrBuf, " posix='%s'",
|
|
virTristateSwitchTypeToString(def->posix_lock));
|
|
}
|
|
|
|
if (def->flock != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(&lockAttrBuf, " flock='%s'",
|
|
virTristateSwitchTypeToString(def->flock));
|
|
}
|
|
|
|
virXMLFormatElement(&binaryBuf, "lock", &lockAttrBuf, NULL);
|
|
}
|
|
|
|
virDomainVirtioOptionsFormat(&driverAttrBuf, def->virtio);
|
|
|
|
virXMLFormatElement(buf, "driver", &driverAttrBuf, &driverBuf);
|
|
virXMLFormatElement(buf, "binary", &binaryAttrBuf, &binaryBuf);
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_FS_TYPE_MOUNT:
|
|
case VIR_DOMAIN_FS_TYPE_BIND:
|
|
if (!def->sock)
|
|
virBufferEscapeString(buf, "<source dir='%s'/>\n", src);
|
|
else
|
|
virBufferEscapeString(buf, "<source socket='%s'/>\n", def->sock);
|
|
break;
|
|
|
|
case VIR_DOMAIN_FS_TYPE_BLOCK:
|
|
virBufferEscapeString(buf, "<source dev='%s'/>\n",
|
|
src);
|
|
break;
|
|
|
|
case VIR_DOMAIN_FS_TYPE_FILE:
|
|
virBufferEscapeString(buf, "<source file='%s'/>\n",
|
|
src);
|
|
break;
|
|
|
|
case VIR_DOMAIN_FS_TYPE_TEMPLATE:
|
|
virBufferEscapeString(buf, "<source name='%s'/>\n",
|
|
src);
|
|
break;
|
|
|
|
case VIR_DOMAIN_FS_TYPE_RAM:
|
|
virBufferAsprintf(buf, "<source usage='%lld' units='KiB'/>\n",
|
|
def->usage / 1024);
|
|
break;
|
|
|
|
case VIR_DOMAIN_FS_TYPE_VOLUME:
|
|
virBufferAddLit(buf, "<source");
|
|
virBufferEscapeString(buf, " pool='%s'", def->src->srcpool->pool);
|
|
virBufferEscapeString(buf, " volume='%s'", def->src->srcpool->volume);
|
|
virBufferAddLit(buf, "/>\n");
|
|
break;
|
|
}
|
|
|
|
virBufferEscapeString(buf, "<target dir='%s'/>\n",
|
|
def->dst);
|
|
|
|
if (def->readonly)
|
|
virBufferAddLit(buf, "<readonly/>\n");
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags | VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT);
|
|
|
|
if (def->space_hard_limit)
|
|
virBufferAsprintf(buf, "<space_hard_limit unit='bytes'>"
|
|
"%llu</space_hard_limit>\n", def->space_hard_limit);
|
|
if (def->space_soft_limit) {
|
|
virBufferAsprintf(buf, "<space_soft_limit unit='bytes'>"
|
|
"%llu</space_soft_limit>\n", def->space_soft_limit);
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</filesystem>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainNetIPInfoFormat(virBuffer *buf,
|
|
virNetDevIPInfo *def)
|
|
{
|
|
size_t i;
|
|
|
|
/* Output IP addresses */
|
|
for (i = 0; i < def->nips; i++) {
|
|
virSocketAddr *address = &def->ips[i]->address;
|
|
char *ipStr = virSocketAddrFormat(address);
|
|
const char *familyStr = NULL;
|
|
|
|
if (!ipStr)
|
|
return -1;
|
|
if (VIR_SOCKET_ADDR_IS_FAMILY(address, AF_INET6))
|
|
familyStr = "ipv6";
|
|
else if (VIR_SOCKET_ADDR_IS_FAMILY(address, AF_INET))
|
|
familyStr = "ipv4";
|
|
virBufferAsprintf(buf, "<ip address='%s'",
|
|
ipStr);
|
|
VIR_FREE(ipStr);
|
|
if (familyStr)
|
|
virBufferAsprintf(buf, " family='%s'", familyStr);
|
|
if (def->ips[i]->prefix)
|
|
virBufferAsprintf(buf, " prefix='%u'", def->ips[i]->prefix);
|
|
if (VIR_SOCKET_ADDR_VALID(&def->ips[i]->peer)) {
|
|
if (!(ipStr = virSocketAddrFormat(&def->ips[i]->peer)))
|
|
return -1;
|
|
virBufferAsprintf(buf, " peer='%s'", ipStr);
|
|
VIR_FREE(ipStr);
|
|
}
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
for (i = 0; i < def->nroutes; i++)
|
|
if (virNetDevIPRouteFormat(buf, def->routes[i]) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainHostdevDefFormatSubsysUSB(virBuffer *buf,
|
|
virDomainHostdevDef *def,
|
|
unsigned int flags,
|
|
bool includeTypeInAddr)
|
|
{
|
|
g_auto(virBuffer) sourceAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) sourceChildBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
virDomainHostdevSubsysUSB *usbsrc = &def->source.subsys.u.usb;
|
|
|
|
if (def->startupPolicy)
|
|
virBufferAsprintf(&sourceAttrBuf, " startupPolicy='%s'",
|
|
virDomainStartupPolicyTypeToString(def->startupPolicy));
|
|
|
|
if (usbsrc->autoAddress && (flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE))
|
|
virBufferAddLit(&sourceAttrBuf, " autoAddress='yes'");
|
|
|
|
if (def->missing && !(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE))
|
|
virBufferAddLit(&sourceAttrBuf, " missing='yes'");
|
|
|
|
if (usbsrc->vendor) {
|
|
virBufferAsprintf(&sourceChildBuf, "<vendor id='0x%.4x'/>\n", usbsrc->vendor);
|
|
virBufferAsprintf(&sourceChildBuf, "<product id='0x%.4x'/>\n", usbsrc->product);
|
|
}
|
|
|
|
if (usbsrc->bus || usbsrc->device)
|
|
virBufferAsprintf(&sourceChildBuf, "<address %sbus='%d' device='%d'/>\n",
|
|
includeTypeInAddr ? "type='usb' " : "",
|
|
usbsrc->bus, usbsrc->device);
|
|
|
|
virXMLFormatElement(buf, "source", &sourceAttrBuf, &sourceChildBuf);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevDefFormatSubsysPCI(virBuffer *buf,
|
|
virDomainHostdevDef *def,
|
|
unsigned int flags,
|
|
bool includeTypeInAddr)
|
|
{
|
|
g_auto(virBuffer) sourceAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) sourceChildBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) origstatesChildBuf = VIR_BUFFER_INIT_CHILD(&sourceChildBuf);
|
|
virDomainHostdevSubsysPCI *pcisrc = &def->source.subsys.u.pci;
|
|
|
|
if (def->writeFiltering != VIR_TRISTATE_BOOL_ABSENT)
|
|
virBufferAsprintf(&sourceAttrBuf, " writeFiltering='%s'",
|
|
virTristateBoolTypeToString(def->writeFiltering));
|
|
|
|
if (pcisrc->backend != VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT) {
|
|
const char *backend = virDomainHostdevSubsysPCIBackendTypeToString(pcisrc->backend);
|
|
|
|
if (!backend) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected pci hostdev driver name type %d"),
|
|
pcisrc->backend);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<driver name='%s'/>\n", backend);
|
|
}
|
|
|
|
virPCIDeviceAddressFormat(&sourceChildBuf, pcisrc->addr, includeTypeInAddr);
|
|
|
|
if ((flags & VIR_DOMAIN_DEF_FORMAT_PCI_ORIG_STATES)) {
|
|
if (def->origstates.states.pci.unbind_from_stub)
|
|
virBufferAddLit(&origstatesChildBuf, "<unbind/>\n");
|
|
|
|
if (def->origstates.states.pci.remove_slot)
|
|
virBufferAddLit(&origstatesChildBuf, "<removeslot/>\n");
|
|
|
|
if (def->origstates.states.pci.reprobe)
|
|
virBufferAddLit(&origstatesChildBuf, "<reprobe/>\n");
|
|
|
|
virXMLFormatElement(&sourceChildBuf, "origstates", NULL, &origstatesChildBuf);
|
|
}
|
|
|
|
virXMLFormatElement(buf, "source", &sourceAttrBuf, &sourceChildBuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevDefFormatSubsysSCSI(virBuffer *buf,
|
|
virDomainHostdevDef *def,
|
|
unsigned int flags,
|
|
bool includeTypeInAddr,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
g_auto(virBuffer) sourceAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) sourceChildBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
virDomainHostdevSubsysSCSI *scsisrc = &def->source.subsys.u.scsi;
|
|
virDomainHostdevSubsysSCSIHost *scsihostsrc = &scsisrc->u.host;
|
|
virDomainHostdevSubsysSCSIiSCSI *iscsisrc = &scsisrc->u.iscsi;
|
|
|
|
if (scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI) {
|
|
virBufferAsprintf(&sourceAttrBuf, " protocol='%s' name='%s'",
|
|
virDomainHostdevSubsysSCSIProtocolTypeToString(scsisrc->protocol),
|
|
iscsisrc->src->path);
|
|
|
|
virBufferAddLit(&sourceChildBuf, "<host");
|
|
virBufferEscapeString(&sourceChildBuf, " name='%s'", iscsisrc->src->hosts[0].name);
|
|
if (iscsisrc->src->hosts[0].port)
|
|
virBufferAsprintf(&sourceChildBuf, " port='%u'", iscsisrc->src->hosts[0].port);
|
|
virBufferAddLit(&sourceChildBuf, "/>\n");
|
|
|
|
if (virDomainDiskSourceFormatPrivateData(&sourceChildBuf, iscsisrc->src,
|
|
flags, xmlopt) < 0)
|
|
return -1;
|
|
|
|
if (iscsisrc->src->auth)
|
|
virStorageAuthDefFormat(&sourceChildBuf, iscsisrc->src->auth);
|
|
|
|
virStorageSourceInitiatorFormatXML(&iscsisrc->src->initiator,
|
|
&sourceChildBuf);
|
|
} else {
|
|
virBufferAsprintf(&sourceChildBuf, "<adapter name='%s'/>\n",
|
|
scsihostsrc->adapter);
|
|
|
|
virBufferAddLit(&sourceChildBuf, "<address");
|
|
if (includeTypeInAddr)
|
|
virBufferAddLit(&sourceChildBuf, " type='scsi'");
|
|
virBufferAsprintf(&sourceChildBuf, " bus='%u' target='%u' unit='%llu'",
|
|
scsihostsrc->bus, scsihostsrc->target, scsihostsrc->unit);
|
|
virBufferAddLit(&sourceChildBuf, "/>\n");
|
|
|
|
if (scsihostsrc->src &&
|
|
virDomainDiskSourceFormatPrivateData(&sourceChildBuf, scsihostsrc->src,
|
|
flags, xmlopt) < 0)
|
|
return -1;
|
|
}
|
|
|
|
virXMLFormatElement(buf, "source", &sourceAttrBuf, &sourceChildBuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainHostdevDefFormatSubsysSCSIHost(virBuffer *buf,
|
|
virDomainHostdevDef *def)
|
|
{
|
|
g_auto(virBuffer) sourceAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
virDomainHostdevSubsysSCSIVHost *hostsrc = &def->source.subsys.u.scsi_host;
|
|
|
|
virBufferAsprintf(&sourceAttrBuf, " protocol='%s' wwpn='%s'",
|
|
virDomainHostdevSubsysSCSIHostProtocolTypeToString(hostsrc->protocol),
|
|
hostsrc->wwpn);
|
|
|
|
virXMLFormatElement(buf, "source", &sourceAttrBuf, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainHostdevDefFormatSubsysMdev(virBuffer *buf,
|
|
virDomainHostdevDef *def)
|
|
{
|
|
g_auto(virBuffer) sourceChildBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
virDomainHostdevSubsysMediatedDev *mdevsrc = &def->source.subsys.u.mdev;
|
|
|
|
virBufferAsprintf(&sourceChildBuf, "<address uuid='%s'/>\n", mdevsrc->uuidstr);
|
|
|
|
virXMLFormatElement(buf, "source", NULL, &sourceChildBuf);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevDefFormatSubsys(virBuffer *buf,
|
|
virDomainHostdevDef *def,
|
|
unsigned int flags,
|
|
bool includeTypeInAddr,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
switch ((virDomainHostdevSubsysType) def->source.subsys.type) {
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
|
|
virDomainHostdevDefFormatSubsysUSB(buf, def, flags, includeTypeInAddr);
|
|
return 0;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI:
|
|
return virDomainHostdevDefFormatSubsysPCI(buf, def, flags, includeTypeInAddr);
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI:
|
|
return virDomainHostdevDefFormatSubsysSCSI(buf, def, flags, includeTypeInAddr, xmlopt);
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST:
|
|
virDomainHostdevDefFormatSubsysSCSIHost(buf, def);
|
|
return 0;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV:
|
|
virDomainHostdevDefFormatSubsysMdev(buf, def);
|
|
return 0;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainHostdevSubsysType, def->source.subsys.type);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainHostdevDefFormatCaps(virBuffer *buf,
|
|
virDomainHostdevDef *def)
|
|
{
|
|
virBufferAddLit(buf, "<source>\n");
|
|
|
|
virBufferAdjustIndent(buf, 2);
|
|
switch (def->source.caps.type) {
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_STORAGE:
|
|
virBufferEscapeString(buf, "<block>%s</block>\n",
|
|
def->source.caps.u.storage.block);
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_MISC:
|
|
virBufferEscapeString(buf, "<char>%s</char>\n",
|
|
def->source.caps.u.misc.chardev);
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET:
|
|
virBufferEscapeString(buf, "<interface>%s</interface>\n",
|
|
def->source.caps.u.net.ifname);
|
|
break;
|
|
default:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected hostdev type %d"),
|
|
def->source.caps.type);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</source>\n");
|
|
|
|
if (def->source.caps.type == VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET &&
|
|
virDomainNetIPInfoFormat(buf, &def->source.caps.u.net.ip) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* virDomainActualNetDefContentsFormat() - format just the subelements
|
|
* of <interface> that may be overridden by what is in the
|
|
* virDomainActualNetDef, but inside the current element, rather
|
|
* than enclosed in an <actual> subelement.
|
|
*/
|
|
static int
|
|
virDomainActualNetDefContentsFormat(virBuffer *buf,
|
|
virDomainNetDef *def,
|
|
bool inSubelement,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainNetType actualType = virDomainNetGetActualType(def);
|
|
|
|
if (actualType == VIR_DOMAIN_NET_TYPE_HOSTDEV) {
|
|
if (virDomainHostdevDefFormatSubsys(buf, virDomainNetGetActualHostdev(def),
|
|
flags, true, xmlopt) < 0) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
virBufferAddLit(buf, "<source");
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_NETWORK && !inSubelement) {
|
|
/* When we're putting our output into the <actual>
|
|
* subelement rather than the main <interface>, the
|
|
* network name and portgroup don't need to be included in
|
|
* the <source> here because the main interface element's
|
|
* <source> has the same info already. If we've been
|
|
* called to output directly into the main element's
|
|
* <source> though (the case here - "!inSubElement"), we
|
|
* *do* need to output network/portgroup, because the
|
|
* caller won't have done it).
|
|
*/
|
|
virBufferEscapeString(buf, " network='%s'",
|
|
def->data.network.name);
|
|
virBufferEscapeString(buf, " portgroup='%s'",
|
|
def->data.network.portgroup);
|
|
if (virUUIDIsValid(def->data.network.portid)) {
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
virUUIDFormat(def->data.network.portid, uuidstr);
|
|
virBufferAsprintf(buf, " portid='%s'", uuidstr);
|
|
}
|
|
}
|
|
if (actualType == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
int macTableManager = virDomainNetGetActualBridgeMACTableManager(def);
|
|
|
|
/* actualType == NETWORK includes the name of the bridge
|
|
* that is used by the network, whether we are
|
|
* "inSubElement" or not.
|
|
*/
|
|
virBufferEscapeString(buf, " bridge='%s'",
|
|
virDomainNetGetActualBridgeName(def));
|
|
if (macTableManager) {
|
|
virBufferAsprintf(buf, " macTableManager='%s'",
|
|
virNetworkBridgeMACTableManagerTypeToString(macTableManager));
|
|
}
|
|
} else if (actualType == VIR_DOMAIN_NET_TYPE_DIRECT) {
|
|
const char *mode;
|
|
|
|
virBufferEscapeString(buf, " dev='%s'",
|
|
virDomainNetGetActualDirectDev(def));
|
|
mode = virNetDevMacVLanModeTypeToString(virDomainNetGetActualDirectMode(def));
|
|
if (!mode) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected source mode %d"),
|
|
virDomainNetGetActualDirectMode(def));
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(buf, " mode='%s'", mode);
|
|
}
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_STATUS &&
|
|
def->data.network.actual && def->data.network.actual->class_id) {
|
|
virBufferAsprintf(buf, "<class id='%u'/>\n",
|
|
def->data.network.actual->class_id);
|
|
}
|
|
|
|
if (virNetDevVlanFormat(virDomainNetGetActualVlan(def), buf) < 0)
|
|
return -1;
|
|
if (virNetDevVPortProfileFormat(virDomainNetGetActualVirtPortProfile(def), buf) < 0)
|
|
return -1;
|
|
if (virNetDevBandwidthFormat(virDomainNetGetActualBandwidth(def), 0, buf) < 0)
|
|
return -1;
|
|
virNetworkPortOptionsFormat(virDomainNetGetActualPortOptionsIsolated(def), buf);
|
|
return 0;
|
|
}
|
|
|
|
/* virDomainActualNetDefFormat() - format the ActualNetDef
|
|
* info inside an <actual> element, as required for internal storage
|
|
* of domain status
|
|
*/
|
|
static int
|
|
virDomainActualNetDefFormat(virBuffer *buf,
|
|
virDomainNetDef *def,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
virDomainNetType type;
|
|
const char *typeStr;
|
|
|
|
if (!def)
|
|
return 0;
|
|
type = virDomainNetGetActualType(def);
|
|
typeStr = virDomainNetTypeToString(type);
|
|
|
|
if (!typeStr) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected net type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<actual type='%s'", typeStr);
|
|
if (type == VIR_DOMAIN_NET_TYPE_HOSTDEV) {
|
|
virDomainHostdevDef *hostdef = virDomainNetGetActualHostdev(def);
|
|
if (hostdef && hostdef->managed)
|
|
virBufferAddLit(buf, " managed='yes'");
|
|
}
|
|
if (def->trustGuestRxFilters)
|
|
virBufferAsprintf(buf, " trustGuestRxFilters='%s'",
|
|
virTristateBoolTypeToString(def->trustGuestRxFilters));
|
|
virBufferAddLit(buf, ">\n");
|
|
|
|
virBufferAdjustIndent(buf, 2);
|
|
if (virDomainActualNetDefContentsFormat(buf, def, true, flags, xmlopt) < 0)
|
|
return -1;
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</actual>\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainVirtioNetGuestOptsFormat(virBuffer *buf,
|
|
virDomainNetDef *def)
|
|
{
|
|
if (def->driver.virtio.guest.csum) {
|
|
virBufferAsprintf(buf, " csum='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.guest.csum));
|
|
}
|
|
if (def->driver.virtio.guest.tso4) {
|
|
virBufferAsprintf(buf, " tso4='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.guest.tso4));
|
|
}
|
|
if (def->driver.virtio.guest.tso6) {
|
|
virBufferAsprintf(buf, " tso6='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.guest.tso6));
|
|
}
|
|
if (def->driver.virtio.guest.ecn) {
|
|
virBufferAsprintf(buf, " ecn='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.guest.ecn));
|
|
}
|
|
if (def->driver.virtio.guest.ufo) {
|
|
virBufferAsprintf(buf, " ufo='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.guest.ufo));
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainVirtioNetHostOptsFormat(virBuffer *buf,
|
|
virDomainNetDef *def)
|
|
{
|
|
if (def->driver.virtio.host.csum) {
|
|
virBufferAsprintf(buf, " csum='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.host.csum));
|
|
}
|
|
if (def->driver.virtio.host.gso) {
|
|
virBufferAsprintf(buf, " gso='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.host.gso));
|
|
}
|
|
if (def->driver.virtio.host.tso4) {
|
|
virBufferAsprintf(buf, " tso4='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.host.tso4));
|
|
}
|
|
if (def->driver.virtio.host.tso6) {
|
|
virBufferAsprintf(buf, " tso6='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.host.tso6));
|
|
}
|
|
if (def->driver.virtio.host.ecn) {
|
|
virBufferAsprintf(buf, " ecn='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.host.ecn));
|
|
}
|
|
if (def->driver.virtio.host.ufo) {
|
|
virBufferAsprintf(buf, " ufo='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.host.ufo));
|
|
}
|
|
if (def->driver.virtio.host.mrg_rxbuf) {
|
|
virBufferAsprintf(buf, " mrg_rxbuf='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.host.mrg_rxbuf));
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainVirtioNetDriverFormat(virBuffer *buf,
|
|
virDomainNetDef *def)
|
|
{
|
|
if (def->driver.virtio.name) {
|
|
virBufferAsprintf(buf, " name='%s'",
|
|
virDomainNetBackendTypeToString(def->driver.virtio.name));
|
|
}
|
|
if (def->driver.virtio.txmode) {
|
|
virBufferAsprintf(buf, " txmode='%s'",
|
|
virDomainNetVirtioTxModeTypeToString(def->driver.virtio.txmode));
|
|
}
|
|
if (def->driver.virtio.ioeventfd) {
|
|
virBufferAsprintf(buf, " ioeventfd='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.ioeventfd));
|
|
}
|
|
if (def->driver.virtio.event_idx) {
|
|
virBufferAsprintf(buf, " event_idx='%s'",
|
|
virTristateSwitchTypeToString(def->driver.virtio.event_idx));
|
|
}
|
|
if (def->driver.virtio.queues)
|
|
virBufferAsprintf(buf, " queues='%u'", def->driver.virtio.queues);
|
|
if (def->driver.virtio.rx_queue_size)
|
|
virBufferAsprintf(buf, " rx_queue_size='%u'",
|
|
def->driver.virtio.rx_queue_size);
|
|
if (def->driver.virtio.tx_queue_size)
|
|
virBufferAsprintf(buf, " tx_queue_size='%u'",
|
|
def->driver.virtio.tx_queue_size);
|
|
|
|
virDomainVirtioOptionsFormat(buf, def->virtio);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainChrSourceReconnectDefFormat(virBuffer *buf,
|
|
virDomainChrSourceReconnectDef *def)
|
|
{
|
|
if (def->enabled == VIR_TRISTATE_BOOL_ABSENT)
|
|
return;
|
|
|
|
virBufferAsprintf(buf, "<reconnect enabled='%s'",
|
|
virTristateBoolTypeToString(def->enabled));
|
|
|
|
if (def->enabled == VIR_TRISTATE_BOOL_YES)
|
|
virBufferAsprintf(buf, " timeout='%u'", def->timeout);
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainNetTeamingInfoFormat(virDomainNetTeamingInfo *teaming,
|
|
virBuffer *buf)
|
|
{
|
|
if (teaming && teaming->type != VIR_DOMAIN_NET_TEAMING_TYPE_NONE) {
|
|
virBufferAsprintf(buf, "<teaming type='%s'",
|
|
virDomainNetTeamingTypeToString(teaming->type));
|
|
virBufferEscapeString(buf, " persistent='%s'", teaming->persistent);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
virDomainNetDefFormat(virBuffer *buf,
|
|
virDomainNetDef *def,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
virDomainNetType actualType = virDomainNetGetActualType(def);
|
|
bool publicActual = false;
|
|
int sourceLines = 0;
|
|
const char *typeStr;
|
|
virDomainHostdevDef *hostdef = NULL;
|
|
char macstr[VIR_MAC_STRING_BUFLEN];
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
const char *prefix = xmlopt ? xmlopt->config.netPrefix : NULL;
|
|
|
|
/* publicActual is true if we should report the current state in
|
|
* def->data.network.actual *instead of* the config (*not* in
|
|
* addition to)
|
|
*/
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
def->data.network.actual &&
|
|
!(flags & (VIR_DOMAIN_DEF_FORMAT_INACTIVE |
|
|
VIR_DOMAIN_DEF_FORMAT_ACTUAL_NET |
|
|
VIR_DOMAIN_DEF_FORMAT_MIGRATABLE)))
|
|
publicActual = true;
|
|
|
|
if (publicActual) {
|
|
if (!(typeStr = virDomainNetTypeToString(actualType))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected actual net type %d"), actualType);
|
|
return -1;
|
|
}
|
|
if (actualType == VIR_DOMAIN_NET_TYPE_HOSTDEV)
|
|
hostdef = virDomainNetGetActualHostdev(def);
|
|
} else {
|
|
if (!(typeStr = virDomainNetTypeToString(def->type))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected net type %d"), def->type);
|
|
return -1;
|
|
}
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_HOSTDEV)
|
|
hostdef = &def->data.hostdev.def;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<interface type='%s'", typeStr);
|
|
if (hostdef && hostdef->managed)
|
|
virBufferAddLit(buf, " managed='yes'");
|
|
if (def->trustGuestRxFilters)
|
|
virBufferAsprintf(buf, " trustGuestRxFilters='%s'",
|
|
virTristateBoolTypeToString(def->trustGuestRxFilters));
|
|
virBufferAddLit(buf, ">\n");
|
|
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAsprintf(buf, "<mac address='%s'",
|
|
virMacAddrFormat(&def->mac, macstr));
|
|
if (def->mac_type)
|
|
virBufferAsprintf(buf, " type='%s'", virDomainNetMacTypeTypeToString(def->mac_type));
|
|
if (def->mac_check != VIR_TRISTATE_BOOL_ABSENT)
|
|
virBufferAsprintf(buf, " check='%s'", virTristateBoolTypeToString(def->mac_check));
|
|
virBufferAddLit(buf, "/>\n");
|
|
|
|
if (publicActual) {
|
|
/* when there is a virDomainActualNetDef, and we haven't been
|
|
* asked to 1) report the domain's inactive XML, or 2) give
|
|
* the internal version of the ActualNetDef separately in an
|
|
* <actual> subelement, we can just put the ActualDef data in
|
|
* the standard place... (this is for public reporting of
|
|
* interface status)
|
|
*/
|
|
if (virDomainActualNetDefContentsFormat(buf, def, false, flags, xmlopt) < 0)
|
|
return -1;
|
|
} else {
|
|
/* ...but if we've asked for the inactive XML (rather than
|
|
* status), or to report the ActualDef as a separate <actual>
|
|
* subelement (this is how we privately store interface
|
|
* status), or there simply *isn't* any ActualNetDef, then
|
|
* format the NetDef's data here, and optionally format the
|
|
* ActualNetDef as an <actual> subelement of this element.
|
|
*/
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
virBufferEscapeString(buf, "<source network='%s'",
|
|
def->data.network.name);
|
|
virBufferEscapeString(buf, " portgroup='%s'",
|
|
def->data.network.portgroup);
|
|
if (virUUIDIsValid(def->data.network.portid) &&
|
|
!(flags & (VIR_DOMAIN_DEF_FORMAT_INACTIVE))) {
|
|
char portidstr[VIR_UUID_STRING_BUFLEN];
|
|
virUUIDFormat(def->data.network.portid, portidstr);
|
|
virBufferEscapeString(buf, " portid='%s'", portidstr);
|
|
}
|
|
sourceLines++;
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
if (def->data.vhostuser->type == VIR_DOMAIN_CHR_TYPE_UNIX) {
|
|
virBufferAddLit(buf, "<source type='unix'");
|
|
virBufferEscapeString(buf, " path='%s'",
|
|
def->data.vhostuser->data.nix.path);
|
|
virBufferAsprintf(buf, " mode='%s'",
|
|
def->data.vhostuser->data.nix.listen ?
|
|
"server" : "client");
|
|
sourceLines++;
|
|
if (def->data.vhostuser->data.nix.reconnect.enabled) {
|
|
virBufferAddLit(buf, ">\n");
|
|
sourceLines++;
|
|
virBufferAdjustIndent(buf, 2);
|
|
virDomainChrSourceReconnectDefFormat(buf,
|
|
&def->data.vhostuser->data.nix.reconnect);
|
|
virBufferAdjustIndent(buf, -2);
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
if (def->data.bridge.brname) {
|
|
virBufferEscapeString(buf, "<source bridge='%s'",
|
|
def->data.bridge.brname);
|
|
sourceLines++;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
if (def->data.socket.address) {
|
|
virBufferAsprintf(buf, "<source address='%s' port='%d'",
|
|
def->data.socket.address,
|
|
def->data.socket.port);
|
|
} else {
|
|
virBufferAsprintf(buf, "<source port='%d'",
|
|
def->data.socket.port);
|
|
}
|
|
sourceLines++;
|
|
|
|
if (def->type != VIR_DOMAIN_NET_TYPE_UDP)
|
|
break;
|
|
|
|
virBufferAddLit(buf, ">\n");
|
|
sourceLines++;
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
virBufferAsprintf(buf, "<local address='%s' port='%d'/>\n",
|
|
def->data.socket.localaddr,
|
|
def->data.socket.localport);
|
|
virBufferAdjustIndent(buf, -2);
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
if (def->data.internal.name) {
|
|
virBufferEscapeString(buf, "<source name='%s'",
|
|
def->data.internal.name);
|
|
sourceLines++;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
virBufferEscapeString(buf, "<source dev='%s'",
|
|
def->data.direct.linkdev);
|
|
virBufferAsprintf(buf, " mode='%s'",
|
|
virNetDevMacVLanModeTypeToString(def->data.direct.mode));
|
|
sourceLines++;
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
if (virDomainHostdevDefFormatSubsys(buf, &def->data.hostdev.def,
|
|
flags, true, xmlopt) < 0) {
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
if (def->data.vdpa.devicepath) {
|
|
virBufferEscapeString(buf, "<source dev='%s'",
|
|
def->data.vdpa.devicepath);
|
|
sourceLines++;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
/* if sourceLines == 0 - no <source> info at all so far
|
|
* sourceLines == 1 - first line written, no terminating ">"
|
|
* sourceLines > 1 - multiple lines, including subelements
|
|
*/
|
|
if (def->hostIP.nips || def->hostIP.nroutes) {
|
|
if (sourceLines == 0) {
|
|
virBufferAddLit(buf, "<source>\n");
|
|
sourceLines += 2;
|
|
} else if (sourceLines == 1) {
|
|
virBufferAddLit(buf, ">\n");
|
|
sourceLines++;
|
|
}
|
|
virBufferAdjustIndent(buf, 2);
|
|
if (virDomainNetIPInfoFormat(buf, &def->hostIP) < 0)
|
|
return -1;
|
|
virBufferAdjustIndent(buf, -2);
|
|
}
|
|
if (sourceLines == 1)
|
|
virBufferAddLit(buf, "/>\n");
|
|
else if (sourceLines > 1)
|
|
virBufferAddLit(buf, "</source>\n");
|
|
|
|
if (virNetDevVlanFormat(&def->vlan, buf) < 0)
|
|
return -1;
|
|
if (virNetDevVPortProfileFormat(def->virtPortProfile, buf) < 0)
|
|
return -1;
|
|
if (virNetDevBandwidthFormat(def->bandwidth, 0, buf) < 0)
|
|
return -1;
|
|
virNetworkPortOptionsFormat(def->isolatedPort, buf);
|
|
|
|
/* ONLY for internal status storage - format the ActualNetDef
|
|
* as a subelement of <interface> so that no persistent config
|
|
* data is overwritten.
|
|
*/
|
|
if (def->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
(flags & VIR_DOMAIN_DEF_FORMAT_ACTUAL_NET) &&
|
|
(virDomainActualNetDefFormat(buf, def, flags, xmlopt) < 0))
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (virDomainNetIPInfoFormat(buf, &def->guestIP) < 0)
|
|
return -1;
|
|
|
|
virBufferEscapeString(buf, "<script path='%s'/>\n",
|
|
def->script);
|
|
virBufferEscapeString(buf, "<downscript path='%s'/>\n",
|
|
def->downscript);
|
|
virBufferEscapeString(buf, "<backenddomain name='%s'/>\n", def->domain_name);
|
|
|
|
if (def->ifname &&
|
|
(def->managed_tap == VIR_TRISTATE_BOOL_NO ||
|
|
!((flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE) &&
|
|
(STRPREFIX(def->ifname, VIR_NET_GENERATED_VNET_PREFIX) ||
|
|
STRPREFIX(def->ifname, VIR_NET_GENERATED_MACVTAP_PREFIX) ||
|
|
STRPREFIX(def->ifname, VIR_NET_GENERATED_MACVLAN_PREFIX) ||
|
|
(prefix && STRPREFIX(def->ifname, prefix)))))) {
|
|
/* Skip auto-generated target names for inactive config. */
|
|
virBufferEscapeString(&attrBuf, " dev='%s'", def->ifname);
|
|
}
|
|
if (def->managed_tap != VIR_TRISTATE_BOOL_ABSENT) {
|
|
virBufferAsprintf(&attrBuf, " managed='%s'",
|
|
virTristateBoolTypeToString(def->managed_tap));
|
|
}
|
|
|
|
virXMLFormatElement(buf, "target", &attrBuf, NULL);
|
|
|
|
if (def->ifname_guest || def->ifname_guest_actual) {
|
|
virBufferAddLit(buf, "<guest");
|
|
/* Skip auto-generated target names for inactive config. */
|
|
if (def->ifname_guest)
|
|
virBufferEscapeString(buf, " dev='%s'", def->ifname_guest);
|
|
|
|
/* Only set if the host is running, so shouldn't pollute output */
|
|
if (def->ifname_guest_actual)
|
|
virBufferEscapeString(buf, " actual='%s'", def->ifname_guest_actual);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
if (virDomainNetGetModelString(def)) {
|
|
virBufferEscapeString(buf, "<model type='%s'/>\n",
|
|
virDomainNetGetModelString(def));
|
|
if (virDomainNetIsVirtioModel(def)) {
|
|
g_auto(virBuffer) driverAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) driverChildBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) hostAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) guestAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virDomainVirtioNetDriverFormat(&driverAttrBuf, def);
|
|
virDomainVirtioNetGuestOptsFormat(&guestAttrBuf, def);
|
|
virDomainVirtioNetHostOptsFormat(&hostAttrBuf, def);
|
|
|
|
virXMLFormatElement(&driverChildBuf, "host", &hostAttrBuf, NULL);
|
|
virXMLFormatElement(&driverChildBuf, "guest", &guestAttrBuf, NULL);
|
|
virXMLFormatElement(buf, "driver", &driverAttrBuf, &driverChildBuf);
|
|
}
|
|
}
|
|
if (def->backend.tap || def->backend.vhost) {
|
|
virBufferAddLit(buf, "<backend");
|
|
virBufferEscapeString(buf, " tap='%s'", def->backend.tap);
|
|
virBufferEscapeString(buf, " vhost='%s'", def->backend.vhost);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
if (def->filter) {
|
|
if (virNWFilterFormatParamAttributes(buf, def->filterparams,
|
|
def->filter) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (def->tune.sndbuf_specified) {
|
|
virBufferAddLit(buf, "<tune>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAsprintf(buf, "<sndbuf>%lu</sndbuf>\n", def->tune.sndbuf);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</tune>\n");
|
|
}
|
|
|
|
virDomainNetTeamingInfoFormat(def->teaming, buf);
|
|
|
|
if (def->linkstate) {
|
|
virBufferAsprintf(buf, "<link state='%s'/>\n",
|
|
virDomainNetInterfaceLinkStateTypeToString(def->linkstate));
|
|
}
|
|
|
|
if (def->mtu)
|
|
virBufferAsprintf(buf, "<mtu size='%u'/>\n", def->mtu);
|
|
|
|
virDomainNetDefCoalesceFormatXML(buf, def->coalesce);
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags | VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT
|
|
| VIR_DOMAIN_DEF_FORMAT_ALLOW_ROM);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</interface>\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Assumes that "<device" has already been generated, and starts
|
|
* output at " type='type'>". */
|
|
static int
|
|
virDomainChrAttrsDefFormat(virBuffer *buf,
|
|
virDomainChrSourceDef *def,
|
|
bool tty_compat)
|
|
{
|
|
const char *type = virDomainChrTypeToString(def->type);
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected char type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
/* Compat with legacy <console tty='/dev/pts/5'/> syntax */
|
|
virBufferAsprintf(buf, " type='%s'", type);
|
|
if (tty_compat) {
|
|
virBufferEscapeString(buf, " tty='%s'",
|
|
def->data.file.path);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virDomainChrSourceDefFormat(virBuffer *buf,
|
|
virDomainChrSourceDef *def,
|
|
unsigned int flags)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
switch ((virDomainChrType)def->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_NULL:
|
|
case VIR_DOMAIN_CHR_TYPE_VC:
|
|
case VIR_DOMAIN_CHR_TYPE_STDIO:
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
|
|
case VIR_DOMAIN_CHR_TYPE_LAST:
|
|
/* nada */
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
if (def->type != VIR_DOMAIN_CHR_TYPE_PTY ||
|
|
(def->data.file.path &&
|
|
!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE))) {
|
|
virBufferEscapeString(&attrBuf, " path='%s'",
|
|
def->data.file.path);
|
|
if (def->type == VIR_DOMAIN_CHR_TYPE_FILE &&
|
|
def->data.file.append != VIR_TRISTATE_SWITCH_ABSENT)
|
|
virBufferAsprintf(&attrBuf, " append='%s'",
|
|
virTristateSwitchTypeToString(def->data.file.append));
|
|
virDomainSourceDefFormatSeclabel(&childBuf, def->nseclabels,
|
|
def->seclabels, flags);
|
|
|
|
virXMLFormatElement(buf, "source", &attrBuf, &childBuf);
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
virBufferEscapeString(buf, "<source master='%s' ",
|
|
def->data.nmdm.master);
|
|
virBufferEscapeString(buf, "slave='%s'/>\n", def->data.nmdm.slave);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UDP:
|
|
if (def->data.udp.bindService || def->data.udp.bindHost) {
|
|
virBufferAddLit(buf, "<source mode='bind'");
|
|
virBufferEscapeString(buf, " host='%s'", def->data.udp.bindHost);
|
|
virBufferEscapeString(buf, " service='%s'", def->data.udp.bindService);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
if (def->data.udp.connectService || def->data.udp.connectHost) {
|
|
virBufferAddLit(buf, "<source mode='connect'");
|
|
virBufferEscapeString(buf, " host='%s'", def->data.udp.connectHost);
|
|
virBufferEscapeString(buf, " service='%s'", def->data.udp.connectService);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_TCP:
|
|
virBufferAsprintf(&attrBuf, " mode='%s' ",
|
|
def->data.tcp.listen ? "bind" : "connect");
|
|
virBufferEscapeString(&attrBuf, "host='%s' ", def->data.tcp.host);
|
|
virBufferEscapeString(&attrBuf, "service='%s'", def->data.tcp.service);
|
|
if (def->data.tcp.haveTLS != VIR_TRISTATE_BOOL_ABSENT &&
|
|
!(flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE &&
|
|
def->data.tcp.tlsFromConfig))
|
|
virBufferAsprintf(&attrBuf, " tls='%s'",
|
|
virTristateBoolTypeToString(def->data.tcp.haveTLS));
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_STATUS)
|
|
virBufferAsprintf(&attrBuf, " tlsFromConfig='%d'",
|
|
def->data.tcp.tlsFromConfig);
|
|
|
|
virDomainChrSourceReconnectDefFormat(&childBuf,
|
|
&def->data.tcp.reconnect);
|
|
|
|
virXMLFormatElement(buf, "source", &attrBuf, &childBuf);
|
|
|
|
virBufferAsprintf(buf, "<protocol type='%s'/>\n",
|
|
virDomainChrTcpProtocolTypeToString(
|
|
def->data.tcp.protocol));
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
if (def->data.nix.path) {
|
|
virBufferAsprintf(&attrBuf, " mode='%s'",
|
|
def->data.nix.listen ? "bind" : "connect");
|
|
virBufferEscapeString(&attrBuf, " path='%s'", def->data.nix.path);
|
|
virDomainSourceDefFormatSeclabel(&childBuf, def->nseclabels,
|
|
def->seclabels, flags);
|
|
|
|
virDomainChrSourceReconnectDefFormat(&childBuf,
|
|
&def->data.nix.reconnect);
|
|
|
|
virXMLFormatElement(buf, "source", &attrBuf, &childBuf);
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
|
|
virBufferEscapeString(buf, "<source channel='%s'/>\n",
|
|
def->data.spiceport.channel);
|
|
break;
|
|
|
|
}
|
|
|
|
if (def->logfile) {
|
|
virBufferEscapeString(buf, "<log file='%s'", def->logfile);
|
|
if (def->logappend != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(buf, " append='%s'",
|
|
virTristateSwitchTypeToString(def->logappend));
|
|
}
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrTargetDefFormat(virBuffer *buf,
|
|
const virDomainChrDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *targetType = virDomainChrTargetTypeToString(def->deviceType,
|
|
def->targetType);
|
|
|
|
switch ((virDomainChrDeviceType) def->deviceType) {
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL: {
|
|
if (!targetType) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Could not format channel target type"));
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(buf, "<target type='%s'", targetType);
|
|
|
|
switch (def->targetType) {
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD: {
|
|
int port = virSocketAddrGetPort(def->target.addr);
|
|
g_autofree char *addr = NULL;
|
|
if (port < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Unable to format guestfwd port"));
|
|
return -1;
|
|
}
|
|
|
|
addr = virSocketAddrFormat(def->target.addr);
|
|
if (addr == NULL)
|
|
return -1;
|
|
|
|
virBufferAsprintf(buf, " address='%s' port='%d'",
|
|
addr, port);
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XEN:
|
|
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO:
|
|
if (def->target.name)
|
|
virBufferEscapeString(buf, " name='%s'", def->target.name);
|
|
|
|
if (def->targetType == VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO &&
|
|
def->state != VIR_DOMAIN_CHR_DEVICE_STATE_DEFAULT &&
|
|
!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)) {
|
|
virBufferAsprintf(buf, " state='%s'",
|
|
virDomainChrDeviceStateTypeToString(def->state));
|
|
}
|
|
break;
|
|
}
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
|
|
if (!targetType) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Could not format console target type"));
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf,
|
|
"<target type='%s' port='%d'/>\n",
|
|
targetType, def->target.port);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
|
|
if (!targetType) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Could not format serial target type"));
|
|
return -1;
|
|
}
|
|
|
|
virBufferAddLit(buf, "<target ");
|
|
|
|
if (def->targetType != VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_NONE) {
|
|
virBufferAsprintf(buf,
|
|
"type='%s' ",
|
|
targetType);
|
|
}
|
|
|
|
virBufferAsprintf(buf,
|
|
"port='%d'",
|
|
def->target.port);
|
|
|
|
if (def->targetModel != VIR_DOMAIN_CHR_SERIAL_TARGET_MODEL_NONE) {
|
|
virBufferAddLit(buf, ">\n");
|
|
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAsprintf(buf,
|
|
"<model name='%s'/>\n",
|
|
virDomainChrSerialTargetModelTypeToString(def->targetModel));
|
|
virBufferAdjustIndent(buf, -2);
|
|
|
|
virBufferAddLit(buf, "</target>\n");
|
|
} else {
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
|
|
virBufferAsprintf(buf, "<target port='%d'/>\n",
|
|
def->target.port);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected char device type %d"),
|
|
def->deviceType);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainChrDefFormat(virBuffer *buf,
|
|
virDomainChrDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *elementName = virDomainChrDeviceTypeToString(def->deviceType);
|
|
bool tty_compat;
|
|
|
|
if (!elementName) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected char device type %d"),
|
|
def->deviceType);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<%s", elementName);
|
|
virBufferAdjustIndent(buf, 2);
|
|
tty_compat = (def->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE &&
|
|
def->target.port == 0 &&
|
|
def->source->type == VIR_DOMAIN_CHR_TYPE_PTY &&
|
|
!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE) &&
|
|
def->source->data.file.path);
|
|
if (virDomainChrAttrsDefFormat(buf, def->source, tty_compat) < 0)
|
|
return -1;
|
|
virBufferAddLit(buf, ">\n");
|
|
|
|
virDomainChrSourceDefFormat(buf, def->source, flags);
|
|
|
|
if (virDomainChrTargetDefFormat(buf, def, flags) < 0)
|
|
return -1;
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAsprintf(buf, "</%s>\n", elementName);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainSmartcardDefFormat(virBuffer *buf,
|
|
virDomainSmartcardDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *mode = virDomainSmartcardTypeToString(def->type);
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
size_t i;
|
|
|
|
if (!mode) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected smartcard type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
|
|
break;
|
|
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
|
|
for (i = 0; i < VIR_DOMAIN_SMARTCARD_NUM_CERTIFICATES; i++) {
|
|
virBufferEscapeString(&childBuf, "<certificate>%s</certificate>\n",
|
|
def->data.cert.file[i]);
|
|
}
|
|
virBufferEscapeString(&childBuf, "<database>%s</database>\n",
|
|
def->data.cert.database);
|
|
break;
|
|
|
|
case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
|
|
virDomainChrSourceDefFormat(&childBuf, def->data.passthru, flags);
|
|
break;
|
|
|
|
default:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected smartcard type %d"), def->type);
|
|
return -1;
|
|
}
|
|
virDomainDeviceInfoFormat(&childBuf, &def->info, flags);
|
|
|
|
virBufferAsprintf(&attrBuf, " mode='%s'", mode);
|
|
if (def->type == VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH &&
|
|
virDomainChrAttrsDefFormat(&attrBuf, def->data.passthru, false) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
virXMLFormatElement(buf, "smartcard", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainSoundCodecDefFormat(virBuffer *buf,
|
|
virDomainSoundCodecDef *def)
|
|
{
|
|
const char *type = virDomainSoundCodecTypeToString(def->type);
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected codec type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<codec type='%s'/>\n", type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainTPMDefFormat(virBuffer *buf,
|
|
virDomainTPMDef *def,
|
|
unsigned int flags)
|
|
{
|
|
virBufferAsprintf(buf, "<tpm model='%s'>\n",
|
|
virDomainTPMModelTypeToString(def->model));
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAsprintf(buf, "<backend type='%s'",
|
|
virDomainTPMBackendTypeToString(def->type));
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferEscapeString(buf, "<device path='%s'/>\n",
|
|
def->data.passthrough.source.data.file.path);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</backend>\n");
|
|
break;
|
|
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
|
|
virBufferAsprintf(buf, " version='%s'",
|
|
virDomainTPMVersionTypeToString(def->version));
|
|
if (def->data.emulator.persistent_state)
|
|
virBufferAddLit(buf, " persistent_state='yes'");
|
|
if (def->data.emulator.hassecretuuid) {
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAsprintf(buf, "<encryption secret='%s'/>\n",
|
|
virUUIDFormat(def->data.emulator.secretuuid, uuidstr));
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</backend>\n");
|
|
} else {
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_TPM_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</tpm>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainSoundDefFormat(virBuffer *buf,
|
|
virDomainSoundDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *model = virDomainSoundModelTypeToString(def->model);
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
size_t i;
|
|
|
|
if (!model) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected sound model %d"), def->model);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < def->ncodecs; i++)
|
|
virDomainSoundCodecDefFormat(&childBuf, def->codecs[i]);
|
|
|
|
if (def->audioId > 0)
|
|
virBufferAsprintf(&childBuf, "<audio id='%d'/>\n", def->audioId);
|
|
|
|
virDomainDeviceInfoFormat(&childBuf, &def->info, flags);
|
|
|
|
virBufferAsprintf(&attrBuf, " model='%s'", model);
|
|
|
|
virXMLFormatElement(buf, "sound", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainAudioCommonFormat(virDomainAudioIOCommon *def,
|
|
virBuffer *childBuf,
|
|
virBuffer *backendAttrBuf,
|
|
const char *direction)
|
|
{
|
|
g_auto(virBuffer) settingsBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (def->fixedSettings == VIR_TRISTATE_BOOL_YES) {
|
|
if (def->frequency)
|
|
virBufferAsprintf(&settingsBuf, " frequency='%u'",
|
|
def->frequency);
|
|
if (def->channels)
|
|
virBufferAsprintf(&settingsBuf, " channels='%u'",
|
|
def->channels);
|
|
if (def->format)
|
|
virBufferAsprintf(&settingsBuf, " format='%s'",
|
|
virDomainAudioFormatTypeToString(def->format));
|
|
}
|
|
|
|
if (def->mixingEngine || def->fixedSettings ||
|
|
def->voices || def->bufferLength ||
|
|
virBufferUse(backendAttrBuf)) {
|
|
virBufferAsprintf(childBuf, "<%s", direction);
|
|
if (def->mixingEngine)
|
|
virBufferAsprintf(childBuf, " mixingEngine='%s'",
|
|
virTristateBoolTypeToString(def->mixingEngine));
|
|
if (def->fixedSettings)
|
|
virBufferAsprintf(childBuf, " fixedSettings='%s'",
|
|
virTristateBoolTypeToString(def->fixedSettings));
|
|
if (def->voices)
|
|
virBufferAsprintf(childBuf, " voices='%u'",
|
|
def->voices);
|
|
if (def->bufferLength)
|
|
virBufferAsprintf(childBuf, " bufferLength='%u'",
|
|
def->bufferLength);
|
|
if (virBufferUse(backendAttrBuf))
|
|
virBufferAdd(childBuf, virBufferCurrentContent(backendAttrBuf), -1);
|
|
if (def->fixedSettings == VIR_TRISTATE_BOOL_YES) {
|
|
virBufferAddLit(childBuf, ">\n");
|
|
virBufferAdjustIndent(childBuf, 2);
|
|
virBufferAddLit(childBuf, "<settings");
|
|
if (virBufferUse(&settingsBuf)) {
|
|
virBufferAdd(childBuf, virBufferCurrentContent(&settingsBuf), -1);
|
|
}
|
|
virBufferAddLit(childBuf, "/>\n");
|
|
virBufferAdjustIndent(childBuf, -2);
|
|
virBufferAsprintf(childBuf, "</%s>\n", direction);
|
|
} else {
|
|
virBufferAddLit(childBuf, "/>\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainAudioALSAFormat(virDomainAudioIOALSA *def,
|
|
virBuffer *buf)
|
|
{
|
|
virBufferEscapeString(buf, " dev='%s'", def->dev);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainAudioCoreAudioFormat(virDomainAudioIOCoreAudio *def,
|
|
virBuffer *buf)
|
|
{
|
|
if (def->bufferCount)
|
|
virBufferAsprintf(buf, " bufferCount='%u'", def->bufferCount);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainAudioJackFormat(virDomainAudioIOJack *def,
|
|
virBuffer *buf)
|
|
{
|
|
virBufferEscapeString(buf, " serverName='%s'", def->serverName);
|
|
virBufferEscapeString(buf, " clientName='%s'", def->clientName);
|
|
virBufferEscapeString(buf, " connectPorts='%s'", def->connectPorts);
|
|
if (def->exactName)
|
|
virBufferAsprintf(buf, " exactName='%s'",
|
|
virTristateBoolTypeToString(def->exactName));
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainAudioOSSFormat(virDomainAudioIOOSS *def,
|
|
virBuffer *buf)
|
|
{
|
|
virBufferEscapeString(buf, " dev='%s'", def->dev);
|
|
if (def->bufferCount)
|
|
virBufferAsprintf(buf, " bufferCount='%u'", def->bufferCount);
|
|
if (def->tryPoll)
|
|
virBufferAsprintf(buf, " tryPoll='%s'",
|
|
virTristateBoolTypeToString(def->tryPoll));
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainAudioPulseAudioFormat(virDomainAudioIOPulseAudio *def,
|
|
virBuffer *buf)
|
|
{
|
|
virBufferEscapeString(buf, " name='%s'", def->name);
|
|
virBufferEscapeString(buf, " streamName='%s'", def->streamName);
|
|
if (def->latency)
|
|
virBufferAsprintf(buf, " latency='%u'", def->latency);
|
|
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainAudioSDLFormat(virDomainAudioIOSDL *def,
|
|
virBuffer *buf)
|
|
{
|
|
if (def->bufferCount)
|
|
virBufferAsprintf(buf, " bufferCount='%u'", def->bufferCount);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainAudioDefFormat(virBuffer *buf,
|
|
virDomainAudioDef *def)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) inputBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) outputBuf = VIR_BUFFER_INITIALIZER;
|
|
const char *type = virDomainAudioTypeTypeToString(def->type);
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected audio type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(&attrBuf, " id='%d' type='%s'", def->id, type);
|
|
|
|
if (def->timerPeriod)
|
|
virBufferAsprintf(&attrBuf, " timerPeriod='%u'", def->timerPeriod);
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_AUDIO_TYPE_NONE:
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_ALSA:
|
|
virDomainAudioALSAFormat(&def->backend.alsa.input, &inputBuf);
|
|
virDomainAudioALSAFormat(&def->backend.alsa.output, &outputBuf);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_COREAUDIO:
|
|
virDomainAudioCoreAudioFormat(&def->backend.coreaudio.input, &inputBuf);
|
|
virDomainAudioCoreAudioFormat(&def->backend.coreaudio.output, &outputBuf);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_JACK:
|
|
virDomainAudioJackFormat(&def->backend.jack.input, &inputBuf);
|
|
virDomainAudioJackFormat(&def->backend.jack.output, &outputBuf);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_OSS:
|
|
if (def->backend.oss.tryMMap)
|
|
virBufferAsprintf(&attrBuf, " tryMMap='%s'",
|
|
virTristateBoolTypeToString(def->backend.oss.tryMMap));
|
|
if (def->backend.oss.exclusive)
|
|
virBufferAsprintf(&attrBuf, " exclusive='%s'",
|
|
virTristateBoolTypeToString(def->backend.oss.exclusive));
|
|
if (def->backend.oss.dspPolicySet)
|
|
virBufferAsprintf(&attrBuf, " dspPolicy='%d'", def->backend.oss.dspPolicy);
|
|
|
|
virDomainAudioOSSFormat(&def->backend.oss.input, &inputBuf);
|
|
virDomainAudioOSSFormat(&def->backend.oss.output, &outputBuf);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_PULSEAUDIO:
|
|
virBufferEscapeString(&attrBuf, " serverName='%s'",
|
|
def->backend.pulseaudio.serverName);
|
|
|
|
virDomainAudioPulseAudioFormat(&def->backend.pulseaudio.input, &inputBuf);
|
|
virDomainAudioPulseAudioFormat(&def->backend.pulseaudio.output, &outputBuf);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_SDL:
|
|
if (def->backend.sdl.driver)
|
|
virBufferAsprintf(&attrBuf, " driver='%s'",
|
|
virDomainAudioSDLDriverTypeToString(
|
|
def->backend.sdl.driver));
|
|
|
|
virDomainAudioSDLFormat(&def->backend.sdl.input, &inputBuf);
|
|
virDomainAudioSDLFormat(&def->backend.sdl.output, &outputBuf);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_SPICE:
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_FILE:
|
|
virBufferEscapeString(&attrBuf, " path='%s'", def->backend.file.path);
|
|
break;
|
|
|
|
case VIR_DOMAIN_AUDIO_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainAudioType, def->type);
|
|
return -1;
|
|
}
|
|
|
|
virDomainAudioCommonFormat(&def->input, &childBuf, &inputBuf, "input");
|
|
virDomainAudioCommonFormat(&def->output, &childBuf, &outputBuf, "output");
|
|
|
|
virXMLFormatElement(buf, "audio", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainMemballoonDefFormat(virBuffer *buf,
|
|
virDomainMemballoonDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *model = virDomainMemballoonModelTypeToString(def->model);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) driverAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (!model) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected memballoon model %d"), def->model);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(&attrBuf, " model='%s'", model);
|
|
|
|
if (def->autodeflate != VIR_TRISTATE_SWITCH_ABSENT)
|
|
virBufferAsprintf(&attrBuf, " autodeflate='%s'",
|
|
virTristateSwitchTypeToString(def->autodeflate));
|
|
|
|
if (def->free_page_reporting != VIR_TRISTATE_SWITCH_ABSENT)
|
|
virBufferAsprintf(&attrBuf, " freePageReporting='%s'",
|
|
virTristateSwitchTypeToString(def->free_page_reporting));
|
|
|
|
if (def->period)
|
|
virBufferAsprintf(&childrenBuf, "<stats period='%i'/>\n", def->period);
|
|
|
|
virDomainDeviceInfoFormat(&childrenBuf, &def->info, flags);
|
|
|
|
virDomainVirtioOptionsFormat(&driverAttrBuf, def->virtio);
|
|
|
|
virXMLFormatElement(&childrenBuf, "driver", &driverAttrBuf, NULL);
|
|
virXMLFormatElement(buf, "memballoon", &attrBuf, &childrenBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virDomainNVRAMDefFormat(virBuffer *buf,
|
|
virDomainNVRAMDef *def,
|
|
unsigned int flags)
|
|
{
|
|
virBufferAddLit(buf, "<nvram>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</nvram>\n");
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainWatchdogDefFormat(virBuffer *buf,
|
|
virDomainWatchdogDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *model = virDomainWatchdogModelTypeToString(def->model);
|
|
const char *action = virDomainWatchdogActionTypeToString(def->action);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (!model) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected watchdog model %d"), def->model);
|
|
return -1;
|
|
}
|
|
|
|
if (!action) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected watchdog action %d"), def->action);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(&attrBuf, " model='%s' action='%s'", model, action);
|
|
|
|
virDomainDeviceInfoFormat(&childBuf, &def->info, flags);
|
|
|
|
virXMLFormatElement(buf, "watchdog", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void virDomainPanicDefFormat(virBuffer *buf, virDomainPanicDef *def)
|
|
{
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (def->model)
|
|
virBufferAsprintf(&attrBuf, " model='%s'",
|
|
virDomainPanicModelTypeToString(def->model));
|
|
|
|
virDomainDeviceInfoFormat(&childrenBuf, &def->info, 0);
|
|
|
|
virXMLFormatElement(buf, "panic", &attrBuf, &childrenBuf);
|
|
}
|
|
|
|
static void
|
|
virDomainShmemDefFormat(virBuffer *buf,
|
|
virDomainShmemDef *def,
|
|
unsigned int flags)
|
|
{
|
|
virBufferEscapeString(buf, "<shmem name='%s'", def->name);
|
|
if (def->role)
|
|
virBufferEscapeString(buf, " role='%s'",
|
|
virDomainShmemRoleTypeToString(def->role));
|
|
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
virBufferAsprintf(buf, "<model type='%s'/>\n",
|
|
virDomainShmemModelTypeToString(def->model));
|
|
|
|
if (def->size)
|
|
virBufferAsprintf(buf, "<size unit='M'>%llu</size>\n", def->size >> 20);
|
|
|
|
if (def->server.enabled) {
|
|
virBufferAddLit(buf, "<server");
|
|
virBufferEscapeString(buf, " path='%s'", def->server.chr.data.nix.path);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
if (def->msi.enabled) {
|
|
virBufferAddLit(buf, "<msi");
|
|
if (def->msi.vectors)
|
|
virBufferAsprintf(buf, " vectors='%u'", def->msi.vectors);
|
|
if (def->msi.ioeventfd)
|
|
virBufferAsprintf(buf, " ioeventfd='%s'",
|
|
virTristateSwitchTypeToString(def->msi.ioeventfd));
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</shmem>\n");
|
|
}
|
|
|
|
static int
|
|
virDomainRNGDefFormat(virBuffer *buf,
|
|
virDomainRNGDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *model = virDomainRNGModelTypeToString(def->model);
|
|
const char *backend = virDomainRNGBackendTypeToString(def->backend);
|
|
g_auto(virBuffer) driverAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virBufferAsprintf(buf, "<rng model='%s'>\n", model);
|
|
virBufferAdjustIndent(buf, 2);
|
|
if (def->rate) {
|
|
virBufferAsprintf(buf, "<rate bytes='%u'", def->rate);
|
|
if (def->period)
|
|
virBufferAsprintf(buf, " period='%u'", def->period);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
virBufferAsprintf(buf, "<backend model='%s'", backend);
|
|
|
|
switch ((virDomainRNGBackend) def->backend) {
|
|
case VIR_DOMAIN_RNG_BACKEND_RANDOM:
|
|
virBufferEscapeString(buf, ">%s</backend>\n", def->source.file);
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_EGD:
|
|
if (virDomainChrAttrsDefFormat(buf, def->source.chardev, false) < 0)
|
|
return -1;
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virDomainChrSourceDefFormat(buf, def->source.chardev, flags);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</backend>\n");
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_BUILTIN:
|
|
virBufferAddLit(buf, "/>\n");
|
|
break;
|
|
|
|
case VIR_DOMAIN_RNG_BACKEND_LAST:
|
|
break;
|
|
}
|
|
|
|
virDomainVirtioOptionsFormat(&driverAttrBuf, def->virtio);
|
|
|
|
virXMLFormatElement(buf, "driver", &driverAttrBuf, NULL);
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</rng>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
virDomainRNGDefFree(virDomainRNGDef *def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
switch ((virDomainRNGBackend) def->backend) {
|
|
case VIR_DOMAIN_RNG_BACKEND_RANDOM:
|
|
g_free(def->source.file);
|
|
break;
|
|
case VIR_DOMAIN_RNG_BACKEND_EGD:
|
|
virObjectUnref(def->source.chardev);
|
|
break;
|
|
case VIR_DOMAIN_RNG_BACKEND_BUILTIN:
|
|
case VIR_DOMAIN_RNG_BACKEND_LAST:
|
|
break;
|
|
}
|
|
|
|
virDomainDeviceInfoClear(&def->info);
|
|
g_free(def->virtio);
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainMemorySourceDefFormat(virBuffer *buf,
|
|
virDomainMemoryDef *def)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_autofree char *bitmap = NULL;
|
|
|
|
switch (def->model) {
|
|
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
|
|
if (def->sourceNodes) {
|
|
if (!(bitmap = virBitmapFormat(def->sourceNodes)))
|
|
return -1;
|
|
|
|
virBufferAsprintf(&childBuf, "<nodemask>%s</nodemask>\n", bitmap);
|
|
}
|
|
|
|
if (def->pagesize)
|
|
virBufferAsprintf(&childBuf, "<pagesize unit='KiB'>%llu</pagesize>\n",
|
|
def->pagesize);
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
|
|
virBufferEscapeString(&childBuf, "<path>%s</path>\n", def->nvdimmPath);
|
|
|
|
if (def->alignsize)
|
|
virBufferAsprintf(&childBuf, "<alignsize unit='KiB'>%llu</alignsize>\n",
|
|
def->alignsize);
|
|
|
|
if (def->nvdimmPmem)
|
|
virBufferAddLit(&childBuf, "<pmem/>\n");
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
|
|
virBufferEscapeString(&childBuf, "<path>%s</path>\n", def->nvdimmPath);
|
|
break;
|
|
|
|
case VIR_DOMAIN_MEMORY_MODEL_NONE:
|
|
case VIR_DOMAIN_MEMORY_MODEL_LAST:
|
|
break;
|
|
}
|
|
|
|
virXMLFormatElement(buf, "source", NULL, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainMemoryTargetDefFormat(virBuffer *buf,
|
|
virDomainMemoryDef *def)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
virBufferAsprintf(&childBuf, "<size unit='KiB'>%llu</size>\n", def->size);
|
|
if (def->targetNode >= 0)
|
|
virBufferAsprintf(&childBuf, "<node>%d</node>\n", def->targetNode);
|
|
if (def->labelsize) {
|
|
g_auto(virBuffer) labelChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf);
|
|
|
|
virBufferAsprintf(&labelChildBuf, "<size unit='KiB'>%llu</size>\n", def->labelsize);
|
|
virXMLFormatElement(&childBuf, "label", NULL, &labelChildBuf);
|
|
}
|
|
if (def->readonly)
|
|
virBufferAddLit(&childBuf, "<readonly/>\n");
|
|
|
|
virXMLFormatElement(buf, "target", NULL, &childBuf);
|
|
}
|
|
|
|
static int
|
|
virDomainMemoryDefFormat(virBuffer *buf,
|
|
virDomainMemoryDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *model = virDomainMemoryModelTypeToString(def->model);
|
|
|
|
virBufferAsprintf(buf, "<memory model='%s'", model);
|
|
if (def->access)
|
|
virBufferAsprintf(buf, " access='%s'",
|
|
virDomainMemoryAccessTypeToString(def->access));
|
|
if (def->discard)
|
|
virBufferAsprintf(buf, " discard='%s'",
|
|
virTristateBoolTypeToString(def->discard));
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
if (def->uuid) {
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
|
|
virUUIDFormat(def->uuid, uuidstr);
|
|
virBufferAsprintf(buf, "<uuid>%s</uuid>\n", uuidstr);
|
|
}
|
|
|
|
if (virDomainMemorySourceDefFormat(buf, def) < 0)
|
|
return -1;
|
|
|
|
virDomainMemoryTargetDefFormat(buf, def);
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</memory>\n");
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virDomainVideoAccelDefFormat(virBuffer *buf,
|
|
virDomainVideoAccelDef *def)
|
|
{
|
|
virBufferAddLit(buf, "<acceleration");
|
|
if (def->accel3d) {
|
|
virBufferAsprintf(buf, " accel3d='%s'",
|
|
virTristateBoolTypeToString(def->accel3d));
|
|
}
|
|
if (def->accel2d) {
|
|
virBufferAsprintf(buf, " accel2d='%s'",
|
|
virTristateBoolTypeToString(def->accel2d));
|
|
}
|
|
virBufferEscapeString(buf, " rendernode='%s'", def->rendernode);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
static void
|
|
virDomainVideoResolutionDefFormat(virBuffer *buf,
|
|
virDomainVideoResolutionDef *def)
|
|
{
|
|
virBufferAddLit(buf, "<resolution");
|
|
if (def->x && def->y) {
|
|
virBufferAsprintf(buf, " x='%u' y='%u'",
|
|
def->x, def->y);
|
|
}
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
static int
|
|
virDomainVideoDefFormat(virBuffer *buf,
|
|
virDomainVideoDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *model = virDomainVideoTypeToString(def->type);
|
|
g_auto(virBuffer) driverBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (!model) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected video model %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAddLit(buf, "<video>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virDomainVirtioOptionsFormat(&driverBuf, def->virtio);
|
|
if (virBufferUse(&driverBuf) || (def->driver && def->driver->vgaconf) ||
|
|
def->backend != VIR_DOMAIN_VIDEO_BACKEND_TYPE_DEFAULT) {
|
|
virBufferAddLit(buf, "<driver");
|
|
if (virBufferUse(&driverBuf))
|
|
virBufferAddBuffer(buf, &driverBuf);
|
|
if (def->driver && def->driver->vgaconf)
|
|
virBufferAsprintf(buf, " vgaconf='%s'",
|
|
virDomainVideoVGAConfTypeToString(def->driver->vgaconf));
|
|
if (def->backend != VIR_DOMAIN_VIDEO_BACKEND_TYPE_DEFAULT)
|
|
virBufferAsprintf(buf, " name='%s'",
|
|
virDomainVideoBackendTypeToString(def->backend));
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
virBufferAsprintf(buf, "<model type='%s'",
|
|
model);
|
|
if (def->ram)
|
|
virBufferAsprintf(buf, " ram='%u'", def->ram);
|
|
if (def->vram)
|
|
virBufferAsprintf(buf, " vram='%u'", def->vram);
|
|
if (def->vram64)
|
|
virBufferAsprintf(buf, " vram64='%u'", def->vram64);
|
|
if (def->vgamem)
|
|
virBufferAsprintf(buf, " vgamem='%u'", def->vgamem);
|
|
if (def->heads)
|
|
virBufferAsprintf(buf, " heads='%u'", def->heads);
|
|
if (def->primary)
|
|
virBufferAddLit(buf, " primary='yes'");
|
|
if (def->accel || def->res) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
if (def->accel)
|
|
virDomainVideoAccelDefFormat(buf, def->accel);
|
|
if (def->res)
|
|
virDomainVideoResolutionDefFormat(buf, def->res);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</model>\n");
|
|
} else {
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</video>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainInputDefFormat(virBuffer *buf,
|
|
virDomainInputDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *type = virDomainInputTypeToString(def->type);
|
|
const char *bus = virDomainInputBusTypeToString(def->bus);
|
|
const char *grab = virDomainInputSourceGrabTypeToString(def->source.grab);
|
|
const char *repeat = virTristateSwitchTypeToString(def->source.repeat);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) driverAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) sourceAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
/* don't format keyboard into migratable XML for backward compatibility */
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE &&
|
|
def->type == VIR_DOMAIN_INPUT_TYPE_KBD &&
|
|
(def->bus == VIR_DOMAIN_INPUT_BUS_PS2 ||
|
|
def->bus == VIR_DOMAIN_INPUT_BUS_XEN))
|
|
return 0;
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected input type %d"), def->type);
|
|
return -1;
|
|
}
|
|
if (!bus) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected input bus type %d"), def->bus);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(&attrBuf, " type='%s'", type);
|
|
if (def->bus != VIR_DOMAIN_INPUT_BUS_NONE)
|
|
virBufferAsprintf(&attrBuf, " bus='%s'", bus);
|
|
|
|
if (def->model) {
|
|
const char *model = virDomainInputModelTypeToString(def->model);
|
|
|
|
if (!model) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected input model %d"), def->model);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(&attrBuf, " model='%s'", model);
|
|
}
|
|
|
|
virDomainVirtioOptionsFormat(&driverAttrBuf, def->virtio);
|
|
|
|
virXMLFormatElement(&childBuf, "driver", &driverAttrBuf, NULL);
|
|
|
|
if (def->type == VIR_DOMAIN_INPUT_TYPE_EVDEV)
|
|
virBufferEscapeString(&sourceAttrBuf, " dev='%s'", def->source.evdev);
|
|
else
|
|
virBufferEscapeString(&sourceAttrBuf, " evdev='%s'", def->source.evdev);
|
|
|
|
if (def->source.grab)
|
|
virBufferAsprintf(&sourceAttrBuf, " grab='%s'", grab);
|
|
if (def->source.repeat)
|
|
virBufferAsprintf(&sourceAttrBuf, " repeat='%s'", repeat);
|
|
|
|
virXMLFormatElement(&childBuf, "source", &sourceAttrBuf, NULL);
|
|
|
|
virDomainDeviceInfoFormat(&childBuf, &def->info, flags);
|
|
|
|
virXMLFormatElement(buf, "input", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainTimerDefFormat(virBuffer *buf,
|
|
virDomainTimerDef *def)
|
|
{
|
|
const char *name = virDomainTimerNameTypeToString(def->name);
|
|
|
|
if (!name) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected timer name %d"), def->name);
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(buf, "<timer name='%s'", name);
|
|
|
|
if (def->present == 0) {
|
|
virBufferAddLit(buf, " present='no'");
|
|
} else if (def->present == 1) {
|
|
virBufferAddLit(buf, " present='yes'");
|
|
}
|
|
|
|
if (def->tickpolicy != -1) {
|
|
const char *tickpolicy
|
|
= virDomainTimerTickpolicyTypeToString(def->tickpolicy);
|
|
if (!tickpolicy) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected timer tickpolicy %d"),
|
|
def->tickpolicy);
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(buf, " tickpolicy='%s'", tickpolicy);
|
|
}
|
|
|
|
if ((def->name == VIR_DOMAIN_TIMER_NAME_PLATFORM)
|
|
|| (def->name == VIR_DOMAIN_TIMER_NAME_RTC)) {
|
|
if (def->track != -1) {
|
|
const char *track
|
|
= virDomainTimerTrackTypeToString(def->track);
|
|
if (!track) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected timer track %d"),
|
|
def->track);
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(buf, " track='%s'", track);
|
|
}
|
|
}
|
|
|
|
if (def->name == VIR_DOMAIN_TIMER_NAME_TSC) {
|
|
if (def->frequency > 0)
|
|
virBufferAsprintf(buf, " frequency='%llu'", def->frequency);
|
|
|
|
if (def->mode != -1) {
|
|
const char *mode
|
|
= virDomainTimerModeTypeToString(def->mode);
|
|
if (!mode) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected timer mode %d"),
|
|
def->mode);
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(buf, " mode='%s'", mode);
|
|
}
|
|
}
|
|
|
|
if (def->catchup.threshold == 0 && def->catchup.slew == 0 &&
|
|
def->catchup.limit == 0) {
|
|
virBufferAddLit(buf, "/>\n");
|
|
} else {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAddLit(buf, "<catchup");
|
|
if (def->catchup.threshold > 0)
|
|
virBufferAsprintf(buf, " threshold='%lu'", def->catchup.threshold);
|
|
if (def->catchup.slew > 0)
|
|
virBufferAsprintf(buf, " slew='%lu'", def->catchup.slew);
|
|
if (def->catchup.limit > 0)
|
|
virBufferAsprintf(buf, " limit='%lu'", def->catchup.limit);
|
|
virBufferAddLit(buf, "/>\n");
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</timer>\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virDomainGraphicsAuthDefFormatAttr(virBuffer *buf,
|
|
virDomainGraphicsAuthDef *def,
|
|
unsigned int flags)
|
|
{
|
|
if (!def->passwd)
|
|
return;
|
|
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_SECURE)
|
|
virBufferEscapeString(buf, " passwd='%s'",
|
|
def->passwd);
|
|
|
|
if (def->expires) {
|
|
g_autoptr(GDateTime) then = NULL;
|
|
g_autofree char *thenstr = NULL;
|
|
|
|
then = g_date_time_new_from_unix_utc(def->validTo);
|
|
thenstr = g_date_time_format(then, "%Y-%m-%dT%H:%M:%S");
|
|
virBufferAsprintf(buf, " passwdValidTo='%s'", thenstr);
|
|
}
|
|
|
|
if (def->connected)
|
|
virBufferEscapeString(buf, " connected='%s'",
|
|
virDomainGraphicsAuthConnectedTypeToString(def->connected));
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainGraphicsListenDefFormat(virBuffer *buf,
|
|
virDomainGraphicsListenDef *def,
|
|
unsigned int flags)
|
|
{
|
|
/* If generating migratable XML, skip listen address
|
|
* dragged in from config file */
|
|
if ((flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE) && def->fromConfig)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "<listen");
|
|
virBufferAsprintf(buf, " type='%s'",
|
|
virDomainGraphicsListenTypeToString(def->type));
|
|
|
|
if (def->address &&
|
|
(def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS ||
|
|
(def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK &&
|
|
!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE)))) {
|
|
/* address may also be set to show current status when type='network',
|
|
* but we don't want to print that if INACTIVE data is requested. */
|
|
virBufferAsprintf(buf, " address='%s'", def->address);
|
|
}
|
|
|
|
if (def->network &&
|
|
(def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK)) {
|
|
virBufferEscapeString(buf, " network='%s'", def->network);
|
|
}
|
|
|
|
if (def->socket &&
|
|
def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET &&
|
|
!(def->autoGenerated &&
|
|
(flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE))) {
|
|
virBufferEscapeString(buf, " socket='%s'", def->socket);
|
|
}
|
|
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_STATUS) {
|
|
virBufferAsprintf(buf, " fromConfig='%d'", def->fromConfig);
|
|
virBufferAsprintf(buf, " autoGenerated='%s'",
|
|
def->autoGenerated ? "yes" : "no");
|
|
}
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainGraphicsListenDefFormatAddr:
|
|
* @buf: buffer where the output XML is written
|
|
* @glisten: first listen element
|
|
* @flags: bit-wise or of VIR_DOMAIN_DEF_FORMAT_*
|
|
*
|
|
* This is used to add a legacy 'listen' attribute into <graphics> element to
|
|
* improve backward compatibility.
|
|
*/
|
|
static void
|
|
virDomainGraphicsListenDefFormatAddr(virBuffer *buf,
|
|
virDomainGraphicsListenDef *glisten,
|
|
unsigned int flags)
|
|
{
|
|
if (!glisten)
|
|
return;
|
|
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE && glisten->fromConfig)
|
|
return;
|
|
|
|
if (glisten->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK &&
|
|
flags & (VIR_DOMAIN_DEF_FORMAT_INACTIVE |
|
|
VIR_DOMAIN_DEF_FORMAT_MIGRATABLE))
|
|
return;
|
|
|
|
if (glisten->address)
|
|
virBufferAsprintf(buf, " listen='%s'", glisten->address);
|
|
}
|
|
|
|
static void
|
|
virDomainSpiceGLDefFormat(virBuffer *buf, virDomainGraphicsDef *def)
|
|
{
|
|
if (def->data.spice.gl == VIR_TRISTATE_BOOL_ABSENT)
|
|
return;
|
|
|
|
virBufferAsprintf(buf, "<gl enable='%s'",
|
|
virTristateBoolTypeToString(def->data.spice.gl));
|
|
virBufferEscapeString(buf, " rendernode='%s'", def->data.spice.rendernode);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
static int
|
|
virDomainGraphicsDefFormat(virBuffer *buf,
|
|
virDomainGraphicsDef *def,
|
|
unsigned int flags)
|
|
{
|
|
virDomainGraphicsListenDef *glisten = virDomainGraphicsGetListen(def, 0);
|
|
const char *type = virDomainGraphicsTypeToString(def->type);
|
|
bool children = false;
|
|
size_t i;
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected net type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<graphics type='%s'", type);
|
|
|
|
switch (def->type) {
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
|
|
if (!glisten) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing listen element for graphics"));
|
|
return -1;
|
|
}
|
|
|
|
switch (glisten->type) {
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET:
|
|
/* To not break migration we shouldn't print the 'socket' attribute
|
|
* if it's auto-generated or if it's based on config option from
|
|
* qemu.conf. If the socket is provided by user we need to print it
|
|
* into migratable XML. */
|
|
if (glisten->socket &&
|
|
!((glisten->autoGenerated || glisten->fromConfig) &&
|
|
(flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE))) {
|
|
virBufferEscapeString(buf, " socket='%s'", glisten->socket);
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS:
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK:
|
|
if (def->data.vnc.port &&
|
|
(!def->data.vnc.autoport || !(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE)))
|
|
virBufferAsprintf(buf, " port='%d'",
|
|
def->data.vnc.port);
|
|
else if (def->data.vnc.autoport)
|
|
virBufferAddLit(buf, " port='-1'");
|
|
|
|
virBufferAsprintf(buf, " autoport='%s'",
|
|
def->data.vnc.autoport ? "yes" : "no");
|
|
|
|
if (def->data.vnc.websocketGenerated &&
|
|
(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE))
|
|
virBufferAddLit(buf, " websocket='-1'");
|
|
else if (def->data.vnc.websocket)
|
|
virBufferAsprintf(buf, " websocket='%d'", def->data.vnc.websocket);
|
|
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_STATUS)
|
|
virBufferAsprintf(buf, " websocketGenerated='%s'",
|
|
def->data.vnc.websocketGenerated ? "yes" : "no");
|
|
|
|
virDomainGraphicsListenDefFormatAddr(buf, glisten, flags);
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE:
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (def->data.vnc.keymap)
|
|
virBufferEscapeString(buf, " keymap='%s'",
|
|
def->data.vnc.keymap);
|
|
|
|
if (def->data.vnc.sharePolicy)
|
|
virBufferAsprintf(buf, " sharePolicy='%s'",
|
|
virDomainGraphicsVNCSharePolicyTypeToString(
|
|
def->data.vnc.sharePolicy));
|
|
|
|
if (def->data.vnc.powerControl)
|
|
virBufferAsprintf(buf, " powerControl='%s'",
|
|
virTristateBoolTypeToString(def->data.vnc.powerControl));
|
|
|
|
virDomainGraphicsAuthDefFormatAttr(buf, &def->data.vnc.auth, flags);
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
|
|
if (def->data.sdl.display)
|
|
virBufferEscapeString(buf, " display='%s'",
|
|
def->data.sdl.display);
|
|
|
|
if (def->data.sdl.xauth)
|
|
virBufferEscapeString(buf, " xauth='%s'",
|
|
def->data.sdl.xauth);
|
|
if (def->data.sdl.fullscreen)
|
|
virBufferAddLit(buf, " fullscreen='yes'");
|
|
|
|
if (!children && def->data.sdl.gl != VIR_TRISTATE_BOOL_ABSENT) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
children = true;
|
|
}
|
|
|
|
if (def->data.sdl.gl != VIR_TRISTATE_BOOL_ABSENT) {
|
|
virBufferAsprintf(buf, "<gl enable='%s'",
|
|
virTristateBoolTypeToString(def->data.sdl.gl));
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
|
|
if (def->data.rdp.port)
|
|
virBufferAsprintf(buf, " port='%d'",
|
|
def->data.rdp.port);
|
|
else if (def->data.rdp.autoport)
|
|
virBufferAddLit(buf, " port='0'");
|
|
|
|
if (def->data.rdp.autoport)
|
|
virBufferAddLit(buf, " autoport='yes'");
|
|
|
|
if (def->data.rdp.replaceUser)
|
|
virBufferAddLit(buf, " replaceUser='yes'");
|
|
|
|
if (def->data.rdp.multiUser)
|
|
virBufferAddLit(buf, " multiUser='yes'");
|
|
|
|
virDomainGraphicsListenDefFormatAddr(buf, glisten, flags);
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
|
|
if (def->data.desktop.display)
|
|
virBufferEscapeString(buf, " display='%s'",
|
|
def->data.desktop.display);
|
|
|
|
if (def->data.desktop.fullscreen)
|
|
virBufferAddLit(buf, " fullscreen='yes'");
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
|
|
if (!glisten) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing listen element for spice graphics"));
|
|
return -1;
|
|
}
|
|
|
|
switch (glisten->type) {
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS:
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK:
|
|
if (def->data.spice.port)
|
|
virBufferAsprintf(buf, " port='%d'",
|
|
def->data.spice.port);
|
|
|
|
if (def->data.spice.tlsPort)
|
|
virBufferAsprintf(buf, " tlsPort='%d'",
|
|
def->data.spice.tlsPort);
|
|
|
|
virBufferAsprintf(buf, " autoport='%s'",
|
|
def->data.spice.autoport ? "yes" : "no");
|
|
|
|
virDomainGraphicsListenDefFormatAddr(buf, glisten, flags);
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE:
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE)
|
|
virBufferAddStr(buf, " autoport='no'");
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET:
|
|
/* If socket is auto-generated based on config option we don't
|
|
* add any listen element into migratable XML because the original
|
|
* listen type is "address".
|
|
* We need to set autoport to make sure that libvirt on destination
|
|
* will parse it as listen type "address", without autoport it is
|
|
* parsed as listen type "none". */
|
|
if ((flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE) &&
|
|
glisten->fromConfig) {
|
|
virBufferAddStr(buf, " autoport='yes'");
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (def->data.spice.keymap)
|
|
virBufferEscapeString(buf, " keymap='%s'",
|
|
def->data.spice.keymap);
|
|
|
|
if (def->data.spice.defaultMode != VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_ANY)
|
|
virBufferAsprintf(buf, " defaultMode='%s'",
|
|
virDomainGraphicsSpiceChannelModeTypeToString(def->data.spice.defaultMode));
|
|
|
|
virDomainGraphicsAuthDefFormatAttr(buf, &def->data.spice.auth, flags);
|
|
break;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
|
|
if (!def->data.egl_headless.rendernode)
|
|
break;
|
|
|
|
if (!children) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
children = true;
|
|
}
|
|
|
|
virBufferAddLit(buf, "<gl");
|
|
virBufferEscapeString(buf, " rendernode='%s'",
|
|
def->data.egl_headless.rendernode);
|
|
virBufferAddLit(buf, "/>\n");
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < def->nListens; i++) {
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_MIGRATABLE) {
|
|
/* If the listen is based on config options from qemu.conf we need
|
|
* to skip it. It's up to user to properly configure both hosts for
|
|
* migration. */
|
|
if (def->listens[i].fromConfig)
|
|
continue;
|
|
|
|
/* If the socket is provided by user in the XML we need to skip this
|
|
* listen type to support migration back to old libvirt since old
|
|
* libvirt supports specifying socket path inside graphics element
|
|
* as 'socket' attribute. Auto-generated socket is a new feature
|
|
* thus we can generate it in the migrateble XML. */
|
|
if (def->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC &&
|
|
def->listens[i].type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET &&
|
|
def->listens[i].socket &&
|
|
!def->listens[i].autoGenerated)
|
|
continue;
|
|
|
|
/* The new listen type none is in the migratable XML represented as
|
|
* port=0 and autoport=no because old libvirt support this
|
|
* configuration for spice. */
|
|
if (def->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE &&
|
|
def->listens[i].type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE)
|
|
continue;
|
|
}
|
|
if (!children) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
children = true;
|
|
}
|
|
virDomainGraphicsListenDefFormat(buf, &def->listens[i], flags);
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) {
|
|
for (i = 0; i < VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_LAST; i++) {
|
|
int mode = def->data.spice.channels[i];
|
|
if (mode == VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_ANY)
|
|
continue;
|
|
|
|
if (!children) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
children = true;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<channel name='%s' mode='%s'/>\n",
|
|
virDomainGraphicsSpiceChannelNameTypeToString(i),
|
|
virDomainGraphicsSpiceChannelModeTypeToString(mode));
|
|
}
|
|
if (!children && (def->data.spice.image || def->data.spice.jpeg ||
|
|
def->data.spice.zlib || def->data.spice.playback ||
|
|
def->data.spice.streaming || def->data.spice.copypaste ||
|
|
def->data.spice.mousemode || def->data.spice.filetransfer ||
|
|
def->data.spice.gl)) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
children = true;
|
|
}
|
|
if (def->data.spice.image)
|
|
virBufferAsprintf(buf, "<image compression='%s'/>\n",
|
|
virDomainGraphicsSpiceImageCompressionTypeToString(def->data.spice.image));
|
|
if (def->data.spice.jpeg)
|
|
virBufferAsprintf(buf, "<jpeg compression='%s'/>\n",
|
|
virDomainGraphicsSpiceJpegCompressionTypeToString(def->data.spice.jpeg));
|
|
if (def->data.spice.zlib)
|
|
virBufferAsprintf(buf, "<zlib compression='%s'/>\n",
|
|
virDomainGraphicsSpiceZlibCompressionTypeToString(def->data.spice.zlib));
|
|
if (def->data.spice.playback)
|
|
virBufferAsprintf(buf, "<playback compression='%s'/>\n",
|
|
virTristateSwitchTypeToString(def->data.spice.playback));
|
|
if (def->data.spice.streaming)
|
|
virBufferAsprintf(buf, "<streaming mode='%s'/>\n",
|
|
virDomainGraphicsSpiceStreamingModeTypeToString(def->data.spice.streaming));
|
|
if (def->data.spice.mousemode)
|
|
virBufferAsprintf(buf, "<mouse mode='%s'/>\n",
|
|
virDomainGraphicsSpiceMouseModeTypeToString(def->data.spice.mousemode));
|
|
if (def->data.spice.copypaste)
|
|
virBufferAsprintf(buf, "<clipboard copypaste='%s'/>\n",
|
|
virTristateBoolTypeToString(def->data.spice.copypaste));
|
|
if (def->data.spice.filetransfer)
|
|
virBufferAsprintf(buf, "<filetransfer enable='%s'/>\n",
|
|
virTristateBoolTypeToString(def->data.spice.filetransfer));
|
|
|
|
virDomainSpiceGLDefFormat(buf, def);
|
|
}
|
|
|
|
if (def->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) {
|
|
if (!children) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
children = true;
|
|
}
|
|
|
|
if (def->data.vnc.audioId > 0)
|
|
virBufferAsprintf(buf, "<audio id='%d'/>\n",
|
|
def->data.vnc.audioId);
|
|
}
|
|
|
|
if (children) {
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</graphics>\n");
|
|
} else {
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHostdevDefFormat(virBuffer *buf,
|
|
virDomainHostdevDef *def,
|
|
unsigned int flags,
|
|
virDomainXMLOption *xmlopt)
|
|
{
|
|
const char *mode = virDomainHostdevModeTypeToString(def->mode);
|
|
virDomainHostdevSubsysSCSI *scsisrc = &def->source.subsys.u.scsi;
|
|
virDomainHostdevSubsysMediatedDev *mdevsrc = &def->source.subsys.u.mdev;
|
|
virDomainHostdevSubsysSCSIVHost *scsihostsrc = &def->source.subsys.u.scsi_host;
|
|
const char *type;
|
|
|
|
if (!mode) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected hostdev mode %d"), def->mode);
|
|
return -1;
|
|
}
|
|
|
|
switch (def->mode) {
|
|
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
|
|
type = virDomainHostdevSubsysTypeToString(def->source.subsys.type);
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected hostdev type %d"),
|
|
def->source.subsys.type);
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
|
|
type = virDomainHostdevCapsTypeToString(def->source.caps.type);
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected hostdev type %d"),
|
|
def->source.caps.type);
|
|
return -1;
|
|
}
|
|
break;
|
|
default:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected hostdev mode %d"), def->mode);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "<hostdev mode='%s' type='%s'",
|
|
mode, type);
|
|
if (def->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) {
|
|
virBufferAsprintf(buf, " managed='%s'",
|
|
def->managed ? "yes" : "no");
|
|
|
|
if (def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI &&
|
|
scsisrc->sgio)
|
|
virBufferAsprintf(buf, " sgio='%s'",
|
|
virDomainDeviceSGIOTypeToString(scsisrc->sgio));
|
|
|
|
if (def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI &&
|
|
scsisrc->rawio) {
|
|
virBufferAsprintf(buf, " rawio='%s'",
|
|
virTristateBoolTypeToString(scsisrc->rawio));
|
|
}
|
|
|
|
if (def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST &&
|
|
scsihostsrc->model) {
|
|
virBufferAsprintf(buf, " model='%s'",
|
|
virDomainHostdevSubsysSCSIVHostModelTypeToString(scsihostsrc->model));
|
|
}
|
|
|
|
if (def->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV) {
|
|
virBufferAsprintf(buf, " model='%s'",
|
|
virMediatedDeviceModelTypeToString(mdevsrc->model));
|
|
if (mdevsrc->display != VIR_TRISTATE_SWITCH_ABSENT)
|
|
virBufferAsprintf(buf, " display='%s'",
|
|
virTristateSwitchTypeToString(mdevsrc->display));
|
|
if (mdevsrc->ramfb != VIR_TRISTATE_SWITCH_ABSENT)
|
|
virBufferAsprintf(buf, " ramfb='%s'",
|
|
virTristateSwitchTypeToString(mdevsrc->ramfb));
|
|
}
|
|
|
|
}
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
switch (def->mode) {
|
|
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
|
|
if (virDomainHostdevDefFormatSubsys(buf, def, flags, false, xmlopt) < 0)
|
|
return -1;
|
|
break;
|
|
case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
|
|
if (virDomainHostdevDefFormatCaps(buf, def) < 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
virDomainNetTeamingInfoFormat(def->teaming, buf);
|
|
|
|
if (def->readonly)
|
|
virBufferAddLit(buf, "<readonly/>\n");
|
|
if (def->shareable)
|
|
virBufferAddLit(buf, "<shareable/>\n");
|
|
|
|
virDomainDeviceInfoFormat(buf, def->info, flags | VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT
|
|
| VIR_DOMAIN_DEF_FORMAT_ALLOW_ROM);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</hostdev>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainRedirdevDefFormat(virBuffer *buf,
|
|
virDomainRedirdevDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *bus;
|
|
|
|
bus = virDomainRedirdevBusTypeToString(def->bus);
|
|
|
|
virBufferAsprintf(buf, "<redirdev bus='%s'", bus);
|
|
if (virDomainChrAttrsDefFormat(buf, def->source, false) < 0)
|
|
return -1;
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
virDomainChrSourceDefFormat(buf, def->source, flags);
|
|
|
|
virDomainDeviceInfoFormat(buf, &def->info, flags | VIR_DOMAIN_DEF_FORMAT_ALLOW_BOOT);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</redirdev>\n");
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virDomainRedirFilterDefFormat(virBuffer *buf,
|
|
virDomainRedirFilterDef *filter)
|
|
{
|
|
size_t i;
|
|
|
|
/* no need format an empty redirfilter */
|
|
if (filter->nusbdevs == 0)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "<redirfilter>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
for (i = 0; i < filter->nusbdevs; i++) {
|
|
virDomainRedirFilterUSBDevDef *usbdev = filter->usbdevs[i];
|
|
virBufferAddLit(buf, "<usbdev");
|
|
if (usbdev->usbClass >= 0)
|
|
virBufferAsprintf(buf, " class='0x%02X'", usbdev->usbClass);
|
|
|
|
if (usbdev->vendor >= 0)
|
|
virBufferAsprintf(buf, " vendor='0x%04X'", usbdev->vendor);
|
|
|
|
if (usbdev->product >= 0)
|
|
virBufferAsprintf(buf, " product='0x%04X'", usbdev->product);
|
|
|
|
if (usbdev->version >= 0)
|
|
virBufferAsprintf(buf, " version='%d.%02d'",
|
|
((usbdev->version & 0xf000) >> 12) * 10 +
|
|
((usbdev->version & 0x0f00) >> 8),
|
|
((usbdev->version & 0x00f0) >> 4) * 10 +
|
|
((usbdev->version & 0x000f) >> 0));
|
|
|
|
virBufferAsprintf(buf, " allow='%s'/>\n", usbdev->allow ? "yes" : "no");
|
|
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</redirfilter>\n");
|
|
}
|
|
|
|
static int
|
|
virDomainHubDefFormat(virBuffer *buf,
|
|
virDomainHubDef *def,
|
|
unsigned int flags)
|
|
{
|
|
const char *type = virDomainHubTypeToString(def->type);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (!type) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected hub type %d"), def->type);
|
|
return -1;
|
|
}
|
|
|
|
virDomainDeviceInfoFormat(&childBuf, &def->info, flags);
|
|
|
|
virBufferAsprintf(&attrBuf, " type='%s'", type);
|
|
|
|
virXMLFormatElement(buf, "hub", &attrBuf, &childBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainResourceDefFormat(virBuffer *buf,
|
|
virDomainResourceDef *def)
|
|
{
|
|
virBufferAddLit(buf, "<resource>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferEscapeString(buf, "<partition>%s</partition>\n", def->partition);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</resource>\n");
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainHugepagesFormatBuf(virBuffer *buf,
|
|
virDomainHugePage *hugepage)
|
|
{
|
|
virBufferAsprintf(buf, "<page size='%llu' unit='KiB'",
|
|
hugepage->size);
|
|
|
|
if (hugepage->nodemask) {
|
|
g_autofree char *nodeset = NULL;
|
|
if (!(nodeset = virBitmapFormat(hugepage->nodemask)))
|
|
return -1;
|
|
virBufferAsprintf(buf, " nodeset='%s'", nodeset);
|
|
}
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virDomainHugepagesFormat(virBuffer *buf,
|
|
virDomainHugePage *hugepages,
|
|
size_t nhugepages)
|
|
{
|
|
size_t i;
|
|
|
|
if (nhugepages == 1 &&
|
|
hugepages[0].size == 0) {
|
|
virBufferAddLit(buf, "<hugepages/>\n");
|
|
return;
|
|
}
|
|
|
|
virBufferAddLit(buf, "<hugepages>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
for (i = 0; i < nhugepages; i++)
|
|
virDomainHugepagesFormatBuf(buf, &hugepages[i]);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</hugepages>\n");
|
|
}
|
|
|
|
static void
|
|
virDomainLoaderDefFormat(virBuffer *buf,
|
|
virDomainLoaderDef *loader)
|
|
{
|
|
const char *readonly = virTristateBoolTypeToString(loader->readonly);
|
|
const char *secure = virTristateBoolTypeToString(loader->secure);
|
|
const char *type = virDomainLoaderTypeToString(loader->type);
|
|
|
|
virBufferAddLit(buf, "<loader");
|
|
|
|
if (loader->readonly)
|
|
virBufferAsprintf(buf, " readonly='%s'", readonly);
|
|
|
|
if (loader->secure)
|
|
virBufferAsprintf(buf, " secure='%s'", secure);
|
|
|
|
if (loader->type != VIR_DOMAIN_LOADER_TYPE_NONE)
|
|
virBufferAsprintf(buf, " type='%s'", type);
|
|
|
|
if (loader->path)
|
|
virBufferEscapeString(buf, ">%s</loader>\n", loader->path);
|
|
else
|
|
virBufferAddLit(buf, "/>\n");
|
|
|
|
if (loader->nvram || loader->templt) {
|
|
virBufferAddLit(buf, "<nvram");
|
|
virBufferEscapeString(buf, " template='%s'", loader->templt);
|
|
if (loader->nvram)
|
|
virBufferEscapeString(buf, ">%s</nvram>\n", loader->nvram);
|
|
else
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
virDomainKeyWrapDefFormat(virBuffer *buf, virDomainKeyWrapDef *keywrap)
|
|
{
|
|
virBufferAddLit(buf, "<keywrap>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
if (keywrap->aes)
|
|
virBufferAsprintf(buf, "<cipher name='aes' state='%s'/>\n",
|
|
virTristateSwitchTypeToString(keywrap->aes));
|
|
|
|
if (keywrap->dea)
|
|
virBufferAsprintf(buf, "<cipher name='dea' state='%s'/>\n",
|
|
virTristateSwitchTypeToString(keywrap->dea));
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</keywrap>\n");
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainSEVDefFormat(virBuffer *buf, virDomainSEVDef *sev)
|
|
{
|
|
if (!sev)
|
|
return;
|
|
|
|
virBufferAsprintf(buf, "<launchSecurity type='%s'>\n",
|
|
virDomainLaunchSecurityTypeToString(sev->sectype));
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
if (sev->haveCbitpos)
|
|
virBufferAsprintf(buf, "<cbitpos>%d</cbitpos>\n", sev->cbitpos);
|
|
|
|
if (sev->haveReducedPhysBits)
|
|
virBufferAsprintf(buf, "<reducedPhysBits>%d</reducedPhysBits>\n",
|
|
sev->reduced_phys_bits);
|
|
virBufferAsprintf(buf, "<policy>0x%04x</policy>\n", sev->policy);
|
|
if (sev->dh_cert)
|
|
virBufferEscapeString(buf, "<dhCert>%s</dhCert>\n", sev->dh_cert);
|
|
|
|
if (sev->session)
|
|
virBufferEscapeString(buf, "<session>%s</session>\n", sev->session);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</launchSecurity>\n");
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainPerfDefFormat(virBuffer *buf, virDomainPerfDef *perf)
|
|
{
|
|
size_t i;
|
|
bool wantPerf = false;
|
|
|
|
for (i = 0; i < VIR_PERF_EVENT_LAST; i++) {
|
|
if (perf->events[i])
|
|
wantPerf = true;
|
|
}
|
|
if (!wantPerf)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "<perf>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
for (i = 0; i < VIR_PERF_EVENT_LAST; i++) {
|
|
if (perf->events[i])
|
|
virBufferAsprintf(buf, "<event name='%s' enabled='%s'/>\n",
|
|
virPerfEventTypeToString(i),
|
|
virTristateBoolTypeToString(perf->events[i]));
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</perf>\n");
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainSchedulerFormat(virBuffer *buf,
|
|
const char *name,
|
|
virDomainThreadSchedParam *sched,
|
|
size_t id,
|
|
bool multiple_threads)
|
|
{
|
|
switch (sched->policy) {
|
|
case VIR_PROC_POLICY_BATCH:
|
|
case VIR_PROC_POLICY_IDLE:
|
|
virBufferAsprintf(buf, "<%ssched", name);
|
|
if (multiple_threads)
|
|
virBufferAsprintf(buf, " %ss='%zu'", name, id);
|
|
virBufferAsprintf(buf, " scheduler='%s'/>\n",
|
|
virProcessSchedPolicyTypeToString(sched->policy));
|
|
break;
|
|
|
|
case VIR_PROC_POLICY_RR:
|
|
case VIR_PROC_POLICY_FIFO:
|
|
virBufferAsprintf(buf, "<%ssched", name);
|
|
if (multiple_threads)
|
|
virBufferAsprintf(buf, " %ss='%zu'", name, id);
|
|
virBufferAsprintf(buf, " scheduler='%s' priority='%d'/>\n",
|
|
virProcessSchedPolicyTypeToString(sched->policy),
|
|
sched->priority);
|
|
break;
|
|
|
|
case VIR_PROC_POLICY_NONE:
|
|
case VIR_PROC_POLICY_LAST:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainCachetuneDefFormatHelper(unsigned int level,
|
|
virCacheType type,
|
|
unsigned int cache,
|
|
unsigned long long size,
|
|
void *opaque)
|
|
{
|
|
const char *unit;
|
|
virBuffer *buf = opaque;
|
|
unsigned long long short_size = virFormatIntPretty(size, &unit);
|
|
|
|
virBufferAsprintf(buf,
|
|
"<cache id='%u' level='%u' type='%s' "
|
|
"size='%llu' unit='%s'/>\n",
|
|
cache, level, virCacheTypeToString(type),
|
|
short_size, unit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainResctrlMonDefFormatHelper(virDomainResctrlMonDef *domresmon,
|
|
virResctrlMonitorType tag,
|
|
virBuffer *buf)
|
|
{
|
|
g_autofree char *vcpus = NULL;
|
|
|
|
if (domresmon->tag != tag)
|
|
return 0;
|
|
|
|
virBufferAddLit(buf, "<monitor ");
|
|
|
|
if (tag == VIR_RESCTRL_MONITOR_TYPE_CACHE) {
|
|
virBufferAsprintf(buf, "level='%u' ",
|
|
VIR_DOMAIN_RESCTRL_MONITOR_CACHELEVEL);
|
|
}
|
|
|
|
vcpus = virBitmapFormat(domresmon->vcpus);
|
|
if (!vcpus)
|
|
return -1;
|
|
|
|
virBufferAsprintf(buf, "vcpus='%s'/>\n", vcpus);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainCachetuneDefFormat(virBuffer *buf,
|
|
virDomainResctrlDef *resctrl,
|
|
unsigned int flags)
|
|
{
|
|
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
size_t i = 0;
|
|
g_autofree char *vcpus = NULL;
|
|
|
|
if (virResctrlAllocForeachCache(resctrl->alloc,
|
|
virDomainCachetuneDefFormatHelper,
|
|
&childrenBuf) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < resctrl->nmonitors; i++) {
|
|
if (virDomainResctrlMonDefFormatHelper(resctrl->monitors[i],
|
|
VIR_RESCTRL_MONITOR_TYPE_CACHE,
|
|
&childrenBuf) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!virBufferUse(&childrenBuf))
|
|
return 0;
|
|
|
|
vcpus = virBitmapFormat(resctrl->vcpus);
|
|
if (!vcpus)
|
|
return -1;
|
|
|
|
virBufferAsprintf(&attrBuf, " vcpus='%s'", vcpus);
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE)) {
|
|
const char *alloc_id = virResctrlAllocGetID(resctrl->alloc);
|
|
if (!alloc_id)
|
|
return -1;
|
|
|
|
virBufferAsprintf(&attrBuf, " id='%s'", alloc_id);
|
|
}
|
|
|
|
virXMLFormatElement(buf, "cachetune", &attrBuf, &childrenBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainMemorytuneDefFormatHelper(unsigned int id,
|
|
unsigned int bandwidth,
|
|
void *opaque)
|
|
{
|
|
virBuffer *buf = opaque;
|
|
|
|
virBufferAsprintf(buf,
|
|
"<node id='%u' bandwidth='%u'/>\n",
|
|
id, bandwidth);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainMemorytuneDefFormat(virBuffer *buf,
|
|
virDomainResctrlDef *resctrl,
|
|
unsigned int flags)
|
|
{
|
|
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_autofree char *vcpus = NULL;
|
|
size_t i = 0;
|
|
|
|
if (virResctrlAllocForeachMemory(resctrl->alloc,
|
|
virDomainMemorytuneDefFormatHelper,
|
|
&childrenBuf) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i< resctrl->nmonitors; i++) {
|
|
if (virDomainResctrlMonDefFormatHelper(resctrl->monitors[i],
|
|
VIR_RESCTRL_MONITOR_TYPE_MEMBW,
|
|
&childrenBuf) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!virBufferUse(&childrenBuf))
|
|
return 0;
|
|
|
|
vcpus = virBitmapFormat(resctrl->vcpus);
|
|
if (!vcpus)
|
|
return -1;
|
|
|
|
virBufferAsprintf(&attrBuf, " vcpus='%s'", vcpus);
|
|
|
|
if (!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE)) {
|
|
const char *alloc_id = virResctrlAllocGetID(resctrl->alloc);
|
|
if (!alloc_id)
|
|
return -1;
|
|
|
|
virBufferAsprintf(&attrBuf, " id='%s'", alloc_id);
|
|
}
|
|
|
|
virXMLFormatElement(buf, "memorytune", &attrBuf, &childrenBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainCputuneDefFormat(virBuffer *buf,
|
|
virDomainDef *def,
|
|
unsigned int flags)
|
|
{
|
|
size_t i;
|
|
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
|
|
if (def->cputune.sharesSpecified)
|
|
virBufferAsprintf(&childrenBuf, "<shares>%llu</shares>\n",
|
|
def->cputune.shares);
|
|
if (def->cputune.period)
|
|
virBufferAsprintf(&childrenBuf, "<period>%llu</period>\n",
|
|
def->cputune.period);
|
|
if (def->cputune.quota)
|
|
virBufferAsprintf(&childrenBuf, "<quota>%lld</quota>\n",
|
|
def->cputune.quota);
|
|
if (def->cputune.global_period)
|
|
virBufferAsprintf(&childrenBuf, "<global_period>%llu</global_period>\n",
|
|
def->cputune.global_period);
|
|
if (def->cputune.global_quota)
|
|
virBufferAsprintf(&childrenBuf, "<global_quota>%lld</global_quota>\n",
|
|
def->cputune.global_quota);
|
|
|
|
if (def->cputune.emulator_period)
|
|
virBufferAsprintf(&childrenBuf, "<emulator_period>%llu"
|
|
"</emulator_period>\n",
|
|
def->cputune.emulator_period);
|
|
|
|
if (def->cputune.emulator_quota)
|
|
virBufferAsprintf(&childrenBuf, "<emulator_quota>%lld"
|
|
"</emulator_quota>\n",
|
|
def->cputune.emulator_quota);
|
|
|
|
if (def->cputune.iothread_period)
|
|
virBufferAsprintf(&childrenBuf, "<iothread_period>%llu"
|
|
"</iothread_period>\n",
|
|
def->cputune.iothread_period);
|
|
|
|
if (def->cputune.iothread_quota)
|
|
virBufferAsprintf(&childrenBuf, "<iothread_quota>%lld"
|
|
"</iothread_quota>\n",
|
|
def->cputune.iothread_quota);
|
|
|
|
for (i = 0; i < def->maxvcpus; i++) {
|
|
char *cpumask;
|
|
virDomainVcpuDef *vcpu = def->vcpus[i];
|
|
|
|
if (!vcpu->cpumask)
|
|
continue;
|
|
|
|
if (!(cpumask = virBitmapFormat(vcpu->cpumask)))
|
|
return -1;
|
|
|
|
virBufferAsprintf(&childrenBuf,
|
|
"<vcpupin vcpu='%zu' cpuset='%s'/>\n", i, cpumask);
|
|
|
|
VIR_FREE(cpumask);
|
|
}
|
|
|
|
if (def->cputune.emulatorpin) {
|
|
char *cpumask;
|
|
virBufferAddLit(&childrenBuf, "<emulatorpin ");
|
|
|
|
if (!(cpumask = virBitmapFormat(def->cputune.emulatorpin)))
|
|
return -1;
|
|
|
|
virBufferAsprintf(&childrenBuf, "cpuset='%s'/>\n", cpumask);
|
|
VIR_FREE(cpumask);
|
|
}
|
|
|
|
for (i = 0; i < def->niothreadids; i++) {
|
|
char *cpumask;
|
|
|
|
/* Ignore iothreadids with no cpumask */
|
|
if (!def->iothreadids[i]->cpumask)
|
|
continue;
|
|
|
|
virBufferAsprintf(&childrenBuf, "<iothreadpin iothread='%u' ",
|
|
def->iothreadids[i]->iothread_id);
|
|
|
|
if (!(cpumask = virBitmapFormat(def->iothreadids[i]->cpumask)))
|
|
return -1;
|
|
|
|
virBufferAsprintf(&childrenBuf, "cpuset='%s'/>\n", cpumask);
|
|
VIR_FREE(cpumask);
|
|
}
|
|
|
|
if (def->cputune.emulatorsched) {
|
|
virDomainSchedulerFormat(&childrenBuf, "emulator",
|
|
def->cputune.emulatorsched, 0, false);
|
|
}
|
|
|
|
for (i = 0; i < def->maxvcpus; i++) {
|
|
virDomainSchedulerFormat(&childrenBuf, "vcpu",
|
|
&def->vcpus[i]->sched, i, true);
|
|
}
|
|
|
|
for (i = 0; i < def->niothreadids; i++) {
|
|
virDomainSchedulerFormat(&childrenBuf, "iothread",
|
|
&def->iothreadids[i]->sched,
|
|
def->iothreadids[i]->iothread_id,
|
|
true);
|
|
}
|
|
|
|
for (i = 0; i < def->nresctrls; i++)
|
|
virDomainCachetuneDefFormat(&childrenBuf, def->resctrls[i], flags);
|
|
|
|
for (i = 0; i < def->nresctrls; i++)
|
|
virDomainMemorytuneDefFormat(&childrenBuf, def->resctrls[i], flags);
|
|
|
|
virXMLFormatElement(buf, "cputune", NULL, &childrenBuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainCpuDefFormat(virBuffer *buf,
|
|
const virDomainDef *def)
|
|
{
|
|
virDomainVcpuDef *vcpu;
|
|
size_t i;
|
|
g_autofree char *cpumask = NULL;
|
|
|
|
virBufferAddLit(buf, "<vcpu");
|
|
virBufferAsprintf(buf, " placement='%s'",
|
|
virDomainCpuPlacementModeTypeToString(def->placement_mode));
|
|
|
|
if (def->cpumask && !virBitmapIsAllSet(def->cpumask)) {
|
|
if ((cpumask = virBitmapFormat(def->cpumask)) == NULL)
|
|
return -1;
|
|
virBufferAsprintf(buf, " cpuset='%s'", cpumask);
|
|
}
|
|
if (virDomainDefHasVcpusOffline(def))
|
|
virBufferAsprintf(buf, " current='%u'", virDomainDefGetVcpus(def));
|
|
virBufferAsprintf(buf, ">%u</vcpu>\n", virDomainDefGetVcpusMax(def));
|
|
|
|
if (def->individualvcpus) {
|
|
virBufferAddLit(buf, "<vcpus>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
for (i = 0; i < def->maxvcpus; i++) {
|
|
vcpu = def->vcpus[i];
|
|
|
|
virBufferAsprintf(buf, "<vcpu id='%zu' enabled='%s'",
|
|
i, vcpu->online ? "yes" : "no");
|
|
if (vcpu->hotpluggable)
|
|
virBufferAsprintf(buf, " hotpluggable='%s'",
|
|
virTristateBoolTypeToString(vcpu->hotpluggable));
|
|
|
|
if (vcpu->order != 0)
|
|
virBufferAsprintf(buf, " order='%d'", vcpu->order);
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</vcpus>\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
virDomainDefIothreadShouldFormat(virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->niothreadids; i++) {
|
|
if (!def->iothreadids[i]->autofill)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainIOMMUDefFormat(virBuffer *buf,
|
|
const virDomainIOMMUDef *iommu)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) driverAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (iommu->intremap != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(&driverAttrBuf, " intremap='%s'",
|
|
virTristateSwitchTypeToString(iommu->intremap));
|
|
}
|
|
if (iommu->caching_mode != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(&driverAttrBuf, " caching_mode='%s'",
|
|
virTristateSwitchTypeToString(iommu->caching_mode));
|
|
}
|
|
if (iommu->eim != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(&driverAttrBuf, " eim='%s'",
|
|
virTristateSwitchTypeToString(iommu->eim));
|
|
}
|
|
if (iommu->iotlb != VIR_TRISTATE_SWITCH_ABSENT) {
|
|
virBufferAsprintf(&driverAttrBuf, " iotlb='%s'",
|
|
virTristateSwitchTypeToString(iommu->iotlb));
|
|
}
|
|
if (iommu->aw_bits > 0) {
|
|
virBufferAsprintf(&driverAttrBuf, " aw_bits='%d'",
|
|
iommu->aw_bits);
|
|
}
|
|
|
|
virXMLFormatElement(&childBuf, "driver", &driverAttrBuf, NULL);
|
|
|
|
virBufferAsprintf(&attrBuf, " model='%s'",
|
|
virDomainIOMMUModelTypeToString(iommu->model));
|
|
|
|
virXMLFormatElement(buf, "iommu", &attrBuf, &childBuf);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainMemtuneFormat(virBuffer *buf,
|
|
const virDomainMemtune *mem)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (virMemoryLimitIsSet(mem->hard_limit)) {
|
|
virBufferAsprintf(&childBuf,
|
|
"<hard_limit unit='KiB'>%llu</hard_limit>\n",
|
|
mem->hard_limit);
|
|
}
|
|
if (virMemoryLimitIsSet(mem->soft_limit)) {
|
|
virBufferAsprintf(&childBuf,
|
|
"<soft_limit unit='KiB'>%llu</soft_limit>\n",
|
|
mem->soft_limit);
|
|
}
|
|
if (mem->min_guarantee) {
|
|
virBufferAsprintf(&childBuf,
|
|
"<min_guarantee unit='KiB'>%llu</min_guarantee>\n",
|
|
mem->min_guarantee);
|
|
}
|
|
if (virMemoryLimitIsSet(mem->swap_hard_limit)) {
|
|
virBufferAsprintf(&childBuf,
|
|
"<swap_hard_limit unit='KiB'>%llu</swap_hard_limit>\n",
|
|
mem->swap_hard_limit);
|
|
}
|
|
|
|
virXMLFormatElement(buf, "memtune", NULL, &childBuf);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainMemorybackingFormat(virBuffer *buf,
|
|
const virDomainMemtune *mem)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
|
|
if (mem->nhugepages)
|
|
virDomainHugepagesFormat(&childBuf, mem->hugepages, mem->nhugepages);
|
|
if (mem->nosharepages)
|
|
virBufferAddLit(&childBuf, "<nosharepages/>\n");
|
|
if (mem->locked)
|
|
virBufferAddLit(&childBuf, "<locked/>\n");
|
|
if (mem->source)
|
|
virBufferAsprintf(&childBuf, "<source type='%s'/>\n",
|
|
virDomainMemorySourceTypeToString(mem->source));
|
|
if (mem->access)
|
|
virBufferAsprintf(&childBuf, "<access mode='%s'/>\n",
|
|
virDomainMemoryAccessTypeToString(mem->access));
|
|
if (mem->allocation)
|
|
virBufferAsprintf(&childBuf, "<allocation mode='%s'/>\n",
|
|
virDomainMemoryAllocationTypeToString(mem->allocation));
|
|
if (mem->discard)
|
|
virBufferAddLit(&childBuf, "<discard/>\n");
|
|
|
|
virXMLFormatElement(buf, "memoryBacking", NULL, &childBuf);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainVsockDefFormat(virBuffer *buf,
|
|
virDomainVsockDef *vsock)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) cidAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) drvAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (vsock->model) {
|
|
virBufferAsprintf(&attrBuf, " model='%s'",
|
|
virDomainVsockModelTypeToString(vsock->model));
|
|
}
|
|
|
|
if (vsock->auto_cid != VIR_TRISTATE_BOOL_ABSENT) {
|
|
virBufferAsprintf(&cidAttrBuf, " auto='%s'",
|
|
virTristateBoolTypeToString(vsock->auto_cid));
|
|
}
|
|
if (vsock->guest_cid != 0)
|
|
virBufferAsprintf(&cidAttrBuf, " address='%u'", vsock->guest_cid);
|
|
virXMLFormatElement(&childBuf, "cid", &cidAttrBuf, NULL);
|
|
|
|
virDomainDeviceInfoFormat(&childBuf, &vsock->info, 0);
|
|
|
|
virDomainVirtioOptionsFormat(&drvAttrBuf, vsock->virtio);
|
|
|
|
virXMLFormatElement(&childBuf, "driver", &drvAttrBuf, NULL);
|
|
virXMLFormatElement(buf, "vsock", &attrBuf, &childBuf);
|
|
}
|
|
|
|
|
|
static void
|
|
virDomainDefFormatBlkiotune(virBuffer *buf,
|
|
virDomainDef *def)
|
|
{
|
|
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
ssize_t n;
|
|
|
|
if (def->blkio.weight)
|
|
virBufferAsprintf(&childrenBuf, "<weight>%u</weight>\n",
|
|
def->blkio.weight);
|
|
|
|
for (n = 0; n < def->blkio.ndevices; n++) {
|
|
virBlkioDevice *dev = &def->blkio.devices[n];
|
|
|
|
if (!dev->weight && !dev->riops && !dev->wiops &&
|
|
!dev->rbps && !dev->wbps)
|
|
continue;
|
|
virBufferAddLit(&childrenBuf, "<device>\n");
|
|
virBufferAdjustIndent(&childrenBuf, 2);
|
|
virBufferEscapeString(&childrenBuf, "<path>%s</path>\n",
|
|
dev->path);
|
|
if (dev->weight)
|
|
virBufferAsprintf(&childrenBuf, "<weight>%u</weight>\n",
|
|
dev->weight);
|
|
if (dev->riops)
|
|
virBufferAsprintf(&childrenBuf, "<read_iops_sec>%u</read_iops_sec>\n",
|
|
dev->riops);
|
|
if (dev->wiops)
|
|
virBufferAsprintf(&childrenBuf, "<write_iops_sec>%u</write_iops_sec>\n",
|
|
dev->wiops);
|
|
if (dev->rbps)
|
|
virBufferAsprintf(&childrenBuf, "<read_bytes_sec>%llu</read_bytes_sec>\n",
|
|
dev->rbps);
|
|
if (dev->wbps)
|
|
virBufferAsprintf(&childrenBuf, "<write_bytes_sec>%llu</write_bytes_sec>\n",
|
|
dev->wbps);
|
|
virBufferAdjustIndent(&childrenBuf, -2);
|
|
virBufferAddLit(&childrenBuf, "</device>\n");
|
|
}
|
|
|
|
virXMLFormatElement(buf, "blkiotune", NULL, &childrenBuf);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefFormatFeatures(virBuffer *buf,
|
|
virDomainDef *def)
|
|
{
|
|
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
|
|
size_t i;
|
|
|
|
for (i = 0; i < VIR_DOMAIN_FEATURE_LAST; i++) {
|
|
g_auto(virBuffer) tmpAttrBuf = VIR_BUFFER_INITIALIZER;
|
|
g_auto(virBuffer) tmpChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf);
|
|
const char *name = virDomainFeatureTypeToString(i);
|
|
size_t j;
|
|
|
|
switch ((virDomainFeature) i) {
|
|
case VIR_DOMAIN_FEATURE_ACPI:
|
|
case VIR_DOMAIN_FEATURE_PAE:
|
|
case VIR_DOMAIN_FEATURE_VIRIDIAN:
|
|
case VIR_DOMAIN_FEATURE_PRIVNET:
|
|
/* NOTE: This is for old style <opt/> booleans. New XML
|
|
* should use the explicit state=on|off output below */
|
|
switch ((virTristateSwitch) def->features[i]) {
|
|
case VIR_TRISTATE_SWITCH_ABSENT:
|
|
break;
|
|
|
|
case VIR_TRISTATE_SWITCH_ON:
|
|
virBufferAsprintf(&childBuf, "<%s/>\n", name);
|
|
break;
|
|
|
|
case VIR_TRISTATE_SWITCH_LAST:
|
|
case VIR_TRISTATE_SWITCH_OFF:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unexpected state of feature '%s'"), name);
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_VMCOREINFO:
|
|
case VIR_DOMAIN_FEATURE_HAP:
|
|
case VIR_DOMAIN_FEATURE_PMU:
|
|
case VIR_DOMAIN_FEATURE_PVSPINLOCK:
|
|
case VIR_DOMAIN_FEATURE_VMPORT:
|
|
case VIR_DOMAIN_FEATURE_HTM:
|
|
case VIR_DOMAIN_FEATURE_NESTED_HV:
|
|
case VIR_DOMAIN_FEATURE_CCF_ASSIST:
|
|
switch ((virTristateSwitch) def->features[i]) {
|
|
case VIR_TRISTATE_SWITCH_LAST:
|
|
case VIR_TRISTATE_SWITCH_ABSENT:
|
|
break;
|
|
|
|
case VIR_TRISTATE_SWITCH_ON:
|
|
virBufferAsprintf(&childBuf, "<%s state='on'/>\n", name);
|
|
break;
|
|
|
|
case VIR_TRISTATE_SWITCH_OFF:
|
|
virBufferAsprintf(&childBuf, "<%s state='off'/>\n", name);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_SMM:
|
|
if (def->features[i] == VIR_TRISTATE_SWITCH_ABSENT)
|
|
break;
|
|
|
|
virBufferAsprintf(&tmpAttrBuf, " state='%s'",
|
|
virTristateSwitchTypeToString(def->features[i]));
|
|
|
|
if (def->features[i] == VIR_TRISTATE_SWITCH_ON &&
|
|
def->tseg_specified) {
|
|
const char *unit;
|
|
unsigned long long short_size = virFormatIntPretty(def->tseg_size,
|
|
&unit);
|
|
|
|
virBufferAsprintf(&tmpChildBuf, "<tseg unit='%s'>%llu</tseg>\n",
|
|
unit, short_size);
|
|
}
|
|
|
|
virXMLFormatElement(&childBuf, "smm", &tmpAttrBuf, &tmpChildBuf);
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_APIC:
|
|
if (def->features[i] == VIR_TRISTATE_SWITCH_ON) {
|
|
virBufferAddLit(&childBuf, "<apic");
|
|
if (def->apic_eoi) {
|
|
virBufferAsprintf(&childBuf, " eoi='%s'",
|
|
virTristateSwitchTypeToString(def->apic_eoi));
|
|
}
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_HYPERV:
|
|
if (def->features[i] != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
virBufferAddLit(&childBuf, "<hyperv>\n");
|
|
virBufferAdjustIndent(&childBuf, 2);
|
|
for (j = 0; j < VIR_DOMAIN_HYPERV_LAST; j++) {
|
|
if (def->hyperv_features[j] == VIR_TRISTATE_SWITCH_ABSENT)
|
|
continue;
|
|
|
|
virBufferAsprintf(&childBuf, "<%s state='%s'",
|
|
virDomainHypervTypeToString(j),
|
|
virTristateSwitchTypeToString(
|
|
def->hyperv_features[j]));
|
|
|
|
switch ((virDomainHyperv) j) {
|
|
case VIR_DOMAIN_HYPERV_RELAXED:
|
|
case VIR_DOMAIN_HYPERV_VAPIC:
|
|
case VIR_DOMAIN_HYPERV_VPINDEX:
|
|
case VIR_DOMAIN_HYPERV_RUNTIME:
|
|
case VIR_DOMAIN_HYPERV_SYNIC:
|
|
case VIR_DOMAIN_HYPERV_RESET:
|
|
case VIR_DOMAIN_HYPERV_FREQUENCIES:
|
|
case VIR_DOMAIN_HYPERV_REENLIGHTENMENT:
|
|
case VIR_DOMAIN_HYPERV_TLBFLUSH:
|
|
case VIR_DOMAIN_HYPERV_IPI:
|
|
case VIR_DOMAIN_HYPERV_EVMCS:
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_SPINLOCKS:
|
|
if (def->hyperv_features[j] != VIR_TRISTATE_SWITCH_ON) {
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
break;
|
|
}
|
|
virBufferAsprintf(&childBuf, " retries='%d'/>\n",
|
|
def->hyperv_spinlocks);
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_STIMER:
|
|
if (def->hyperv_features[j] != VIR_TRISTATE_SWITCH_ON) {
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
break;
|
|
}
|
|
if (def->hyperv_stimer_direct == VIR_TRISTATE_SWITCH_ON) {
|
|
virBufferAddLit(&childBuf, ">\n");
|
|
virBufferAdjustIndent(&childBuf, 2);
|
|
virBufferAddLit(&childBuf, "<direct state='on'/>\n");
|
|
virBufferAdjustIndent(&childBuf, -2);
|
|
virBufferAddLit(&childBuf, "</stimer>\n");
|
|
} else {
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_VENDOR_ID:
|
|
if (def->hyperv_features[j] != VIR_TRISTATE_SWITCH_ON) {
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
break;
|
|
}
|
|
virBufferEscapeString(&childBuf, " value='%s'/>\n",
|
|
def->hyperv_vendor_id);
|
|
break;
|
|
|
|
case VIR_DOMAIN_HYPERV_LAST:
|
|
break;
|
|
}
|
|
}
|
|
virBufferAdjustIndent(&childBuf, -2);
|
|
virBufferAddLit(&childBuf, "</hyperv>\n");
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_KVM:
|
|
if (def->features[i] != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
virBufferAddLit(&childBuf, "<kvm>\n");
|
|
virBufferAdjustIndent(&childBuf, 2);
|
|
for (j = 0; j < VIR_DOMAIN_KVM_LAST; j++) {
|
|
switch ((virDomainKVM) j) {
|
|
case VIR_DOMAIN_KVM_HIDDEN:
|
|
case VIR_DOMAIN_KVM_DEDICATED:
|
|
case VIR_DOMAIN_KVM_POLLCONTROL:
|
|
if (def->kvm_features[j])
|
|
virBufferAsprintf(&childBuf, "<%s state='%s'/>\n",
|
|
virDomainKVMTypeToString(j),
|
|
virTristateSwitchTypeToString(
|
|
def->kvm_features[j]));
|
|
break;
|
|
|
|
case VIR_DOMAIN_KVM_LAST:
|
|
break;
|
|
}
|
|
}
|
|
virBufferAdjustIndent(&childBuf, -2);
|
|
virBufferAddLit(&childBuf, "</kvm>\n");
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_XEN:
|
|
if (def->features[i] != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
virBufferAddLit(&childBuf, "<xen>\n");
|
|
virBufferAdjustIndent(&childBuf, 2);
|
|
for (j = 0; j < VIR_DOMAIN_XEN_LAST; j++) {
|
|
if (def->xen_features[j] == VIR_TRISTATE_SWITCH_ABSENT)
|
|
continue;
|
|
|
|
virBufferAsprintf(&childBuf, "<%s state='%s'",
|
|
virDomainXenTypeToString(j),
|
|
virTristateSwitchTypeToString(
|
|
def->xen_features[j]));
|
|
|
|
switch ((virDomainXen) j) {
|
|
case VIR_DOMAIN_XEN_E820_HOST:
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
break;
|
|
case VIR_DOMAIN_XEN_PASSTHROUGH:
|
|
if (def->xen_features[j] != VIR_TRISTATE_SWITCH_ON) {
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
break;
|
|
}
|
|
if (def->xen_passthrough_mode == VIR_DOMAIN_XEN_PASSTHROUGH_MODE_SYNC_PT ||
|
|
def->xen_passthrough_mode == VIR_DOMAIN_XEN_PASSTHROUGH_MODE_SHARE_PT) {
|
|
virBufferEscapeString(&childBuf, " mode='%s'/>\n",
|
|
virDomainXenPassthroughModeTypeToString(def->xen_passthrough_mode));
|
|
} else {
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_XEN_LAST:
|
|
break;
|
|
}
|
|
}
|
|
virBufferAdjustIndent(&childBuf, -2);
|
|
virBufferAddLit(&childBuf, "</xen>\n");
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_CAPABILITIES:
|
|
for (j = 0; j < VIR_DOMAIN_PROCES_CAPS_FEATURE_LAST; j++) {
|
|
if (def->caps_features[j] != VIR_TRISTATE_SWITCH_ABSENT)
|
|
virBufferAsprintf(&tmpChildBuf, "<%s state='%s'/>\n",
|
|
virDomainProcessCapsFeatureTypeToString(j),
|
|
virTristateSwitchTypeToString(def->caps_features[j]));
|
|
}
|
|
|
|
/* the 'default' policy should be printed if any capability is present */
|
|
if (def->features[i] != VIR_DOMAIN_CAPABILITIES_POLICY_DEFAULT ||
|
|
virBufferUse(&tmpChildBuf))
|
|
virBufferAsprintf(&tmpAttrBuf, " policy='%s'",
|
|
virDomainCapabilitiesPolicyTypeToString(def->features[i]));
|
|
|
|
virXMLFormatElement(&childBuf, "capabilities", &tmpAttrBuf, &tmpChildBuf);
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_GIC:
|
|
if (def->features[i] == VIR_TRISTATE_SWITCH_ON) {
|
|
virBufferAddLit(&childBuf, "<gic");
|
|
if (def->gic_version != VIR_GIC_VERSION_NONE)
|
|
virBufferAsprintf(&childBuf, " version='%s'",
|
|
virGICVersionTypeToString(def->gic_version));
|
|
virBufferAddLit(&childBuf, "/>\n");
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_IOAPIC:
|
|
if (def->features[i] == VIR_DOMAIN_IOAPIC_NONE)
|
|
break;
|
|
|
|
virBufferAsprintf(&childBuf, "<ioapic driver='%s'/>\n",
|
|
virDomainIOAPICTypeToString(def->features[i]));
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_HPT:
|
|
if (def->features[i] != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
if (def->hpt_resizing != VIR_DOMAIN_HPT_RESIZING_NONE) {
|
|
virBufferAsprintf(&tmpAttrBuf,
|
|
" resizing='%s'",
|
|
virDomainHPTResizingTypeToString(def->hpt_resizing));
|
|
}
|
|
if (def->hpt_maxpagesize > 0) {
|
|
virBufferAsprintf(&tmpChildBuf,
|
|
"<maxpagesize unit='KiB'>%llu</maxpagesize>\n",
|
|
def->hpt_maxpagesize);
|
|
}
|
|
|
|
virXMLFormatElement(&childBuf, "hpt", &tmpAttrBuf, &tmpChildBuf);
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_MSRS:
|
|
if (def->features[i] != VIR_TRISTATE_SWITCH_ON)
|
|
break;
|
|
|
|
virBufferAsprintf(&childBuf, "<msrs unknown='%s'/>\n",
|
|
virDomainMsrsUnknownTypeToString(def->msrs_features[VIR_DOMAIN_MSRS_UNKNOWN]));
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_CFPC:
|
|
if (def->features[i] == VIR_DOMAIN_CFPC_NONE)
|
|
break;
|
|
|
|
virBufferAsprintf(&childBuf, "<cfpc value='%s'/>\n",
|
|
virDomainCFPCTypeToString(def->features[i]));
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_SBBC:
|
|
if (def->features[i] == VIR_DOMAIN_SBBC_NONE)
|
|
break;
|
|
|
|
virBufferAsprintf(&childBuf, "<sbbc value='%s'/>\n",
|
|
virDomainSBBCTypeToString(def->features[i]));
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_IBS:
|
|
if (def->features[i] == VIR_DOMAIN_IBS_NONE)
|
|
break;
|
|
|
|
virBufferAsprintf(&childBuf, "<ibs value='%s'/>\n",
|
|
virDomainIBSTypeToString(def->features[i]));
|
|
break;
|
|
|
|
case VIR_DOMAIN_FEATURE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
virXMLFormatElement(buf, "features", NULL, &childBuf);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virDomainDefFormatInternal(virDomainDef *def,
|
|
virDomainXMLOption *xmlopt,
|
|
virBuffer *buf,
|
|
unsigned int flags)
|
|
{
|
|
return virDomainDefFormatInternalSetRootName(def, xmlopt, buf,
|
|
"domain", flags);
|
|
}
|
|
|
|
|
|
/* This internal version appends to an existing buffer
|
|
* (possibly with auto-indent), rather than flattening
|
|
* to string.
|
|
* Return -1 on failure. */
|
|
int
|
|
virDomainDefFormatInternalSetRootName(virDomainDef *def,
|
|
virDomainXMLOption *xmlopt,
|
|
virBuffer *buf,
|
|
const char *rootname,
|
|
unsigned int flags)
|
|
{
|
|
unsigned char *uuid;
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
const char *type = NULL;
|
|
int n;
|
|
size_t i;
|
|
|
|
virCheckFlags(VIR_DOMAIN_DEF_FORMAT_COMMON_FLAGS |
|
|
VIR_DOMAIN_DEF_FORMAT_STATUS |
|
|
VIR_DOMAIN_DEF_FORMAT_ACTUAL_NET |
|
|
VIR_DOMAIN_DEF_FORMAT_PCI_ORIG_STATES |
|
|
VIR_DOMAIN_DEF_FORMAT_CLOCK_ADJUST,
|
|
-1);
|
|
|
|
if (!(type = virDomainVirtTypeToString(def->virtType))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected domain type %d"), def->virtType);
|
|
return -1;
|
|
}
|
|
|
|
if (def->id == -1)
|
|
flags |= VIR_DOMAIN_DEF_FORMAT_INACTIVE;
|
|
|
|
virBufferAsprintf(buf, "<%s type='%s'", rootname, type);
|
|
if (!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE))
|
|
virBufferAsprintf(buf, " id='%d'", def->id);
|
|
if (def->namespaceData && def->ns.format)
|
|
virXMLNamespaceFormatNS(buf, &def->ns);
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
virBufferEscapeString(buf, "<name>%s</name>\n", def->name);
|
|
|
|
uuid = def->uuid;
|
|
virUUIDFormat(uuid, uuidstr);
|
|
virBufferAsprintf(buf, "<uuid>%s</uuid>\n", uuidstr);
|
|
|
|
if (def->genidRequested) {
|
|
char genidstr[VIR_UUID_STRING_BUFLEN];
|
|
|
|
virUUIDFormat(def->genid, genidstr);
|
|
virBufferAsprintf(buf, "<genid>%s</genid>\n", genidstr);
|
|
}
|
|
|
|
virBufferEscapeString(buf, "<title>%s</title>\n", def->title);
|
|
|
|
virBufferEscapeString(buf, "<description>%s</description>\n",
|
|
def->description);
|
|
|
|
if (virXMLFormatMetadata(buf, def->metadata) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefHasMemoryHotplug(def)) {
|
|
virBufferAsprintf(buf,
|
|
"<maxMemory slots='%u' unit='KiB'>%llu</maxMemory>\n",
|
|
def->mem.memory_slots, def->mem.max_memory);
|
|
}
|
|
|
|
virBufferAddLit(buf, "<memory");
|
|
if (def->mem.dump_core)
|
|
virBufferAsprintf(buf, " dumpCore='%s'",
|
|
virTristateSwitchTypeToString(def->mem.dump_core));
|
|
virBufferAsprintf(buf, " unit='KiB'>%llu</memory>\n",
|
|
virDomainDefGetMemoryTotal(def));
|
|
|
|
virBufferAsprintf(buf, "<currentMemory unit='KiB'>%llu</currentMemory>\n",
|
|
def->mem.cur_balloon);
|
|
|
|
virDomainDefFormatBlkiotune(buf, def);
|
|
|
|
virDomainMemtuneFormat(buf, &def->mem);
|
|
virDomainMemorybackingFormat(buf, &def->mem);
|
|
|
|
if (virDomainCpuDefFormat(buf, def) < 0)
|
|
return -1;
|
|
|
|
if (def->niothreadids > 0) {
|
|
virBufferAsprintf(buf, "<iothreads>%zu</iothreads>\n",
|
|
def->niothreadids);
|
|
if (virDomainDefIothreadShouldFormat(def)) {
|
|
virBufferAddLit(buf, "<iothreadids>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
for (i = 0; i < def->niothreadids; i++) {
|
|
virBufferAsprintf(buf, "<iothread id='%u'/>\n",
|
|
def->iothreadids[i]->iothread_id);
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</iothreadids>\n");
|
|
}
|
|
}
|
|
|
|
if (virDomainCputuneDefFormat(buf, def, flags) < 0)
|
|
return -1;
|
|
|
|
if (virDomainNumatuneFormatXML(buf, def->numa) < 0)
|
|
return -1;
|
|
|
|
if (def->resource)
|
|
virDomainResourceDefFormat(buf, def->resource);
|
|
|
|
for (i = 0; i < def->nsysinfo; i++) {
|
|
if (virSysinfoFormat(buf, def->sysinfo[i]) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (def->os.bootloader) {
|
|
virBufferEscapeString(buf, "<bootloader>%s</bootloader>\n",
|
|
def->os.bootloader);
|
|
virBufferEscapeString(buf,
|
|
"<bootloader_args>%s</bootloader_args>\n",
|
|
def->os.bootloaderArgs);
|
|
}
|
|
|
|
virBufferAddLit(buf, "<os");
|
|
if (def->os.firmware)
|
|
virBufferAsprintf(buf, " firmware='%s'",
|
|
virDomainOsDefFirmwareTypeToString(def->os.firmware));
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAddLit(buf, "<type");
|
|
if (def->os.arch)
|
|
virBufferAsprintf(buf, " arch='%s'", virArchToString(def->os.arch));
|
|
if (def->os.machine)
|
|
virBufferAsprintf(buf, " machine='%s'", def->os.machine);
|
|
/*
|
|
* HACK: For xen driver we previously used bogus 'linux' as the
|
|
* os type for paravirt, whereas capabilities declare it to
|
|
* be 'xen'. So we convert to the former for backcompat
|
|
*/
|
|
if (def->virtType == VIR_DOMAIN_VIRT_XEN &&
|
|
def->os.type == VIR_DOMAIN_OSTYPE_XEN)
|
|
virBufferAsprintf(buf, ">%s</type>\n",
|
|
virDomainOSTypeToString(VIR_DOMAIN_OSTYPE_LINUX));
|
|
else
|
|
virBufferAsprintf(buf, ">%s</type>\n",
|
|
virDomainOSTypeToString(def->os.type));
|
|
|
|
if (def->os.firmwareFeatures) {
|
|
virBufferAddLit(buf, "<firmware>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
for (i = 0; i < VIR_DOMAIN_OS_DEF_FIRMWARE_FEATURE_LAST; i++) {
|
|
if (def->os.firmwareFeatures[i] == VIR_TRISTATE_BOOL_ABSENT)
|
|
continue;
|
|
|
|
virBufferAsprintf(buf, "<feature enabled='%s' name='%s'/>\n",
|
|
virTristateBoolTypeToString(def->os.firmwareFeatures[i]),
|
|
virDomainOsDefFirmwareFeatureTypeToString(i));
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
|
|
virBufferAddLit(buf, "</firmware>\n");
|
|
}
|
|
|
|
virBufferEscapeString(buf, "<init>%s</init>\n",
|
|
def->os.init);
|
|
for (i = 0; def->os.initargv && def->os.initargv[i]; i++)
|
|
virBufferEscapeString(buf, "<initarg>%s</initarg>\n",
|
|
def->os.initargv[i]);
|
|
for (i = 0; def->os.initenv && def->os.initenv[i]; i++)
|
|
virBufferAsprintf(buf, "<initenv name='%s'>%s</initenv>\n",
|
|
def->os.initenv[i]->name, def->os.initenv[i]->value);
|
|
if (def->os.initdir)
|
|
virBufferEscapeString(buf, "<initdir>%s</initdir>\n",
|
|
def->os.initdir);
|
|
if (def->os.inituser)
|
|
virBufferAsprintf(buf, "<inituser>%s</inituser>\n", def->os.inituser);
|
|
if (def->os.initgroup)
|
|
virBufferAsprintf(buf, "<initgroup>%s</initgroup>\n", def->os.initgroup);
|
|
|
|
if (def->os.loader)
|
|
virDomainLoaderDefFormat(buf, def->os.loader);
|
|
virBufferEscapeString(buf, "<kernel>%s</kernel>\n",
|
|
def->os.kernel);
|
|
virBufferEscapeString(buf, "<initrd>%s</initrd>\n",
|
|
def->os.initrd);
|
|
virBufferEscapeString(buf, "<cmdline>%s</cmdline>\n",
|
|
def->os.cmdline);
|
|
virBufferEscapeString(buf, "<dtb>%s</dtb>\n",
|
|
def->os.dtb);
|
|
virBufferEscapeString(buf, "<root>%s</root>\n",
|
|
def->os.root);
|
|
if (def->os.slic_table) {
|
|
virBufferAddLit(buf, "<acpi>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferEscapeString(buf, "<table type='slic'>%s</table>\n",
|
|
def->os.slic_table);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</acpi>\n");
|
|
}
|
|
|
|
if (!def->os.bootloader) {
|
|
for (n = 0; n < def->os.nBootDevs; n++) {
|
|
const char *boottype =
|
|
virDomainBootTypeToString(def->os.bootDevs[n]);
|
|
if (!boottype) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected boot device type %d"),
|
|
def->os.bootDevs[n]);
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(buf, "<boot dev='%s'/>\n", boottype);
|
|
}
|
|
|
|
if (def->os.bootmenu) {
|
|
virBufferAsprintf(buf, "<bootmenu enable='%s'",
|
|
virTristateBoolTypeToString(def->os.bootmenu));
|
|
if (def->os.bm_timeout_set)
|
|
virBufferAsprintf(buf, " timeout='%u'", def->os.bm_timeout);
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
|
|
if (def->os.bios.useserial || def->os.bios.rt_set) {
|
|
virBufferAddLit(buf, "<bios");
|
|
if (def->os.bios.useserial)
|
|
virBufferAsprintf(buf, " useserial='%s'",
|
|
virTristateBoolTypeToString(def->os.bios.useserial));
|
|
if (def->os.bios.rt_set)
|
|
virBufferAsprintf(buf, " rebootTimeout='%d'", def->os.bios.rt_delay);
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
if (def->os.smbios_mode) {
|
|
const char *mode;
|
|
|
|
mode = virDomainSmbiosModeTypeToString(def->os.smbios_mode);
|
|
if (mode == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected smbios mode %d"), def->os.smbios_mode);
|
|
return -1;
|
|
}
|
|
virBufferAsprintf(buf, "<smbios mode='%s'/>\n", mode);
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</os>\n");
|
|
|
|
|
|
if (def->idmap.uidmap) {
|
|
virBufferAddLit(buf, "<idmap>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
for (i = 0; i < def->idmap.nuidmap; i++) {
|
|
virBufferAsprintf(buf,
|
|
"<uid start='%u' target='%u' count='%u'/>\n",
|
|
def->idmap.uidmap[i].start,
|
|
def->idmap.uidmap[i].target,
|
|
def->idmap.uidmap[i].count);
|
|
}
|
|
for (i = 0; i < def->idmap.ngidmap; i++) {
|
|
virBufferAsprintf(buf,
|
|
"<gid start='%u' target='%u' count='%u'/>\n",
|
|
def->idmap.gidmap[i].start,
|
|
def->idmap.gidmap[i].target,
|
|
def->idmap.gidmap[i].count);
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</idmap>\n");
|
|
}
|
|
|
|
if (virDomainDefFormatFeatures(buf, def) < 0)
|
|
return -1;
|
|
|
|
if (virCPUDefFormatBufFull(buf, def->cpu, def->numa) < 0)
|
|
return -1;
|
|
|
|
virBufferAsprintf(buf, "<clock offset='%s'",
|
|
virDomainClockOffsetTypeToString(def->clock.offset));
|
|
switch (def->clock.offset) {
|
|
case VIR_DOMAIN_CLOCK_OFFSET_LOCALTIME:
|
|
case VIR_DOMAIN_CLOCK_OFFSET_UTC:
|
|
if (def->clock.data.utc_reset)
|
|
virBufferAddLit(buf, " adjustment='reset'");
|
|
break;
|
|
case VIR_DOMAIN_CLOCK_OFFSET_VARIABLE:
|
|
virBufferAsprintf(buf, " adjustment='%lld' basis='%s'",
|
|
def->clock.data.variable.adjustment,
|
|
virDomainClockBasisTypeToString(def->clock.data.variable.basis));
|
|
if (flags & VIR_DOMAIN_DEF_FORMAT_CLOCK_ADJUST) {
|
|
if (def->clock.data.variable.adjustment0)
|
|
virBufferAsprintf(buf, " adjustment0='%lld'",
|
|
def->clock.data.variable.adjustment0);
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE:
|
|
virBufferEscapeString(buf, " timezone='%s'", def->clock.data.timezone);
|
|
break;
|
|
}
|
|
if (def->clock.ntimers == 0) {
|
|
virBufferAddLit(buf, "/>\n");
|
|
} else {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
for (n = 0; n < def->clock.ntimers; n++) {
|
|
if (virDomainTimerDefFormat(buf, def->clock.timers[n]) < 0)
|
|
return -1;
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</clock>\n");
|
|
}
|
|
|
|
if (virDomainEventActionDefFormat(buf, def->onPoweroff,
|
|
"on_poweroff",
|
|
virDomainLifecycleActionTypeToString) < 0)
|
|
return -1;
|
|
if (virDomainEventActionDefFormat(buf, def->onReboot,
|
|
"on_reboot",
|
|
virDomainLifecycleActionTypeToString) < 0)
|
|
return -1;
|
|
if (virDomainEventActionDefFormat(buf, def->onCrash,
|
|
"on_crash",
|
|
virDomainLifecycleActionTypeToString) < 0)
|
|
return -1;
|
|
if (def->onLockFailure != VIR_DOMAIN_LOCK_FAILURE_DEFAULT &&
|
|
virDomainEventActionDefFormat(buf, def->onLockFailure,
|
|
"on_lockfailure",
|
|
virDomainLockFailureTypeToString) < 0)
|
|
return -1;
|
|
|
|
if (def->pm.s3 || def->pm.s4) {
|
|
virBufferAddLit(buf, "<pm>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
if (def->pm.s3) {
|
|
virBufferAsprintf(buf, "<suspend-to-mem enabled='%s'/>\n",
|
|
virTristateBoolTypeToString(def->pm.s3));
|
|
}
|
|
if (def->pm.s4) {
|
|
virBufferAsprintf(buf, "<suspend-to-disk enabled='%s'/>\n",
|
|
virTristateBoolTypeToString(def->pm.s4));
|
|
}
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</pm>\n");
|
|
}
|
|
|
|
virDomainPerfDefFormat(buf, &def->perf);
|
|
|
|
virBufferAddLit(buf, "<devices>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
|
|
virBufferEscapeString(buf, "<emulator>%s</emulator>\n",
|
|
def->emulator);
|
|
|
|
for (n = 0; n < def->ndisks; n++)
|
|
if (virDomainDiskDefFormat(buf, def->disks[n], flags, xmlopt) < 0)
|
|
return -1;
|
|
|
|
for (n = 0; n < def->ncontrollers; n++)
|
|
if (virDomainControllerDefFormat(buf, def->controllers[n], flags) < 0)
|
|
return -1;
|
|
|
|
for (n = 0; n < def->nleases; n++)
|
|
virDomainLeaseDefFormat(buf, def->leases[n]);
|
|
|
|
for (n = 0; n < def->nfss; n++)
|
|
if (virDomainFSDefFormat(buf, def->fss[n], flags) < 0)
|
|
return -1;
|
|
|
|
for (n = 0; n < def->nnets; n++)
|
|
if (virDomainNetDefFormat(buf, def->nets[n], xmlopt, flags) < 0)
|
|
return -1;
|
|
|
|
for (n = 0; n < def->nsmartcards; n++)
|
|
if (virDomainSmartcardDefFormat(buf, def->smartcards[n], flags) < 0)
|
|
return -1;
|
|
|
|
for (n = 0; n < def->nserials; n++)
|
|
if (virDomainChrDefFormat(buf, def->serials[n], flags) < 0)
|
|
return -1;
|
|
|
|
for (n = 0; n < def->nparallels; n++)
|
|
if (virDomainChrDefFormat(buf, def->parallels[n], flags) < 0)
|
|
return -1;
|
|
|
|
for (n = 0; n < def->nconsoles; n++) {
|
|
virDomainChrDef console;
|
|
/* Back compat, ignore the console element for hvm guests
|
|
* if it is type == serial
|
|
*/
|
|
if (def->os.type == VIR_DOMAIN_OSTYPE_HVM &&
|
|
(def->consoles[n]->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL ||
|
|
def->consoles[n]->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_NONE) &&
|
|
(n < def->nserials)) {
|
|
memcpy(&console, def->serials[n], sizeof(console));
|
|
console.deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE;
|
|
console.targetType = VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL;
|
|
} else {
|
|
memcpy(&console, def->consoles[n], sizeof(console));
|
|
}
|
|
if (virDomainChrDefFormat(buf, &console, flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (n = 0; n < def->nchannels; n++)
|
|
if (virDomainChrDefFormat(buf, def->channels[n], flags) < 0)
|
|
return -1;
|
|
|
|
for (n = 0; n < def->ninputs; n++) {
|
|
if (virDomainInputDefFormat(buf, def->inputs[n], flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (n = 0; n < def->ntpms; n++) {
|
|
if (virDomainTPMDefFormat(buf, def->tpms[n], flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (n = 0; n < def->ngraphics; n++) {
|
|
if (virDomainGraphicsDefFormat(buf, def->graphics[n], flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (n = 0; n < def->nsounds; n++) {
|
|
if (virDomainSoundDefFormat(buf, def->sounds[n], flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (n = 0; n < def->naudios; n++) {
|
|
if (virDomainAudioDefFormat(buf, def->audios[n]) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (n = 0; n < def->nvideos; n++) {
|
|
if (virDomainVideoDefFormat(buf, def->videos[n], flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
for (n = 0; n < def->nhostdevs; n++) {
|
|
/* If parentnet != NONE, this is just a pointer to the
|
|
* hostdev in a higher-level device (e.g. virDomainNetDef),
|
|
* and will have already been formatted there.
|
|
*/
|
|
if (!def->hostdevs[n]->parentnet &&
|
|
virDomainHostdevDefFormat(buf, def->hostdevs[n], flags, xmlopt) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
for (n = 0; n < def->nredirdevs; n++) {
|
|
if (virDomainRedirdevDefFormat(buf, def->redirdevs[n], flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (def->redirfilter)
|
|
virDomainRedirFilterDefFormat(buf, def->redirfilter);
|
|
|
|
for (n = 0; n < def->nhubs; n++) {
|
|
if (virDomainHubDefFormat(buf, def->hubs[n], flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (def->watchdog)
|
|
virDomainWatchdogDefFormat(buf, def->watchdog, flags);
|
|
|
|
if (def->memballoon)
|
|
virDomainMemballoonDefFormat(buf, def->memballoon, flags);
|
|
|
|
for (n = 0; n < def->nrngs; n++) {
|
|
if (virDomainRNGDefFormat(buf, def->rngs[n], flags))
|
|
return -1;
|
|
}
|
|
|
|
if (def->nvram)
|
|
virDomainNVRAMDefFormat(buf, def->nvram, flags);
|
|
|
|
for (n = 0; n < def->npanics; n++)
|
|
virDomainPanicDefFormat(buf, def->panics[n]);
|
|
|
|
for (n = 0; n < def->nshmems; n++)
|
|
virDomainShmemDefFormat(buf, def->shmems[n], flags);
|
|
|
|
for (n = 0; n < def->nmems; n++) {
|
|
if (virDomainMemoryDefFormat(buf, def->mems[n], flags) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (def->iommu)
|
|
virDomainIOMMUDefFormat(buf, def->iommu);
|
|
|
|
if (def->vsock)
|
|
virDomainVsockDefFormat(buf, def->vsock);
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</devices>\n");
|
|
|
|
for (n = 0; n < def->nseclabels; n++)
|
|
virSecurityLabelDefFormat(buf, def->seclabels[n], flags);
|
|
|
|
if (def->keywrap)
|
|
virDomainKeyWrapDefFormat(buf, def->keywrap);
|
|
|
|
virDomainSEVDefFormat(buf, def->sev);
|
|
|
|
if (def->namespaceData && def->ns.format) {
|
|
if ((def->ns.format)(buf, def->namespaceData) < 0)
|
|
return -1;
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAsprintf(buf, "</%s>\n", rootname);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Converts VIR_DOMAIN_XML_COMMON_FLAGS into VIR_DOMAIN_DEF_FORMAT_*
|
|
* flags, and silently ignores any other flags. Note that the caller
|
|
* should validate the set of flags it is willing to accept; see also
|
|
* the comment on VIR_DOMAIN_XML_COMMON_FLAGS about security
|
|
* considerations with adding new flags. */
|
|
unsigned int virDomainDefFormatConvertXMLFlags(unsigned int flags)
|
|
{
|
|
unsigned int formatFlags = 0;
|
|
|
|
if (flags & VIR_DOMAIN_XML_SECURE)
|
|
formatFlags |= VIR_DOMAIN_DEF_FORMAT_SECURE;
|
|
if (flags & VIR_DOMAIN_XML_INACTIVE)
|
|
formatFlags |= VIR_DOMAIN_DEF_FORMAT_INACTIVE;
|
|
if (flags & VIR_DOMAIN_XML_MIGRATABLE)
|
|
formatFlags |= VIR_DOMAIN_DEF_FORMAT_MIGRATABLE;
|
|
|
|
return formatFlags;
|
|
}
|
|
|
|
|
|
char *
|
|
virDomainDefFormat(virDomainDef *def,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
virCheckFlags(VIR_DOMAIN_DEF_FORMAT_COMMON_FLAGS, NULL);
|
|
if (virDomainDefFormatInternal(def, xmlopt, &buf, flags) < 0)
|
|
return NULL;
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
}
|
|
|
|
|
|
char *
|
|
virDomainObjFormat(virDomainObj *obj,
|
|
virDomainXMLOption *xmlopt,
|
|
unsigned int flags)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
int state;
|
|
int reason;
|
|
size_t i;
|
|
|
|
state = virDomainObjGetState(obj, &reason);
|
|
virBufferAsprintf(&buf, "<domstatus state='%s' reason='%s' pid='%lld'>\n",
|
|
virDomainStateTypeToString(state),
|
|
virDomainStateReasonToString(state, reason),
|
|
(long long)obj->pid);
|
|
virBufferAdjustIndent(&buf, 2);
|
|
|
|
for (i = 0; i < VIR_DOMAIN_TAINT_LAST; i++) {
|
|
if (obj->taint & (1 << i))
|
|
virBufferAsprintf(&buf, "<taint flag='%s'/>\n",
|
|
virDomainTaintTypeToString(i));
|
|
}
|
|
|
|
for (i = 0; i < obj->ndeprecations; i++) {
|
|
virBufferEscapeString(&buf, "<deprecation>%s</deprecation>\n",
|
|
obj->deprecations[i]);
|
|
}
|
|
|
|
if (xmlopt->privateData.format &&
|
|
xmlopt->privateData.format(&buf, obj) < 0)
|
|
return NULL;
|
|
|
|
if (virDomainDefFormatInternal(obj->def, xmlopt, &buf, flags) < 0)
|
|
return NULL;
|
|
|
|
virBufferAdjustIndent(&buf, -2);
|
|
virBufferAddLit(&buf, "</domstatus>\n");
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
}
|
|
|
|
static bool
|
|
virDomainDeviceIsUSB(virDomainDeviceDef *dev)
|
|
{
|
|
int t = dev->type;
|
|
if ((t == VIR_DOMAIN_DEVICE_DISK &&
|
|
dev->data.disk->bus == VIR_DOMAIN_DISK_BUS_USB) ||
|
|
(t == VIR_DOMAIN_DEVICE_INPUT &&
|
|
dev->data.input->bus == VIR_DOMAIN_INPUT_BUS_USB) ||
|
|
(t == VIR_DOMAIN_DEVICE_HOSTDEV &&
|
|
dev->data.hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
|
|
dev->data.hostdev->source.subsys.type ==
|
|
VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) ||
|
|
(t == VIR_DOMAIN_DEVICE_HUB &&
|
|
dev->data.hub->type == VIR_DOMAIN_HUB_TYPE_USB) ||
|
|
(t == VIR_DOMAIN_DEVICE_REDIRDEV &&
|
|
dev->data.redirdev->bus == VIR_DOMAIN_REDIRDEV_BUS_USB))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
typedef struct _virDomainCompatibleDeviceData virDomainCompatibleDeviceData;
|
|
struct _virDomainCompatibleDeviceData {
|
|
virDomainDeviceInfo *newInfo;
|
|
virDomainDeviceInfo *oldInfo;
|
|
};
|
|
|
|
static int
|
|
virDomainDeviceInfoCheckBootIndex(virDomainDef *def G_GNUC_UNUSED,
|
|
virDomainDeviceDef *device G_GNUC_UNUSED,
|
|
virDomainDeviceInfo *info,
|
|
void *opaque)
|
|
{
|
|
virDomainCompatibleDeviceData *data = opaque;
|
|
|
|
/* Ignore the device we're about to update */
|
|
if (data->oldInfo == info)
|
|
return 0;
|
|
|
|
if (info->bootIndex == data->newInfo->bootIndex) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("boot order %u is already used by another device"),
|
|
data->newInfo->bootIndex);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virDomainDefCompatibleDevice(virDomainDef *def,
|
|
virDomainDeviceDef *dev,
|
|
virDomainDeviceDef *oldDev,
|
|
virDomainDeviceAction action,
|
|
bool live)
|
|
{
|
|
virDomainCompatibleDeviceData data = {
|
|
.newInfo = virDomainDeviceGetInfo(dev),
|
|
.oldInfo = NULL,
|
|
};
|
|
|
|
if (oldDev)
|
|
data.oldInfo = virDomainDeviceGetInfo(oldDev);
|
|
|
|
if (action == VIR_DOMAIN_DEVICE_ACTION_UPDATE &&
|
|
live &&
|
|
(data.newInfo && data.oldInfo &&
|
|
data.newInfo->alias && data.oldInfo->alias &&
|
|
STRNEQ(data.newInfo->alias, data.oldInfo->alias))) {
|
|
virReportError(VIR_ERR_OPERATION_DENIED, "%s",
|
|
_("changing device alias is not allowed"));
|
|
return -1;
|
|
}
|
|
|
|
if (!virDomainDefHasUSB(def) &&
|
|
def->os.type != VIR_DOMAIN_OSTYPE_EXE &&
|
|
virDomainDeviceIsUSB(dev)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Device configuration is not compatible: "
|
|
"Domain has no USB bus support"));
|
|
return -1;
|
|
}
|
|
|
|
if (data.newInfo && data.newInfo->bootIndex > 0) {
|
|
if (def->os.nBootDevs > 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("per-device boot elements cannot be used"
|
|
" together with os/boot elements"));
|
|
return -1;
|
|
}
|
|
if (virDomainDeviceInfoIterate(def,
|
|
virDomainDeviceInfoCheckBootIndex,
|
|
&data) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (dev->type == VIR_DOMAIN_DEVICE_MEMORY) {
|
|
unsigned long long sz = dev->data.memory->size;
|
|
|
|
if (!virDomainDefHasMemoryHotplug(def)) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("cannot use/hotplug a memory device when domain "
|
|
"'maxMemory' is not defined"));
|
|
return -1;
|
|
}
|
|
|
|
if ((virDomainDefGetMemoryTotal(def) + sz) > def->mem.max_memory) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Attaching memory device with size '%llu' would "
|
|
"exceed domain's maxMemory config size '%llu'"),
|
|
sz, def->mem.max_memory);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virDomainDefSaveXML(virDomainDef *def,
|
|
const char *configDir,
|
|
const char *xml)
|
|
{
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
g_autofree char *configFile = NULL;
|
|
|
|
if (!configDir)
|
|
return 0;
|
|
|
|
if ((configFile = virDomainConfigFile(configDir, def->name)) == NULL)
|
|
return -1;
|
|
|
|
if (g_mkdir_with_parents(configDir, 0777) < 0) {
|
|
virReportSystemError(errno,
|
|
_("cannot create config directory '%s'"),
|
|
configDir);
|
|
return -1;
|
|
}
|
|
|
|
virUUIDFormat(def->uuid, uuidstr);
|
|
return virXMLSaveFile(configFile,
|
|
virXMLPickShellSafeComment(def->name, uuidstr), "edit",
|
|
xml);
|
|
}
|
|
|
|
int
|
|
virDomainDefSave(virDomainDef *def,
|
|
virDomainXMLOption *xmlopt,
|
|
const char *configDir)
|
|
{
|
|
g_autofree char *xml = NULL;
|
|
|
|
if (!(xml = virDomainDefFormat(def, xmlopt, VIR_DOMAIN_DEF_FORMAT_SECURE)))
|
|
return -1;
|
|
|
|
return virDomainDefSaveXML(def, configDir, xml);
|
|
}
|
|
|
|
int
|
|
virDomainObjSave(virDomainObj *obj,
|
|
virDomainXMLOption *xmlopt,
|
|
const char *statusDir)
|
|
{
|
|
unsigned int flags = (VIR_DOMAIN_DEF_FORMAT_SECURE |
|
|
VIR_DOMAIN_DEF_FORMAT_STATUS |
|
|
VIR_DOMAIN_DEF_FORMAT_ACTUAL_NET |
|
|
VIR_DOMAIN_DEF_FORMAT_PCI_ORIG_STATES |
|
|
VIR_DOMAIN_DEF_FORMAT_CLOCK_ADJUST);
|
|
|
|
g_autofree char *xml = NULL;
|
|
|
|
if (!(xml = virDomainObjFormat(obj, xmlopt, flags)))
|
|
return -1;
|
|
|
|
return virDomainDefSaveXML(obj->def, statusDir, xml);
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDeleteConfig(const char *configDir,
|
|
const char *autostartDir,
|
|
virDomainObj *dom)
|
|
{
|
|
g_autofree char *configFile = NULL;
|
|
g_autofree char *autostartLink = NULL;
|
|
|
|
if ((configFile = virDomainConfigFile(configDir, dom->def->name)) == NULL)
|
|
return -1;
|
|
if ((autostartLink = virDomainConfigFile(autostartDir,
|
|
dom->def->name)) == NULL)
|
|
return -1;
|
|
|
|
/* Not fatal if this doesn't work */
|
|
unlink(autostartLink);
|
|
dom->autostart = 0;
|
|
|
|
if (unlink(configFile) < 0 &&
|
|
errno != ENOENT) {
|
|
virReportSystemError(errno,
|
|
_("cannot remove config %s"),
|
|
configFile);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char
|
|
*virDomainConfigFile(const char *dir,
|
|
const char *name)
|
|
{
|
|
char *ret;
|
|
|
|
ret = g_strdup_printf("%s/%s.xml", dir, name);
|
|
return ret;
|
|
}
|
|
|
|
/* Translates a device name of the form (regex) "[fhv]d[a-z]+" into
|
|
* the corresponding bus,index combination (e.g. sda => (0,0), sdi (1,1),
|
|
* hdd => (1,1), vdaa => (0,26))
|
|
* @param disk The disk device
|
|
* @param busIdx parsed bus number
|
|
* @param devIdx parsed device number
|
|
* @return 0 on success, -1 on failure
|
|
*/
|
|
int
|
|
virDiskNameToBusDeviceIndex(virDomainDiskDef *disk,
|
|
int *busIdx,
|
|
int *devIdx)
|
|
{
|
|
int idx = virDiskNameToIndex(disk->dst);
|
|
if (idx < 0)
|
|
return -1;
|
|
|
|
switch (disk->bus) {
|
|
case VIR_DOMAIN_DISK_BUS_IDE:
|
|
*busIdx = idx / 2;
|
|
*devIdx = idx % 2;
|
|
break;
|
|
case VIR_DOMAIN_DISK_BUS_SCSI:
|
|
*busIdx = idx / 7;
|
|
*devIdx = idx % 7;
|
|
break;
|
|
case VIR_DOMAIN_DISK_BUS_FDC:
|
|
case VIR_DOMAIN_DISK_BUS_USB:
|
|
case VIR_DOMAIN_DISK_BUS_VIRTIO:
|
|
case VIR_DOMAIN_DISK_BUS_XEN:
|
|
case VIR_DOMAIN_DISK_BUS_SD:
|
|
case VIR_DOMAIN_DISK_BUS_NONE:
|
|
case VIR_DOMAIN_DISK_BUS_SATA:
|
|
case VIR_DOMAIN_DISK_BUS_UML:
|
|
case VIR_DOMAIN_DISK_BUS_LAST:
|
|
default:
|
|
*busIdx = 0;
|
|
*devIdx = idx;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virDomainFSInsert(virDomainDef *def, virDomainFSDef *fs)
|
|
{
|
|
return VIR_APPEND_ELEMENT(def->fss, def->nfss, fs);
|
|
}
|
|
|
|
virDomainFSDef *
|
|
virDomainFSRemove(virDomainDef *def, size_t i)
|
|
{
|
|
virDomainFSDef *fs = def->fss[i];
|
|
|
|
VIR_DELETE_ELEMENT(def->fss, i, def->nfss);
|
|
return fs;
|
|
}
|
|
|
|
virDomainFSDef *
|
|
virDomainGetFilesystemForTarget(virDomainDef *def,
|
|
const char *target)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nfss; i++) {
|
|
if (STREQ(def->fss[i]->dst, target))
|
|
return def->fss[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainChrDefForeach(virDomainDef *def,
|
|
bool abortOnError,
|
|
virDomainChrDefIterator iter,
|
|
void *opaque)
|
|
{
|
|
size_t i;
|
|
int rc = 0;
|
|
|
|
for (i = 0; i < def->nserials; i++) {
|
|
if ((iter)(def,
|
|
def->serials[i],
|
|
opaque) < 0)
|
|
rc = -1;
|
|
|
|
if (abortOnError && rc != 0)
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < def->nparallels; i++) {
|
|
if ((iter)(def,
|
|
def->parallels[i],
|
|
opaque) < 0)
|
|
rc = -1;
|
|
|
|
if (abortOnError && rc != 0)
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < def->nchannels; i++) {
|
|
if ((iter)(def,
|
|
def->channels[i],
|
|
opaque) < 0)
|
|
rc = -1;
|
|
|
|
if (abortOnError && rc != 0)
|
|
return rc;
|
|
}
|
|
for (i = 0; i < def->nconsoles; i++) {
|
|
if (virDomainSkipBackcompatConsole(def, i, false))
|
|
continue;
|
|
if ((iter)(def,
|
|
def->consoles[i],
|
|
opaque) < 0)
|
|
rc = -1;
|
|
|
|
if (abortOnError && rc != 0)
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainSmartcardDefForeach(virDomainDef *def,
|
|
bool abortOnError,
|
|
virDomainSmartcardDefIterator iter,
|
|
void *opaque)
|
|
{
|
|
size_t i;
|
|
int rc = 0;
|
|
|
|
for (i = 0; i < def->nsmartcards; i++) {
|
|
if ((iter)(def,
|
|
def->smartcards[i],
|
|
opaque) < 0)
|
|
rc = -1;
|
|
|
|
if (abortOnError && rc != 0)
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainUSBDeviceDefForeach(virDomainDef *def,
|
|
virDomainUSBDeviceDefIterator iter,
|
|
void *opaque,
|
|
bool skipHubs)
|
|
{
|
|
size_t i;
|
|
|
|
/* usb-hub */
|
|
if (!skipHubs) {
|
|
for (i = 0; i < def->nhubs; i++) {
|
|
virDomainHubDef *hub = def->hubs[i];
|
|
if (hub->type == VIR_DOMAIN_HUB_TYPE_USB) {
|
|
if (iter(&hub->info, opaque) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* usb-host */
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
virDomainHostdevDef *hostdev = def->hostdevs[i];
|
|
if (hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) {
|
|
if (iter(hostdev->info, opaque) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* usb-storage */
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
virDomainDiskDef *disk = def->disks[i];
|
|
if (disk->bus == VIR_DOMAIN_DISK_BUS_USB) {
|
|
if (iter(&disk->info, opaque) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* TODO: add def->nets here when libvirt starts supporting usb-net */
|
|
|
|
/* usb-ccid */
|
|
for (i = 0; i < def->ncontrollers; i++) {
|
|
virDomainControllerDef *cont = def->controllers[i];
|
|
if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_CCID) {
|
|
if (iter(&cont->info, opaque) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* usb-kbd, usb-mouse, usb-tablet */
|
|
for (i = 0; i < def->ninputs; i++) {
|
|
virDomainInputDef *input = def->inputs[i];
|
|
|
|
if (input->bus == VIR_DOMAIN_INPUT_BUS_USB) {
|
|
if (iter(&input->info, opaque) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* usb-serial */
|
|
for (i = 0; i < def->nserials; i++) {
|
|
virDomainChrDef *serial = def->serials[i];
|
|
if (serial->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB) {
|
|
if (iter(&serial->info, opaque) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* usb-audio model=usb */
|
|
for (i = 0; i < def->nsounds; i++) {
|
|
virDomainSoundDef *sound = def->sounds[i];
|
|
if (sound->model == VIR_DOMAIN_SOUND_MODEL_USB) {
|
|
if (iter(&sound->info, opaque) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* usb-redir */
|
|
for (i = 0; i < def->nredirdevs; i++) {
|
|
virDomainRedirdevDef *redirdev = def->redirdevs[i];
|
|
if (redirdev->bus == VIR_DOMAIN_REDIRDEV_BUS_USB) {
|
|
if (iter(&redirdev->info, opaque) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Copy src into a new definition; with the quality of the copy
|
|
* depending on the migratable flag (false for transitions between
|
|
* persistent and active, true for transitions across save files or
|
|
* snapshots). */
|
|
virDomainDef *
|
|
virDomainDefCopy(virDomainDef *src,
|
|
virDomainXMLOption *xmlopt,
|
|
void *parseOpaque,
|
|
bool migratable)
|
|
{
|
|
unsigned int format_flags = VIR_DOMAIN_DEF_FORMAT_SECURE;
|
|
unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE |
|
|
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE;
|
|
g_autofree char *xml = NULL;
|
|
|
|
if (migratable)
|
|
format_flags |= VIR_DOMAIN_DEF_FORMAT_INACTIVE | VIR_DOMAIN_DEF_FORMAT_MIGRATABLE;
|
|
|
|
/* Easiest to clone via a round-trip through XML. */
|
|
if (!(xml = virDomainDefFormat(src, xmlopt, format_flags)))
|
|
return NULL;
|
|
|
|
return virDomainDefParseString(xml, xmlopt, parseOpaque, parse_flags);
|
|
}
|
|
|
|
virDomainDef *
|
|
virDomainObjCopyPersistentDef(virDomainObj *dom,
|
|
virDomainXMLOption *xmlopt,
|
|
void *parseOpaque)
|
|
{
|
|
virDomainDef *cur;
|
|
|
|
cur = virDomainObjGetPersistentDef(xmlopt, dom, parseOpaque);
|
|
if (!cur) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to get persistent definition object"));
|
|
return NULL;
|
|
}
|
|
|
|
return virDomainDefCopy(cur, xmlopt, parseOpaque, false);
|
|
}
|
|
|
|
|
|
virDomainState
|
|
virDomainObjGetState(virDomainObj *dom, int *reason)
|
|
{
|
|
if (reason)
|
|
*reason = dom->state.reason;
|
|
|
|
return dom->state.state;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainObjSetState(virDomainObj *dom, virDomainState state, int reason)
|
|
{
|
|
int last;
|
|
|
|
switch (state) {
|
|
case VIR_DOMAIN_NOSTATE:
|
|
last = VIR_DOMAIN_NOSTATE_LAST;
|
|
break;
|
|
case VIR_DOMAIN_RUNNING:
|
|
last = VIR_DOMAIN_RUNNING_LAST;
|
|
break;
|
|
case VIR_DOMAIN_BLOCKED:
|
|
last = VIR_DOMAIN_BLOCKED_LAST;
|
|
break;
|
|
case VIR_DOMAIN_PAUSED:
|
|
last = VIR_DOMAIN_PAUSED_LAST;
|
|
break;
|
|
case VIR_DOMAIN_SHUTDOWN:
|
|
last = VIR_DOMAIN_SHUTDOWN_LAST;
|
|
break;
|
|
case VIR_DOMAIN_SHUTOFF:
|
|
last = VIR_DOMAIN_SHUTOFF_LAST;
|
|
break;
|
|
case VIR_DOMAIN_CRASHED:
|
|
last = VIR_DOMAIN_CRASHED_LAST;
|
|
break;
|
|
case VIR_DOMAIN_PMSUSPENDED:
|
|
last = VIR_DOMAIN_PMSUSPENDED_LAST;
|
|
break;
|
|
case VIR_DOMAIN_LAST:
|
|
default:
|
|
VIR_ERROR(_("invalid domain state: %d"), state);
|
|
return;
|
|
}
|
|
|
|
dom->state.state = state;
|
|
if (reason > 0 && reason < last)
|
|
dom->state.reason = reason;
|
|
else
|
|
dom->state.reason = 0;
|
|
}
|
|
|
|
|
|
const char *
|
|
virDomainStateReasonToString(virDomainState state, int reason)
|
|
{
|
|
switch (state) {
|
|
case VIR_DOMAIN_NOSTATE:
|
|
return virDomainNostateReasonTypeToString(reason);
|
|
case VIR_DOMAIN_RUNNING:
|
|
return virDomainRunningReasonTypeToString(reason);
|
|
case VIR_DOMAIN_BLOCKED:
|
|
return virDomainBlockedReasonTypeToString(reason);
|
|
case VIR_DOMAIN_PAUSED:
|
|
return virDomainPausedReasonTypeToString(reason);
|
|
case VIR_DOMAIN_SHUTDOWN:
|
|
return virDomainShutdownReasonTypeToString(reason);
|
|
case VIR_DOMAIN_SHUTOFF:
|
|
return virDomainShutoffReasonTypeToString(reason);
|
|
case VIR_DOMAIN_CRASHED:
|
|
return virDomainCrashedReasonTypeToString(reason);
|
|
case VIR_DOMAIN_PMSUSPENDED:
|
|
return virDomainPMSuspendedReasonTypeToString(reason);
|
|
case VIR_DOMAIN_LAST:
|
|
break;
|
|
}
|
|
VIR_WARN("Unexpected domain state: %d", state);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainStateReasonFromString(virDomainState state, const char *reason)
|
|
{
|
|
switch (state) {
|
|
case VIR_DOMAIN_NOSTATE:
|
|
return virDomainNostateReasonTypeFromString(reason);
|
|
case VIR_DOMAIN_RUNNING:
|
|
return virDomainRunningReasonTypeFromString(reason);
|
|
case VIR_DOMAIN_BLOCKED:
|
|
return virDomainBlockedReasonTypeFromString(reason);
|
|
case VIR_DOMAIN_PAUSED:
|
|
return virDomainPausedReasonTypeFromString(reason);
|
|
case VIR_DOMAIN_SHUTDOWN:
|
|
return virDomainShutdownReasonTypeFromString(reason);
|
|
case VIR_DOMAIN_SHUTOFF:
|
|
return virDomainShutoffReasonTypeFromString(reason);
|
|
case VIR_DOMAIN_CRASHED:
|
|
return virDomainCrashedReasonTypeFromString(reason);
|
|
case VIR_DOMAIN_PMSUSPENDED:
|
|
return virDomainPMSuspendedReasonTypeFromString(reason);
|
|
case VIR_DOMAIN_LAST:
|
|
break;
|
|
}
|
|
VIR_WARN("Unexpected domain state: %d", state);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* Some access functions to gloss over the difference between NetDef
|
|
* (<interface>) and ActualNetDef (<actual>). If the NetDef has an
|
|
* ActualNetDef, return the requested value from the ActualNetDef,
|
|
* otherwise return the value from the NetDef.
|
|
*/
|
|
|
|
virDomainNetType
|
|
virDomainNetGetActualType(const virDomainNetDef *iface)
|
|
{
|
|
if (iface->type != VIR_DOMAIN_NET_TYPE_NETWORK)
|
|
return iface->type;
|
|
if (!iface->data.network.actual)
|
|
return iface->type;
|
|
return iface->data.network.actual->type;
|
|
}
|
|
|
|
const char *
|
|
virDomainNetGetActualBridgeName(const virDomainNetDef *iface)
|
|
{
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_BRIDGE)
|
|
return iface->data.bridge.brname;
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual &&
|
|
(iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_NETWORK))
|
|
return iface->data.network.actual->data.bridge.brname;
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
virDomainNetGetActualBridgeMACTableManager(const virDomainNetDef *iface)
|
|
{
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual &&
|
|
(iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_BRIDGE ||
|
|
iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_NETWORK))
|
|
return iface->data.network.actual->data.bridge.macTableManager;
|
|
return 0;
|
|
}
|
|
|
|
const char *
|
|
virDomainNetGetActualDirectDev(const virDomainNetDef *iface)
|
|
{
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_DIRECT)
|
|
return iface->data.direct.linkdev;
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual &&
|
|
iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_DIRECT)
|
|
return iface->data.network.actual->data.direct.linkdev;
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
virDomainNetGetActualDirectMode(const virDomainNetDef *iface)
|
|
{
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_DIRECT)
|
|
return iface->data.direct.mode;
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual &&
|
|
iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_DIRECT)
|
|
return iface->data.network.actual->data.direct.mode;
|
|
return 0;
|
|
}
|
|
|
|
virDomainHostdevDef *
|
|
virDomainNetGetActualHostdev(virDomainNetDef *iface)
|
|
{
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_HOSTDEV)
|
|
return &iface->data.hostdev.def;
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual &&
|
|
iface->data.network.actual->type == VIR_DOMAIN_NET_TYPE_HOSTDEV)
|
|
return &iface->data.network.actual->data.hostdev.def;
|
|
return NULL;
|
|
}
|
|
|
|
const virNetDevVPortProfile *
|
|
virDomainNetGetActualVirtPortProfile(const virDomainNetDef *iface)
|
|
{
|
|
switch (iface->type) {
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
return iface->virtPortProfile;
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
if (!iface->data.network.actual)
|
|
return NULL;
|
|
switch (iface->data.network.actual->type) {
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
return iface->data.network.actual->virtPortProfile;
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
break;
|
|
}
|
|
return NULL;
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
const virNetDevBandwidth *
|
|
virDomainNetGetActualBandwidth(const virDomainNetDef *iface)
|
|
{
|
|
/* if there is an ActualNetDef, *always* return
|
|
* its bandwidth rather than the NetDef's bandwidth.
|
|
*/
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual)
|
|
return iface->data.network.actual->bandwidth;
|
|
return iface->bandwidth;
|
|
}
|
|
|
|
const virNetDevVlan *
|
|
virDomainNetGetActualVlan(const virDomainNetDef *iface)
|
|
{
|
|
const virNetDevVlan *vlan = &iface->vlan;
|
|
|
|
/* if there is an ActualNetDef, *always* return
|
|
* its vlan rather than the NetDef's vlan.
|
|
*/
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual)
|
|
vlan = &iface->data.network.actual->vlan;
|
|
|
|
if (vlan->nTags > 0)
|
|
return vlan;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
virTristateBool
|
|
virDomainNetGetActualPortOptionsIsolated(const virDomainNetDef *iface)
|
|
{
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual) {
|
|
return iface->data.network.actual->isolatedPort;
|
|
}
|
|
return iface->isolatedPort;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainNetGetActualTrustGuestRxFilters(const virDomainNetDef *iface)
|
|
{
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK &&
|
|
iface->data.network.actual)
|
|
return (iface->data.network.actual->trustGuestRxFilters
|
|
== VIR_TRISTATE_BOOL_YES);
|
|
return iface->trustGuestRxFilters == VIR_TRISTATE_BOOL_YES;
|
|
}
|
|
|
|
const char *
|
|
virDomainNetGetModelString(const virDomainNetDef *net)
|
|
{
|
|
if (net->model)
|
|
return virDomainNetModelTypeToString(net->model);
|
|
return net->modelstr;
|
|
}
|
|
|
|
int
|
|
virDomainNetSetModelString(virDomainNetDef *net,
|
|
const char *model)
|
|
{
|
|
size_t i;
|
|
|
|
VIR_FREE(net->modelstr);
|
|
net->model = VIR_DOMAIN_NET_MODEL_UNKNOWN;
|
|
if (!model)
|
|
return 0;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(virDomainNetModelTypeList); i++) {
|
|
if (STRCASEEQ(virDomainNetModelTypeList[i], model)) {
|
|
net->model = i;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (strspn(model, NET_MODEL_CHARS) < strlen(model)) {
|
|
virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
_("Model name contains invalid characters"));
|
|
return -1;
|
|
}
|
|
|
|
net->modelstr = g_strdup(model);
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
virDomainNetIsVirtioModel(const virDomainNetDef *net)
|
|
{
|
|
return (net->model == VIR_DOMAIN_NET_MODEL_VIRTIO ||
|
|
net->model == VIR_DOMAIN_NET_MODEL_VIRTIO_TRANSITIONAL ||
|
|
net->model == VIR_DOMAIN_NET_MODEL_VIRTIO_NON_TRANSITIONAL);
|
|
}
|
|
|
|
|
|
/* Return listens[i] from the appropriate union for the graphics
|
|
* type, or NULL if this is an unsuitable type, or the index is out of
|
|
* bounds. If force0 is TRUE, i == 0, and there is no listen array,
|
|
* allocate one with a single item. */
|
|
virDomainGraphicsListenDef *
|
|
virDomainGraphicsGetListen(virDomainGraphicsDef *def, size_t i)
|
|
{
|
|
if (!def->listens || (def->nListens <= i))
|
|
return NULL;
|
|
|
|
return &def->listens[i];
|
|
}
|
|
|
|
|
|
int
|
|
virDomainGraphicsListenAppendAddress(virDomainGraphicsDef *def,
|
|
const char *address)
|
|
{
|
|
virDomainGraphicsListenDef glisten;
|
|
|
|
memset(&glisten, 0, sizeof(glisten));
|
|
|
|
glisten.type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS;
|
|
|
|
glisten.address = g_strdup(address);
|
|
|
|
if (VIR_APPEND_ELEMENT_COPY(def->listens, def->nListens, glisten) < 0)
|
|
goto error;
|
|
|
|
return 0;
|
|
error:
|
|
VIR_FREE(glisten.address);
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainGraphicsListenAppendSocket(virDomainGraphicsDef *def,
|
|
const char *socketPath)
|
|
{
|
|
virDomainGraphicsListenDef glisten;
|
|
|
|
memset(&glisten, 0, sizeof(glisten));
|
|
|
|
glisten.type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET;
|
|
|
|
glisten.socket = g_strdup(socketPath);
|
|
|
|
if (VIR_APPEND_ELEMENT_COPY(def->listens, def->nListens, glisten) < 0)
|
|
goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
VIR_FREE(glisten.socket);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainNetFind:
|
|
* @def: domain's def
|
|
* @device: could be the interface name or MAC address
|
|
*
|
|
* Finds a domain's net def, given the interface name or MAC address
|
|
*
|
|
* Returns a pointer to the net def or NULL if not found (error is reported).
|
|
*/
|
|
virDomainNetDef *
|
|
virDomainNetFind(virDomainDef *def, const char *device)
|
|
{
|
|
bool isMac = false;
|
|
virMacAddr mac;
|
|
size_t i;
|
|
|
|
if (virMacAddrParse(device, &mac) == 0)
|
|
isMac = true;
|
|
|
|
if (isMac) {
|
|
for (i = 0; i < def->nnets; i++) {
|
|
if (virMacAddrCmp(&mac, &def->nets[i]->mac) == 0)
|
|
return def->nets[i];
|
|
}
|
|
} else { /* ifname */
|
|
virDomainNetDef *net = NULL;
|
|
|
|
if ((net = virDomainNetFindByName(def, device)))
|
|
return net;
|
|
}
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("'%s' is not a known interface"), device);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainNetFindByName:
|
|
* @def: domain's def
|
|
* @ifname: interface name
|
|
*
|
|
* Finds a domain's net def given the interface name.
|
|
*
|
|
* Returns a pointer to the net def or NULL if not found.
|
|
*/
|
|
virDomainNetDef *
|
|
virDomainNetFindByName(virDomainDef *def,
|
|
const char *ifname)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
if (STREQ_NULLABLE(ifname, def->nets[i]->ifname))
|
|
return def->nets[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDeviceDefCopy:
|
|
* @caps: Capabilities
|
|
* @def: Domain definition to which @src belongs
|
|
* @src: source to be copied
|
|
*
|
|
* virDomainDeviceDefCopy does a deep copy of only the parts of a
|
|
* DeviceDef that are valid when just the flag VIR_DOMAIN_DEF_PARSE_INACTIVE is
|
|
* set. This means that any part of the device xml that is conditionally
|
|
* parsed/formatted based on some other flag being set (or on the INACTIVE
|
|
* flag being reset) *will not* be copied to the destination. Caveat emptor.
|
|
*
|
|
* Returns a pointer to copied @src or NULL in case of error.
|
|
*/
|
|
virDomainDeviceDef *
|
|
virDomainDeviceDefCopy(virDomainDeviceDef *src,
|
|
const virDomainDef *def,
|
|
virDomainXMLOption *xmlopt,
|
|
void *parseOpaque)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
int flags = VIR_DOMAIN_DEF_FORMAT_INACTIVE | VIR_DOMAIN_DEF_FORMAT_SECURE;
|
|
int rc = -1;
|
|
g_autofree char *xmlStr = NULL;
|
|
|
|
switch ((virDomainDeviceType) src->type) {
|
|
case VIR_DOMAIN_DEVICE_DISK:
|
|
rc = virDomainDiskDefFormat(&buf, src->data.disk, flags, xmlopt);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_LEASE:
|
|
virDomainLeaseDefFormat(&buf, src->data.lease);
|
|
rc = 0;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_FS:
|
|
rc = virDomainFSDefFormat(&buf, src->data.fs, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_NET:
|
|
rc = virDomainNetDefFormat(&buf, src->data.net, xmlopt, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_INPUT:
|
|
rc = virDomainInputDefFormat(&buf, src->data.input, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SOUND:
|
|
rc = virDomainSoundDefFormat(&buf, src->data.sound, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_VIDEO:
|
|
rc = virDomainVideoDefFormat(&buf, src->data.video, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_HOSTDEV:
|
|
rc = virDomainHostdevDefFormat(&buf, src->data.hostdev, flags, xmlopt);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_WATCHDOG:
|
|
rc = virDomainWatchdogDefFormat(&buf, src->data.watchdog, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_CONTROLLER:
|
|
rc = virDomainControllerDefFormat(&buf, src->data.controller, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_GRAPHICS:
|
|
rc = virDomainGraphicsDefFormat(&buf, src->data.graphics, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_HUB:
|
|
rc = virDomainHubDefFormat(&buf, src->data.hub, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_REDIRDEV:
|
|
rc = virDomainRedirdevDefFormat(&buf, src->data.redirdev, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_RNG:
|
|
rc = virDomainRNGDefFormat(&buf, src->data.rng, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_CHR:
|
|
rc = virDomainChrDefFormat(&buf, src->data.chr, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_TPM:
|
|
rc = virDomainTPMDefFormat(&buf, src->data.tpm, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_PANIC:
|
|
virDomainPanicDefFormat(&buf, src->data.panic);
|
|
rc = 0;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_MEMORY:
|
|
rc = virDomainMemoryDefFormat(&buf, src->data.memory, flags);
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_SHMEM:
|
|
virDomainShmemDefFormat(&buf, src->data.shmem, flags);
|
|
rc = 0;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_VSOCK:
|
|
virDomainVsockDefFormat(&buf, src->data.vsock);
|
|
rc = 0;
|
|
break;
|
|
case VIR_DOMAIN_DEVICE_AUDIO:
|
|
rc = virDomainAudioDefFormat(&buf, src->data.audio);
|
|
break;
|
|
|
|
case VIR_DOMAIN_DEVICE_NONE:
|
|
case VIR_DOMAIN_DEVICE_SMARTCARD:
|
|
case VIR_DOMAIN_DEVICE_MEMBALLOON:
|
|
case VIR_DOMAIN_DEVICE_NVRAM:
|
|
case VIR_DOMAIN_DEVICE_IOMMU:
|
|
case VIR_DOMAIN_DEVICE_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Copying definition of '%d' type "
|
|
"is not implemented yet."),
|
|
src->type);
|
|
return NULL;
|
|
}
|
|
|
|
if (rc < 0)
|
|
return NULL;
|
|
|
|
xmlStr = virBufferContentAndReset(&buf);
|
|
return virDomainDeviceDefParse(xmlStr, def, xmlopt, parseOpaque,
|
|
VIR_DOMAIN_DEF_PARSE_INACTIVE |
|
|
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE);
|
|
}
|
|
|
|
|
|
virSecurityLabelDef *
|
|
virDomainDefGetSecurityLabelDef(virDomainDef *def, const char *model)
|
|
{
|
|
size_t i;
|
|
virSecurityLabelDef *seclabel = NULL;
|
|
|
|
if (def == NULL || model == NULL)
|
|
return NULL;
|
|
|
|
for (i = 0; i < def->nseclabels; i++) {
|
|
if (def->seclabels[i]->model == NULL)
|
|
continue;
|
|
if (STREQ(def->seclabels[i]->model, model))
|
|
return def->seclabels[i];
|
|
}
|
|
|
|
return seclabel;
|
|
}
|
|
|
|
|
|
virSecurityDeviceLabelDef *
|
|
virDomainChrSourceDefGetSecurityLabelDef(virDomainChrSourceDef *def,
|
|
const char *model)
|
|
{
|
|
size_t i;
|
|
|
|
if (def == NULL)
|
|
return NULL;
|
|
|
|
for (i = 0; i < def->nseclabels; i++) {
|
|
if (STREQ_NULLABLE(def->seclabels[i]->model, model))
|
|
return def->seclabels[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
const char *devAlias;
|
|
virDomainDeviceDef *dev;
|
|
} virDomainDefFindDeviceCallbackData;
|
|
|
|
static int
|
|
virDomainDefFindDeviceCallback(virDomainDef *def G_GNUC_UNUSED,
|
|
virDomainDeviceDef *dev,
|
|
virDomainDeviceInfo *info,
|
|
void *opaque)
|
|
{
|
|
virDomainDefFindDeviceCallbackData *data = opaque;
|
|
|
|
if (STREQ_NULLABLE(info->alias, data->devAlias)) {
|
|
*data->dev = *dev;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virDomainDefFindDevice(virDomainDef *def,
|
|
const char *devAlias,
|
|
virDomainDeviceDef *dev,
|
|
bool reportError)
|
|
{
|
|
virDomainDefFindDeviceCallbackData data = { devAlias, dev };
|
|
|
|
dev->type = VIR_DOMAIN_DEVICE_NONE;
|
|
virDomainDeviceInfoIterateFlags(def, virDomainDefFindDeviceCallback,
|
|
DOMAIN_DEVICE_ITERATE_ALL_CONSOLES,
|
|
&data);
|
|
|
|
if (dev->type == VIR_DOMAIN_DEVICE_NONE) {
|
|
if (reportError) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("no device found with alias %s"), devAlias);
|
|
} else {
|
|
VIR_DEBUG("no device found with alias %s", devAlias);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
virDomainAudioDef *
|
|
virDomainDefFindAudioByID(const virDomainDef *def,
|
|
int id)
|
|
{
|
|
size_t i;
|
|
if (id != 0) {
|
|
for (i = 0; i < def->naudios; i++)
|
|
if (def->audios[i]->id == id)
|
|
return def->audios[i];
|
|
} else if (def->naudios) {
|
|
return def->audios[0];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainSoundModelSupportsCodecs(virDomainSoundDef *def)
|
|
{
|
|
return def->model == VIR_DOMAIN_SOUND_MODEL_ICH6 ||
|
|
def->model == VIR_DOMAIN_SOUND_MODEL_ICH9;
|
|
}
|
|
|
|
bool
|
|
virDomainAudioIOCommonIsSet(virDomainAudioIOCommon *common)
|
|
{
|
|
return common->mixingEngine ||
|
|
common->fixedSettings ||
|
|
common->frequency ||
|
|
common->channels ||
|
|
common->voices ||
|
|
common->format ||
|
|
common->bufferLength;
|
|
}
|
|
|
|
char *
|
|
virDomainObjGetMetadata(virDomainObj *vm,
|
|
int type,
|
|
const char *uri,
|
|
unsigned int flags)
|
|
{
|
|
virDomainDef *def;
|
|
char *ret = NULL;
|
|
|
|
virCheckFlags(VIR_DOMAIN_AFFECT_LIVE |
|
|
VIR_DOMAIN_AFFECT_CONFIG, NULL);
|
|
|
|
if (type >= VIR_DOMAIN_METADATA_LAST) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("unknown metadata type '%d'"), type);
|
|
return NULL;
|
|
}
|
|
|
|
if (!(def = virDomainObjGetOneDef(vm, flags)))
|
|
return NULL;
|
|
|
|
switch ((virDomainMetadataType) type) {
|
|
case VIR_DOMAIN_METADATA_DESCRIPTION:
|
|
ret = g_strdup(def->description);
|
|
break;
|
|
|
|
case VIR_DOMAIN_METADATA_TITLE:
|
|
ret = g_strdup(def->title);
|
|
break;
|
|
|
|
case VIR_DOMAIN_METADATA_ELEMENT:
|
|
if (!def->metadata)
|
|
break;
|
|
|
|
if (virXMLExtractNamespaceXML(def->metadata, uri, &ret) < 0)
|
|
return NULL;
|
|
break;
|
|
|
|
case VIR_DOMAIN_METADATA_LAST:
|
|
break;
|
|
}
|
|
|
|
if (!ret)
|
|
virReportError(VIR_ERR_NO_DOMAIN_METADATA, "%s",
|
|
_("Requested metadata element is not present"));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDefSetMetadata(virDomainDef *def,
|
|
int type,
|
|
const char *metadata,
|
|
const char *key,
|
|
const char *uri)
|
|
{
|
|
g_autoptr(xmlDoc) doc = NULL;
|
|
xmlNodePtr old;
|
|
g_autoptr(xmlNode) new = NULL;
|
|
|
|
if (type >= VIR_DOMAIN_METADATA_LAST) {
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
|
_("unknown metadata type '%d'"), type);
|
|
return -1;
|
|
}
|
|
|
|
switch ((virDomainMetadataType) type) {
|
|
case VIR_DOMAIN_METADATA_DESCRIPTION:
|
|
g_clear_pointer(&def->description, g_free);
|
|
|
|
if (STRNEQ_NULLABLE(metadata, ""))
|
|
def->description = g_strdup(metadata);
|
|
break;
|
|
|
|
case VIR_DOMAIN_METADATA_TITLE:
|
|
g_clear_pointer(&def->title, g_free);
|
|
|
|
if (STRNEQ_NULLABLE(metadata, ""))
|
|
def->title = g_strdup(metadata);
|
|
break;
|
|
|
|
case VIR_DOMAIN_METADATA_ELEMENT:
|
|
if (metadata) {
|
|
|
|
/* parse and modify the xml from the user */
|
|
if (!(doc = virXMLParseString(metadata, _("(metadata_xml)"))))
|
|
return -1;
|
|
|
|
if (virXMLInjectNamespace(doc->children, uri, key) < 0)
|
|
return -1;
|
|
|
|
/* create the root node if needed */
|
|
if (!def->metadata)
|
|
def->metadata = virXMLNewNode(NULL, "metadata");
|
|
|
|
if (!(new = xmlCopyNode(doc->children, 1))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Failed to copy XML node"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* remove possible other nodes sharing the namespace */
|
|
while ((old = virXMLFindChildNodeByNs(def->metadata, uri))) {
|
|
xmlUnlinkNode(old);
|
|
xmlFreeNode(old);
|
|
}
|
|
|
|
if (new) {
|
|
if (!(xmlAddChild(def->metadata, new))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("failed to add metadata to XML document"));
|
|
return -1;
|
|
}
|
|
new = NULL;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_METADATA_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainObjSetMetadata(virDomainObj *vm,
|
|
int type,
|
|
const char *metadata,
|
|
const char *key,
|
|
const char *uri,
|
|
virDomainXMLOption *xmlopt,
|
|
const char *stateDir,
|
|
const char *configDir,
|
|
unsigned int flags)
|
|
{
|
|
virDomainDef *def;
|
|
virDomainDef *persistentDef;
|
|
|
|
virCheckFlags(VIR_DOMAIN_AFFECT_LIVE |
|
|
VIR_DOMAIN_AFFECT_CONFIG, -1);
|
|
|
|
if (virDomainObjGetDefs(vm, flags, &def, &persistentDef) < 0)
|
|
return -1;
|
|
|
|
if (def) {
|
|
if (virDomainDefSetMetadata(def, type, metadata, key, uri) < 0)
|
|
return -1;
|
|
|
|
if (virDomainObjSave(vm, xmlopt, stateDir) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (persistentDef) {
|
|
if (virDomainDefSetMetadata(persistentDef, type, metadata, key,
|
|
uri) < 0)
|
|
return -1;
|
|
|
|
if (virDomainDefSave(persistentDef, xmlopt, configDir) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefNeedsPlacementAdvice(virDomainDef *def)
|
|
{
|
|
if (def->placement_mode == VIR_DOMAIN_CPU_PLACEMENT_MODE_AUTO)
|
|
return true;
|
|
|
|
if (virDomainNumatuneHasPlacementAuto(def->numa))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDiskDefCheckDuplicateInfo(const virDomainDiskDef *a,
|
|
const virDomainDiskDef *b)
|
|
{
|
|
if (STREQ(a->dst, b->dst)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("target '%s' duplicated for disk sources '%s' and '%s'"),
|
|
a->dst,
|
|
NULLSTR(virDomainDiskGetSource(a)),
|
|
NULLSTR(virDomainDiskGetSource(b)));
|
|
return -1;
|
|
}
|
|
|
|
/* Duplicate WWN/serial isn't usually problematic for the OS and
|
|
* forbidding it would possibly inhibit using multipath configurations */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefHasMemballoon:
|
|
* @def: domain definition
|
|
*
|
|
* Returns true if domain has a memory ballooning device configured.
|
|
*/
|
|
bool
|
|
virDomainDefHasMemballoon(const virDomainDef *def)
|
|
{
|
|
return def->memballoon &&
|
|
def->memballoon->model != VIR_DOMAIN_MEMBALLOON_MODEL_NONE;
|
|
}
|
|
|
|
|
|
#define VIR_DOMAIN_SHORT_NAME_MAX 20
|
|
|
|
/**
|
|
* virDomainDefGetShortName:
|
|
* @vm: Machine for which to get a name
|
|
* @unique: Make sure the name is unique (use id as well)
|
|
*
|
|
* Shorten domain name to avoid possible path length limitations.
|
|
*/
|
|
char *
|
|
virDomainDefGetShortName(const virDomainDef *def)
|
|
{
|
|
wchar_t wshortname[VIR_DOMAIN_SHORT_NAME_MAX + 1] = {0};
|
|
size_t len = 0;
|
|
g_autofree char *shortname = NULL;
|
|
|
|
/* No need to do the whole conversion thing when there are no multibyte
|
|
* characters. The same applies for illegal sequences as they can occur
|
|
* with incompatible locales. */
|
|
len = mbstowcs(NULL, def->name, 0);
|
|
if ((len == (size_t) -1 && errno == EILSEQ) ||
|
|
len == strlen(def->name)) {
|
|
return g_strdup_printf("%d-%.*s", def->id, VIR_DOMAIN_SHORT_NAME_MAX,
|
|
def->name);
|
|
}
|
|
|
|
if (len == (size_t) -1) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Cannot convert domain name to "
|
|
"wide character string"));
|
|
return NULL;
|
|
}
|
|
|
|
if (mbstowcs(wshortname, def->name, VIR_DOMAIN_SHORT_NAME_MAX) == (size_t) -1) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Cannot convert domain name to "
|
|
"wide character string"));
|
|
return NULL;
|
|
}
|
|
|
|
len = wcstombs(NULL, wshortname, 0);
|
|
if (len == (size_t) -1) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Cannot convert wide character string "
|
|
"back to multi-byte domain name"));
|
|
return NULL;
|
|
}
|
|
|
|
shortname = g_new0(char, len + 1);
|
|
|
|
if (wcstombs(shortname, wshortname, len) == (size_t) -1) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Cannot convert wide character string "
|
|
"back to multi-byte domain name"));
|
|
return NULL;
|
|
}
|
|
|
|
return g_strdup_printf("%d-%s", def->id, shortname);
|
|
}
|
|
|
|
#undef VIR_DOMAIN_SHORT_NAME_MAX
|
|
|
|
int
|
|
virDomainGetBlkioParametersAssignFromDef(virDomainDef *def,
|
|
virTypedParameterPtr params,
|
|
int *nparams,
|
|
int maxparams)
|
|
{
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
char *data = NULL;
|
|
size_t i;
|
|
|
|
#define VIR_BLKIO_ASSIGN(param, format, name) \
|
|
if (*nparams < maxparams) { \
|
|
for (i = 0; i < def->blkio.ndevices; i++) { \
|
|
if (!def->blkio.devices[i].param) \
|
|
continue; \
|
|
virBufferAsprintf(&buf, "%s," format ",", \
|
|
def->blkio.devices[i].path, \
|
|
def->blkio.devices[i].param); \
|
|
} \
|
|
virBufferTrim(&buf, ","); \
|
|
data = virBufferContentAndReset(&buf); \
|
|
if (virTypedParameterAssign(&(params[(*nparams)++]), name, \
|
|
VIR_TYPED_PARAM_STRING, data) < 0) \
|
|
goto error; \
|
|
data = NULL; \
|
|
}
|
|
|
|
/* blkiotune.device_weight */
|
|
VIR_BLKIO_ASSIGN(weight, "%u", VIR_DOMAIN_BLKIO_DEVICE_WEIGHT);
|
|
/* blkiotune.device_read_iops */
|
|
VIR_BLKIO_ASSIGN(riops, "%u", VIR_DOMAIN_BLKIO_DEVICE_READ_IOPS);
|
|
/* blkiotune.device_write_iops */
|
|
VIR_BLKIO_ASSIGN(wiops, "%u", VIR_DOMAIN_BLKIO_DEVICE_WRITE_IOPS);
|
|
/* blkiotune.device_read_bps */
|
|
VIR_BLKIO_ASSIGN(rbps, "%llu", VIR_DOMAIN_BLKIO_DEVICE_READ_BPS);
|
|
/* blkiotune.device_write_bps */
|
|
VIR_BLKIO_ASSIGN(wbps, "%llu", VIR_DOMAIN_BLKIO_DEVICE_WRITE_BPS);
|
|
|
|
#undef VIR_BLKIO_ASSIGN
|
|
|
|
return 0;
|
|
|
|
error:
|
|
VIR_FREE(data);
|
|
return -1;
|
|
}
|
|
|
|
|
|
void
|
|
virDomainDefVcpuOrderClear(virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->maxvcpus; i++)
|
|
def->vcpus[i]->order = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDiskSetBlockIOTune:
|
|
* @disk: The disk to set block I/O tuning on
|
|
* @info: The BlockIoTuneInfo to be set on the @disk
|
|
*
|
|
* Set the block I/O tune settings from @info on the @disk, but error out early
|
|
* in case of any error. That is to make sure nothing will fail half-way.
|
|
*/
|
|
void
|
|
virDomainDiskSetBlockIOTune(virDomainDiskDef *disk,
|
|
virDomainBlockIoTuneInfo *info)
|
|
{
|
|
char *tmp_group = NULL;
|
|
|
|
tmp_group = g_strdup(info->group_name);
|
|
|
|
VIR_FREE(disk->blkdeviotune.group_name);
|
|
disk->blkdeviotune = *info;
|
|
disk->blkdeviotune.group_name = g_steal_pointer(&tmp_group);
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainNetTypeSharesHostView:
|
|
* @net: interface
|
|
*
|
|
* Some types of interfaces "share" the host view. For instance,
|
|
* for macvtap interface, every domain RX is the host RX too. And
|
|
* every domain TX is host TX too. IOW, for some types of
|
|
* interfaces guest and host are on the same side of RX/TX
|
|
* barrier. This is important so that we set up QoS correctly and
|
|
* report proper stats.
|
|
*/
|
|
bool
|
|
virDomainNetTypeSharesHostView(const virDomainNetDef *net)
|
|
{
|
|
virDomainNetType actualType = virDomainNetGetActualType(net);
|
|
switch (actualType) {
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
return true;
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virNetworkPortDef *
|
|
virDomainNetDefToNetworkPort(virDomainDef *dom,
|
|
virDomainNetDef *iface)
|
|
{
|
|
g_autoptr(virNetworkPortDef) port = NULL;
|
|
|
|
if (iface->type != VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Expected an interface of type 'network' not '%s'"),
|
|
virDomainNetTypeToString(iface->type));
|
|
return NULL;
|
|
}
|
|
|
|
port = g_new0(virNetworkPortDef, 1);
|
|
|
|
if (virUUIDGenerate(port->uuid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Failed to generate UUID"));
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(port->owneruuid, dom->uuid, VIR_UUID_BUFLEN);
|
|
port->ownername = g_strdup(dom->name);
|
|
|
|
port->group = g_strdup(iface->data.network.portgroup);
|
|
|
|
memcpy(&port->mac, &iface->mac, VIR_MAC_BUFLEN);
|
|
|
|
if (virNetDevVPortProfileCopy(&port->virtPortProfile, iface->virtPortProfile) < 0)
|
|
return NULL;
|
|
|
|
if (virNetDevBandwidthCopy(&port->bandwidth, iface->bandwidth) < 0)
|
|
return NULL;
|
|
|
|
if (virNetDevVlanCopy(&port->vlan, &iface->vlan) < 0)
|
|
return NULL;
|
|
|
|
port->isolatedPort = iface->isolatedPort;
|
|
port->trustGuestRxFilters = iface->trustGuestRxFilters;
|
|
|
|
return g_steal_pointer(&port);
|
|
}
|
|
|
|
int
|
|
virDomainNetDefActualFromNetworkPort(virDomainNetDef *iface,
|
|
virNetworkPortDef *port)
|
|
{
|
|
virDomainActualNetDef *actual = NULL;
|
|
|
|
if (iface->type != VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Expected an interface of type 'network' not '%s'"),
|
|
virDomainNetTypeToString(iface->type));
|
|
return -1;
|
|
}
|
|
|
|
actual = g_new0(virDomainActualNetDef, 1);
|
|
|
|
switch ((virNetworkPortPlugType)port->plugtype) {
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_NONE:
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_NETWORK:
|
|
actual->type = VIR_DOMAIN_NET_TYPE_NETWORK;
|
|
actual->data.bridge.brname = g_strdup(port->plug.bridge.brname);
|
|
actual->data.bridge.macTableManager = port->plug.bridge.macTableManager;
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_BRIDGE:
|
|
actual->type = VIR_DOMAIN_NET_TYPE_BRIDGE;
|
|
actual->data.bridge.brname = g_strdup(port->plug.bridge.brname);
|
|
actual->data.bridge.macTableManager = port->plug.bridge.macTableManager;
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_DIRECT:
|
|
actual->type = VIR_DOMAIN_NET_TYPE_DIRECT;
|
|
actual->data.direct.linkdev = g_strdup(port->plug.direct.linkdev);
|
|
actual->data.direct.mode = port->plug.direct.mode;
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_HOSTDEV_PCI:
|
|
actual->type = VIR_DOMAIN_NET_TYPE_HOSTDEV;
|
|
actual->data.hostdev.def.parentnet = iface;
|
|
actual->data.hostdev.def.info = &iface->info;
|
|
actual->data.hostdev.def.mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
|
|
switch (port->plug.hostdevpci.managed) {
|
|
case VIR_TRISTATE_BOOL_YES:
|
|
actual->data.hostdev.def.managed = true;
|
|
break;
|
|
case VIR_TRISTATE_BOOL_ABSENT:
|
|
case VIR_TRISTATE_BOOL_NO:
|
|
actual->data.hostdev.def.managed = false;
|
|
break;
|
|
}
|
|
actual->data.hostdev.def.source.subsys.type = VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI;
|
|
actual->data.hostdev.def.source.subsys.u.pci.addr = port->plug.hostdevpci.addr;
|
|
switch ((virNetworkForwardDriverNameType)port->plug.hostdevpci.driver) {
|
|
case VIR_NETWORK_FORWARD_DRIVER_NAME_DEFAULT:
|
|
actual->data.hostdev.def.source.subsys.u.pci.backend =
|
|
VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_DRIVER_NAME_KVM:
|
|
actual->data.hostdev.def.source.subsys.u.pci.backend =
|
|
VIR_DOMAIN_HOSTDEV_PCI_BACKEND_KVM;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_DRIVER_NAME_VFIO:
|
|
actual->data.hostdev.def.source.subsys.u.pci.backend =
|
|
VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_DRIVER_NAME_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardDriverNameType,
|
|
port->plug.hostdevpci.driver);
|
|
goto error;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_NETWORK_PORT_PLUG_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkPortPlugType, port->plugtype);
|
|
goto error;
|
|
}
|
|
|
|
if (virNetDevVPortProfileCopy(&actual->virtPortProfile, port->virtPortProfile) < 0)
|
|
goto error;
|
|
|
|
if (virNetDevBandwidthCopy(&actual->bandwidth, port->bandwidth) < 0)
|
|
goto error;
|
|
|
|
if (virNetDevVlanCopy(&actual->vlan, &port->vlan) < 0)
|
|
goto error;
|
|
|
|
actual->isolatedPort = port->isolatedPort;
|
|
actual->class_id = port->class_id;
|
|
actual->trustGuestRxFilters = port->trustGuestRxFilters;
|
|
|
|
virDomainActualNetDefFree(iface->data.network.actual);
|
|
iface->data.network.actual = actual;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
virDomainActualNetDefFree(actual);
|
|
return -1;
|
|
}
|
|
|
|
virNetworkPortDef *
|
|
virDomainNetDefActualToNetworkPort(virDomainDef *dom,
|
|
virDomainNetDef *iface)
|
|
{
|
|
virDomainActualNetDef *actual;
|
|
g_autoptr(virNetworkPortDef) port = NULL;
|
|
|
|
if (!iface->data.network.actual) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Missing actual data for interface '%s'"),
|
|
iface->ifname);
|
|
return NULL;
|
|
}
|
|
|
|
actual = iface->data.network.actual;
|
|
|
|
if (iface->type != VIR_DOMAIN_NET_TYPE_NETWORK) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Expected an interface of type 'network' not '%s'"),
|
|
virDomainNetTypeToString(iface->type));
|
|
return NULL;
|
|
}
|
|
|
|
port = g_new0(virNetworkPortDef, 1);
|
|
|
|
if (virUUIDIsValid(iface->data.network.portid)) {
|
|
memcpy(port->uuid, iface->data.network.portid, VIR_UUID_BUFLEN);
|
|
} else if (virUUIDGenerate(port->uuid) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
"%s", _("Failed to generate UUID"));
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(port->owneruuid, dom->uuid, VIR_UUID_BUFLEN);
|
|
port->ownername = g_strdup(dom->name);
|
|
|
|
port->group = g_strdup(iface->data.network.portgroup);
|
|
|
|
memcpy(&port->mac, &iface->mac, VIR_MAC_BUFLEN);
|
|
|
|
switch (virDomainNetGetActualType(iface)) {
|
|
case VIR_DOMAIN_NET_TYPE_NETWORK:
|
|
port->plugtype = VIR_NETWORK_PORT_PLUG_TYPE_NETWORK;
|
|
port->plug.bridge.brname = g_strdup(actual->data.bridge.brname);
|
|
port->plug.bridge.macTableManager = actual->data.bridge.macTableManager;
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_BRIDGE:
|
|
port->plugtype = VIR_NETWORK_PORT_PLUG_TYPE_BRIDGE;
|
|
port->plug.bridge.brname = g_strdup(actual->data.bridge.brname);
|
|
port->plug.bridge.macTableManager = actual->data.bridge.macTableManager;
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_DIRECT:
|
|
port->plugtype = VIR_NETWORK_PORT_PLUG_TYPE_DIRECT;
|
|
port->plug.direct.linkdev = g_strdup(actual->data.direct.linkdev);
|
|
port->plug.direct.mode = actual->data.direct.mode;
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
|
|
port->plugtype = VIR_NETWORK_PORT_PLUG_TYPE_HOSTDEV_PCI;
|
|
if (actual->data.hostdev.def.mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS ||
|
|
actual->data.hostdev.def.source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Actual interface '%s' hostdev was not a PCI device"),
|
|
iface->ifname);
|
|
return NULL;
|
|
}
|
|
port->plug.hostdevpci.managed = virTristateBoolFromBool(actual->data.hostdev.def.managed);
|
|
port->plug.hostdevpci.addr = actual->data.hostdev.def.source.subsys.u.pci.addr;
|
|
switch ((virDomainHostdevSubsysPCIBackendType)actual->data.hostdev.def.source.subsys.u.pci.backend) {
|
|
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT:
|
|
port->plug.hostdevpci.driver = VIR_NETWORK_FORWARD_DRIVER_NAME_DEFAULT;
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_KVM:
|
|
port->plug.hostdevpci.driver = VIR_NETWORK_FORWARD_DRIVER_NAME_KVM;
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO:
|
|
port->plug.hostdevpci.driver = VIR_NETWORK_FORWARD_DRIVER_NAME_VFIO;
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_XEN:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Unexpected PCI backend 'xen'"));
|
|
break;
|
|
|
|
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainHostdevSubsysPCIBackendType,
|
|
actual->data.hostdev.def.source.subsys.u.pci.backend);
|
|
return NULL;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_CLIENT:
|
|
case VIR_DOMAIN_NET_TYPE_ETHERNET:
|
|
case VIR_DOMAIN_NET_TYPE_INTERNAL:
|
|
case VIR_DOMAIN_NET_TYPE_MCAST:
|
|
case VIR_DOMAIN_NET_TYPE_SERVER:
|
|
case VIR_DOMAIN_NET_TYPE_UDP:
|
|
case VIR_DOMAIN_NET_TYPE_USER:
|
|
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
|
|
case VIR_DOMAIN_NET_TYPE_VDPA:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("Unexpected network port type %s"),
|
|
virDomainNetTypeToString(virDomainNetGetActualType(iface)));
|
|
return NULL;
|
|
|
|
case VIR_DOMAIN_NET_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkPortPlugType, port->plugtype);
|
|
return NULL;
|
|
}
|
|
|
|
if (virNetDevVPortProfileCopy(&port->virtPortProfile, actual->virtPortProfile) < 0)
|
|
return NULL;
|
|
|
|
if (virNetDevBandwidthCopy(&port->bandwidth, actual->bandwidth) < 0)
|
|
return NULL;
|
|
|
|
if (virNetDevVlanCopy(&port->vlan, &actual->vlan) < 0)
|
|
return NULL;
|
|
|
|
port->isolatedPort = actual->isolatedPort;
|
|
port->class_id = actual->class_id;
|
|
port->trustGuestRxFilters = actual->trustGuestRxFilters;
|
|
|
|
return g_steal_pointer(&port);
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainNetCreatePort(virConnectPtr conn,
|
|
virDomainDef *dom,
|
|
virDomainNetDef *iface,
|
|
unsigned int flags)
|
|
{
|
|
virErrorPtr save_err;
|
|
g_autoptr(virNetwork) net = NULL;
|
|
g_autoptr(virNetworkPortDef) portdef = NULL;
|
|
g_autoptr(virNetworkPort) port = NULL;
|
|
g_autofree char *portxml = NULL;
|
|
|
|
if (!(net = virNetworkLookupByName(conn, iface->data.network.name)))
|
|
return -1;
|
|
|
|
if (flags & VIR_NETWORK_PORT_CREATE_RECLAIM) {
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
char macstr[VIR_MAC_STRING_BUFLEN];
|
|
|
|
virUUIDFormat(iface->data.network.portid, uuidstr);
|
|
virMacAddrFormat(&iface->mac, macstr);
|
|
|
|
/* if the port is already registered, then we are done */
|
|
if (virUUIDIsValid(iface->data.network.portid) &&
|
|
(port = virNetworkPortLookupByUUID(net, iface->data.network.portid))) {
|
|
VIR_DEBUG("network: %s domain: %s mac: %s port: %s - already registered, skipping",
|
|
iface->data.network.name, dom->name, macstr, uuidstr);
|
|
return 0;
|
|
}
|
|
|
|
/* otherwise we need to create a new port */
|
|
VIR_DEBUG("network: %s domain: %s mac: %s port: %s - not found, reclaiming",
|
|
iface->data.network.name, dom->name, macstr, uuidstr);
|
|
if (!(portdef = virDomainNetDefActualToNetworkPort(dom, iface)))
|
|
return -1;
|
|
} else {
|
|
if (!(portdef = virDomainNetDefToNetworkPort(dom, iface)))
|
|
return -1;
|
|
}
|
|
|
|
if (!(portxml = virNetworkPortDefFormat(portdef)))
|
|
return -1;
|
|
|
|
/* prepare to re-use portdef */
|
|
virNetworkPortDefFree(portdef);
|
|
portdef = NULL;
|
|
|
|
if (!(port = virNetworkPortCreateXML(net, portxml, flags)))
|
|
return -1;
|
|
|
|
/* prepare to re-use portxml */
|
|
VIR_FREE(portxml);
|
|
|
|
if (!(portxml = virNetworkPortGetXMLDesc(port, 0)) ||
|
|
!(portdef = virNetworkPortDefParseString(portxml)) ||
|
|
virDomainNetDefActualFromNetworkPort(iface, portdef) < 0) {
|
|
virErrorPreserveLast(&save_err);
|
|
virNetworkPortDelete(port, 0);
|
|
virErrorRestore(&save_err);
|
|
return -1;
|
|
}
|
|
|
|
virNetworkPortGetUUID(port, iface->data.network.portid);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virDomainNetAllocateActualDevice(virConnectPtr conn,
|
|
virDomainDef *dom,
|
|
virDomainNetDef *iface)
|
|
{
|
|
return virDomainNetCreatePort(conn, dom, iface, 0);
|
|
}
|
|
|
|
void
|
|
virDomainNetNotifyActualDevice(virConnectPtr conn,
|
|
virDomainDef *dom,
|
|
virDomainNetDef *iface)
|
|
{
|
|
virDomainNetType actualType = virDomainNetGetActualType(iface);
|
|
|
|
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK && conn
|
|
&& virDomainNetCreatePort(conn, dom, iface,
|
|
VIR_NETWORK_PORT_CREATE_RECLAIM) < 0) {
|
|
return;
|
|
}
|
|
|
|
if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
|
|
actualType == VIR_DOMAIN_NET_TYPE_BRIDGE) {
|
|
/*
|
|
* NB: we can't notify the guest of any MTU change anyway,
|
|
* so there is no point in trying to learn the actualMTU
|
|
* (final arg to virNetDevTapReattachBridge())
|
|
*/
|
|
ignore_value(virNetDevTapReattachBridge(iface->ifname,
|
|
virDomainNetGetActualBridgeName(iface),
|
|
&iface->mac, dom->uuid,
|
|
virDomainNetGetActualVirtPortProfile(iface),
|
|
virDomainNetGetActualVlan(iface),
|
|
virDomainNetGetActualPortOptionsIsolated(iface),
|
|
iface->mtu, NULL));
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
virDomainNetReleaseActualDevice(virConnectPtr conn,
|
|
virDomainDef *dom G_GNUC_UNUSED,
|
|
virDomainNetDef *iface)
|
|
{
|
|
virNetworkPtr net = NULL;
|
|
virNetworkPortPtr port = NULL;
|
|
int ret = -1;
|
|
|
|
/* Port might not exist if a failure occurred during VM startup */
|
|
if (!virUUIDIsValid(iface->data.network.portid)) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(net = virNetworkLookupByName(conn, iface->data.network.name)))
|
|
goto cleanup;
|
|
|
|
if (!(port = virNetworkPortLookupByUUID(net, iface->data.network.portid)))
|
|
goto cleanup;
|
|
|
|
if (virNetworkPortDelete(port, 0) < 0)
|
|
goto cleanup;
|
|
|
|
cleanup:
|
|
virObjectUnref(port);
|
|
virObjectUnref(net);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainNetBandwidthToTypedParams(virNetDevBandwidth *bandwidth,
|
|
virTypedParameterPtr *params,
|
|
int *nparams)
|
|
{
|
|
int maxparams = 0;
|
|
|
|
if ((bandwidth->in != NULL) &&
|
|
(virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_IN_AVERAGE,
|
|
bandwidth->in->average) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_IN_PEAK,
|
|
bandwidth->in->peak) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_IN_FLOOR,
|
|
bandwidth->in->floor) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_IN_BURST,
|
|
bandwidth->in->burst) < 0))
|
|
goto error;
|
|
|
|
if ((bandwidth->out != NULL) &&
|
|
(virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_OUT_AVERAGE,
|
|
bandwidth->out->average) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_OUT_PEAK,
|
|
bandwidth->out->peak) < 0 ||
|
|
virTypedParamsAddUInt(params, nparams, &maxparams,
|
|
VIR_NETWORK_PORT_BANDWIDTH_OUT_BURST,
|
|
bandwidth->out->burst) < 0))
|
|
goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
virTypedParamsFree(*params, *nparams);
|
|
*params = NULL;
|
|
*nparams = 0;
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainNetBandwidthUpdate(virDomainNetDef *iface,
|
|
virNetDevBandwidth *newBandwidth)
|
|
{
|
|
virNetworkPtr net = NULL;
|
|
virNetworkPortPtr port = NULL;
|
|
virTypedParameterPtr params = NULL;
|
|
int nparams = 0;
|
|
g_autoptr(virConnect) conn = NULL;
|
|
int ret = -1;
|
|
|
|
if (!(conn = virGetConnectNetwork()))
|
|
goto cleanup;
|
|
|
|
if (!(net = virNetworkLookupByName(conn, iface->data.network.name)))
|
|
goto cleanup;
|
|
|
|
if (!(port = virNetworkPortLookupByUUID(net, iface->data.network.portid)))
|
|
goto cleanup;
|
|
|
|
if (virDomainNetBandwidthToTypedParams(newBandwidth, ¶ms, &nparams) < 0)
|
|
goto cleanup;
|
|
|
|
if (virNetworkPortSetParameters(port, params, nparams, 0) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virTypedParamsFree(params, nparams);
|
|
virObjectUnref(port);
|
|
virObjectUnref(net);
|
|
return ret;
|
|
}
|
|
|
|
/* virDomainNetResolveActualType:
|
|
* @iface: the original NetDef from the domain
|
|
*
|
|
* Looks up the network reference by iface, and returns the actual
|
|
* type of the connection without allocating any resources.
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
int
|
|
virDomainNetResolveActualType(virDomainNetDef *iface)
|
|
{
|
|
virNetworkDef *def = NULL;
|
|
int ret = -1;
|
|
g_autofree char *xml = NULL;
|
|
g_autoptr(virConnect) conn = NULL;
|
|
g_autoptr(virNetwork) net = NULL;
|
|
|
|
if (iface->type != VIR_DOMAIN_NET_TYPE_NETWORK)
|
|
return iface->type;
|
|
|
|
if (iface->data.network.actual)
|
|
return iface->data.network.actual->type;
|
|
|
|
if (!(conn = virGetConnectNetwork()))
|
|
return -1;
|
|
|
|
if (!(net = virNetworkLookupByName(conn, iface->data.network.name)))
|
|
goto cleanup;
|
|
|
|
if (!(xml = virNetworkGetXMLDesc(net, 0)))
|
|
goto cleanup;
|
|
|
|
if (!(def = virNetworkDefParseString(xml, NULL)))
|
|
goto cleanup;
|
|
|
|
switch ((virNetworkForwardType) def->forward.type) {
|
|
case VIR_NETWORK_FORWARD_NONE:
|
|
case VIR_NETWORK_FORWARD_NAT:
|
|
case VIR_NETWORK_FORWARD_ROUTE:
|
|
case VIR_NETWORK_FORWARD_OPEN:
|
|
/* for these forward types, the actual net type really *is*
|
|
* NETWORK; we just keep the info from the portgroup in
|
|
* iface->data.network.actual
|
|
*/
|
|
ret = VIR_DOMAIN_NET_TYPE_NETWORK;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_HOSTDEV:
|
|
ret = VIR_DOMAIN_NET_TYPE_HOSTDEV;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_BRIDGE:
|
|
if (def->bridge) {
|
|
/* <forward type='bridge'/> <bridge name='xxx'/>
|
|
* is VIR_DOMAIN_NET_TYPE_BRIDGE
|
|
*/
|
|
ret = VIR_DOMAIN_NET_TYPE_BRIDGE;
|
|
break;
|
|
}
|
|
|
|
/* intentionally fall through to the direct case for
|
|
* VIR_NETWORK_FORWARD_BRIDGE with no bridge device defined
|
|
*/
|
|
G_GNUC_FALLTHROUGH;
|
|
|
|
case VIR_NETWORK_FORWARD_PRIVATE:
|
|
case VIR_NETWORK_FORWARD_VEPA:
|
|
case VIR_NETWORK_FORWARD_PASSTHROUGH:
|
|
/* <forward type='bridge|private|vepa|passthrough'> are all
|
|
* VIR_DOMAIN_NET_TYPE_DIRECT.
|
|
*/
|
|
ret = VIR_DOMAIN_NET_TYPE_DIRECT;
|
|
break;
|
|
|
|
case VIR_NETWORK_FORWARD_LAST:
|
|
default:
|
|
virReportEnumRangeError(virNetworkForwardType, def->forward.type);
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
virNetworkDefFree(def);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskAddISCSIPoolSourceHost(virStorageSource *src,
|
|
virStoragePoolDef *pooldef)
|
|
{
|
|
g_auto(GStrv) tokens = NULL;
|
|
|
|
/* Only support one host */
|
|
if (pooldef->source.nhost != 1) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("Expected exactly 1 host for the storage pool"));
|
|
return -1;
|
|
}
|
|
|
|
/* iscsi pool only supports one host */
|
|
src->nhosts = 1;
|
|
src->hosts = g_new0(virStorageNetHostDef, 1);
|
|
|
|
src->hosts[0].name = g_strdup(pooldef->source.hosts[0].name);
|
|
|
|
if (pooldef->source.hosts[0].port != 0)
|
|
src->hosts[0].port = pooldef->source.hosts[0].port;
|
|
else
|
|
src->hosts[0].port = 3260;
|
|
|
|
/* iscsi volume has name like "unit:0:0:1" */
|
|
if (!(tokens = g_strsplit(src->srcpool->volume, ":", 0)))
|
|
return -1;
|
|
|
|
if (g_strv_length(tokens) != 4) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected iscsi volume name '%s'"),
|
|
src->srcpool->volume);
|
|
return -1;
|
|
}
|
|
|
|
/* iscsi pool has only one source device path */
|
|
src->path = g_strdup_printf("%s/%s", pooldef->source.devices[0].path,
|
|
tokens[3]);
|
|
|
|
/* Storage pool have not supported these 2 attributes yet,
|
|
* use the defaults.
|
|
*/
|
|
src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
|
|
src->hosts[0].socket = NULL;
|
|
|
|
src->protocol = VIR_STORAGE_NET_PROTOCOL_ISCSI;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskTranslateSourcePoolAuth(virStorageSource *src,
|
|
virStoragePoolSource *source)
|
|
{
|
|
/* Only necessary when authentication set */
|
|
if (!source->auth)
|
|
return 0;
|
|
|
|
src->auth = virStorageAuthDefCopy(source->auth);
|
|
if (!src->auth)
|
|
return -1;
|
|
/* A <disk> doesn't use <auth type='%s', so clear that out for the disk */
|
|
src->auth->authType = VIR_STORAGE_AUTH_TYPE_NONE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainDiskTranslateISCSIDirect(virStorageSource *src,
|
|
virStoragePoolDef *pooldef)
|
|
{
|
|
src->srcpool->actualtype = VIR_STORAGE_TYPE_NETWORK;
|
|
src->protocol = VIR_STORAGE_NET_PROTOCOL_ISCSI;
|
|
|
|
if (virDomainDiskTranslateSourcePoolAuth(src,
|
|
&pooldef->source) < 0)
|
|
return -1;
|
|
|
|
/* Source pool may not fill in the secrettype field,
|
|
* so we need to do so here
|
|
*/
|
|
if (src->auth && !src->auth->secrettype) {
|
|
const char *secrettype =
|
|
virSecretUsageTypeToString(VIR_SECRET_USAGE_TYPE_ISCSI);
|
|
src->auth->secrettype = g_strdup(secrettype);
|
|
}
|
|
|
|
if (virDomainDiskAddISCSIPoolSourceHost(src, pooldef) < 0)
|
|
return -1;
|
|
|
|
if (!src->initiator.iqn && pooldef->source.initiator.iqn &&
|
|
virStorageSourceInitiatorCopy(&src->initiator,
|
|
&pooldef->source.initiator) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
virDomainStorageSourceTranslateSourcePool(virStorageSource *src,
|
|
virConnectPtr conn)
|
|
{
|
|
virStorageVolInfo info;
|
|
g_autoptr(virStoragePoolDef) pooldef = NULL;
|
|
g_autofree char *poolxml = NULL;
|
|
g_autoptr(virStoragePool) pool = NULL;
|
|
g_autoptr(virStorageVol) vol = NULL;
|
|
|
|
if (!(pool = virStoragePoolLookupByName(conn, src->srcpool->pool)))
|
|
return -1;
|
|
|
|
if (virStoragePoolIsActive(pool) != 1) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("storage pool '%s' containing volume '%s' "
|
|
"is not active"),
|
|
src->srcpool->pool, src->srcpool->volume);
|
|
return -1;
|
|
}
|
|
|
|
if (!(vol = virStorageVolLookupByName(pool, src->srcpool->volume)))
|
|
return -1;
|
|
|
|
if (virStorageVolGetInfo(vol, &info) < 0)
|
|
return -1;
|
|
|
|
if (!(poolxml = virStoragePoolGetXMLDesc(pool, 0)))
|
|
return -1;
|
|
|
|
if (!(pooldef = virStoragePoolDefParseString(poolxml)))
|
|
return -1;
|
|
|
|
src->srcpool->pooltype = pooldef->type;
|
|
src->srcpool->voltype = info.type;
|
|
|
|
if (src->srcpool->mode && pooldef->type != VIR_STORAGE_POOL_ISCSI) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("disk source mode is only valid when "
|
|
"storage pool is of iscsi type"));
|
|
return -1;
|
|
}
|
|
|
|
VIR_FREE(src->path);
|
|
virStorageNetHostDefFree(src->nhosts, src->hosts);
|
|
src->nhosts = 0;
|
|
src->hosts = NULL;
|
|
virStorageAuthDefFree(src->auth);
|
|
src->auth = NULL;
|
|
|
|
switch ((virStoragePoolType) pooldef->type) {
|
|
case VIR_STORAGE_POOL_DIR:
|
|
case VIR_STORAGE_POOL_FS:
|
|
case VIR_STORAGE_POOL_NETFS:
|
|
case VIR_STORAGE_POOL_LOGICAL:
|
|
case VIR_STORAGE_POOL_DISK:
|
|
case VIR_STORAGE_POOL_SCSI:
|
|
case VIR_STORAGE_POOL_ZFS:
|
|
case VIR_STORAGE_POOL_VSTORAGE:
|
|
if (!(src->path = virStorageVolGetPath(vol)))
|
|
return -1;
|
|
|
|
switch (info.type) {
|
|
case VIR_STORAGE_VOL_FILE:
|
|
src->srcpool->actualtype = VIR_STORAGE_TYPE_FILE;
|
|
break;
|
|
|
|
case VIR_STORAGE_VOL_DIR:
|
|
src->srcpool->actualtype = VIR_STORAGE_TYPE_DIR;
|
|
break;
|
|
|
|
case VIR_STORAGE_VOL_BLOCK:
|
|
src->srcpool->actualtype = VIR_STORAGE_TYPE_BLOCK;
|
|
break;
|
|
|
|
case VIR_STORAGE_VOL_PLOOP:
|
|
src->srcpool->actualtype = VIR_STORAGE_TYPE_FILE;
|
|
break;
|
|
|
|
case VIR_STORAGE_VOL_NETWORK:
|
|
case VIR_STORAGE_VOL_NETDIR:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected storage volume type '%s' "
|
|
"for storage pool type '%s'"),
|
|
virStorageVolTypeToString(info.type),
|
|
virStoragePoolTypeToString(pooldef->type));
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_STORAGE_POOL_ISCSI_DIRECT:
|
|
if (virDomainDiskTranslateISCSIDirect(src, pooldef) < 0)
|
|
return -1;
|
|
|
|
break;
|
|
|
|
case VIR_STORAGE_POOL_ISCSI:
|
|
switch (src->srcpool->mode) {
|
|
case VIR_STORAGE_SOURCE_POOL_MODE_DEFAULT:
|
|
case VIR_STORAGE_SOURCE_POOL_MODE_LAST:
|
|
src->srcpool->mode = VIR_STORAGE_SOURCE_POOL_MODE_HOST;
|
|
G_GNUC_FALLTHROUGH;
|
|
case VIR_STORAGE_SOURCE_POOL_MODE_HOST:
|
|
src->srcpool->actualtype = VIR_STORAGE_TYPE_BLOCK;
|
|
if (!(src->path = virStorageVolGetPath(vol)))
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_STORAGE_SOURCE_POOL_MODE_DIRECT:
|
|
if (virDomainDiskTranslateISCSIDirect(src, pooldef) < 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case VIR_STORAGE_POOL_MPATH:
|
|
case VIR_STORAGE_POOL_RBD:
|
|
case VIR_STORAGE_POOL_SHEEPDOG:
|
|
case VIR_STORAGE_POOL_GLUSTER:
|
|
case VIR_STORAGE_POOL_LAST:
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("using '%s' pools for backing 'volume' disks "
|
|
"isn't yet supported"),
|
|
virStoragePoolTypeToString(pooldef->type));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virDomainDiskTranslateSourcePool(virDomainDiskDef *def)
|
|
{
|
|
g_autoptr(virConnect) conn = NULL;
|
|
virStorageSource *n;
|
|
|
|
for (n = def->src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
if (n->type != VIR_STORAGE_TYPE_VOLUME || !n->srcpool)
|
|
continue;
|
|
|
|
if (!conn) {
|
|
if (!(conn = virGetConnectStorage()))
|
|
return -1;
|
|
}
|
|
|
|
if (virDomainStorageSourceTranslateSourcePool(n, conn) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (def->startupPolicy != 0 &&
|
|
virStorageSourceGetActualType(def->src) != VIR_STORAGE_TYPE_FILE) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("'startupPolicy' is only valid for 'file' type volume"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDiskGetDetectZeroesMode:
|
|
* @discard: disk/image sector discard setting
|
|
* @detect_zeroes: disk/image zero sector detection mode
|
|
*
|
|
* As a convenience syntax, if discards are ignored and zero detection is set
|
|
* to 'unmap', then simply behave like zero detection is set to 'on'. But
|
|
* don't change it in the XML for easier adjustments. This behaviour is
|
|
* documented.
|
|
*/
|
|
int
|
|
virDomainDiskGetDetectZeroesMode(virDomainDiskDiscard discard,
|
|
virDomainDiskDetectZeroes detect_zeroes)
|
|
{
|
|
if (discard != VIR_DOMAIN_DISK_DISCARD_UNMAP &&
|
|
detect_zeroes == VIR_DOMAIN_DISK_DETECT_ZEROES_UNMAP)
|
|
return VIR_DOMAIN_DISK_DETECT_ZEROES_ON;
|
|
|
|
return detect_zeroes;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainDefHasManagedPR:
|
|
* @def: domain definition
|
|
*
|
|
* Returns true if any of the domain disks requires the use of the managed
|
|
* persistent reservations infrastructure.
|
|
*/
|
|
bool
|
|
virDomainDefHasManagedPR(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
if (virStorageSourceChainHasManagedPR(def->disks[i]->src))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasNVMeDisk(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
if (virStorageSourceChainHasNVMe(def->disks[i]->src))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasVFIOHostdev(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
if (virHostdevIsVFIODevice(def->hostdevs[i]))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasMdevHostdev(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nhostdevs; i++) {
|
|
if (virHostdevIsMdevDevice(def->hostdevs[i]))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasVDPANet(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->nnets; i++) {
|
|
if (virDomainNetGetActualType(def->nets[i]) == VIR_DOMAIN_NET_TYPE_VDPA)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasOldStyleUEFI(const virDomainDef *def)
|
|
{
|
|
return def->os.loader &&
|
|
def->os.loader->type == VIR_DOMAIN_LOADER_TYPE_PFLASH;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainDefHasOldStyleROUEFI(const virDomainDef *def)
|
|
{
|
|
return virDomainDefHasOldStyleUEFI(def) &&
|
|
def->os.loader->readonly == VIR_TRISTATE_BOOL_YES;
|
|
}
|
|
|
|
|
|
/**
|
|
* virDomainGraphicsDefHasOpenGL:
|
|
* @def: domain definition
|
|
*
|
|
* Returns true if a domain config contains at least one <graphics> element
|
|
* with OpenGL support enabled, false otherwise.
|
|
*/
|
|
bool
|
|
virDomainGraphicsDefHasOpenGL(const virDomainDef *def)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < def->ngraphics; i++) {
|
|
virDomainGraphicsDef *graphics = def->graphics[i];
|
|
|
|
/* we only care about OpenGL support for a given type here */
|
|
switch (graphics->type) {
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
|
|
continue;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
|
|
if (graphics->data.sdl.gl == VIR_TRISTATE_BOOL_YES)
|
|
return true;
|
|
|
|
continue;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
|
|
if (graphics->data.spice.gl == VIR_TRISTATE_BOOL_YES)
|
|
return true;
|
|
|
|
continue;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
|
|
return true;
|
|
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainGraphicsSupportsRenderNode(const virDomainGraphicsDef *graphics)
|
|
{
|
|
bool ret = false;
|
|
|
|
if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE ||
|
|
graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS)
|
|
ret = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
const char *
|
|
virDomainGraphicsGetRenderNode(const virDomainGraphicsDef *graphics)
|
|
{
|
|
const char *ret = NULL;
|
|
|
|
switch (graphics->type) {
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
|
|
ret = graphics->data.spice.rendernode;
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
|
|
ret = graphics->data.egl_headless.rendernode;
|
|
break;
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
|
|
case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainGraphicsNeedsAutoRenderNode(const virDomainGraphicsDef *graphics)
|
|
{
|
|
if (!virDomainGraphicsSupportsRenderNode(graphics))
|
|
return false;
|
|
|
|
if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE &&
|
|
graphics->data.spice.gl != VIR_TRISTATE_BOOL_YES)
|
|
return false;
|
|
|
|
if (virDomainGraphicsGetRenderNode(graphics))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainBlockIoTuneInfoHasBasic(const virDomainBlockIoTuneInfo *iotune)
|
|
{
|
|
return iotune->total_bytes_sec ||
|
|
iotune->read_bytes_sec ||
|
|
iotune->write_bytes_sec ||
|
|
iotune->total_iops_sec ||
|
|
iotune->read_iops_sec ||
|
|
iotune->write_iops_sec;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainBlockIoTuneInfoHasMax(const virDomainBlockIoTuneInfo *iotune)
|
|
{
|
|
return iotune->total_bytes_sec_max ||
|
|
iotune->read_bytes_sec_max ||
|
|
iotune->write_bytes_sec_max ||
|
|
iotune->total_iops_sec_max ||
|
|
iotune->read_iops_sec_max ||
|
|
iotune->write_iops_sec_max ||
|
|
iotune->size_iops_sec;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainBlockIoTuneInfoHasMaxLength(const virDomainBlockIoTuneInfo *iotune)
|
|
{
|
|
return iotune->total_bytes_sec_max_length ||
|
|
iotune->read_bytes_sec_max_length ||
|
|
iotune->write_bytes_sec_max_length ||
|
|
iotune->total_iops_sec_max_length ||
|
|
iotune->read_iops_sec_max_length ||
|
|
iotune->write_iops_sec_max_length;
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainBlockIoTuneInfoHasAny(const virDomainBlockIoTuneInfo *iotune)
|
|
{
|
|
return virDomainBlockIoTuneInfoHasBasic(iotune) ||
|
|
virDomainBlockIoTuneInfoHasMax(iotune) ||
|
|
virDomainBlockIoTuneInfoHasMaxLength(iotune);
|
|
}
|
|
|
|
|
|
void
|
|
virDomainBlockIoTuneInfoCopy(const virDomainBlockIoTuneInfo *src,
|
|
virDomainBlockIoTuneInfo *dst)
|
|
{
|
|
*dst = *src;
|
|
dst->group_name = g_strdup(src->group_name);
|
|
}
|
|
|
|
|
|
bool
|
|
virDomainBlockIoTuneInfoEqual(const virDomainBlockIoTuneInfo *a,
|
|
const virDomainBlockIoTuneInfo *b)
|
|
{
|
|
return a->total_bytes_sec == b->total_bytes_sec &&
|
|
a->read_bytes_sec == b->read_bytes_sec &&
|
|
a->write_bytes_sec == b->write_bytes_sec &&
|
|
a->total_iops_sec == b->total_iops_sec &&
|
|
a->read_iops_sec == b->read_iops_sec &&
|
|
a->write_iops_sec == b->write_iops_sec &&
|
|
a->total_bytes_sec_max == b->total_bytes_sec_max &&
|
|
a->read_bytes_sec_max == b->read_bytes_sec_max &&
|
|
a->write_bytes_sec_max == b->write_bytes_sec_max &&
|
|
a->total_iops_sec_max == b->total_iops_sec_max &&
|
|
a->read_iops_sec_max == b->read_iops_sec_max &&
|
|
a->write_iops_sec_max == b->write_iops_sec_max &&
|
|
a->size_iops_sec == b->size_iops_sec &&
|
|
a->total_bytes_sec_max_length == b->total_bytes_sec_max_length &&
|
|
a->read_bytes_sec_max_length == b->read_bytes_sec_max_length &&
|
|
a->write_bytes_sec_max_length == b->write_bytes_sec_max_length &&
|
|
a->total_iops_sec_max_length == b->total_iops_sec_max_length &&
|
|
a->read_iops_sec_max_length == b->read_iops_sec_max_length &&
|
|
a->write_iops_sec_max_length == b->write_iops_sec_max_length;
|
|
}
|
|
|
|
|
|
/**
|
|
* virHostdevIsSCSIDevice:
|
|
* @hostdev: host device to check
|
|
*
|
|
* Returns true if @hostdev is a SCSI device, false otherwise.
|
|
*/
|
|
bool
|
|
virHostdevIsSCSIDevice(const virDomainHostdevDef *hostdev)
|
|
{
|
|
return hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
|
|
hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI;
|
|
}
|
|
|
|
|
|
/**
|
|
* virHostdevIsMdevDevice:
|
|
* @hostdev: host device to check
|
|
*
|
|
* Returns true if @hostdev is a Mediated device, false otherwise.
|
|
*/
|
|
bool
|
|
virHostdevIsMdevDevice(const virDomainHostdevDef *hostdev)
|
|
{
|
|
return hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
|
|
hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV;
|
|
}
|
|
|
|
|
|
/**
|
|
* virHostdevIsVFIODevice:
|
|
* @hostdev: host device to check
|
|
*
|
|
* Returns true if @hostdev is a PCI device with VFIO backend, false otherwise.
|
|
*/
|
|
bool
|
|
virHostdevIsVFIODevice(const virDomainHostdevDef *hostdev)
|
|
{
|
|
return hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
|
|
hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI &&
|
|
hostdev->source.subsys.u.pci.backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO;
|
|
}
|