libvirt/src/qemu/qemu_command.c
Daniel P. Berrange cfb92c9b0c Remove broken error reporting in QEMU mac filtering
The qemu_bridge_filter.c file had some helpers for calling
the ebtablesXXX functions todo bridge filtering. The only
thing these helpers did was to overwrite the original error
message from the ebtables code. For added fun, the callers
of these helpers overwrote the errors yet again. For even
more fun, one of the helpers called another helper and
overwrite its errors too.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2014-03-11 11:04:55 +00:00

12283 lines
423 KiB
C

/*
* qemu_command.c: QEMU command generation
*
* Copyright (C) 2006-2014 Red Hat, Inc.
* Copyright (C) 2006 Daniel P. Berrange
*
* 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/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include <config.h>
#include "qemu_command.h"
#include "qemu_hostdev.h"
#include "qemu_capabilities.h"
#include "cpu/cpu.h"
#include "dirname.h"
#include "passfd.h"
#include "viralloc.h"
#include "virlog.h"
#include "virarch.h"
#include "virerror.h"
#include "virfile.h"
#include "virnetdev.h"
#include "virstring.h"
#include "viruuid.h"
#include "c-ctype.h"
#include "domain_nwfilter.h"
#include "domain_audit.h"
#include "domain_conf.h"
#include "snapshot_conf.h"
#include "storage_conf.h"
#include "network/bridge_driver.h"
#include "virnetdevtap.h"
#include "base64.h"
#include "device_conf.h"
#include "virstoragefile.h"
#include "virtpm.h"
#include "virscsi.h"
#if defined(__linux__)
# include <linux/capability.h>
#endif
#include <sys/stat.h>
#include <fcntl.h>
#define VIR_FROM_THIS VIR_FROM_QEMU
#define VIO_ADDR_NET 0x1000ul
#define VIO_ADDR_SCSI 0x2000ul
#define VIO_ADDR_SERIAL 0x30000000ul
#define VIO_ADDR_NVRAM 0x3000ul
VIR_ENUM_DECL(virDomainDiskQEMUBus)
VIR_ENUM_IMPL(virDomainDiskQEMUBus, VIR_DOMAIN_DISK_BUS_LAST,
"ide",
"floppy",
"scsi",
"virtio",
"xen",
"usb",
"uml",
"sata",
"sd")
VIR_ENUM_DECL(qemuDiskCacheV1)
VIR_ENUM_DECL(qemuDiskCacheV2)
VIR_ENUM_IMPL(qemuDiskCacheV1, VIR_DOMAIN_DISK_CACHE_LAST,
"default",
"off",
"off", /* writethrough not supported, so for safety, disable */
"on", /* Old 'on' was equivalent to 'writeback' */
"off", /* directsync not supported, for safety, disable */
"off"); /* unsafe not supported, for safety, disable */
VIR_ENUM_IMPL(qemuDiskCacheV2, VIR_DOMAIN_DISK_CACHE_LAST,
"default",
"none",
"writethrough",
"writeback",
"directsync",
"unsafe");
VIR_ENUM_DECL(qemuVideo)
VIR_ENUM_IMPL(qemuVideo, VIR_DOMAIN_VIDEO_TYPE_LAST,
"std",
"cirrus",
"vmware",
"", /* no arg needed for xen */
"", /* don't support vbox */
"qxl");
VIR_ENUM_DECL(qemuDeviceVideo)
VIR_ENUM_IMPL(qemuDeviceVideo, VIR_DOMAIN_VIDEO_TYPE_LAST,
"VGA",
"cirrus-vga",
"vmware-svga",
"", /* no device for xen */
"", /* don't support vbox */
"qxl-vga");
VIR_ENUM_DECL(qemuSoundCodec)
VIR_ENUM_IMPL(qemuSoundCodec, VIR_DOMAIN_SOUND_CODEC_TYPE_LAST,
"hda-duplex",
"hda-micro");
VIR_ENUM_DECL(qemuControllerModelUSB)
VIR_ENUM_IMPL(qemuControllerModelUSB, VIR_DOMAIN_CONTROLLER_MODEL_USB_LAST,
"piix3-usb-uhci",
"piix4-usb-uhci",
"usb-ehci",
"ich9-usb-ehci1",
"ich9-usb-uhci1",
"ich9-usb-uhci2",
"ich9-usb-uhci3",
"vt82c686b-usb-uhci",
"pci-ohci",
"nec-usb-xhci",
"none");
VIR_ENUM_DECL(qemuDomainFSDriver)
VIR_ENUM_IMPL(qemuDomainFSDriver, VIR_DOMAIN_FS_DRIVER_TYPE_LAST,
"local",
"local",
"handle",
NULL,
NULL);
/**
* qemuPhysIfaceConnect:
* @def: the definition of the VM (needed by 802.1Qbh and audit)
* @driver: pointer to the driver instance
* @net: pointer to he VM's interface description with direct device type
* @qemuCaps: flags for qemu
* @vmop: VM operation type
*
* Returns a filedescriptor on success or -1 in case of error.
*/
int
qemuPhysIfaceConnect(virDomainDefPtr def,
virQEMUDriverPtr driver,
virDomainNetDefPtr net,
virQEMUCapsPtr qemuCaps,
enum virNetDevVPortProfileOp vmop)
{
int rc;
char *res_ifname = NULL;
int vnet_hdr = 0;
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNET_HDR) &&
net->model && STREQ(net->model, "virtio"))
vnet_hdr = 1;
rc = virNetDevMacVLanCreateWithVPortProfile(
net->ifname, &net->mac,
virDomainNetGetActualDirectDev(net),
virDomainNetGetActualDirectMode(net),
true, vnet_hdr, def->uuid,
virDomainNetGetActualVirtPortProfile(net),
&res_ifname,
vmop, cfg->stateDir,
virDomainNetGetActualBandwidth(net));
if (rc >= 0) {
if (virSecurityManagerSetTapFDLabel(driver->securityManager,
def, rc) < 0)
goto error;
virDomainAuditNetDevice(def, net, res_ifname, true);
VIR_FREE(net->ifname);
net->ifname = res_ifname;
}
virObjectUnref(cfg);
return rc;
error:
ignore_value(virNetDevMacVLanDeleteWithVPortProfile(
res_ifname, &net->mac,
virDomainNetGetActualDirectDev(net),
virDomainNetGetActualDirectMode(net),
virDomainNetGetActualVirtPortProfile(net),
cfg->stateDir));
VIR_FREE(res_ifname);
virObjectUnref(cfg);
return -1;
}
/**
* qemuCreateInBridgePortWithHelper:
* @cfg: the configuration object in which the helper name is looked up
* @brname: the bridge name
* @ifname: the returned interface name
* @macaddr: the returned MAC address
* @tapfd: file descriptor return value for the new tap device
* @flags: OR of virNetDevTapCreateFlags:
* VIR_NETDEV_TAP_CREATE_VNET_HDR
* - Enable IFF_VNET_HDR on the tap device
*
* This function creates a new tap device on a bridge using an external
* helper. The final name for the bridge will be stored in @ifname.
*
* Returns 0 in case of success or -1 on failure
*/
static int qemuCreateInBridgePortWithHelper(virQEMUDriverConfigPtr cfg,
const char *brname,
char **ifname,
int *tapfd,
unsigned int flags)
{
virCommandPtr cmd;
int pair[2] = { -1, -1 };
if ((flags & ~VIR_NETDEV_TAP_CREATE_VNET_HDR) != VIR_NETDEV_TAP_CREATE_IFUP)
return -1;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) {
virReportSystemError(errno, "%s", _("failed to create socket"));
return -1;
}
cmd = virCommandNew(cfg->bridgeHelperName);
if (flags & VIR_NETDEV_TAP_CREATE_VNET_HDR)
virCommandAddArgFormat(cmd, "--use-vnet");
virCommandAddArgFormat(cmd, "--br=%s", brname);
virCommandAddArgFormat(cmd, "--fd=%d", pair[1]);
virCommandPassFD(cmd, pair[1],
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
virCommandClearCaps(cmd);
#ifdef CAP_NET_ADMIN
virCommandAllowCap(cmd, CAP_NET_ADMIN);
#endif
if (virCommandRunAsync(cmd, NULL) < 0) {
*tapfd = -1;
goto cleanup;
}
do {
*tapfd = recvfd(pair[0], 0);
} while (*tapfd < 0 && errno == EINTR);
if (*tapfd < 0) {
virReportSystemError(errno, "%s",
_("failed to retrieve file descriptor for interface"));
goto cleanup;
}
if (virNetDevTapGetName(*tapfd, ifname) < 0 ||
virCommandWait(cmd, NULL) < 0) {
VIR_FORCE_CLOSE(*tapfd);
*tapfd = -1;
}
cleanup:
virCommandFree(cmd);
VIR_FORCE_CLOSE(pair[0]);
return *tapfd < 0 ? -1 : 0;
}
int
qemuNetworkIfaceConnect(virDomainDefPtr def,
virConnectPtr conn,
virQEMUDriverPtr driver,
virDomainNetDefPtr net,
virQEMUCapsPtr qemuCaps,
int *tapfd,
int *tapfdSize)
{
char *brname = NULL;
int ret = -1;
unsigned int tap_create_flags = VIR_NETDEV_TAP_CREATE_IFUP;
bool template_ifname = false;
int actualType = virDomainNetGetActualType(net);
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK) {
int active;
bool fail = false;
virErrorPtr errobj;
virNetworkPtr network = virNetworkLookupByName(conn,
net->data.network.name);
if (!network)
return ret;
active = virNetworkIsActive(network);
if (active != 1) {
fail = true;
if (active == 0)
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Network '%s' is not active."),
net->data.network.name);
}
if (!fail) {
brname = virNetworkGetBridgeName(network);
if (brname == NULL)
fail = true;
}
/* Make sure any above failure is preserved */
errobj = virSaveLastError();
virNetworkFree(network);
virSetError(errobj);
virFreeError(errobj);
if (fail)
return ret;
} else if (actualType == VIR_DOMAIN_NET_TYPE_BRIDGE) {
if (VIR_STRDUP(brname, virDomainNetGetActualBridgeName(net)) < 0)
return ret;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Network type %d is not supported"),
virDomainNetGetActualType(net));
return ret;
}
if (!net->ifname ||
STRPREFIX(net->ifname, VIR_NET_GENERATED_PREFIX) ||
strchr(net->ifname, '%')) {
VIR_FREE(net->ifname);
if (VIR_STRDUP(net->ifname, VIR_NET_GENERATED_PREFIX "%d") < 0)
goto cleanup;
/* avoid exposing vnet%d in getXMLDesc or error outputs */
template_ifname = true;
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNET_HDR) &&
net->model && STREQ(net->model, "virtio")) {
tap_create_flags |= VIR_NETDEV_TAP_CREATE_VNET_HDR;
}
if (cfg->privileged) {
if (virNetDevTapCreateInBridgePort(brname, &net->ifname, &net->mac,
def->uuid, tapfd, *tapfdSize,
virDomainNetGetActualVirtPortProfile(net),
virDomainNetGetActualVlan(net),
tap_create_flags) < 0) {
virDomainAuditNetDevice(def, net, "/dev/net/tun", false);
goto cleanup;
}
} else {
if (qemuCreateInBridgePortWithHelper(cfg, brname,
&net->ifname,
tapfd, tap_create_flags) < 0) {
virDomainAuditNetDevice(def, net, "/dev/net/tun", false);
goto cleanup;
}
/* qemuCreateInBridgePortWithHelper can only create a single FD */
if (*tapfdSize > 1) {
VIR_WARN("Ignoring multiqueue network request");
*tapfdSize = 1;
}
}
virDomainAuditNetDevice(def, net, "/dev/net/tun", true);
if (cfg->macFilter &&
ebtablesAddForwardAllowIn(driver->ebtables,
net->ifname,
&net->mac) < 0)
goto cleanup;
if (virNetDevBandwidthSet(net->ifname,
virDomainNetGetActualBandwidth(net),
false) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot set bandwidth limits on %s"),
net->ifname);
goto cleanup;
}
if (net->filter && net->ifname &&
virDomainConfNWFilterInstantiate(conn, def->uuid, net) < 0) {
goto cleanup;
}
ret = 0;
cleanup:
if (ret < 0) {
size_t i;
for (i = 0; i < *tapfdSize && tapfd[i] >= 0; i++)
VIR_FORCE_CLOSE(tapfd[i]);
if (template_ifname)
VIR_FREE(net->ifname);
}
VIR_FREE(brname);
virObjectUnref(cfg);
return ret;
}
static bool
qemuDomainSupportsNicdev(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
virDomainNetDefPtr net)
{
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
return false;
/* non-virtio ARM nics require legacy -net nic */
if (((def->os.arch == VIR_ARCH_ARMV7L) ||
(def->os.arch == VIR_ARCH_AARCH64)) &&
net->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO)
return false;
return true;
}
static bool
qemuDomainSupportsNetdev(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
virDomainNetDefPtr net)
{
if (!qemuDomainSupportsNicdev(def, qemuCaps, net))
return false;
return virQEMUCapsGet(qemuCaps, QEMU_CAPS_NETDEV);
}
/**
* qemuOpenVhostNet:
* @def: domain definition
* @net: network definition
* @qemuCaps: qemu binary capabilities
* @vhostfd: array of opened vhost-net device
* @vhostfdSize: number of file descriptors in @vhostfd array
*
* Open vhost-net, multiple times - if requested.
* In case, no vhost-net is needed, @vhostfdSize is set to 0
* and 0 is returned.
*
* Returns: 0 on success
* -1 on failure
*/
int
qemuOpenVhostNet(virDomainDefPtr def,
virDomainNetDefPtr net,
virQEMUCapsPtr qemuCaps,
int *vhostfd,
int *vhostfdSize)
{
size_t i;
/* If running a plain QEMU guest, or
* if the config says explicitly to not use vhost, return now*/
if (def->virtType != VIR_DOMAIN_VIRT_KVM ||
net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_QEMU) {
*vhostfdSize = 0;
return 0;
}
/* If qemu doesn't support vhost-net mode (including the -netdev command
* option), don't try to open the device.
*/
if (!(virQEMUCapsGet(qemuCaps, QEMU_CAPS_VHOST_NET) &&
qemuDomainSupportsNetdev(def, qemuCaps, net))) {
if (net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_VHOST) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("vhost-net is not supported with "
"this QEMU binary"));
return -1;
}
*vhostfdSize = 0;
return 0;
}
/* If the nic model isn't virtio, don't try to open. */
if (!(net->model && STREQ(net->model, "virtio"))) {
if (net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_VHOST) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("vhost-net is only supported for "
"virtio network interfaces"));
return -1;
}
*vhostfdSize = 0;
return 0;
}
for (i = 0; i < *vhostfdSize; i++) {
vhostfd[i] = open("/dev/vhost-net", O_RDWR);
/* If the config says explicitly to use vhost and we couldn't open it,
* report an error.
*/
if (vhostfd[i] < 0) {
virDomainAuditNetDevice(def, net, "/dev/vhost-net", false);
if (net->driver.virtio.name == VIR_DOMAIN_NET_BACKEND_TYPE_VHOST) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("vhost-net was requested for an interface, "
"but is unavailable"));
goto error;
}
VIR_WARN("Unable to open vhost-net. Opened so far %zu, requested %d",
i, *vhostfdSize);
*vhostfdSize = i;
break;
}
}
virDomainAuditNetDevice(def, net, "/dev/vhost-net", *vhostfdSize);
return 0;
error:
while (i--)
VIR_FORCE_CLOSE(vhostfd[i]);
return -1;
}
int
qemuNetworkPrepareDevices(virDomainDefPtr def)
{
int ret = -1;
size_t i;
for (i = 0; i < def->nnets; i++) {
virDomainNetDefPtr net = def->nets[i];
int actualType;
/* If appropriate, grab a physical device from the configured
* network's pool of devices, or resolve bridge device name
* to the one defined in the network definition.
*/
if (networkAllocateActualDevice(def, net) < 0)
goto cleanup;
actualType = virDomainNetGetActualType(net);
if (actualType == VIR_DOMAIN_NET_TYPE_HOSTDEV &&
net->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
/* Each type='hostdev' network device must also have a
* corresponding entry in the hostdevs array. For netdevs
* that are hardcoded as type='hostdev', this is already
* done by the parser, but for those allocated from a
* network / determined at runtime, we need to do it
* separately.
*/
virDomainHostdevDefPtr hostdev = virDomainNetGetActualHostdev(net);
if (virDomainHostdevFind(def, hostdev, NULL) >= 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("PCI device %04x:%02x:%02x.%x "
"allocated from network %s is already "
"in use by domain %s"),
hostdev->source.subsys.u.pci.addr.domain,
hostdev->source.subsys.u.pci.addr.bus,
hostdev->source.subsys.u.pci.addr.slot,
hostdev->source.subsys.u.pci.addr.function,
net->data.network.name,
def->name);
goto cleanup;
}
if (virDomainHostdevInsert(def, hostdev) < 0)
goto cleanup;
}
}
ret = 0;
cleanup:
return ret;
}
static int qemuDomainDeviceAliasIndex(const virDomainDeviceInfo *info,
const char *prefix)
{
int idx;
if (!info->alias)
return -1;
if (!STRPREFIX(info->alias, prefix))
return -1;
if (virStrToLong_i(info->alias + strlen(prefix), NULL, 10, &idx) < 0)
return -1;
return idx;
}
int qemuDomainNetVLAN(virDomainNetDefPtr def)
{
return qemuDomainDeviceAliasIndex(&def->info, "net");
}
/* Names used before -drive existed */
static int qemuAssignDeviceDiskAliasLegacy(virDomainDiskDefPtr disk)
{
char *dev_name;
if (VIR_STRDUP(dev_name,
disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM &&
STREQ(disk->dst, "hdc") ? "cdrom" : disk->dst) < 0)
return -1;
disk->info.alias = dev_name;
return 0;
}
char *qemuDeviceDriveHostAlias(virDomainDiskDefPtr disk,
virQEMUCapsPtr qemuCaps)
{
char *ret;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
ignore_value(virAsprintf(&ret, "%s%s", QEMU_DRIVE_HOST_PREFIX,
disk->info.alias));
} else {
ignore_value(VIR_STRDUP(ret, disk->info.alias));
}
return ret;
}
/* Names used before -drive supported the id= option */
static int qemuAssignDeviceDiskAliasFixed(virDomainDiskDefPtr disk)
{
int busid, devid;
int ret;
char *dev_name;
if (virDiskNameToBusDeviceIndex(disk, &busid, &devid) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot convert disk '%s' to bus/device index"),
disk->dst);
return -1;
}
switch (disk->bus) {
case VIR_DOMAIN_DISK_BUS_IDE:
if (disk->device== VIR_DOMAIN_DISK_DEVICE_DISK)
ret = virAsprintf(&dev_name, "ide%d-hd%d", busid, devid);
else
ret = virAsprintf(&dev_name, "ide%d-cd%d", busid, devid);
break;
case VIR_DOMAIN_DISK_BUS_SCSI:
if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK)
ret = virAsprintf(&dev_name, "scsi%d-hd%d", busid, devid);
else
ret = virAsprintf(&dev_name, "scsi%d-cd%d", busid, devid);
break;
case VIR_DOMAIN_DISK_BUS_FDC:
ret = virAsprintf(&dev_name, "floppy%d", devid);
break;
case VIR_DOMAIN_DISK_BUS_VIRTIO:
ret = virAsprintf(&dev_name, "virtio%d", devid);
break;
case VIR_DOMAIN_DISK_BUS_XEN:
ret = virAsprintf(&dev_name, "xenblk%d", devid);
break;
case VIR_DOMAIN_DISK_BUS_SD:
ret = virAsprintf(&dev_name, "sd%d", devid);
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported disk name mapping for bus '%s'"),
virDomainDiskBusTypeToString(disk->bus));
return -1;
}
if (ret == -1)
return -1;
disk->info.alias = dev_name;
return 0;
}
static int
qemuSetScsiControllerModel(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
int *model)
{
if (*model > 0) {
switch (*model) {
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_LSI)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU doesn't support "
"the LSI 53C895A SCSI controller"));
return -1;
}
break;
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_SCSI)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU doesn't support "
"virtio scsi controller"));
return -1;
}
break;
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_IBMVSCSI:
/*TODO: need checking work here if necessary */
break;
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSISAS1078:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_MEGASAS)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU doesn't support "
"the LSI SAS1078 controller"));
return -1;
}
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported controller model: %s"),
virDomainControllerModelSCSITypeToString(*model));
return -1;
}
} else {
if ((def->os.arch == VIR_ARCH_PPC64) &&
STREQ(def->os.machine, "pseries")) {
*model = VIR_DOMAIN_CONTROLLER_MODEL_SCSI_IBMVSCSI;
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_LSI)) {
*model = VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC;
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_SCSI)) {
*model = VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to determine model for scsi controller"));
return -1;
}
}
return 0;
}
/* Our custom -drive naming scheme used with id= */
static int
qemuAssignDeviceDiskAliasCustom(virDomainDefPtr def,
virDomainDiskDefPtr disk,
virQEMUCapsPtr qemuCaps)
{
const char *prefix = virDomainDiskBusTypeToString(disk->bus);
int controllerModel = -1;
if (disk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE) {
if (disk->bus == VIR_DOMAIN_DISK_BUS_SCSI) {
controllerModel =
virDomainDeviceFindControllerModel(def, &disk->info,
VIR_DOMAIN_CONTROLLER_TYPE_SCSI);
if ((qemuSetScsiControllerModel(def, qemuCaps, &controllerModel)) < 0)
return -1;
}
if (disk->bus != VIR_DOMAIN_DISK_BUS_SCSI ||
controllerModel == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC) {
if (virAsprintf(&disk->info.alias, "%s%d-%d-%d", prefix,
disk->info.addr.drive.controller,
disk->info.addr.drive.bus,
disk->info.addr.drive.unit) < 0)
return -1;
} else {
if (virAsprintf(&disk->info.alias, "%s%d-%d-%d-%d", prefix,
disk->info.addr.drive.controller,
disk->info.addr.drive.bus,
disk->info.addr.drive.target,
disk->info.addr.drive.unit) < 0)
return -1;
}
} else {
int idx = virDiskNameToIndex(disk->dst);
if (virAsprintf(&disk->info.alias, "%s-disk%d", prefix, idx) < 0)
return -1;
}
return 0;
}
int
qemuAssignDeviceDiskAlias(virDomainDefPtr vmdef,
virDomainDiskDefPtr def,
virQEMUCapsPtr qemuCaps)
{
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE)) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
return qemuAssignDeviceDiskAliasCustom(vmdef, def, qemuCaps);
else
return qemuAssignDeviceDiskAliasFixed(def);
} else {
return qemuAssignDeviceDiskAliasLegacy(def);
}
}
int
qemuAssignDeviceNetAlias(virDomainDefPtr def, virDomainNetDefPtr net, int idx)
{
if (idx == -1) {
size_t i;
idx = 0;
for (i = 0; i < def->nnets; i++) {
int thisidx;
if (virDomainNetGetActualType(def->nets[i])
== VIR_DOMAIN_NET_TYPE_HOSTDEV) {
/* type='hostdev' interfaces have a hostdev%d alias */
continue;
}
if ((thisidx = qemuDomainDeviceAliasIndex(&def->nets[i]->info, "net")) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to determine device index for network device"));
return -1;
}
if (thisidx >= idx)
idx = thisidx + 1;
}
}
if (virAsprintf(&net->info.alias, "net%d", idx) < 0)
return -1;
return 0;
}
int
qemuAssignDeviceHostdevAlias(virDomainDefPtr def, virDomainHostdevDefPtr hostdev, int idx)
{
if (idx == -1) {
size_t i;
idx = 0;
for (i = 0; i < def->nhostdevs; i++) {
int thisidx;
if ((thisidx = qemuDomainDeviceAliasIndex(def->hostdevs[i]->info, "hostdev")) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to determine device index for hostdev device"));
return -1;
}
if (thisidx >= idx)
idx = thisidx + 1;
}
}
if (virAsprintf(&hostdev->info->alias, "hostdev%d", idx) < 0)
return -1;
return 0;
}
int
qemuAssignDeviceRedirdevAlias(virDomainDefPtr def, virDomainRedirdevDefPtr redirdev, int idx)
{
if (idx == -1) {
size_t i;
idx = 0;
for (i = 0; i < def->nredirdevs; i++) {
int thisidx;
if ((thisidx = qemuDomainDeviceAliasIndex(&def->redirdevs[i]->info, "redir")) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to determine device index for redirected device"));
return -1;
}
if (thisidx >= idx)
idx = thisidx + 1;
}
}
if (virAsprintf(&redirdev->info.alias, "redir%d", idx) < 0)
return -1;
return 0;
}
int
qemuAssignDeviceControllerAlias(virDomainControllerDefPtr controller)
{
const char *prefix = virDomainControllerTypeToString(controller->type);
if (controller->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
/* only pcie-root uses a different naming convention
* ("pcie.0"), because it is hardcoded that way in qemu. All
* other buses use the consistent "pci.%u".
*/
if (controller->model == VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT)
return virAsprintf(&controller->info.alias, "pcie.%d", controller->idx);
else
return virAsprintf(&controller->info.alias, "pci.%d", controller->idx);
}
return virAsprintf(&controller->info.alias, "%s%d", prefix, controller->idx);
}
static ssize_t
qemuGetNextChrDevIndex(virDomainDefPtr def,
virDomainChrDefPtr chr,
const char *prefix)
{
const virDomainChrDef **arrPtr;
size_t cnt;
size_t i;
ssize_t idx = 0;
const char *prefix2 = NULL;
if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE)
prefix2 = "serial";
virDomainChrGetDomainPtrs(def, chr->deviceType, &arrPtr, &cnt);
for (i = 0; i < cnt; i++) {
ssize_t thisidx;
if (((thisidx = qemuDomainDeviceAliasIndex(&arrPtr[i]->info, prefix)) < 0) &&
(prefix2 &&
(thisidx = qemuDomainDeviceAliasIndex(&arrPtr[i]->info, prefix2)) < 0)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to determine device index for character device"));
return -1;
}
if (thisidx >= idx)
idx = thisidx + 1;
}
return idx;
}
int
qemuAssignDeviceChrAlias(virDomainDefPtr def,
virDomainChrDefPtr chr,
ssize_t idx)
{
const char *prefix = NULL;
switch ((enum virDomainChrDeviceType) chr->deviceType) {
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
prefix = "parallel";
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
prefix = "serial";
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
prefix = "console";
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
prefix = "channel";
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
return -1;
}
if (idx == -1 && (idx = qemuGetNextChrDevIndex(def, chr, prefix)) < 0)
return -1;
return virAsprintf(&chr->info.alias, "%s%zd", prefix, idx);
}
int
qemuAssignDeviceAliases(virDomainDefPtr def, virQEMUCapsPtr qemuCaps)
{
size_t i;
for (i = 0; i < def->ndisks; i++) {
if (qemuAssignDeviceDiskAlias(def, def->disks[i], qemuCaps) < 0)
return -1;
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NET_NAME) ||
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
for (i = 0; i < def->nnets; i++) {
/* type='hostdev' interfaces are also on the hostdevs list,
* and will have their alias assigned with other hostdevs.
*/
if (virDomainNetGetActualType(def->nets[i])
!= VIR_DOMAIN_NET_TYPE_HOSTDEV &&
qemuAssignDeviceNetAlias(def, def->nets[i], i) < 0) {
return -1;
}
}
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
return 0;
for (i = 0; i < def->nfss; i++) {
if (virAsprintf(&def->fss[i]->info.alias, "fs%zu", i) < 0)
return -1;
}
for (i = 0; i < def->nsounds; i++) {
if (virAsprintf(&def->sounds[i]->info.alias, "sound%zu", i) < 0)
return -1;
}
for (i = 0; i < def->nhostdevs; i++) {
if (qemuAssignDeviceHostdevAlias(def, def->hostdevs[i], i) < 0)
return -1;
}
for (i = 0; i < def->nredirdevs; i++) {
if (qemuAssignDeviceRedirdevAlias(def, def->redirdevs[i], i) < 0)
return -1;
}
for (i = 0; i < def->nvideos; i++) {
if (virAsprintf(&def->videos[i]->info.alias, "video%zu", i) < 0)
return -1;
}
for (i = 0; i < def->ncontrollers; i++) {
if (qemuAssignDeviceControllerAlias(def->controllers[i]) < 0)
return -1;
}
for (i = 0; i < def->ninputs; i++) {
if (virAsprintf(&def->inputs[i]->info.alias, "input%zu", i) < 0)
return -1;
}
for (i = 0; i < def->nparallels; i++) {
if (qemuAssignDeviceChrAlias(def, def->parallels[i], i) < 0)
return -1;
}
for (i = 0; i < def->nserials; i++) {
if (qemuAssignDeviceChrAlias(def, def->serials[i], i) < 0)
return -1;
}
for (i = 0; i < def->nchannels; i++) {
if (qemuAssignDeviceChrAlias(def, def->channels[i], i) < 0)
return -1;
}
for (i = 0; i < def->nconsoles; i++) {
if (qemuAssignDeviceChrAlias(def, def->consoles[i], i) < 0)
return -1;
}
for (i = 0; i < def->nhubs; i++) {
if (virAsprintf(&def->hubs[i]->info.alias, "hub%zu", i) < 0)
return -1;
}
for (i = 0; i < def->nsmartcards; i++) {
if (virAsprintf(&def->smartcards[i]->info.alias, "smartcard%zu", i) < 0)
return -1;
}
if (def->watchdog) {
if (virAsprintf(&def->watchdog->info.alias, "watchdog%d", 0) < 0)
return -1;
}
if (def->memballoon) {
if (virAsprintf(&def->memballoon->info.alias, "balloon%d", 0) < 0)
return -1;
}
if (def->rng) {
if (virAsprintf(&def->rng->info.alias, "rng%d", 0) < 0)
return -1;
}
if (def->tpm) {
if (virAsprintf(&def->tpm->info.alias, "tpm%d", 0) < 0)
return -1;
}
return 0;
}
/* S390 ccw bus support */
struct _qemuDomainCCWAddressSet {
virHashTablePtr defined;
virDomainDeviceCCWAddress next;
};
static char*
qemuCCWAddressAsString(virDomainDeviceCCWAddressPtr addr)
{
char *addrstr = NULL;
ignore_value(virAsprintf(&addrstr, "%x.%x.%04x",
addr->cssid,
addr->ssid,
addr->devno));
return addrstr;
}
static int
qemuCCWAdressIncrement(virDomainDeviceCCWAddressPtr addr)
{
virDomainDeviceCCWAddress ccwaddr = *addr;
/* We are not touching subchannel sets and channel subsystems */
if (++ccwaddr.devno > VIR_DOMAIN_DEVICE_CCW_MAX_DEVNO)
return -1;
*addr = ccwaddr;
return 0;
}
static void
qemuDomainCCWAddressSetFreeEntry(void *payload,
const void *name ATTRIBUTE_UNUSED)
{
VIR_FREE(payload);
}
int qemuDomainCCWAddressAssign(virDomainDeviceInfoPtr dev,
qemuDomainCCWAddressSetPtr addrs,
bool autoassign)
{
int ret = -1;
char *addr = NULL;
if (dev->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW)
return 0;
if (!autoassign && dev->addr.ccw.assigned) {
if (!(addr = qemuCCWAddressAsString(&dev->addr.ccw)))
goto cleanup;
if (virHashLookup(addrs->defined, addr)) {
virReportError(VIR_ERR_XML_ERROR,
_("The CCW devno '%s' is in use already "),
addr);
goto cleanup;
}
} else if (autoassign && !dev->addr.ccw.assigned) {
if (!(addr = qemuCCWAddressAsString(&addrs->next)) < 0)
goto cleanup;
while (virHashLookup(addrs->defined, addr)) {
if (qemuCCWAdressIncrement(&addrs->next) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("There are no more free CCW devnos."));
goto cleanup;
}
VIR_FREE(addr);
addr = qemuCCWAddressAsString(&addrs->next);
}
dev->addr.ccw = addrs->next;
dev->addr.ccw.assigned = true;
} else {
return 0;
}
if (virHashAddEntry(addrs->defined, addr, addr) < 0)
goto cleanup;
else
addr = NULL; /* memory will be freed by hash table */
ret = 0;
cleanup:
VIR_FREE(addr);
return ret;
}
static void
qemuDomainPrimeVirtioDeviceAddresses(virDomainDefPtr def,
enum virDomainDeviceAddressType type)
{
/*
declare address-less virtio devices to be of address type 'type'
disks, networks, consoles, controllers, memballoon and rng in this
order
*/
size_t i;
for (i = 0; i < def->ndisks; i++) {
if (def->disks[i]->bus == VIR_DOMAIN_DISK_BUS_VIRTIO &&
def->disks[i]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
def->disks[i]->info.type = type;
}
for (i = 0; i < def->nnets; i++) {
if (STREQ(def->nets[i]->model, "virtio") &&
def->nets[i]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
def->nets[i]->info.type = type;
}
}
for (i = 0; i < def->ncontrollers; i++) {
if ((def->controllers[i]->type ==
VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL ||
def->controllers[i]->type ==
VIR_DOMAIN_CONTROLLER_TYPE_SCSI) &&
def->controllers[i]->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
def->controllers[i]->info.type = type;
}
if (def->memballoon &&
def->memballoon->model == VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO &&
def->memballoon->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
def->memballoon->info.type = type;
if (def->rng &&
def->rng->model == VIR_DOMAIN_RNG_MODEL_VIRTIO &&
def->rng->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
def->rng->info.type = type;
}
static int
qemuDomainCCWAddressAllocate(virDomainDefPtr def ATTRIBUTE_UNUSED,
virDomainDeviceDefPtr dev ATTRIBUTE_UNUSED,
virDomainDeviceInfoPtr info,
void *data)
{
return qemuDomainCCWAddressAssign(info, data, true);
}
static int
qemuDomainCCWAddressValidate(virDomainDefPtr def ATTRIBUTE_UNUSED,
virDomainDeviceDefPtr dev ATTRIBUTE_UNUSED,
virDomainDeviceInfoPtr info,
void *data)
{
return qemuDomainCCWAddressAssign(info, data, false);
}
static int
qemuDomainCCWAddressReleaseAddr(qemuDomainCCWAddressSetPtr addrs,
virDomainDeviceInfoPtr dev)
{
char *addr;
int ret;
addr = qemuCCWAddressAsString(&(dev->addr.ccw));
if (!addr)
return -1;
if ((ret = virHashRemoveEntry(addrs->defined, addr)) == 0 &&
dev->addr.ccw.cssid == addrs->next.cssid &&
dev->addr.ccw.ssid == addrs->next.ssid &&
dev->addr.ccw.devno < addrs->next.devno) {
addrs->next.devno = dev->addr.ccw.devno;
addrs->next.assigned = false;
}
VIR_FREE(addr);
return ret;
}
void qemuDomainCCWAddressSetFree(qemuDomainCCWAddressSetPtr addrs)
{
if (!addrs)
return;
virHashFree(addrs->defined);
VIR_FREE(addrs);
}
static qemuDomainCCWAddressSetPtr
qemuDomainCCWAddressSetCreate(void)
{
qemuDomainCCWAddressSetPtr addrs = NULL;
if (VIR_ALLOC(addrs) < 0)
goto error;
if (!(addrs->defined = virHashCreate(10, qemuDomainCCWAddressSetFreeEntry)))
goto error;
/* must use cssid = 0xfe (254) for virtio-ccw devices */
addrs->next.cssid = 254;
addrs->next.ssid = 0;
addrs->next.devno = 0;
addrs->next.assigned = 0;
return addrs;
error:
qemuDomainCCWAddressSetFree(addrs);
return NULL;
}
/*
* Three steps populating CCW devnos
* 1. Allocate empty address set
* 2. Gather addresses with explicit devno
* 3. Assign defaults to the rest
*/
static int
qemuDomainAssignS390Addresses(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
virDomainObjPtr obj)
{
int ret = -1;
qemuDomainCCWAddressSetPtr addrs = NULL;
qemuDomainObjPrivatePtr priv = NULL;
if (STREQLEN(def->os.machine, "s390-ccw", 8) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_CCW)) {
qemuDomainPrimeVirtioDeviceAddresses(
def, VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW);
if (!(addrs = qemuDomainCCWAddressSetCreate()))
goto cleanup;
if (virDomainDeviceInfoIterate(def, qemuDomainCCWAddressValidate,
addrs) < 0)
goto cleanup;
if (virDomainDeviceInfoIterate(def, qemuDomainCCWAddressAllocate,
addrs) < 0)
goto cleanup;
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_S390)) {
/* deal with legacy virtio-s390 */
qemuDomainPrimeVirtioDeviceAddresses(
def, VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390);
}
if (obj && obj->privateData) {
priv = obj->privateData;
if (addrs) {
/* if this is the live domain object, we persist the CCW addresses*/
qemuDomainCCWAddressSetFree(priv->ccwaddrs);
priv->persistentAddrs = 1;
priv->ccwaddrs = addrs;
addrs = NULL;
} else {
priv->persistentAddrs = 0;
}
}
ret = 0;
cleanup:
qemuDomainCCWAddressSetFree(addrs);
return ret;
}
static int
qemuDomainAssignARMVirtioMMIOAddresses(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps)
{
if (((def->os.arch == VIR_ARCH_ARMV7L) ||
(def->os.arch == VIR_ARCH_AARCH64)) &&
(STRPREFIX(def->os.machine, "vexpress-") ||
STREQ(def->os.machine, "virt")) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VIRTIO_MMIO)) {
qemuDomainPrimeVirtioDeviceAddresses(
def, VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO);
}
return 0;
}
static int
qemuSpaprVIOFindByReg(virDomainDefPtr def ATTRIBUTE_UNUSED,
virDomainDeviceDefPtr device ATTRIBUTE_UNUSED,
virDomainDeviceInfoPtr info, void *opaque)
{
virDomainDeviceInfoPtr target = opaque;
if (info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO)
return 0;
/* Match a dev that has a reg, is not us, and has a matching reg */
if (info->addr.spaprvio.has_reg && info != target &&
info->addr.spaprvio.reg == target->addr.spaprvio.reg)
/* Has to be < 0 so virDomainDeviceInfoIterate() will exit */
return -1;
return 0;
}
static int
qemuAssignSpaprVIOAddress(virDomainDefPtr def, virDomainDeviceInfoPtr info,
unsigned long long default_reg)
{
bool user_reg;
int ret;
if (info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO)
return 0;
/* Check if the user has assigned the reg already, if so use it */
user_reg = info->addr.spaprvio.has_reg;
if (!user_reg) {
info->addr.spaprvio.reg = default_reg;
info->addr.spaprvio.has_reg = true;
}
ret = virDomainDeviceInfoIterate(def, qemuSpaprVIOFindByReg, info);
while (ret != 0) {
if (user_reg) {
virReportError(VIR_ERR_XML_ERROR,
_("spapr-vio address %#llx already in use"),
info->addr.spaprvio.reg);
return -EEXIST;
}
/* We assigned the reg, so try a new value */
info->addr.spaprvio.reg += 0x1000;
ret = virDomainDeviceInfoIterate(def, qemuSpaprVIOFindByReg, info);
}
return 0;
}
int qemuDomainAssignSpaprVIOAddresses(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps)
{
size_t i;
int ret = -1;
int model;
/* Default values match QEMU. See spapr_(llan|vscsi|vty).c */
for (i = 0; i < def->nnets; i++) {
if (def->nets[i]->model &&
STREQ(def->nets[i]->model, "spapr-vlan"))
def->nets[i]->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO;
if (qemuAssignSpaprVIOAddress(def, &def->nets[i]->info,
VIO_ADDR_NET) < 0)
goto cleanup;
}
for (i = 0; i < def->ncontrollers; i++) {
model = def->controllers[i]->model;
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI) {
if (qemuSetScsiControllerModel(def, qemuCaps, &model) < 0)
goto cleanup;
}
if (model == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_IBMVSCSI &&
def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI)
def->controllers[i]->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO;
if (qemuAssignSpaprVIOAddress(def, &def->controllers[i]->info,
VIO_ADDR_SCSI) < 0)
goto cleanup;
}
for (i = 0; i < def->nserials; i++) {
if (def->serials[i]->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL &&
(def->os.arch == VIR_ARCH_PPC64) &&
STREQ(def->os.machine, "pseries"))
def->serials[i]->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO;
if (qemuAssignSpaprVIOAddress(def, &def->serials[i]->info,
VIO_ADDR_SERIAL) < 0)
goto cleanup;
}
if (def->nvram) {
if (def->os.arch == VIR_ARCH_PPC64 &&
STREQ(def->os.machine, "pseries"))
def->nvram->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO;
if (qemuAssignSpaprVIOAddress(def, &def->nvram->info,
VIO_ADDR_NVRAM) < 0)
goto cleanup;
}
/* No other devices are currently supported on spapr-vio */
ret = 0;
cleanup:
return ret;
}
#define QEMU_PCI_ADDRESS_SLOT_LAST 31
#define QEMU_PCI_ADDRESS_FUNCTION_LAST 7
typedef struct {
virDomainControllerModelPCI model;
/* flags an min/max can be computed from model, but
* having them ready makes life easier.
*/
qemuDomainPCIConnectFlags flags;
size_t minSlot, maxSlot; /* usually 0,0 or 1,31 */
/* Each bit in a slot represents one function on that slot. If the
* bit is set, that function is in use by a device.
*/
uint8_t slots[QEMU_PCI_ADDRESS_SLOT_LAST + 1];
} qemuDomainPCIAddressBus;
typedef qemuDomainPCIAddressBus *qemuDomainPCIAddressBusPtr;
struct _qemuDomainPCIAddressSet {
qemuDomainPCIAddressBus *buses;
size_t nbuses;
virDevicePCIAddress lastaddr;
qemuDomainPCIConnectFlags lastFlags;
bool dryRun; /* on a dry run, new buses are auto-added
and addresses aren't saved in device infos */
};
static bool
qemuDomainPCIAddressFlagsCompatible(virDevicePCIAddressPtr addr,
const char *addrStr,
qemuDomainPCIConnectFlags busFlags,
qemuDomainPCIConnectFlags devFlags,
bool reportError,
bool fromConfig)
{
virErrorNumber errType = (fromConfig
? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);
qemuDomainPCIConnectFlags flagsMatchMask = QEMU_PCI_CONNECT_TYPES_MASK;
if (fromConfig)
flagsMatchMask |= QEMU_PCI_CONNECT_TYPE_EITHER_IF_CONFIG;
/* If this bus doesn't allow the type of connection (PCI
* vs. PCIe) required by the device, or if the device requires
* hot-plug and this bus doesn't have it, return false.
*/
if (!(devFlags & busFlags & flagsMatchMask)) {
if (reportError) {
if (devFlags & QEMU_PCI_CONNECT_TYPE_PCI) {
virReportError(errType,
_("PCI bus is not compatible with the device "
"at %s. Device requires a standard PCI slot, "
"which is not provided by bus %.4x:%.2x"),
addrStr, addr->domain, addr->bus);
} else if (devFlags & QEMU_PCI_CONNECT_TYPE_PCIE) {
virReportError(errType,
_("PCI bus is not compatible with the device "
"at %s. Device requires a PCI Express slot, "
"which is not provided by bus %.4x:%.2x"),
addrStr, addr->domain, addr->bus);
} else {
/* this should never happen. If it does, there is a
* bug in the code that sets the flag bits for devices.
*/
virReportError(errType,
_("The device information for %s has no PCI "
"connection types listed"), addrStr);
}
}
return false;
}
if ((devFlags & QEMU_PCI_CONNECT_HOTPLUGGABLE) &&
!(busFlags & QEMU_PCI_CONNECT_HOTPLUGGABLE)) {
if (reportError) {
virReportError(errType,
_("PCI bus is not compatible with the device "
"at %s. Device requires hot-plug capability, "
"which is not provided by bus %.4x:%.2x"),
addrStr, addr->domain, addr->bus);
}
return false;
}
return true;
}
/* Verify that the address is in bounds for the chosen bus, and
* that the bus is of the correct type for the device (via
* comparing the flags).
*/
static bool
qemuDomainPCIAddressValidate(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr addr,
const char *addrStr,
qemuDomainPCIConnectFlags flags,
bool fromConfig)
{
qemuDomainPCIAddressBusPtr bus;
virErrorNumber errType = (fromConfig
? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);
if (addrs->nbuses == 0) {
virReportError(errType, "%s", _("No PCI buses available"));
return false;
}
if (addr->domain != 0) {
virReportError(errType,
_("Invalid PCI address %s. "
"Only PCI domain 0 is available"),
addrStr);
return false;
}
if (addr->bus >= addrs->nbuses) {
virReportError(errType,
_("Invalid PCI address %s. "
"Only PCI buses up to %zu are available"),
addrStr, addrs->nbuses - 1);
return false;
}
bus = &addrs->buses[addr->bus];
/* assure that at least one of the requested connection types is
* provided by this bus
*/
if (!qemuDomainPCIAddressFlagsCompatible(addr, addrStr, bus->flags,
flags, true, fromConfig))
return false;
/* some "buses" are really just a single port */
if (bus->minSlot && addr->slot < bus->minSlot) {
virReportError(errType,
_("Invalid PCI address %s. slot must be >= %zu"),
addrStr, bus->minSlot);
return false;
}
if (addr->slot > bus->maxSlot) {
virReportError(errType,
_("Invalid PCI address %s. slot must be <= %zu"),
addrStr, bus->maxSlot);
return false;
}
if (addr->function > QEMU_PCI_ADDRESS_FUNCTION_LAST) {
virReportError(errType,
_("Invalid PCI address %s. function must be <= %u"),
addrStr, QEMU_PCI_ADDRESS_FUNCTION_LAST);
return false;
}
return true;
}
static int
qemuDomainPCIAddressBusSetModel(qemuDomainPCIAddressBusPtr bus,
virDomainControllerModelPCI model)
{
switch (model) {
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
bus->flags = (QEMU_PCI_CONNECT_HOTPLUGGABLE |
QEMU_PCI_CONNECT_TYPE_PCI);
bus->minSlot = 1;
bus->maxSlot = QEMU_PCI_ADDRESS_SLOT_LAST;
break;
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
/* slots 1 - 31, no hotplug, PCIe only unless the address was
* specified in user config *and* the particular device being
* attached also allows it
*/
bus->flags = (QEMU_PCI_CONNECT_TYPE_PCIE |
QEMU_PCI_CONNECT_TYPE_EITHER_IF_CONFIG);
bus->minSlot = 1;
bus->maxSlot = QEMU_PCI_ADDRESS_SLOT_LAST;
break;
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
/* slots 1 - 31, standard PCI slots,
* but *not* hot-pluggable */
bus->flags = QEMU_PCI_CONNECT_TYPE_PCI;
bus->minSlot = 1;
bus->maxSlot = QEMU_PCI_ADDRESS_SLOT_LAST;
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid PCI controller model %d"), model);
return -1;
}
bus->model = model;
return 0;
}
/* Ensure addr fits in the address set, by expanding it if needed.
* This will only grow if the flags say that we need a normal
* hot-pluggable PCI slot. If we need a different type of slot, it
* will fail.
*
* Return value:
* -1 = OOM
* 0 = no action performed
* >0 = number of buses added
*/
static int
qemuDomainPCIAddressSetGrow(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr addr,
qemuDomainPCIConnectFlags flags)
{
int add;
size_t i;
add = addr->bus - addrs->nbuses + 1;
i = addrs->nbuses;
if (add <= 0)
return 0;
/* auto-grow only works when we're adding plain PCI devices */
if (!(flags & QEMU_PCI_CONNECT_TYPE_PCI)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot automatically add a new PCI bus for a "
"device requiring a slot other than standard PCI."));
return -1;
}
if (VIR_EXPAND_N(addrs->buses, addrs->nbuses, add) < 0)
return -1;
for (; i < addrs->nbuses; i++) {
/* Any time we auto-add a bus, we will want a multi-slot
* bus. Currently the only type of bus we will auto-add is a
* pci-bridge, which is hot-pluggable and provides standard
* PCI slots.
*/
qemuDomainPCIAddressBusSetModel(&addrs->buses[i],
VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE);
}
return add;
}
static char *
qemuDomainPCIAddressAsString(virDevicePCIAddressPtr addr)
{
char *str;
ignore_value(virAsprintf(&str, "%.4x:%.2x:%.2x.%.1x",
addr->domain,
addr->bus,
addr->slot,
addr->function));
return str;
}
static int
qemuCollectPCIAddress(virDomainDefPtr def ATTRIBUTE_UNUSED,
virDomainDeviceDefPtr device,
virDomainDeviceInfoPtr info,
void *opaque)
{
qemuDomainPCIAddressSetPtr addrs = opaque;
int ret = -1;
virDevicePCIAddressPtr addr = &info->addr.pci;
bool entireSlot;
/* flags may be changed from default below */
qemuDomainPCIConnectFlags flags = (QEMU_PCI_CONNECT_HOTPLUGGABLE |
QEMU_PCI_CONNECT_TYPE_PCI);
if ((info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI)
|| ((device->type == VIR_DOMAIN_DEVICE_HOSTDEV) &&
(device->data.hostdev->parent.type != VIR_DOMAIN_DEVICE_NONE))) {
/* If a hostdev has a parent, its info will be a part of the
* parent, and will have its address collected during the scan
* of the parent's device type.
*/
return 0;
}
/* Change flags according to differing requirements of different
* devices.
*/
switch (device->type) {
case VIR_DOMAIN_DEVICE_CONTROLLER:
switch (device->data.controller->type) {
case VIR_DOMAIN_CONTROLLER_TYPE_PCI:
switch (device->data.controller->model) {
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
/* pci-bridge needs a PCI slot, but it isn't
* hot-pluggable, so it doesn't need a hot-pluggable slot.
*/
flags = QEMU_PCI_CONNECT_TYPE_PCI;
break;
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
/* pci-bridge needs a PCIe slot, but it isn't
* hot-pluggable, so it doesn't need a hot-pluggable slot.
*/
flags = QEMU_PCI_CONNECT_TYPE_PCIE;
break;
default:
break;
}
break;
case VIR_DOMAIN_CONTROLLER_TYPE_SATA:
/* SATA controllers aren't hot-plugged, and can be put in
* either a PCI or PCIe slot
*/
flags = QEMU_PCI_CONNECT_TYPE_PCI | QEMU_PCI_CONNECT_TYPE_PCIE;
break;
case VIR_DOMAIN_CONTROLLER_TYPE_USB:
/* allow UHCI and EHCI controllers to be manually placed on
* the PCIe bus (but don't put them there automatically)
*/
switch (device->data.controller->model) {
case VIR_DOMAIN_CONTROLLER_MODEL_USB_EHCI:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_EHCI1:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI1:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI2:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI3:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_VT82C686B_UHCI:
flags = (QEMU_PCI_CONNECT_TYPE_PCI |
QEMU_PCI_CONNECT_TYPE_EITHER_IF_CONFIG);
break;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_NEC_XHCI:
/* should this be PCIE-only? Or do we need to allow PCI
* for backward compatibility?
*/
flags = QEMU_PCI_CONNECT_TYPE_PCI | QEMU_PCI_CONNECT_TYPE_PCIE;
break;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_PCI_OHCI:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX3_UHCI:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX4_UHCI:
/* Allow these for PCI only */
break;
}
}
break;
case VIR_DOMAIN_DEVICE_SOUND:
switch (device->data.sound->model) {
case VIR_DOMAIN_SOUND_MODEL_ICH6:
case VIR_DOMAIN_SOUND_MODEL_ICH9:
flags = (QEMU_PCI_CONNECT_TYPE_PCI |
QEMU_PCI_CONNECT_TYPE_EITHER_IF_CONFIG);
break;
}
break;
case VIR_DOMAIN_DEVICE_VIDEO:
/* video cards aren't hot-plugged, and can be put in either a
* PCI or PCIe slot
*/
flags = QEMU_PCI_CONNECT_TYPE_PCI | QEMU_PCI_CONNECT_TYPE_PCIE;
break;
}
/* Ignore implicit controllers on slot 0:0:1.0:
* implicit IDE controller on 0:0:1.1 (no qemu command line)
* implicit USB controller on 0:0:1.2 (-usb)
*
* If the machine does have a PCI bus, they will get reserved
* in qemuAssignDevicePCISlots().
*/
/* These are the IDE and USB controllers in the PIIX3, hardcoded
* to bus 0 slot 1. They cannot be attached to a PCIe slot, only
* PCI.
*/
if (device->type == VIR_DOMAIN_DEVICE_CONTROLLER && addr->domain == 0 &&
addr->bus == 0 && addr->slot == 1) {
virDomainControllerDefPtr cont = device->data.controller;
if ((cont->type == VIR_DOMAIN_CONTROLLER_TYPE_IDE && cont->idx == 0 &&
addr->function == 1) ||
(cont->type == VIR_DOMAIN_CONTROLLER_TYPE_USB && cont->idx == 0 &&
(cont->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX3_UHCI ||
cont->model == -1) && addr->function == 2)) {
/* Note the check for nbuses > 0 - if there are no PCI
* buses, we skip this check. This is a quirk required for
* some machinetypes such as s390, which pretend to have a
* PCI bus for long enough to generate the "-usb" on the
* commandline, but that don't really care if a PCI bus
* actually exists. */
if (addrs->nbuses > 0 &&
!(addrs->buses[0].flags & QEMU_PCI_CONNECT_TYPE_PCI)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Bus 0 must be PCI for integrated PIIX3 "
"USB or IDE controllers"));
return -1;
} else {
return 0;
}
}
}
entireSlot = (addr->function == 0 &&
addr->multi != VIR_DEVICE_ADDRESS_PCI_MULTI_ON);
if (qemuDomainPCIAddressReserveAddr(addrs, addr, flags,
entireSlot, true) < 0)
goto cleanup;
ret = 0;
cleanup:
return ret;
}
static bool
qemuDomainSupportsPCI(virDomainDefPtr def) {
if ((def->os.arch != VIR_ARCH_ARMV7L) && (def->os.arch != VIR_ARCH_AARCH64))
return true;
if (STREQ(def->os.machine, "versatilepb"))
return true;
return false;
}
int
qemuDomainAssignPCIAddresses(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
virDomainObjPtr obj)
{
int ret = -1;
qemuDomainPCIAddressSetPtr addrs = NULL;
qemuDomainObjPrivatePtr priv = NULL;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
int max_idx = -1;
int nbuses = 0;
size_t i;
int rv;
qemuDomainPCIConnectFlags flags = QEMU_PCI_CONNECT_TYPE_PCI;
for (i = 0; i < def->ncontrollers; i++) {
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
if ((int) def->controllers[i]->idx > max_idx)
max_idx = def->controllers[i]->idx;
}
}
nbuses = max_idx + 1;
if (nbuses > 0 &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_PCI_BRIDGE)) {
virDomainDeviceInfo info;
/* 1st pass to figure out how many PCI bridges we need */
if (!(addrs = qemuDomainPCIAddressSetCreate(def, nbuses, true)))
goto cleanup;
if (qemuAssignDevicePCISlots(def, qemuCaps, addrs) < 0)
goto cleanup;
/* Reserve 1 extra slot for a (potential) bridge */
if (qemuDomainPCIAddressReserveNextSlot(addrs, &info, flags) < 0)
goto cleanup;
for (i = 1; i < addrs->nbuses; i++) {
qemuDomainPCIAddressBusPtr bus = &addrs->buses[i];
if ((rv = virDomainDefMaybeAddController(
def, VIR_DOMAIN_CONTROLLER_TYPE_PCI,
i, bus->model)) < 0)
goto cleanup;
/* If we added a new bridge, we will need one more address */
if (rv > 0 && qemuDomainPCIAddressReserveNextSlot(addrs, &info,
flags) < 0)
goto cleanup;
}
nbuses = addrs->nbuses;
qemuDomainPCIAddressSetFree(addrs);
addrs = NULL;
} else if (max_idx > 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("PCI bridges are not supported "
"by this QEMU binary"));
goto cleanup;
}
if (!(addrs = qemuDomainPCIAddressSetCreate(def, nbuses, false)))
goto cleanup;
if (qemuDomainSupportsPCI(def)) {
if (qemuAssignDevicePCISlots(def, qemuCaps, addrs) < 0)
goto cleanup;
}
}
if (obj && obj->privateData) {
priv = obj->privateData;
if (addrs) {
/* if this is the live domain object, we persist the PCI addresses*/
qemuDomainPCIAddressSetFree(priv->pciaddrs);
priv->persistentAddrs = 1;
priv->pciaddrs = addrs;
addrs = NULL;
} else {
priv->persistentAddrs = 0;
}
}
ret = 0;
cleanup:
qemuDomainPCIAddressSetFree(addrs);
return ret;
}
int qemuDomainAssignAddresses(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
virDomainObjPtr obj)
{
int rc;
rc = qemuDomainAssignSpaprVIOAddresses(def, qemuCaps);
if (rc)
return rc;
rc = qemuDomainAssignS390Addresses(def, qemuCaps, obj);
if (rc)
return rc;
rc = qemuDomainAssignARMVirtioMMIOAddresses(def, qemuCaps);
if (rc)
return rc;
return qemuDomainAssignPCIAddresses(def, qemuCaps, obj);
}
qemuDomainPCIAddressSetPtr
qemuDomainPCIAddressSetCreate(virDomainDefPtr def,
unsigned int nbuses,
bool dryRun)
{
qemuDomainPCIAddressSetPtr addrs;
size_t i;
if (VIR_ALLOC(addrs) < 0)
goto error;
if (VIR_ALLOC_N(addrs->buses, nbuses) < 0)
goto error;
addrs->nbuses = nbuses;
addrs->dryRun = dryRun;
/* As a safety measure, set default model='pci-root' for first pci
* controller and 'pci-bridge' for all subsequent. After setting
* those defaults, then scan the config and set the actual model
* for all addrs[idx]->bus that already have a corresponding
* controller in the config.
*
*/
if (nbuses > 0)
qemuDomainPCIAddressBusSetModel(&addrs->buses[0],
VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT);
for (i = 1; i < nbuses; i++) {
qemuDomainPCIAddressBusSetModel(&addrs->buses[i],
VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE);
}
for (i = 0; i < def->ncontrollers; i++) {
size_t idx = def->controllers[i]->idx;
if (def->controllers[i]->type != VIR_DOMAIN_CONTROLLER_TYPE_PCI)
continue;
if (idx >= addrs->nbuses) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Inappropriate new pci controller index %zu "
"not found in addrs"), idx);
goto error;
}
if (qemuDomainPCIAddressBusSetModel(&addrs->buses[idx],
def->controllers[i]->model) < 0)
goto error;
}
if (virDomainDeviceInfoIterate(def, qemuCollectPCIAddress, addrs) < 0)
goto error;
return addrs;
error:
qemuDomainPCIAddressSetFree(addrs);
return NULL;
}
/*
* Check if the PCI slot is used by another device.
*/
static bool qemuDomainPCIAddressSlotInUse(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr addr)
{
return !!addrs->buses[addr->bus].slots[addr->slot];
}
/*
* Reserve a slot (or just one function) for a device. If
* reserveEntireSlot is true, all functions for the slot are reserved,
* otherwise only one. If fromConfig is true, the address being
* requested came directly from the config and errors should be worded
* appropriately. If fromConfig is false, the address was
* automatically created by libvirt, so it is an internal error (not
* XML).
*/
int
qemuDomainPCIAddressReserveAddr(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr addr,
qemuDomainPCIConnectFlags flags,
bool reserveEntireSlot,
bool fromConfig)
{
int ret = -1;
char *addrStr = NULL;
qemuDomainPCIAddressBusPtr bus;
virErrorNumber errType = (fromConfig
? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);
if (!(addrStr = qemuDomainPCIAddressAsString(addr)))
goto cleanup;
/* Add an extra bus if necessary */
if (addrs->dryRun && qemuDomainPCIAddressSetGrow(addrs, addr, flags) < 0)
goto cleanup;
/* Check that the requested bus exists, is the correct type, and we
* are asking for a valid slot
*/
if (!qemuDomainPCIAddressValidate(addrs, addr, addrStr, flags, fromConfig))
goto cleanup;
bus = &addrs->buses[addr->bus];
if (reserveEntireSlot) {
if (bus->slots[addr->slot]) {
virReportError(errType,
_("Attempted double use of PCI slot %s "
"(may need \"multifunction='on'\" for "
"device on function 0)"), addrStr);
goto cleanup;
}
bus->slots[addr->slot] = 0xFF; /* reserve all functions of slot */
VIR_DEBUG("Reserving PCI slot %s (multifunction='off')", addrStr);
} else {
if (bus->slots[addr->slot] & (1 << addr->function)) {
if (addr->function == 0) {
virReportError(errType,
_("Attempted double use of PCI Address %s"),
addrStr);
} else {
virReportError(errType,
_("Attempted double use of PCI Address %s "
"(may need \"multifunction='on'\" "
"for device on function 0)"), addrStr);
}
goto cleanup;
}
bus->slots[addr->slot] |= (1 << addr->function);
VIR_DEBUG("Reserving PCI address %s", addrStr);
}
ret = 0;
cleanup:
VIR_FREE(addrStr);
return ret;
}
int
qemuDomainPCIAddressReserveSlot(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr addr,
qemuDomainPCIConnectFlags flags)
{
return qemuDomainPCIAddressReserveAddr(addrs, addr, flags, true, false);
}
int qemuDomainPCIAddressEnsureAddr(qemuDomainPCIAddressSetPtr addrs,
virDomainDeviceInfoPtr dev)
{
int ret = -1;
char *addrStr = NULL;
/* Flags should be set according to the particular device,
* but only the caller knows the type of device. Currently this
* function is only used for hot-plug, though, and hot-plug is
* only supported for standard PCI devices, so we can safely use
* the setting below */
qemuDomainPCIConnectFlags flags = (QEMU_PCI_CONNECT_HOTPLUGGABLE |
QEMU_PCI_CONNECT_TYPE_PCI);
if (!(addrStr = qemuDomainPCIAddressAsString(&dev->addr.pci)))
goto cleanup;
if (dev->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
/* We do not support hotplug multi-function PCI device now, so we should
* reserve the whole slot. The function of the PCI device must be 0.
*/
if (dev->addr.pci.function != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Only PCI device addresses with function=0"
" are supported"));
goto cleanup;
}
if (!qemuDomainPCIAddressValidate(addrs, &dev->addr.pci,
addrStr, flags, true))
goto cleanup;
ret = qemuDomainPCIAddressReserveSlot(addrs, &dev->addr.pci, flags);
} else {
ret = qemuDomainPCIAddressReserveNextSlot(addrs, dev, flags);
}
cleanup:
VIR_FREE(addrStr);
return ret;
}
int qemuDomainPCIAddressReleaseAddr(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr addr)
{
addrs->buses[addr->bus].slots[addr->slot] &= ~(1 << addr->function);
return 0;
}
static int
qemuDomainPCIAddressReleaseSlot(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr addr)
{
/* permit any kind of connection type in validation, since we
* already had it, and are giving it back.
*/
qemuDomainPCIConnectFlags flags = QEMU_PCI_CONNECT_TYPES_MASK;
int ret = -1;
char *addrStr = NULL;
if (!(addrStr = qemuDomainPCIAddressAsString(addr)))
goto cleanup;
if (!qemuDomainPCIAddressValidate(addrs, addr, addrStr, flags, false))
goto cleanup;
addrs->buses[addr->bus].slots[addr->slot] = 0;
ret = 0;
cleanup:
VIR_FREE(addrStr);
return ret;
}
void qemuDomainPCIAddressSetFree(qemuDomainPCIAddressSetPtr addrs)
{
if (!addrs)
return;
VIR_FREE(addrs->buses);
VIR_FREE(addrs);
}
static int
qemuDomainPCIAddressGetNextSlot(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr next_addr,
qemuDomainPCIConnectFlags flags)
{
/* default to starting the search for a free slot from
* 0000:00:00.0
*/
virDevicePCIAddress a = { 0, 0, 0, 0, false };
char *addrStr = NULL;
/* except if this search is for the exact same type of device as
* last time, continue the search from the previous match
*/
if (flags == addrs->lastFlags)
a = addrs->lastaddr;
if (addrs->nbuses == 0) {
virReportError(VIR_ERR_XML_ERROR, "%s", _("No PCI buses available"));
goto error;
}
/* Start the search at the last used bus and slot */
for (a.slot++; a.bus < addrs->nbuses; a.bus++) {
if (!(addrStr = qemuDomainPCIAddressAsString(&a)))
goto error;
if (!qemuDomainPCIAddressFlagsCompatible(&a, addrStr,
addrs->buses[a.bus].flags,
flags, false, false)) {
VIR_FREE(addrStr);
VIR_DEBUG("PCI bus %.4x:%.2x is not compatible with the device",
a.domain, a.bus);
continue;
}
for (; a.slot <= QEMU_PCI_ADDRESS_SLOT_LAST; a.slot++) {
if (!qemuDomainPCIAddressSlotInUse(addrs, &a))
goto success;
VIR_DEBUG("PCI slot %.4x:%.2x:%.2x already in use",
a.domain, a.bus, a.slot);
}
a.slot = 1;
VIR_FREE(addrStr);
}
/* There were no free slots after the last used one */
if (addrs->dryRun) {
/* a is already set to the first new bus and slot 1 */
if (qemuDomainPCIAddressSetGrow(addrs, &a, flags) < 0)
goto error;
goto success;
} else if (flags == addrs->lastFlags) {
/* Check the buses from 0 up to the last used one */
for (a.bus = 0; a.bus <= addrs->lastaddr.bus; a.bus++) {
addrStr = NULL;
if (!(addrStr = qemuDomainPCIAddressAsString(&a)))
goto error;
if (!qemuDomainPCIAddressFlagsCompatible(&a, addrStr,
addrs->buses[a.bus].flags,
flags, false, false)) {
VIR_DEBUG("PCI bus %.4x:%.2x is not compatible with the device",
a.domain, a.bus);
continue;
}
for (a.slot = 1; a.slot <= QEMU_PCI_ADDRESS_SLOT_LAST; a.slot++) {
if (!qemuDomainPCIAddressSlotInUse(addrs, &a))
goto success;
VIR_DEBUG("PCI slot %.4x:%.2x:%.2x already in use",
a.domain, a.bus, a.slot);
}
}
}
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("No more available PCI slots"));
error:
VIR_FREE(addrStr);
return -1;
success:
VIR_DEBUG("Found free PCI slot %.4x:%.2x:%.2x",
a.domain, a.bus, a.slot);
*next_addr = a;
VIR_FREE(addrStr);
return 0;
}
int
qemuDomainPCIAddressReserveNextSlot(qemuDomainPCIAddressSetPtr addrs,
virDomainDeviceInfoPtr dev,
qemuDomainPCIConnectFlags flags)
{
virDevicePCIAddress addr;
if (qemuDomainPCIAddressGetNextSlot(addrs, &addr, flags) < 0)
return -1;
if (qemuDomainPCIAddressReserveSlot(addrs, &addr, flags) < 0)
return -1;
if (!addrs->dryRun) {
dev->type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
dev->addr.pci = addr;
}
addrs->lastaddr = addr;
addrs->lastFlags = flags;
return 0;
}
void
qemuDomainReleaseDeviceAddress(virDomainObjPtr vm,
virDomainDeviceInfoPtr info,
const char *devstr)
{
qemuDomainObjPrivatePtr priv = vm->privateData;
if (!devstr)
devstr = info->alias;
if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW &&
STREQLEN(vm->def->os.machine, "s390-ccw", 8) &&
virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_VIRTIO_CCW) &&
qemuDomainCCWAddressReleaseAddr(priv->ccwaddrs, info) < 0)
VIR_WARN("Unable to release CCW address on %s",
NULLSTR(devstr));
else if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI &&
virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE) &&
qemuDomainPCIAddressReleaseSlot(priv->pciaddrs,
&info->addr.pci) < 0)
VIR_WARN("Unable to release PCI address on %s",
NULLSTR(devstr));
}
#define IS_USB2_CONTROLLER(ctrl) \
(((ctrl)->type == VIR_DOMAIN_CONTROLLER_TYPE_USB) && \
((ctrl)->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_EHCI1 || \
(ctrl)->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI1 || \
(ctrl)->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI2 || \
(ctrl)->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI3))
static int
qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
qemuDomainPCIAddressSetPtr addrs)
{
int ret = -1;
size_t i;
virDevicePCIAddress tmp_addr;
bool qemuDeviceVideoUsable = virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VIDEO_PRIMARY);
char *addrStr = NULL;
qemuDomainPCIConnectFlags flags = QEMU_PCI_CONNECT_HOTPLUGGABLE | QEMU_PCI_CONNECT_TYPE_PCI;
/* Verify that first IDE and USB controllers (if any) is on the PIIX3, fn 1 */
for (i = 0; i < def->ncontrollers; i++) {
/* First IDE controller lives on the PIIX3 at slot=1, function=1 */
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_IDE &&
def->controllers[i]->idx == 0) {
if (def->controllers[i]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
if (def->controllers[i]->info.addr.pci.domain != 0 ||
def->controllers[i]->info.addr.pci.bus != 0 ||
def->controllers[i]->info.addr.pci.slot != 1 ||
def->controllers[i]->info.addr.pci.function != 1) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Primary IDE controller must have PCI address 0:0:1.1"));
goto cleanup;
}
} else {
def->controllers[i]->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
def->controllers[i]->info.addr.pci.domain = 0;
def->controllers[i]->info.addr.pci.bus = 0;
def->controllers[i]->info.addr.pci.slot = 1;
def->controllers[i]->info.addr.pci.function = 1;
}
} else if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_USB &&
def->controllers[i]->idx == 0 &&
(def->controllers[i]->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX3_UHCI ||
def->controllers[i]->model == -1)) {
if (def->controllers[i]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
if (def->controllers[i]->info.addr.pci.domain != 0 ||
def->controllers[i]->info.addr.pci.bus != 0 ||
def->controllers[i]->info.addr.pci.slot != 1 ||
def->controllers[i]->info.addr.pci.function != 2) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("PIIX3 USB controller must have PCI address 0:0:1.2"));
goto cleanup;
}
} else {
def->controllers[i]->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
def->controllers[i]->info.addr.pci.domain = 0;
def->controllers[i]->info.addr.pci.bus = 0;
def->controllers[i]->info.addr.pci.slot = 1;
def->controllers[i]->info.addr.pci.function = 2;
}
}
}
/* PIIX3 (ISA bridge, IDE controller, something else unknown, USB controller)
* hardcoded slot=1, multifunction device
*/
if (addrs->nbuses) {
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.slot = 1;
if (qemuDomainPCIAddressReserveSlot(addrs, &tmp_addr, flags) < 0)
goto cleanup;
}
if (def->nvideos > 0) {
/* Because the PIIX3 integrated IDE/USB controllers are
* already at slot 1, when qemu looks for the first free slot
* to place the VGA controller (which is always the first
* device added after integrated devices), it *always* ends up
* at slot 2.
*/
virDomainVideoDefPtr primaryVideo = def->videos[0];
if (primaryVideo->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.slot = 2;
if (!(addrStr = qemuDomainPCIAddressAsString(&tmp_addr)))
goto cleanup;
if (!qemuDomainPCIAddressValidate(addrs, &tmp_addr,
addrStr, flags, false))
goto cleanup;
if (qemuDomainPCIAddressSlotInUse(addrs, &tmp_addr)) {
if (qemuDeviceVideoUsable) {
if (qemuDomainPCIAddressReserveNextSlot(addrs,
&primaryVideo->info,
flags) < 0)
goto cleanup;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("PCI address 0:0:2.0 is in use, "
"QEMU needs it for primary video"));
goto cleanup;
}
} else {
if (qemuDomainPCIAddressReserveSlot(addrs, &tmp_addr, flags) < 0)
goto cleanup;
primaryVideo->info.addr.pci = tmp_addr;
primaryVideo->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
}
} else if (!qemuDeviceVideoUsable) {
if (primaryVideo->info.addr.pci.domain != 0 ||
primaryVideo->info.addr.pci.bus != 0 ||
primaryVideo->info.addr.pci.slot != 2 ||
primaryVideo->info.addr.pci.function != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Primary video card must have PCI address 0:0:2.0"));
goto cleanup;
}
/* If TYPE==PCI, then qemuCollectPCIAddress() function
* has already reserved the address, so we must skip */
}
} else if (addrs->nbuses && !qemuDeviceVideoUsable) {
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.slot = 2;
if (qemuDomainPCIAddressSlotInUse(addrs, &tmp_addr)) {
VIR_DEBUG("PCI address 0:0:2.0 in use, future addition of a video"
" device will not be possible without manual"
" intervention");
virResetLastError();
} else if (qemuDomainPCIAddressReserveSlot(addrs, &tmp_addr, flags) < 0) {
goto cleanup;
}
}
ret = 0;
cleanup:
VIR_FREE(addrStr);
return ret;
}
static bool
qemuDomainMachineIsQ35(virDomainDefPtr def)
{
return (STRPREFIX(def->os.machine, "pc-q35") ||
STREQ(def->os.machine, "q35"));
}
static bool
qemuDomainMachineIsI440FX(virDomainDefPtr def)
{
return (STREQ(def->os.machine, "pc") ||
STRPREFIX(def->os.machine, "pc-0.") ||
STRPREFIX(def->os.machine, "pc-1.") ||
STRPREFIX(def->os.machine, "pc-i440") ||
STRPREFIX(def->os.machine, "rhel"));
}
static int
qemuDomainValidateDevicePCISlotsQ35(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
qemuDomainPCIAddressSetPtr addrs)
{
int ret = -1;
size_t i;
virDevicePCIAddress tmp_addr;
bool qemuDeviceVideoUsable = virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VIDEO_PRIMARY);
char *addrStr = NULL;
qemuDomainPCIConnectFlags flags = QEMU_PCI_CONNECT_TYPE_PCIE;
for (i = 0; i < def->ncontrollers; i++) {
switch (def->controllers[i]->type) {
case VIR_DOMAIN_CONTROLLER_TYPE_SATA:
/* Verify that the first SATA controller is at 00:1F.2 the
* q35 machine type *always* has a SATA controller at this
* address.
*/
if (def->controllers[i]->idx == 0) {
if (def->controllers[i]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
if (def->controllers[i]->info.addr.pci.domain != 0 ||
def->controllers[i]->info.addr.pci.bus != 0 ||
def->controllers[i]->info.addr.pci.slot != 0x1F ||
def->controllers[i]->info.addr.pci.function != 2) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Primary SATA controller must have PCI address 0:0:1f.2"));
goto cleanup;
}
} else {
def->controllers[i]->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
def->controllers[i]->info.addr.pci.domain = 0;
def->controllers[i]->info.addr.pci.bus = 0;
def->controllers[i]->info.addr.pci.slot = 0x1F;
def->controllers[i]->info.addr.pci.function = 2;
}
}
break;
case VIR_DOMAIN_CONTROLLER_TYPE_PCI:
if (def->controllers[i]->model == VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE &&
def->controllers[i]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
/* Try to assign this bridge to 00:1E.0 (because that
* is its standard location on real hardware) unless
* it's already taken, but don't insist on it.
*/
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.slot = 0x1E;
if (!qemuDomainPCIAddressSlotInUse(addrs, &tmp_addr)) {
if (qemuDomainPCIAddressReserveAddr(addrs, &tmp_addr,
flags, true, false) < 0)
goto cleanup;
def->controllers[i]->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
def->controllers[i]->info.addr.pci.domain = 0;
def->controllers[i]->info.addr.pci.bus = 0;
def->controllers[i]->info.addr.pci.slot = 0x1E;
def->controllers[i]->info.addr.pci.function = 0;
}
}
break;
}
}
/* Reserve slot 0x1F function 0 (ISA bridge, not in config model)
* and function 3 (SMBus, also not (yet) in config model). As with
* the SATA controller, these devices are always present in a q35
* machine; there is no way to not have them.
*/
if (addrs->nbuses) {
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.slot = 0x1F;
tmp_addr.function = 0;
tmp_addr.multi = 1;
if (qemuDomainPCIAddressReserveAddr(addrs, &tmp_addr, flags,
false, false) < 0)
goto cleanup;
tmp_addr.function = 3;
tmp_addr.multi = 0;
if (qemuDomainPCIAddressReserveAddr(addrs, &tmp_addr, flags,
false, false) < 0)
goto cleanup;
}
if (def->nvideos > 0) {
/* NB: unlike the pc machinetypes, on q35 machinetypes the
* integrated devices are at slot 0x1f, so when qemu looks for
* the first free lot for the first VGA, it will always be at
* slot 1 (which was used up by the integrated PIIX3 devices
* on pc machinetypes).
*/
virDomainVideoDefPtr primaryVideo = def->videos[0];
if (primaryVideo->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.slot = 1;
if (!(addrStr = qemuDomainPCIAddressAsString(&tmp_addr)))
goto cleanup;
if (!qemuDomainPCIAddressValidate(addrs, &tmp_addr,
addrStr, flags, false))
goto cleanup;
if (qemuDomainPCIAddressSlotInUse(addrs, &tmp_addr)) {
if (qemuDeviceVideoUsable) {
if (qemuDomainPCIAddressReserveNextSlot(addrs,
&primaryVideo->info,
flags) < 0)
goto cleanup;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("PCI address 0:0:1.0 is in use, "
"QEMU needs it for primary video"));
goto cleanup;
}
} else {
if (qemuDomainPCIAddressReserveSlot(addrs, &tmp_addr, flags) < 0)
goto cleanup;
primaryVideo->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
primaryVideo->info.addr.pci = tmp_addr;
}
} else if (!qemuDeviceVideoUsable) {
if (primaryVideo->info.addr.pci.domain != 0 ||
primaryVideo->info.addr.pci.bus != 0 ||
primaryVideo->info.addr.pci.slot != 1 ||
primaryVideo->info.addr.pci.function != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Primary video card must have PCI address 0:0:1.0"));
goto cleanup;
}
/* If TYPE==PCI, then qemuCollectPCIAddress() function
* has already reserved the address, so we must skip */
}
} else if (addrs->nbuses && !qemuDeviceVideoUsable) {
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.slot = 1;
if (qemuDomainPCIAddressSlotInUse(addrs, &tmp_addr)) {
VIR_DEBUG("PCI address 0:0:1.0 in use, future addition of a video"
" device will not be possible without manual"
" intervention");
virResetLastError();
} else if (qemuDomainPCIAddressReserveSlot(addrs, &tmp_addr, flags) < 0) {
goto cleanup;
}
}
ret = 0;
cleanup:
VIR_FREE(addrStr);
return ret;
}
/*
* This assigns static PCI slots to all configured devices.
* The ordering here is chosen to match the ordering used
* with old QEMU < 0.12, so that if a user updates a QEMU
* host from old QEMU to QEMU >= 0.12, their guests should
* get PCI addresses in the same order as before.
*
* NB, if they previously hotplugged devices then all bets
* are off. Hotplug for old QEMU was unfixably broken wrt
* to stable PCI addressing.
*
* Order is:
*
* - Host bridge (slot 0)
* - PIIX3 ISA bridge, IDE controller, something else unknown, USB controller (slot 1)
* - Video (slot 2)
*
* Incrementally assign slots from 3 onwards:
*
* - Net
* - Sound
* - SCSI controllers
* - VirtIO block
* - VirtIO balloon
* - Host device passthrough
* - Watchdog (not IB700)
*
* Prior to this function being invoked, qemuCollectPCIAddress() will have
* added all existing PCI addresses from the 'def' to 'addrs'. Thus this
* function must only try to reserve addresses if info.type == NONE and
* skip over info.type == PCI
*/
int
qemuAssignDevicePCISlots(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
qemuDomainPCIAddressSetPtr addrs)
{
size_t i, j;
qemuDomainPCIConnectFlags flags;
virDevicePCIAddress tmp_addr;
if ((STRPREFIX(def->os.machine, "pc-0.") ||
STRPREFIX(def->os.machine, "pc-1.") ||
STRPREFIX(def->os.machine, "pc-i440") ||
STREQ(def->os.machine, "pc") ||
STRPREFIX(def->os.machine, "rhel")) &&
qemuValidateDevicePCISlotsPIIX3(def, qemuCaps, addrs) < 0) {
goto error;
}
if (qemuDomainMachineIsQ35(def) &&
qemuDomainValidateDevicePCISlotsQ35(def, qemuCaps, addrs) < 0) {
goto error;
}
/* PCI controllers */
for (i = 0; i < def->ncontrollers; i++) {
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI) {
if (def->controllers[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
continue;
switch (def->controllers[i]->model) {
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
/* pci-root and pcie-root are implicit in the machine,
* and needs no address */
continue;
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
/* pci-bridge doesn't require hot-plug
* (although it does provide hot-plug in its slots)
*/
flags = QEMU_PCI_CONNECT_TYPE_PCI;
break;
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
/* dmi-to-pci-bridge requires a non-hotplug PCIe
* slot
*/
flags = QEMU_PCI_CONNECT_TYPE_PCIE;
break;
default:
flags = QEMU_PCI_CONNECT_HOTPLUGGABLE | QEMU_PCI_CONNECT_TYPE_PCI;
break;
}
if (qemuDomainPCIAddressReserveNextSlot(addrs,
&def->controllers[i]->info,
flags) < 0)
goto error;
}
}
flags = QEMU_PCI_CONNECT_HOTPLUGGABLE | QEMU_PCI_CONNECT_TYPE_PCI;
for (i = 0; i < def->nfss; i++) {
if (def->fss[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
continue;
/* Only support VirtIO-9p-pci so far. If that changes,
* we might need to skip devices here */
if (qemuDomainPCIAddressReserveNextSlot(addrs, &def->fss[i]->info,
flags) < 0)
goto error;
}
/* Network interfaces */
for (i = 0; i < def->nnets; i++) {
/* type='hostdev' network devices might be USB, and are also
* in hostdevs list anyway, so handle them with other hostdevs
* instead of here.
*/
if ((def->nets[i]->type == VIR_DOMAIN_NET_TYPE_HOSTDEV) ||
(def->nets[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)) {
continue;
}
if (qemuDomainPCIAddressReserveNextSlot(addrs, &def->nets[i]->info,
flags) < 0)
goto error;
}
/* Sound cards */
for (i = 0; i < def->nsounds; i++) {
if (def->sounds[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
continue;
/* Skip ISA sound card, and PCSPK */
if (def->sounds[i]->model == VIR_DOMAIN_SOUND_MODEL_SB16 ||
def->sounds[i]->model == VIR_DOMAIN_SOUND_MODEL_PCSPK)
continue;
if (qemuDomainPCIAddressReserveNextSlot(addrs, &def->sounds[i]->info,
flags) < 0)
goto error;
}
/* Device controllers (SCSI, USB, but not IDE, FDC or CCID) */
for (i = 0; i < def->ncontrollers; i++) {
/* PCI controllers have been dealt with earlier */
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI)
continue;
/* USB controller model 'none' doesn't need a PCI address */
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_USB &&
def->controllers[i]->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_NONE)
continue;
/* FDC lives behind the ISA bridge; CCID is a usb device */
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_FDC ||
def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_CCID)
continue;
/* First IDE controller lives on the PIIX3 at slot=1, function=1,
dealt with earlier on*/
if (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_IDE &&
def->controllers[i]->idx == 0)
continue;
if (def->controllers[i]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO)
continue;
if (def->controllers[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
continue;
/* USB2 needs special handling to put all companions in the same slot */
if (IS_USB2_CONTROLLER(def->controllers[i])) {
virDevicePCIAddress addr = { 0, 0, 0, 0, false };
memset(&tmp_addr, 0, sizeof(tmp_addr));
for (j = 0; j < i; j++) {
if (IS_USB2_CONTROLLER(def->controllers[j]) &&
def->controllers[j]->idx == def->controllers[i]->idx) {
addr = def->controllers[j]->info.addr.pci;
break;
}
}
switch (def->controllers[i]->model) {
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_EHCI1:
addr.function = 7;
break;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI1:
addr.function = 0;
addr.multi = VIR_DEVICE_ADDRESS_PCI_MULTI_ON;
break;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI2:
addr.function = 1;
break;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI3:
addr.function = 2;
break;
}
if (addr.slot == 0) {
/* This is the first part of the controller, so need
* to find a free slot & then reserve a function */
if (qemuDomainPCIAddressGetNextSlot(addrs, &tmp_addr, flags) < 0)
goto error;
addr.bus = tmp_addr.bus;
addr.slot = tmp_addr.slot;
addrs->lastaddr = addr;
addrs->lastaddr.function = 0;
addrs->lastaddr.multi = 0;
}
/* Finally we can reserve the slot+function */
if (qemuDomainPCIAddressReserveAddr(addrs, &addr, flags,
false, false) < 0)
goto error;
def->controllers[i]->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
def->controllers[i]->info.addr.pci = addr;
} else {
if (qemuDomainPCIAddressReserveNextSlot(addrs,
&def->controllers[i]->info,
flags) < 0)
goto error;
}
}
/* Disks (VirtIO only for now) */
for (i = 0; i < def->ndisks; i++) {
/* Only VirtIO disks use PCI addrs */
if (def->disks[i]->bus != VIR_DOMAIN_DISK_BUS_VIRTIO)
continue;
/* don't touch s390 devices */
if (def->disks[i]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI ||
def->disks[i]->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390 ||
def->disks[i]->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW)
continue;
if (def->disks[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("virtio only support device address type 'PCI'"));
goto error;
}
if (qemuDomainPCIAddressReserveNextSlot(addrs, &def->disks[i]->info,
flags) < 0)
goto error;
}
/* Host PCI devices */
for (i = 0; i < def->nhostdevs; i++) {
if (def->hostdevs[i]->info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
continue;
if (def->hostdevs[i]->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS ||
def->hostdevs[i]->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI)
continue;
if (qemuDomainPCIAddressReserveNextSlot(addrs,
def->hostdevs[i]->info,
flags) < 0)
goto error;
}
/* VirtIO balloon */
if (def->memballoon &&
def->memballoon->model == VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO &&
def->memballoon->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
if (qemuDomainPCIAddressReserveNextSlot(addrs,
&def->memballoon->info,
flags) < 0)
goto error;
}
/* VirtIO RNG */
if (def->rng &&
def->rng->model == VIR_DOMAIN_RNG_MODEL_VIRTIO &&
def->rng->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
if (qemuDomainPCIAddressReserveNextSlot(addrs,
&def->rng->info, flags) < 0)
goto error;
}
/* A watchdog - skip IB700, it is not a PCI device */
if (def->watchdog &&
def->watchdog->model != VIR_DOMAIN_WATCHDOG_MODEL_IB700 &&
def->watchdog->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
if (qemuDomainPCIAddressReserveNextSlot(addrs, &def->watchdog->info,
flags) < 0)
goto error;
}
/* Assign a PCI slot to the primary video card if there is not an
* assigned address. */
if (def->nvideos > 0 &&
def->videos[0]->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE) {
if (qemuDomainPCIAddressReserveNextSlot(addrs, &def->videos[0]->info,
flags) < 0)
goto error;
}
/* Further non-primary video cards which have to be qxl type */
for (i = 1; i < def->nvideos; i++) {
if (def->videos[i]->type != VIR_DOMAIN_VIDEO_TYPE_QXL) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("non-primary video device must be type of 'qxl'"));
goto error;
}
if (def->videos[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
continue;
if (qemuDomainPCIAddressReserveNextSlot(addrs, &def->videos[i]->info,
flags) < 0)
goto error;
}
for (i = 0; i < def->ninputs; i++) {
/* Nada - none are PCI based (yet) */
}
for (i = 0; i < def->nparallels; i++) {
/* Nada - none are PCI based (yet) */
}
for (i = 0; i < def->nserials; i++) {
/* Nada - none are PCI based (yet) */
}
for (i = 0; i < def->nchannels; i++) {
/* Nada - none are PCI based (yet) */
}
for (i = 0; i < def->nhubs; i++) {
/* Nada - none are PCI based (yet) */
}
return 0;
error:
return -1;
}
static void
qemuUsbId(virBufferPtr buf, int idx)
{
if (idx == 0)
virBufferAddLit(buf, "usb");
else
virBufferAsprintf(buf, "usb%d", idx);
}
static int
qemuBuildDeviceAddressStr(virBufferPtr buf,
virDomainDefPtr domainDef,
virDomainDeviceInfoPtr info,
virQEMUCapsPtr qemuCaps)
{
int ret = -1;
char *devStr = NULL;
if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
const char *contAlias = NULL;
size_t i;
if (!(devStr = qemuDomainPCIAddressAsString(&info->addr.pci)))
goto cleanup;
for (i = 0; i < domainDef->ncontrollers; i++) {
virDomainControllerDefPtr cont = domainDef->controllers[i];
if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI &&
cont->idx == info->addr.pci.bus) {
contAlias = cont->info.alias;
if (!contAlias) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Device alias was not set for PCI "
"controller with index %u required "
"for device at address %s"),
info->addr.pci.bus, devStr);
goto cleanup;
}
break;
}
}
if (!contAlias) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Could not find PCI "
"controller with index %u required "
"for device at address %s"),
info->addr.pci.bus, devStr);
goto cleanup;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_PCI_MULTIFUNCTION)) {
if (info->addr.pci.function != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Only PCI device addresses with function=0 "
"are supported with this QEMU binary"));
goto cleanup;
}
if (info->addr.pci.multi == VIR_DEVICE_ADDRESS_PCI_MULTI_ON) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("'multifunction=on' is not supported with "
"this QEMU binary"));
goto cleanup;
}
}
/*
* PCI bridge support is required for multiple buses
* 'pci.%u' is the ID of the bridge as specified in
* qemuBuildControllerDevStr
*
* PCI_MULTIBUS capability indicates that the implicit
* PCI bus is named 'pci.0' instead of 'pci'.
*/
if (info->addr.pci.bus != 0) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_PCI_BRIDGE)) {
virBufferAsprintf(buf, ",bus=%s", contAlias);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Multiple PCI buses are not supported "
"with this QEMU binary"));
goto cleanup;
}
} else {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_PCI_MULTIBUS)) {
virBufferAsprintf(buf, ",bus=%s", contAlias);
} else {
virBufferAddLit(buf, ",bus=pci");
}
}
if (info->addr.pci.multi == VIR_DEVICE_ADDRESS_PCI_MULTI_ON)
virBufferAddLit(buf, ",multifunction=on");
else if (info->addr.pci.multi == VIR_DEVICE_ADDRESS_PCI_MULTI_OFF)
virBufferAddLit(buf, ",multifunction=off");
virBufferAsprintf(buf, ",addr=0x%x", info->addr.pci.slot);
if (info->addr.pci.function != 0)
virBufferAsprintf(buf, ".0x%x", info->addr.pci.function);
} else if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB) {
virBufferAddLit(buf, ",bus=");
qemuUsbId(buf, info->addr.usb.bus);
virBufferAsprintf(buf, ".0,port=%s", info->addr.usb.port);
} else if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO) {
if (info->addr.spaprvio.has_reg)
virBufferAsprintf(buf, ",reg=0x%llx", info->addr.spaprvio.reg);
} else if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW) {
if (info->addr.ccw.assigned)
virBufferAsprintf(buf, ",devno=%x.%x.%04x",
info->addr.ccw.cssid,
info->addr.ccw.ssid,
info->addr.ccw.devno);
}
ret = 0;
cleanup:
VIR_FREE(devStr);
return ret;
}
static int
qemuBuildRomStr(virBufferPtr buf,
virDomainDeviceInfoPtr info,
virQEMUCapsPtr qemuCaps)
{
if (info->rombar || info->romfile) {
if (info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("rombar and romfile are supported only for PCI devices"));
return -1;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_PCI_ROMBAR)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("rombar and romfile not supported in this QEMU binary"));
return -1;
}
switch (info->rombar) {
case VIR_DOMAIN_PCI_ROMBAR_OFF:
virBufferAddLit(buf, ",rombar=0");
break;
case VIR_DOMAIN_PCI_ROMBAR_ON:
virBufferAddLit(buf, ",rombar=1");
break;
default:
break;
}
if (info->romfile)
virBufferAsprintf(buf, ",romfile=%s", info->romfile);
}
return 0;
}
static int
qemuBuildIoEventFdStr(virBufferPtr buf,
enum virDomainIoEventFd use,
virQEMUCapsPtr qemuCaps)
{
if (use && virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_IOEVENTFD))
virBufferAsprintf(buf, ",ioeventfd=%s",
virDomainIoEventFdTypeToString(use));
return 0;
}
#define QEMU_SERIAL_PARAM_ACCEPTED_CHARS \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
static int
qemuSafeSerialParamValue(const char *value)
{
if (strspn(value, QEMU_SERIAL_PARAM_ACCEPTED_CHARS) != strlen(value)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("driver serial '%s' contains unsafe characters"),
value);
return -1;
}
return 0;
}
static char *
qemuGetSecretString(virConnectPtr conn,
const char *scheme,
bool encoded,
int diskSecretType,
char *username,
unsigned char *uuid, char *usage,
virSecretUsageType secretUsageType)
{
size_t secret_size;
virSecretPtr sec = NULL;
char *secret = NULL;
char uuidStr[VIR_UUID_STRING_BUFLEN];
/* look up secret */
switch (diskSecretType) {
case VIR_DOMAIN_DISK_SECRET_TYPE_UUID:
sec = virSecretLookupByUUID(conn, uuid);
virUUIDFormat(uuid, uuidStr);
break;
case VIR_DOMAIN_DISK_SECRET_TYPE_USAGE:
sec = virSecretLookupByUsage(conn, secretUsageType, usage);
break;
}
if (!sec) {
if (diskSecretType == VIR_DOMAIN_DISK_SECRET_TYPE_UUID) {
virReportError(VIR_ERR_NO_SECRET,
_("%s no secret matches uuid '%s'"),
scheme, uuidStr);
} else {
virReportError(VIR_ERR_NO_SECRET,
_("%s no secret matches usage value '%s'"),
scheme, usage);
}
goto cleanup;
}
secret = (char *)conn->secretDriver->secretGetValue(sec, &secret_size, 0,
VIR_SECRET_GET_VALUE_INTERNAL_CALL);
if (!secret) {
if (diskSecretType == VIR_DOMAIN_DISK_SECRET_TYPE_UUID) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("could not get value of the secret for "
"username '%s' using uuid '%s'"),
username, uuidStr);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("could not get value of the secret for "
"username '%s' using usage value '%s'"),
username, usage);
}
goto cleanup;
}
if (encoded) {
char *base64 = NULL;
base64_encode_alloc(secret, secret_size, &base64);
VIR_FREE(secret);
if (!base64) {
virReportOOMError();
goto cleanup;
}
secret = base64;
}
cleanup:
virObjectUnref(sec);
return secret;
}
static int qemuAddRBDHost(virDomainDiskDefPtr disk, char *hostport)
{
char *port;
size_t skip;
char **parts;
if (VIR_EXPAND_N(disk->hosts, disk->nhosts, 1) < 0)
return -1;
if ((port = strchr(hostport, ']'))) {
/* ipv6, strip brackets */
hostport += 1;
skip = 3;
} else {
port = strstr(hostport, "\\:");
skip = 2;
}
if (port) {
*port = '\0';
port += skip;
if (VIR_STRDUP(disk->hosts[disk->nhosts - 1].port, port) < 0)
goto error;
} else {
if (VIR_STRDUP(disk->hosts[disk->nhosts - 1].port, "6789") < 0)
goto error;
}
parts = virStringSplit(hostport, "\\:", 0);
if (!parts)
goto error;
disk->hosts[disk->nhosts-1].name = virStringJoin((const char **)parts, ":");
virStringFreeList(parts);
if (!disk->hosts[disk->nhosts-1].name)
goto error;
disk->hosts[disk->nhosts-1].transport = VIR_DOMAIN_DISK_PROTO_TRANS_TCP;
disk->hosts[disk->nhosts-1].socket = NULL;
return 0;
error:
VIR_FREE(disk->hosts[disk->nhosts-1].port);
VIR_FREE(disk->hosts[disk->nhosts-1].name);
return -1;
}
/* disk->src initially has everything after the rbd: prefix */
static int qemuParseRBDString(virDomainDiskDefPtr disk)
{
char *options = NULL;
char *p, *e, *next;
p = strchr(disk->src, ':');
if (p) {
if (VIR_STRDUP(options, p + 1) < 0)
goto error;
*p = '\0';
}
/* options */
if (!options)
return 0; /* all done */
p = options;
while (*p) {
/* find : delimiter or end of string */
for (e = p; *e && *e != ':'; ++e) {
if (*e == '\\') {
e++;
if (*e == '\0')
break;
}
}
if (*e == '\0') {
next = e; /* last kv pair */
} else {
next = e + 1;
*e = '\0';
}
if (STRPREFIX(p, "id=") &&
VIR_STRDUP(disk->auth.username, p + strlen("id=")) < 0)
goto error;
if (STRPREFIX(p, "mon_host=")) {
char *h, *sep;
h = p + strlen("mon_host=");
while (h < e) {
for (sep = h; sep < e; ++sep) {
if (*sep == '\\' && (sep[1] == ',' ||
sep[1] == ';' ||
sep[1] == ' ')) {
*sep = '\0';
sep += 2;
break;
}
}
if (qemuAddRBDHost(disk, h) < 0)
goto error;
h = sep;
}
}
p = next;
}
VIR_FREE(options);
return 0;
error:
VIR_FREE(options);
return -1;
}
static int
qemuParseDriveURIString(virDomainDiskDefPtr def, virURIPtr uri,
const char *scheme)
{
int ret = -1;
char *transp = NULL;
char *sock = NULL;
char *volimg = NULL;
char *secret = NULL;
if (VIR_ALLOC(def->hosts) < 0)
goto error;
transp = strchr(uri->scheme, '+');
if (transp)
*transp++ = 0;
if (!STREQ(uri->scheme, scheme)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid transport/scheme '%s'"), uri->scheme);
goto error;
}
if (!transp) {
def->hosts->transport = VIR_DOMAIN_DISK_PROTO_TRANS_TCP;
} else {
def->hosts->transport = virDomainDiskProtocolTransportTypeFromString(transp);
if (def->hosts->transport < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid %s transport type '%s'"), scheme, transp);
goto error;
}
}
def->nhosts = 0; /* set to 1 once everything succeeds */
if (def->hosts->transport != VIR_DOMAIN_DISK_PROTO_TRANS_UNIX) {
if (VIR_STRDUP(def->hosts->name, uri->server) < 0)
goto error;
if (virAsprintf(&def->hosts->port, "%d", uri->port) < 0)
goto error;
} else {
def->hosts->name = NULL;
def->hosts->port = 0;
if (uri->query) {
if (STRPREFIX(uri->query, "socket=")) {
sock = strchr(uri->query, '=') + 1;
if (VIR_STRDUP(def->hosts->socket, sock) < 0)
goto error;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Invalid query parameter '%s'"), uri->query);
goto error;
}
}
}
if (uri->path) {
volimg = uri->path + 1; /* skip the prefix slash */
VIR_FREE(def->src);
if (VIR_STRDUP(def->src, volimg) < 0)
goto error;
} else {
VIR_FREE(def->src);
def->src = NULL;
}
if (uri->user) {
secret = strchr(uri->user, ':');
if (secret)
*secret = '\0';
if (VIR_STRDUP(def->auth.username, uri->user) < 0)
goto error;
}
def->nhosts = 1;
ret = 0;
cleanup:
virURIFree(uri);
return ret;
error:
virDomainDiskHostDefClear(def->hosts);
VIR_FREE(def->hosts);
goto cleanup;
}
static int
qemuParseGlusterString(virDomainDiskDefPtr def)
{
virURIPtr uri = NULL;
if (!(uri = virURIParse(def->src)))
return -1;
return qemuParseDriveURIString(def, uri, "gluster");
}
static int
qemuParseISCSIString(virDomainDiskDefPtr def)
{
virURIPtr uri = NULL;
char *slash;
unsigned lun;
if (!(uri = virURIParse(def->src)))
return -1;
if (uri->path &&
(slash = strchr(uri->path + 1, '/')) != NULL) {
if (slash[1] == '\0')
*slash = '\0';
else if (virStrToLong_ui(slash + 1, NULL, 10, &lun) == -1) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid name '%s' for iSCSI disk"), def->src);
return -1;
}
}
return qemuParseDriveURIString(def, uri, "iscsi");
}
static int
qemuParseNBDString(virDomainDiskDefPtr disk)
{
virDomainDiskHostDefPtr h = NULL;
char *host, *port;
char *src;
virURIPtr uri = NULL;
if (strstr(disk->src, "://")) {
if (!(uri = virURIParse(disk->src)))
return -1;
return qemuParseDriveURIString(disk, uri, "nbd");
}
if (VIR_ALLOC(h) < 0)
goto error;
host = disk->src + strlen("nbd:");
if (STRPREFIX(host, "unix:/")) {
src = strchr(host + strlen("unix:"), ':');
if (src)
*src++ = '\0';
h->transport = VIR_DOMAIN_DISK_PROTO_TRANS_UNIX;
if (VIR_STRDUP(h->socket, host + strlen("unix:")) < 0)
goto error;
} else {
port = strchr(host, ':');
if (!port) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse nbd filename '%s'"), disk->src);
goto error;
}
*port++ = '\0';
if (VIR_STRDUP(h->name, host) < 0)
goto error;
src = strchr(port, ':');
if (src)
*src++ = '\0';
if (VIR_STRDUP(h->port, port) < 0)
goto error;
}
if (src && STRPREFIX(src, "exportname=")) {
if (VIR_STRDUP(src, strchr(src, '=') + 1) < 0)
goto error;
} else {
src = NULL;
}
VIR_FREE(disk->src);
disk->src = src;
disk->nhosts = 1;
disk->hosts = h;
return 0;
error:
virDomainDiskHostDefClear(h);
VIR_FREE(h);
return -1;
}
static int
qemuNetworkDriveGetPort(int protocol,
const char *port)
{
int ret = 0;
if (port) {
if (virStrToLong_i(port, NULL, 10, &ret) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("failed to parse port number '%s'"),
port);
return -1;
}
return ret;
}
switch ((enum virDomainDiskProtocol) protocol) {
case VIR_DOMAIN_DISK_PROTOCOL_HTTP:
return 80;
case VIR_DOMAIN_DISK_PROTOCOL_HTTPS:
return 443;
case VIR_DOMAIN_DISK_PROTOCOL_FTP:
return 21;
case VIR_DOMAIN_DISK_PROTOCOL_FTPS:
return 990;
case VIR_DOMAIN_DISK_PROTOCOL_TFTP:
return 69;
case VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG:
return 7000;
case VIR_DOMAIN_DISK_PROTOCOL_NBD:
return 10809;
case VIR_DOMAIN_DISK_PROTOCOL_ISCSI:
case VIR_DOMAIN_DISK_PROTOCOL_GLUSTER:
/* no default port specified */
return 0;
case VIR_DOMAIN_DISK_PROTOCOL_RBD:
case VIR_DOMAIN_DISK_PROTOCOL_LAST:
/* not aplicable */
return -1;
}
return -1;
}
#define QEMU_DEFAULT_NBD_PORT "10809"
char *
qemuBuildNetworkDriveURI(int protocol,
const char *src,
size_t nhosts,
virDomainDiskHostDefPtr hosts,
const char *username,
const char *secret)
{
char *ret = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
virURIPtr uri = NULL;
size_t i;
switch ((enum virDomainDiskProtocol) protocol) {
case VIR_DOMAIN_DISK_PROTOCOL_NBD:
if (nhosts != 1) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("protocol '%s' accepts only one host"),
virDomainDiskProtocolTypeToString(protocol));
goto cleanup;
}
if (!((hosts->name && strchr(hosts->name, ':')) ||
(hosts->transport == VIR_DOMAIN_DISK_PROTO_TRANS_TCP &&
!hosts->name) ||
(hosts->transport == VIR_DOMAIN_DISK_PROTO_TRANS_UNIX &&
hosts->socket &&
hosts->socket[0] != '/'))) {
virBufferAddLit(&buf, "nbd:");
switch (hosts->transport) {
case VIR_DOMAIN_DISK_PROTO_TRANS_TCP:
virBufferStrcat(&buf, hosts->name, NULL);
virBufferAsprintf(&buf, ":%s",
hosts->port ? hosts->port :
QEMU_DEFAULT_NBD_PORT);
break;
case VIR_DOMAIN_DISK_PROTO_TRANS_UNIX:
if (!hosts->socket) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("socket attribute required for "
"unix transport"));
goto cleanup;
}
virBufferAsprintf(&buf, "unix:%s", hosts->socket);
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("nbd does not support transport '%s'"),
virDomainDiskProtocolTransportTypeToString(hosts->transport));
goto cleanup;
}
if (src)
virBufferAsprintf(&buf, ":exportname=%s", src);
if (virBufferError(&buf) < 0) {
virReportOOMError();
goto cleanup;
}
ret = virBufferContentAndReset(&buf);
goto cleanup;
}
/* fallthrough */
/* NBD code uses same formatting scheme as others in some cases */
case VIR_DOMAIN_DISK_PROTOCOL_HTTP:
case VIR_DOMAIN_DISK_PROTOCOL_HTTPS:
case VIR_DOMAIN_DISK_PROTOCOL_FTP:
case VIR_DOMAIN_DISK_PROTOCOL_FTPS:
case VIR_DOMAIN_DISK_PROTOCOL_TFTP:
case VIR_DOMAIN_DISK_PROTOCOL_ISCSI:
case VIR_DOMAIN_DISK_PROTOCOL_GLUSTER:
if (nhosts != 1) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("protocol '%s' accepts only one host"),
virDomainDiskProtocolTypeToString(protocol));
goto cleanup;
}
if (VIR_ALLOC(uri) < 0)
goto cleanup;
if (hosts->transport == VIR_DOMAIN_DISK_PROTO_TRANS_TCP) {
if (VIR_STRDUP(uri->scheme,
virDomainDiskProtocolTypeToString(protocol)) < 0)
goto cleanup;
} else {
if (virAsprintf(&uri->scheme, "%s+%s",
virDomainDiskProtocolTypeToString(protocol),
virDomainDiskProtocolTransportTypeToString(hosts->transport)) < 0)
goto cleanup;
}
if ((uri->port = qemuNetworkDriveGetPort(protocol, hosts->port)) < 0)
goto cleanup;
if (src &&
virAsprintf(&uri->path, "%s%s",
src[0] == '/' ? "" : "/",
src) < 0)
goto cleanup;
if (hosts->socket &&
virAsprintf(&uri->query, "socket=%s", hosts->socket) < 0)
goto cleanup;
if (username) {
if (secret) {
if (virAsprintf(&uri->user, "%s:%s", username, secret) < 0)
goto cleanup;
} else {
if (VIR_STRDUP(uri->user, username) < 0)
goto cleanup;
}
}
if (VIR_STRDUP(uri->server, hosts->name) < 0)
goto cleanup;
ret = virURIFormat(uri);
break;
case VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG:
if (!src) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("missing disk source for 'sheepdog' protocol"));
goto cleanup;
}
if (nhosts == 0) {
if (virAsprintf(&ret, "sheepdog:%s", src) < 0)
goto cleanup;
} else if (nhosts == 1) {
if (virAsprintf(&ret, "sheepdog:%s:%s:%s",
hosts->name,
hosts->port ? hosts->port : "7000",
src) < 0)
goto cleanup;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("protocol 'sheepdog' accepts up to one host"));
goto cleanup;
}
break;
case VIR_DOMAIN_DISK_PROTOCOL_RBD:
if (strchr(src, ':')) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("':' not allowed in RBD source volume name '%s'"),
src);
goto cleanup;
}
virBufferStrcat(&buf, "rbd:", src, NULL);
if (username) {
virBufferEscape(&buf, '\\', ":", ":id=%s", username);
virBufferEscape(&buf, '\\', ":",
":key=%s:auth_supported=cephx\\;none",
secret);
} else {
virBufferAddLit(&buf, ":auth_supported=none");
}
if (nhosts > 0) {
virBufferAddLit(&buf, ":mon_host=");
for (i = 0; i < nhosts; i++) {
if (i)
virBufferAddLit(&buf, "\\;");
/* assume host containing : is ipv6 */
if (strchr(hosts[i].name, ':'))
virBufferEscape(&buf, '\\', ":", "[%s]", hosts[i].name);
else
virBufferAsprintf(&buf, "%s", hosts[i].name);
if (hosts[i].port)
virBufferAsprintf(&buf, "\\:%s", hosts[i].port);
}
}
if (virBufferError(&buf) < 0) {
virReportOOMError();
goto cleanup;
}
ret = virBufferContentAndReset(&buf);
break;
case VIR_DOMAIN_DISK_PROTOCOL_LAST:
goto cleanup;
}
cleanup:
virBufferFreeAndReset(&buf);
virURIFree(uri);
return ret;
}
int
qemuGetDriveSourceString(int type,
const char *src,
int protocol,
size_t nhosts,
virDomainDiskHostDefPtr hosts,
const char *username,
const char *secret,
char **path)
{
*path = NULL;
switch ((enum virDomainDiskType) type) {
case VIR_DOMAIN_DISK_TYPE_BLOCK:
case VIR_DOMAIN_DISK_TYPE_FILE:
case VIR_DOMAIN_DISK_TYPE_DIR:
if (!src)
return 1;
if (VIR_STRDUP(*path, src) < 0)
return -1;
break;
case VIR_DOMAIN_DISK_TYPE_NETWORK:
if (!(*path = qemuBuildNetworkDriveURI(protocol,
src,
nhosts,
hosts,
username,
secret)))
return -1;
break;
case VIR_DOMAIN_DISK_TYPE_VOLUME:
case VIR_DOMAIN_DISK_TYPE_LAST:
break;
}
return 0;
}
static int
qemuDomainDiskGetSourceString(virConnectPtr conn,
virDomainDiskDefPtr disk,
char **source)
{
int actualType = virDomainDiskGetActualType(disk);
char *secret = NULL;
int ret = -1;
*source = NULL;
if (actualType == VIR_DOMAIN_DISK_TYPE_NETWORK &&
disk->auth.username &&
(disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_ISCSI ||
disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_RBD)) {
bool encode = false;
int secretType = VIR_SECRET_USAGE_TYPE_ISCSI;
if (disk->protocol == VIR_DOMAIN_DISK_PROTOCOL_RBD) {
/* qemu requires the secret to be encoded for RBD */
encode = true;
secretType = VIR_SECRET_USAGE_TYPE_CEPH;
}
if (!(secret = qemuGetSecretString(conn,
virDomainDiskProtocolTypeToString(disk->protocol),
encode,
disk->auth.secretType,
disk->auth.username,
disk->auth.secret.uuid,
disk->auth.secret.usage,
secretType)))
goto cleanup;
}
ret = qemuGetDriveSourceString(virDomainDiskGetActualType(disk),
disk->src,
disk->protocol,
disk->nhosts,
disk->hosts,
disk->auth.username,
secret,
source);
cleanup:
VIR_FREE(secret);
return ret;
}
char *
qemuBuildDriveStr(virConnectPtr conn,
virDomainDiskDefPtr disk,
bool bootable,
virQEMUCapsPtr qemuCaps)
{
virBuffer opt = VIR_BUFFER_INITIALIZER;
const char *bus = virDomainDiskQEMUBusTypeToString(disk->bus);
const char *trans =
virDomainDiskGeometryTransTypeToString(disk->geometry.trans);
int idx = virDiskNameToIndex(disk->dst);
int busid = -1, unitid = -1;
char *source = NULL;
int actualType = virDomainDiskGetActualType(disk);
if (idx < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unsupported disk type '%s'"), disk->dst);
goto error;
}
switch (disk->bus) {
case VIR_DOMAIN_DISK_BUS_SCSI:
if (disk->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("unexpected address type for scsi disk"));
goto error;
}
/* Setting bus= attr for SCSI drives, causes a controller
* to be created. Yes this is slightly odd. It is not possible
* to have > 1 bus on a SCSI controller (yet). */
if (disk->info.addr.drive.bus != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("SCSI controller only supports 1 bus"));
goto error;
}
busid = disk->info.addr.drive.controller;
unitid = disk->info.addr.drive.unit;
break;
case VIR_DOMAIN_DISK_BUS_IDE:
if (disk->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("unexpected address type for ide disk"));
goto error;
}
/* We can only have 1 IDE controller (currently) */
if (disk->info.addr.drive.controller != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Only 1 %s controller is supported"), bus);
goto error;
}
busid = disk->info.addr.drive.bus;
unitid = disk->info.addr.drive.unit;
break;
case VIR_DOMAIN_DISK_BUS_FDC:
if (disk->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("unexpected address type for fdc disk"));
goto error;
}
/* We can only have 1 FDC controller (currently) */
if (disk->info.addr.drive.controller != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Only 1 %s controller is supported"), bus);
goto error;
}
/* We can only have 1 FDC bus (currently) */
if (disk->info.addr.drive.bus != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Only 1 %s bus is supported"), bus);
goto error;
}
if (disk->info.addr.drive.target != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("target must be 0 for controller fdc"));
goto error;
}
unitid = disk->info.addr.drive.unit;
break;
case VIR_DOMAIN_DISK_BUS_VIRTIO:
idx = -1;
break;
case VIR_DOMAIN_DISK_BUS_XEN:
case VIR_DOMAIN_DISK_BUS_SD:
/* Xen and SD have no address type currently, so assign
* based on index */
break;
}
if (qemuDomainDiskGetSourceString(conn, disk, &source) < 0)
goto error;
if (source &&
!((disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY ||
disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) &&
disk->tray_status == VIR_DOMAIN_DISK_TRAY_OPEN)) {
virBufferAddLit(&opt, "file=");
switch (actualType) {
case VIR_DOMAIN_DISK_TYPE_DIR:
/* QEMU only supports magic FAT format for now */
if (disk->format > 0 && disk->format != VIR_STORAGE_FILE_FAT) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unsupported disk driver type for '%s'"),
virStorageFileFormatTypeToString(disk->format));
goto error;
}
if (!disk->readonly) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot create virtual FAT disks in read-write mode"));
goto error;
}
virBufferAddLit(&opt, "fat:");
if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY)
virBufferAddLit(&opt, "floppy:");
break;
case VIR_DOMAIN_DISK_TYPE_BLOCK:
if (disk->tray_status == VIR_DOMAIN_DISK_TRAY_OPEN) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
disk->type == VIR_DOMAIN_DISK_TYPE_VOLUME ?
_("tray status 'open' is invalid for block type volume") :
_("tray status 'open' is invalid for block type disk"));
goto error;
}
break;
default:
break;
}
virBufferEscape(&opt, ',', ",", "%s,", source);
}
VIR_FREE(source);
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
virBufferAddLit(&opt, "if=none");
else
virBufferAsprintf(&opt, "if=%s", bus);
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) {
if (disk->bus == VIR_DOMAIN_DISK_BUS_SCSI) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_CD))
virBufferAddLit(&opt, ",media=cdrom");
} else if (disk->bus == VIR_DOMAIN_DISK_BUS_IDE) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_IDE_CD))
virBufferAddLit(&opt, ",media=cdrom");
} else {
virBufferAddLit(&opt, ",media=cdrom");
}
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virBufferAsprintf(&opt, ",id=%s%s", QEMU_DRIVE_HOST_PREFIX, disk->info.alias);
} else {
if (busid == -1 && unitid == -1) {
if (idx != -1)
virBufferAsprintf(&opt, ",index=%d", idx);
} else {
if (busid != -1)
virBufferAsprintf(&opt, ",bus=%d", busid);
if (unitid != -1)
virBufferAsprintf(&opt, ",unit=%d", unitid);
}
}
if (bootable &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_BOOT) &&
(disk->device == VIR_DOMAIN_DISK_DEVICE_DISK ||
disk->device == VIR_DOMAIN_DISK_DEVICE_LUN) &&
disk->bus != VIR_DOMAIN_DISK_BUS_IDE)
virBufferAddLit(&opt, ",boot=on");
if (disk->readonly &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_READONLY))
virBufferAddLit(&opt, ",readonly=on");
if (disk->transient) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("transient disks not supported yet"));
goto error;
}
if (disk->format > 0 &&
disk->type != VIR_DOMAIN_DISK_TYPE_DIR &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_FORMAT))
virBufferAsprintf(&opt, ",format=%s",
virStorageFileFormatTypeToString(disk->format));
/* generate geometry command string */
if (disk->geometry.cylinders > 0 &&
disk->geometry.heads > 0 &&
disk->geometry.sectors > 0) {
virBufferAsprintf(&opt, ",cyls=%u,heads=%u,secs=%u",
disk->geometry.cylinders,
disk->geometry.heads,
disk->geometry.sectors);
if (disk->geometry.trans != VIR_DOMAIN_DISK_TRANS_DEFAULT)
virBufferEscapeString(&opt, ",trans=%s", trans);
}
if (disk->serial &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_SERIAL)) {
if (qemuSafeSerialParamValue(disk->serial) < 0)
goto error;
virBufferAsprintf(&opt, ",serial=%s", disk->serial);
}
if (disk->cachemode) {
const char *mode = NULL;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_CACHE_V2)) {
mode = qemuDiskCacheV2TypeToString(disk->cachemode);
if (disk->cachemode == VIR_DOMAIN_DISK_CACHE_DIRECTSYNC &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_CACHE_DIRECTSYNC)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("disk cache mode 'directsync' is not "
"supported by this QEMU"));
goto error;
} else if (disk->cachemode == VIR_DOMAIN_DISK_CACHE_UNSAFE &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_CACHE_UNSAFE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("disk cache mode 'unsafe' is not "
"supported by this QEMU"));
goto error;
}
} else {
mode = qemuDiskCacheV1TypeToString(disk->cachemode);
}
virBufferAsprintf(&opt, ",cache=%s", mode);
} else if (disk->shared && !disk->readonly) {
virBufferAddLit(&opt, ",cache=off");
}
if (disk->copy_on_read) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_COPY_ON_READ)) {
virBufferAsprintf(&opt, ",copy-on-read=%s",
virDomainDiskCopyOnReadTypeToString(disk->copy_on_read));
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("copy_on_read is not supported by this QEMU binary"));
goto error;
}
}
if (disk->discard) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_DISCARD)) {
virBufferAsprintf(&opt, ",discard=%s",
virDomainDiskDiscardTypeToString(disk->discard));
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("discard is not supported by this QEMU binary"));
goto error;
}
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_MONITOR_JSON)) {
const char *wpolicy = NULL, *rpolicy = NULL;
if (disk->error_policy)
wpolicy = virDomainDiskErrorPolicyTypeToString(disk->error_policy);
if (disk->rerror_policy)
rpolicy = virDomainDiskErrorPolicyTypeToString(disk->rerror_policy);
if (disk->error_policy == VIR_DOMAIN_DISK_ERROR_POLICY_ENOSPACE) {
/* in the case of enospace, the option is spelled
* differently in qemu, and it's only valid for werror,
* not for rerror, so leave leave rerror NULL.
*/
wpolicy = "enospc";
} else if (!rpolicy) {
/* for other policies, rpolicy can match wpolicy */
rpolicy = wpolicy;
}
if (wpolicy)
virBufferAsprintf(&opt, ",werror=%s", wpolicy);
if (rpolicy)
virBufferAsprintf(&opt, ",rerror=%s", rpolicy);
}
if (disk->iomode) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_AIO)) {
virBufferAsprintf(&opt, ",aio=%s",
virDomainDiskIoTypeToString(disk->iomode));
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("disk aio mode not supported with this "
"QEMU binary"));
goto error;
}
}
/* block I/O throttling */
if ((disk->blkdeviotune.total_bytes_sec ||
disk->blkdeviotune.read_bytes_sec ||
disk->blkdeviotune.write_bytes_sec ||
disk->blkdeviotune.total_iops_sec ||
disk->blkdeviotune.read_iops_sec ||
disk->blkdeviotune.write_iops_sec) &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_IOTUNE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("block I/O throttling not supported with this "
"QEMU binary"));
goto error;
}
if (disk->blkdeviotune.total_bytes_sec) {
virBufferAsprintf(&opt, ",bps=%llu",
disk->blkdeviotune.total_bytes_sec);
}
if (disk->blkdeviotune.read_bytes_sec) {
virBufferAsprintf(&opt, ",bps_rd=%llu",
disk->blkdeviotune.read_bytes_sec);
}
if (disk->blkdeviotune.write_bytes_sec) {
virBufferAsprintf(&opt, ",bps_wr=%llu",
disk->blkdeviotune.write_bytes_sec);
}
if (disk->blkdeviotune.total_iops_sec) {
virBufferAsprintf(&opt, ",iops=%llu",
disk->blkdeviotune.total_iops_sec);
}
if (disk->blkdeviotune.read_iops_sec) {
virBufferAsprintf(&opt, ",iops_rd=%llu",
disk->blkdeviotune.read_iops_sec);
}
if (disk->blkdeviotune.write_iops_sec) {
virBufferAsprintf(&opt, ",iops_wr=%llu",
disk->blkdeviotune.write_iops_sec);
}
if (virBufferError(&opt)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&opt);
error:
VIR_FREE(source);
virBufferFreeAndReset(&opt);
return NULL;
}
char *
qemuBuildDriveDevStr(virDomainDefPtr def,
virDomainDiskDefPtr disk,
int bootindex,
virQEMUCapsPtr qemuCaps)
{
virBuffer opt = VIR_BUFFER_INITIALIZER;
const char *bus = virDomainDiskQEMUBusTypeToString(disk->bus);
int idx = virDiskNameToIndex(disk->dst);
int controllerModel;
if (idx < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unsupported disk type '%s'"), disk->dst);
goto error;
}
if (disk->wwn) {
if ((disk->bus != VIR_DOMAIN_DISK_BUS_IDE) &&
(disk->bus != VIR_DOMAIN_DISK_BUS_SCSI)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Only ide and scsi disk support wwn"));
goto error;
}
}
if ((disk->vendor || disk->product) &&
disk->bus != VIR_DOMAIN_DISK_BUS_SCSI) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Only scsi disk supports vendor and product"));
goto error;
}
if (disk->device == VIR_DOMAIN_DISK_DEVICE_LUN) {
/* make sure that both the bus and the qemu binary support
* type='lun' (SG_IO).
*/
if (disk->bus != VIR_DOMAIN_DISK_BUS_VIRTIO &&
disk->bus != VIR_DOMAIN_DISK_BUS_SCSI) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("disk device='lun' is not supported for bus='%s'"),
bus);
goto error;
}
if (disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK) {
if (disk->protocol != VIR_DOMAIN_DISK_PROTOCOL_ISCSI) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("disk device='lun' is not supported for protocol='%s'"),
virDomainDiskProtocolTypeToString(disk->protocol));
goto error;
}
} else if (!virDomainDiskSourceIsBlockType(disk)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("disk device='lun' is only valid for block type disk source"));
goto error;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_BLK_SG_IO)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("disk device='lun' is not supported by this QEMU"));
goto error;
}
if (disk->wwn) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Setting wwn is not supported for lun device"));
goto error;
}
if (disk->vendor || disk->product) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Setting vendor or product is not supported "
"for lun device"));
goto error;
}
}
switch (disk->bus) {
case VIR_DOMAIN_DISK_BUS_IDE:
if (disk->info.addr.drive.target != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("target must be 0 for ide controller"));
goto error;
}
if (disk->wwn &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_IDE_DRIVE_WWN)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Setting wwn for ide disk is not supported "
"by this QEMU"));
goto error;
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_IDE_CD)) {
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM)
virBufferAddLit(&opt, "ide-cd");
else
virBufferAddLit(&opt, "ide-hd");
} else {
virBufferAddLit(&opt, "ide-drive");
}
virBufferAsprintf(&opt, ",bus=ide.%d,unit=%d",
disk->info.addr.drive.bus,
disk->info.addr.drive.unit);
break;
case VIR_DOMAIN_DISK_BUS_SCSI:
if (disk->device == VIR_DOMAIN_DISK_DEVICE_LUN) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_BLOCK)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU doesn't support scsi-block for "
"lun passthrough"));
goto error;
}
}
if (disk->wwn &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_DISK_WWN)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Setting wwn for scsi disk is not supported "
"by this QEMU"));
goto error;
}
/* Properties wwn, vendor and product were introduced in the
* same QEMU release (1.2.0).
*/
if ((disk->vendor || disk->product) &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_DISK_WWN)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Setting vendor or product for scsi disk is not "
"supported by this QEMU"));
goto error;
}
controllerModel =
virDomainDeviceFindControllerModel(def, &disk->info,
VIR_DOMAIN_CONTROLLER_TYPE_SCSI);
if ((qemuSetScsiControllerModel(def, qemuCaps, &controllerModel)) < 0)
goto error;
if (controllerModel == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC) {
if (disk->info.addr.drive.target != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("target must be 0 for controller "
"model 'lsilogic'"));
goto error;
}
if (disk->device == VIR_DOMAIN_DISK_DEVICE_LUN) {
virBufferAddLit(&opt, "scsi-block");
} else {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_CD)) {
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM)
virBufferAddLit(&opt, "scsi-cd");
else
virBufferAddLit(&opt, "scsi-hd");
} else {
virBufferAddLit(&opt, "scsi-disk");
}
}
virBufferAsprintf(&opt, ",bus=scsi%d.%d,scsi-id=%d",
disk->info.addr.drive.controller,
disk->info.addr.drive.bus,
disk->info.addr.drive.unit);
} else {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_DISK_CHANNEL)) {
if (disk->info.addr.drive.target > 7) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU doesn't support target "
"greater than 7"));
goto error;
}
if ((disk->info.addr.drive.bus != disk->info.addr.drive.unit) &&
(disk->info.addr.drive.bus != 0)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU only supports both bus and "
"unit equal to 0"));
goto error;
}
}
if (disk->device != VIR_DOMAIN_DISK_DEVICE_LUN) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCSI_CD)) {
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM)
virBufferAddLit(&opt, "scsi-cd");
else
virBufferAddLit(&opt, "scsi-hd");
} else {
virBufferAddLit(&opt, "scsi-disk");
}
} else {
virBufferAddLit(&opt, "scsi-block");
}
virBufferAsprintf(&opt, ",bus=scsi%d.0,channel=%d,scsi-id=%d,lun=%d",
disk->info.addr.drive.controller,
disk->info.addr.drive.bus,
disk->info.addr.drive.target,
disk->info.addr.drive.unit);
}
break;
case VIR_DOMAIN_DISK_BUS_SATA:
if (disk->info.addr.drive.bus != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("bus must be 0 for ide controller"));
goto error;
}
if (disk->info.addr.drive.target != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("target must be 0 for ide controller"));
goto error;
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_IDE_CD)) {
if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM)
virBufferAddLit(&opt, "ide-cd");
else
virBufferAddLit(&opt, "ide-hd");
} else {
virBufferAddLit(&opt, "ide-drive");
}
if (qemuDomainMachineIsQ35(def) &&
disk->info.addr.drive.controller == 0) {
/* Q35 machines have an implicit ahci (sata) controller at
* 00:1F.2 which for some reason is hardcoded with the id
* "ide" instead of the seemingly more reasonable "ahci0"
* or "sata0".
*/
virBufferAsprintf(&opt, ",bus=ide.%d", disk->info.addr.drive.unit);
} else {
/* All other ahci controllers have been created by
* libvirt, and we gave them the id "ahci${n}" where ${n}
* is the controller number. So we identify them that way.
*/
virBufferAsprintf(&opt, ",bus=ahci%d.%d",
disk->info.addr.drive.controller,
disk->info.addr.drive.unit);
}
break;
case VIR_DOMAIN_DISK_BUS_VIRTIO:
if (disk->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW) {
virBufferAddLit(&opt, "virtio-blk-ccw");
} else if (disk->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390) {
virBufferAddLit(&opt, "virtio-blk-s390");
} else if (disk->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO) {
virBufferAddLit(&opt, "virtio-blk-device");
} else {
virBufferAddLit(&opt, "virtio-blk-pci");
}
qemuBuildIoEventFdStr(&opt, disk->ioeventfd, qemuCaps);
if (disk->event_idx &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_BLK_EVENT_IDX)) {
virBufferAsprintf(&opt, ",event_idx=%s",
virDomainVirtioEventIdxTypeToString(disk->event_idx));
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_BLK_SCSI)) {
/* if sg_io is true but the scsi option isn't supported,
* that means it's just always on in this version of qemu.
*/
virBufferAsprintf(&opt, ",scsi=%s",
(disk->device == VIR_DOMAIN_DISK_DEVICE_LUN)
? "on" : "off");
}
if (qemuBuildDeviceAddressStr(&opt, def, &disk->info, qemuCaps) < 0)
goto error;
break;
case VIR_DOMAIN_DISK_BUS_USB:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_USB_STORAGE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU doesn't support '-device "
"usb-storage'"));
goto error;
}
virBufferAddLit(&opt, "usb-storage");
if (qemuBuildDeviceAddressStr(&opt, def, &disk->info, qemuCaps) < 0)
goto error;
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unsupported disk bus '%s' with device setup"), bus);
goto error;
}
virBufferAsprintf(&opt, ",drive=%s%s", QEMU_DRIVE_HOST_PREFIX, disk->info.alias);
virBufferAsprintf(&opt, ",id=%s", disk->info.alias);
if (bootindex && virQEMUCapsGet(qemuCaps, QEMU_CAPS_BOOTINDEX))
virBufferAsprintf(&opt, ",bootindex=%d", bootindex);
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKIO)) {
if (disk->blockio.logical_block_size > 0)
virBufferAsprintf(&opt, ",logical_block_size=%u",
disk->blockio.logical_block_size);
if (disk->blockio.physical_block_size > 0)
virBufferAsprintf(&opt, ",physical_block_size=%u",
disk->blockio.physical_block_size);
}
if (disk->wwn) {
if (STRPREFIX(disk->wwn, "0x"))
virBufferAsprintf(&opt, ",wwn=%s", disk->wwn);
else
virBufferAsprintf(&opt, ",wwn=0x%s", disk->wwn);
}
if (disk->vendor)
virBufferAsprintf(&opt, ",vendor=%s", disk->vendor);
if (disk->product)
virBufferAsprintf(&opt, ",product=%s", disk->product);
if (disk->bus == VIR_DOMAIN_DISK_BUS_USB) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_USB_STORAGE_REMOVABLE)) {
if (disk->removable == VIR_DOMAIN_FEATURE_STATE_ON)
virBufferAddLit(&opt, ",removable=on");
else
virBufferAddLit(&opt, ",removable=off");
} else {
if (disk->removable != VIR_DOMAIN_FEATURE_STATE_DEFAULT) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU doesn't support setting the "
"removable flag of USB storage devices"));
goto error;
}
}
}
if (virBufferError(&opt)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&opt);
error:
virBufferFreeAndReset(&opt);
return NULL;
}
char *qemuBuildFSStr(virDomainFSDefPtr fs,
virQEMUCapsPtr qemuCaps ATTRIBUTE_UNUSED)
{
virBuffer opt = VIR_BUFFER_INITIALIZER;
const char *driver = qemuDomainFSDriverTypeToString(fs->fsdriver);
const char *wrpolicy = virDomainFSWrpolicyTypeToString(fs->wrpolicy);
if (fs->type != VIR_DOMAIN_FS_TYPE_MOUNT) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("only supports mount filesystem type"));
goto error;
}
if (!driver) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Filesystem driver type not supported"));
goto error;
}
virBufferAdd(&opt, driver, -1);
if (fs->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_PATH ||
fs->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_DEFAULT) {
if (fs->accessmode == VIR_DOMAIN_FS_ACCESSMODE_MAPPED) {
virBufferAddLit(&opt, ",security_model=mapped");
} else if (fs->accessmode == VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH) {
virBufferAddLit(&opt, ",security_model=passthrough");
} else if (fs->accessmode == VIR_DOMAIN_FS_ACCESSMODE_SQUASH) {
virBufferAddLit(&opt, ",security_model=none");
}
} else {
/* For other fs drivers, default(passthru) should always
* be supported */
if (fs->accessmode != VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("only supports passthrough accessmode"));
goto error;
}
}
if (fs->wrpolicy) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_FSDEV_WRITEOUT)) {
virBufferAsprintf(&opt, ",writeout=%s", wrpolicy);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("filesystem writeout not supported"));
goto error;
}
}
virBufferAsprintf(&opt, ",id=%s%s", QEMU_FSDEV_HOST_PREFIX, fs->info.alias);
virBufferAsprintf(&opt, ",path=%s", fs->src);
if (fs->readonly) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_FSDEV_READONLY)) {
virBufferAddLit(&opt, ",readonly");
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("readonly filesystem is not supported by this "
"QEMU binary"));
goto error;
}
}
if (virBufferError(&opt)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&opt);
error:
virBufferFreeAndReset(&opt);
return NULL;
}
char *
qemuBuildFSDevStr(virDomainDefPtr def,
virDomainFSDefPtr fs,
virQEMUCapsPtr qemuCaps)
{
virBuffer opt = VIR_BUFFER_INITIALIZER;
if (fs->type != VIR_DOMAIN_FS_TYPE_MOUNT) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("can only passthrough directories"));
goto error;
}
virBufferAddLit(&opt, "virtio-9p-pci");
virBufferAsprintf(&opt, ",id=%s", fs->info.alias);
virBufferAsprintf(&opt, ",fsdev=%s%s", QEMU_FSDEV_HOST_PREFIX, fs->info.alias);
virBufferAsprintf(&opt, ",mount_tag=%s", fs->dst);
if (qemuBuildDeviceAddressStr(&opt, def, &fs->info, qemuCaps) < 0)
goto error;
if (virBufferError(&opt)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&opt);
error:
virBufferFreeAndReset(&opt);
return NULL;
}
static int
qemuControllerModelUSBToCaps(int model)
{
switch (model) {
case VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX3_UHCI:
return QEMU_CAPS_PIIX3_USB_UHCI;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX4_UHCI:
return QEMU_CAPS_PIIX4_USB_UHCI;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_EHCI:
return QEMU_CAPS_USB_EHCI;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_EHCI1:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI1:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI2:
case VIR_DOMAIN_CONTROLLER_MODEL_USB_ICH9_UHCI3:
return QEMU_CAPS_ICH9_USB_EHCI1;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_VT82C686B_UHCI:
return QEMU_CAPS_VT82C686B_USB_UHCI;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_PCI_OHCI:
return QEMU_CAPS_PCI_OHCI;
case VIR_DOMAIN_CONTROLLER_MODEL_USB_NEC_XHCI:
return QEMU_CAPS_NEC_USB_XHCI;
default:
return -1;
}
}
static int
qemuBuildUSBControllerDevStr(virDomainDefPtr domainDef,
virDomainControllerDefPtr def,
virQEMUCapsPtr qemuCaps,
virBuffer *buf)
{
const char *smodel;
int model, flags;
model = def->model;
if (model == -1) {
if (domainDef->os.arch == VIR_ARCH_PPC64)
model = VIR_DOMAIN_CONTROLLER_MODEL_USB_PCI_OHCI;
else
model = VIR_DOMAIN_CONTROLLER_MODEL_USB_PIIX3_UHCI;
}
smodel = qemuControllerModelUSBTypeToString(model);
flags = qemuControllerModelUSBToCaps(model);
if (flags == -1 || !virQEMUCapsGet(qemuCaps, flags)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("%s not supported in this QEMU binary"), smodel);
return -1;
}
virBufferAsprintf(buf, "%s", smodel);
if (def->info.mastertype == VIR_DOMAIN_CONTROLLER_MASTER_USB) {
virBufferAddLit(buf, ",masterbus=");
qemuUsbId(buf, def->idx);
virBufferAsprintf(buf, ".0,firstport=%d", def->info.master.usb.startport);
} else {
virBufferAddLit(buf, ",id=");
qemuUsbId(buf, def->idx);
}
return 0;
}
char *
qemuBuildControllerDevStr(virDomainDefPtr domainDef,
virDomainControllerDefPtr def,
virQEMUCapsPtr qemuCaps,
int *nusbcontroller)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
int model;
if (def->queues &&
!(def->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI &&
def->model == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("'queues' is only supported by virtio-scsi controller"));
return NULL;
}
switch (def->type) {
case VIR_DOMAIN_CONTROLLER_TYPE_SCSI:
model = def->model;
if ((qemuSetScsiControllerModel(domainDef, qemuCaps, &model)) < 0)
return NULL;
switch (model) {
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_VIRTIO_SCSI:
if (def->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW)
virBufferAddLit(&buf, "virtio-scsi-ccw");
else if (def->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390)
virBufferAddLit(&buf, "virtio-scsi-s390");
else if (def->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO)
virBufferAddLit(&buf, "virtio-scsi-device");
else
virBufferAddLit(&buf, "virtio-scsi-pci");
break;
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC:
virBufferAddLit(&buf, "lsi");
break;
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_IBMVSCSI:
virBufferAddLit(&buf, "spapr-vscsi");
break;
case VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSISAS1078:
virBufferAddLit(&buf, "megasas");
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported controller model: %s"),
virDomainControllerModelSCSITypeToString(def->model));
}
virBufferAsprintf(&buf, ",id=scsi%d", def->idx);
break;
case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL:
if (def->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
virBufferAddLit(&buf, "virtio-serial-pci");
} else if (def->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW) {
virBufferAddLit(&buf, "virtio-serial-ccw");
} else if (def->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390) {
virBufferAddLit(&buf, "virtio-serial-s390");
} else if (def->info.type ==
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO) {
virBufferAddLit(&buf, "virtio-serial-device");
} else {
virBufferAddLit(&buf, "virtio-serial");
}
virBufferAsprintf(&buf, ",id=" QEMU_VIRTIO_SERIAL_PREFIX "%d",
def->idx);
if (def->opts.vioserial.ports != -1) {
virBufferAsprintf(&buf, ",max_ports=%d",
def->opts.vioserial.ports);
}
if (def->opts.vioserial.vectors != -1) {
virBufferAsprintf(&buf, ",vectors=%d",
def->opts.vioserial.vectors);
}
break;
case VIR_DOMAIN_CONTROLLER_TYPE_CCID:
virBufferAsprintf(&buf, "usb-ccid,id=ccid%d", def->idx);
break;
case VIR_DOMAIN_CONTROLLER_TYPE_SATA:
virBufferAsprintf(&buf, "ahci,id=ahci%d", def->idx);
break;
case VIR_DOMAIN_CONTROLLER_TYPE_USB:
if (qemuBuildUSBControllerDevStr(domainDef, def, qemuCaps, &buf) == -1)
goto error;
if (nusbcontroller)
*nusbcontroller += 1;
break;
case VIR_DOMAIN_CONTROLLER_TYPE_PCI:
switch (def->model) {
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_BRIDGE:
if (def->idx == 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("PCI bridge index should be > 0"));
goto error;
}
virBufferAsprintf(&buf, "pci-bridge,chassis_nr=%d,id=pci.%d",
def->idx, def->idx);
break;
case VIR_DOMAIN_CONTROLLER_MODEL_DMI_TO_PCI_BRIDGE:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_DMI_TO_PCI_BRIDGE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("The dmi-to-pci-bridge (i82801b11-bridge) "
"controller is not supported in this QEMU binary"));
goto error;
}
if (def->idx == 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("dmi-to-pci-bridge index should be > 0"));
goto error;
}
virBufferAsprintf(&buf, "i82801b11-bridge,id=pci.%d", def->idx);
break;
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("wrong function called for pci-root/pcie-root"));
return NULL;
}
break;
/* We always get an IDE controller, whether we want it or not. */
case VIR_DOMAIN_CONTROLLER_TYPE_IDE:
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unknown controller type: %s"),
virDomainControllerTypeToString(def->type));
goto error;
}
if (def->queues)
virBufferAsprintf(&buf, ",num_queues=%u", def->queues);
if (qemuBuildDeviceAddressStr(&buf, domainDef, &def->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildNicStr(virDomainNetDefPtr net,
const char *prefix,
int vlan)
{
char *str;
char macaddr[VIR_MAC_STRING_BUFLEN];
ignore_value(virAsprintf(&str,
"%smacaddr=%s,vlan=%d%s%s%s%s",
prefix ? prefix : "",
virMacAddrFormat(&net->mac, macaddr),
vlan,
(net->model ? ",model=" : ""),
(net->model ? net->model : ""),
(net->info.alias ? ",name=" : ""),
(net->info.alias ? net->info.alias : "")));
return str;
}
char *
qemuBuildNicDevStr(virDomainDefPtr def,
virDomainNetDefPtr net,
int vlan,
int bootindex,
int vhostfdSize,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *nic = net->model;
bool usingVirtio = false;
char macaddr[VIR_MAC_STRING_BUFLEN];
if (STREQ(net->model, "virtio")) {
if (net->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW)
nic = "virtio-net-ccw";
else if (net->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390)
nic = "virtio-net-s390";
else if (net->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO)
nic = "virtio-net-device";
else
nic = "virtio-net-pci";
usingVirtio = true;
}
virBufferAdd(&buf, nic, -1);
if (usingVirtio && net->driver.virtio.txmode) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_TX_ALG)) {
virBufferAddLit(&buf, ",tx=");
switch (net->driver.virtio.txmode) {
case VIR_DOMAIN_NET_VIRTIO_TX_MODE_IOTHREAD:
virBufferAddLit(&buf, "bh");
break;
case VIR_DOMAIN_NET_VIRTIO_TX_MODE_TIMER:
virBufferAddLit(&buf, "timer");
break;
default:
/* this should never happen, if it does, we need
* to add another case to this switch.
*/
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("unrecognized virtio-net-pci 'tx' option"));
goto error;
}
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("virtio-net-pci 'tx' option not supported in this QEMU binary"));
goto error;
}
}
if (usingVirtio) {
qemuBuildIoEventFdStr(&buf, net->driver.virtio.ioeventfd, qemuCaps);
if (net->driver.virtio.event_idx &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_NET_EVENT_IDX)) {
virBufferAsprintf(&buf, ",event_idx=%s",
virDomainVirtioEventIdxTypeToString(net->driver.virtio.event_idx));
}
}
if (usingVirtio && vhostfdSize > 1) {
/* As advised at http://www.linux-kvm.org/page/Multiqueue
* we should add vectors=2*N+2 where N is the vhostfdSize */
virBufferAsprintf(&buf, ",mq=on,vectors=%d", 2 * vhostfdSize + 2);
}
if (vlan == -1)
virBufferAsprintf(&buf, ",netdev=host%s", net->info.alias);
else
virBufferAsprintf(&buf, ",vlan=%d", vlan);
virBufferAsprintf(&buf, ",id=%s", net->info.alias);
virBufferAsprintf(&buf, ",mac=%s",
virMacAddrFormat(&net->mac, macaddr));
if (qemuBuildDeviceAddressStr(&buf, def, &net->info, qemuCaps) < 0)
goto error;
if (qemuBuildRomStr(&buf, &net->info, qemuCaps) < 0)
goto error;
if (bootindex && virQEMUCapsGet(qemuCaps, QEMU_CAPS_BOOTINDEX))
virBufferAsprintf(&buf, ",bootindex=%d", bootindex);
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildHostNetStr(virDomainNetDefPtr net,
virQEMUDriverPtr driver,
char type_sep,
int vlan,
char **tapfd,
int tapfdSize,
char **vhostfd,
int vhostfdSize)
{
bool is_tap = false;
virBuffer buf = VIR_BUFFER_INITIALIZER;
enum virDomainNetType netType = virDomainNetGetActualType(net);
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
size_t i;
if (net->script && netType != VIR_DOMAIN_NET_TYPE_ETHERNET) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("scripts are not supported on interfaces of type %s"),
virDomainNetTypeToString(netType));
virObjectUnref(cfg);
return NULL;
}
switch (netType) {
/*
* If type='bridge', and we're running as privileged user
* or -netdev bridge is not supported then it will fall
* through, -net tap,fd
*/
case VIR_DOMAIN_NET_TYPE_BRIDGE:
case VIR_DOMAIN_NET_TYPE_NETWORK:
case VIR_DOMAIN_NET_TYPE_DIRECT:
virBufferAsprintf(&buf, "tap%c", type_sep);
/* for one tapfd 'fd=' shall be used,
* for more than one 'fds=' is the right choice */
if (tapfdSize == 1) {
virBufferAsprintf(&buf, "fd=%s", tapfd[0]);
} else {
virBufferAddLit(&buf, "fds=");
for (i = 0; i < tapfdSize; i++) {
if (i)
virBufferAddChar(&buf, ':');
virBufferAdd(&buf, tapfd[i], -1);
}
}
type_sep = ',';
is_tap = true;
break;
case VIR_DOMAIN_NET_TYPE_ETHERNET:
virBufferAddLit(&buf, "tap");
if (net->ifname) {
virBufferAsprintf(&buf, "%cifname=%s", type_sep, net->ifname);
type_sep = ',';
}
if (net->script) {
virBufferAsprintf(&buf, "%cscript=%s", type_sep,
net->script);
type_sep = ',';
}
is_tap = true;
break;
case VIR_DOMAIN_NET_TYPE_CLIENT:
virBufferAsprintf(&buf, "socket%cconnect=%s:%d",
type_sep,
net->data.socket.address,
net->data.socket.port);
type_sep = ',';
break;
case VIR_DOMAIN_NET_TYPE_SERVER:
virBufferAsprintf(&buf, "socket%clisten=%s:%d",
type_sep,
net->data.socket.address,
net->data.socket.port);
type_sep = ',';
break;
case VIR_DOMAIN_NET_TYPE_MCAST:
virBufferAsprintf(&buf, "socket%cmcast=%s:%d",
type_sep,
net->data.socket.address,
net->data.socket.port);
type_sep = ',';
break;
case VIR_DOMAIN_NET_TYPE_USER:
default:
virBufferAddLit(&buf, "user");
break;
}
if (vlan >= 0) {
virBufferAsprintf(&buf, "%cvlan=%d", type_sep, vlan);
if (net->info.alias)
virBufferAsprintf(&buf, ",name=host%s",
net->info.alias);
} else {
virBufferAsprintf(&buf, "%cid=host%s",
type_sep, net->info.alias);
}
if (is_tap) {
if (vhostfdSize) {
virBufferAddLit(&buf, ",vhost=on,");
if (vhostfdSize == 1) {
virBufferAsprintf(&buf, "vhostfd=%s", vhostfd[0]);
} else {
virBufferAddLit(&buf, "vhostfds=");
for (i = 0; i < vhostfdSize; i++) {
if (i)
virBufferAddChar(&buf, ':');
virBufferAdd(&buf, vhostfd[i], -1);
}
}
}
if (net->tune.sndbuf_specified)
virBufferAsprintf(&buf, ",sndbuf=%lu", net->tune.sndbuf);
}
virObjectUnref(cfg);
if (virBufferError(&buf)) {
virBufferFreeAndReset(&buf);
virReportOOMError();
return NULL;
}
return virBufferContentAndReset(&buf);
}
char *
qemuBuildWatchdogDevStr(virDomainDefPtr def,
virDomainWatchdogDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *model = virDomainWatchdogModelTypeToString(dev->model);
if (!model) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing watchdog model"));
goto error;
}
virBufferAsprintf(&buf, "%s,id=%s", model, dev->info.alias);
if (qemuBuildDeviceAddressStr(&buf, def, &dev->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildMemballoonDevStr(virDomainDefPtr def,
virDomainMemballoonDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
switch (dev->info.type) {
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI:
virBufferAddLit(&buf, "virtio-balloon-pci");
break;
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW:
virBufferAddLit(&buf, "virtio-balloon-ccw");
break;
case VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO:
virBufferAddLit(&buf, "virtio-balloon-device");
break;
default:
virReportError(VIR_ERR_XML_ERROR,
_("memballoon unsupported with address type '%s'"),
virDomainDeviceAddressTypeToString(dev->info.type));
goto error;
}
virBufferAsprintf(&buf, ",id=%s", dev->info.alias);
if (qemuBuildDeviceAddressStr(&buf, def, &dev->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *
qemuBuildNVRAMDevStr(virDomainNVRAMDefPtr dev)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (dev->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO &&
dev->info.addr.spaprvio.has_reg) {
virBufferAsprintf(&buf, "spapr-nvram.reg=0x%llx",
dev->info.addr.spaprvio.reg);
} else {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("nvram address type must be spaprvio"));
goto error;
}
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildUSBInputDevStr(virDomainDefPtr def,
virDomainInputDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
switch (dev->type) {
case VIR_DOMAIN_INPUT_TYPE_MOUSE:
virBufferAsprintf(&buf, "usb-mouse,id=%s", dev->info.alias);
break;
case VIR_DOMAIN_INPUT_TYPE_TABLET:
virBufferAsprintf(&buf, "usb-tablet,id=%s", dev->info.alias);
break;
case VIR_DOMAIN_INPUT_TYPE_KBD:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_USB_KBD))
goto error;
virBufferAsprintf(&buf, "usb-kbd,id=%s", dev->info.alias);
break;
}
if (qemuBuildDeviceAddressStr(&buf, def, &dev->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildSoundDevStr(virDomainDefPtr def,
virDomainSoundDefPtr sound,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *model = virDomainSoundModelTypeToString(sound->model);
if (!model) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("invalid sound model"));
goto error;
}
/* Hack for devices with different names in QEMU and libvirt */
switch (sound->model) {
case VIR_DOMAIN_SOUND_MODEL_ES1370:
model = "ES1370";
break;
case VIR_DOMAIN_SOUND_MODEL_AC97:
model = "AC97";
break;
case VIR_DOMAIN_SOUND_MODEL_ICH6:
model = "intel-hda";
break;
case VIR_DOMAIN_SOUND_MODEL_ICH9:
model = "ich9-intel-hda";
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_ICH9_INTEL_HDA)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("The ich9-intel-hda audio controller "
"is not supported in this QEMU binary"));
goto error;
}
break;
}
virBufferAsprintf(&buf, "%s,id=%s", model, sound->info.alias);
if (qemuBuildDeviceAddressStr(&buf, def, &sound->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static int
qemuSoundCodecTypeToCaps(int type)
{
switch (type) {
case VIR_DOMAIN_SOUND_CODEC_TYPE_DUPLEX:
return QEMU_CAPS_HDA_DUPLEX;
case VIR_DOMAIN_SOUND_CODEC_TYPE_MICRO:
return QEMU_CAPS_HDA_MICRO;
default:
return -1;
}
}
static char *
qemuBuildSoundCodecStr(virDomainSoundDefPtr sound,
virDomainSoundCodecDefPtr codec,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *stype;
int type, flags;
type = codec->type;
stype = qemuSoundCodecTypeToString(type);
flags = qemuSoundCodecTypeToCaps(type);
if (flags == -1 || !virQEMUCapsGet(qemuCaps, flags)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("%s not supported in this QEMU binary"), stype);
goto error;
}
virBufferAsprintf(&buf, "%s,id=%s-codec%d,bus=%s.0,cad=%d",
stype, sound->info.alias, codec->cad, sound->info.alias, codec->cad);
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *
qemuBuildDeviceVideoStr(virDomainDefPtr def,
virDomainVideoDefPtr video,
virQEMUCapsPtr qemuCaps,
bool primary)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *model;
if (primary) {
model = qemuDeviceVideoTypeToString(video->type);
if (!model || STREQ(model, "")) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("video type %s is not supported with QEMU"),
virDomainVideoTypeToString(video->type));
goto error;
}
} else {
if (video->type != VIR_DOMAIN_VIDEO_TYPE_QXL) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("non-primary video device must be type of 'qxl'"));
goto error;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_QXL)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("only one video card is currently supported"));
goto error;
}
model = "qxl";
}
virBufferAsprintf(&buf, "%s,id=%s", model, video->info.alias);
if (video->type == VIR_DOMAIN_VIDEO_TYPE_QXL) {
if (video->vram > (UINT_MAX / 1024)) {
virReportError(VIR_ERR_OVERFLOW,
_("value for 'vram' must be less than '%u'"),
UINT_MAX / 1024);
goto error;
}
if (video->ram > (UINT_MAX / 1024)) {
virReportError(VIR_ERR_OVERFLOW,
_("value for 'ram' must be less than '%u'"),
UINT_MAX / 1024);
goto error;
}
/* QEMU accepts bytes for ram_size. */
virBufferAsprintf(&buf, ",ram_size=%u", video->ram * 1024);
/* QEMU accepts bytes for vram_size. */
virBufferAsprintf(&buf, ",vram_size=%u", video->vram * 1024);
}
if (qemuBuildDeviceAddressStr(&buf, def, &video->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
int
qemuOpenPCIConfig(virDomainHostdevDefPtr dev)
{
char *path = NULL;
int configfd = -1;
if (virAsprintf(&path, "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/config",
dev->source.subsys.u.pci.addr.domain,
dev->source.subsys.u.pci.addr.bus,
dev->source.subsys.u.pci.addr.slot,
dev->source.subsys.u.pci.addr.function) < 0)
return -1;
configfd = open(path, O_RDWR, 0);
if (configfd < 0)
virReportSystemError(errno, _("Failed opening %s"), path);
VIR_FREE(path);
return configfd;
}
char *
qemuBuildPCIHostdevDevStr(virDomainDefPtr def,
virDomainHostdevDefPtr dev,
const char *configfd,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
int backend = dev->source.subsys.u.pci.backend;
/* caller has to assign proper passthrough backend type */
switch ((virDomainHostdevSubsysPciBackendType) backend) {
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_KVM:
virBufferAddLit(&buf, "pci-assign");
if (configfd && *configfd)
virBufferAsprintf(&buf, ",configfd=%s", configfd);
break;
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO:
virBufferAddLit(&buf, "vfio-pci");
break;
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT:
case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_TYPE_LAST:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid PCI passthrough type '%s'"),
virDomainHostdevSubsysPciBackendTypeToString(backend));
break;
}
virBufferAsprintf(&buf, ",host=%.2x:%.2x.%.1x",
dev->source.subsys.u.pci.addr.bus,
dev->source.subsys.u.pci.addr.slot,
dev->source.subsys.u.pci.addr.function);
virBufferAsprintf(&buf, ",id=%s", dev->info->alias);
if (dev->info->bootIndex)
virBufferAsprintf(&buf, ",bootindex=%d", dev->info->bootIndex);
if (qemuBuildDeviceAddressStr(&buf, def, dev->info, qemuCaps) < 0)
goto error;
if (qemuBuildRomStr(&buf, dev->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildPCIHostdevPCIDevStr(virDomainHostdevDefPtr dev)
{
char *ret = NULL;
ignore_value(virAsprintf(&ret, "host=%.2x:%.2x.%.1x",
dev->source.subsys.u.pci.addr.bus,
dev->source.subsys.u.pci.addr.slot,
dev->source.subsys.u.pci.addr.function));
return ret;
}
char *
qemuBuildRedirdevDevStr(virDomainDefPtr def,
virDomainRedirdevDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
size_t i;
virBuffer buf = VIR_BUFFER_INITIALIZER;
virDomainRedirFilterDefPtr redirfilter = def->redirfilter;
if (dev->bus != VIR_DOMAIN_REDIRDEV_BUS_USB) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Redirection bus %s is not supported by QEMU"),
virDomainRedirdevBusTypeToString(dev->bus));
goto error;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_USB_REDIR)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("USB redirection is not supported "
"by this version of QEMU"));
goto error;
}
virBufferAsprintf(&buf, "usb-redir,chardev=char%s,id=%s",
dev->info.alias,
dev->info.alias);
if (redirfilter && redirfilter->nusbdevs) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_USB_REDIR_FILTER)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("USB redirection filter is not "
"supported by this version of QEMU"));
goto error;
}
virBufferAddLit(&buf, ",filter=");
for (i = 0; i < redirfilter->nusbdevs; i++) {
virDomainRedirFilterUsbDevDefPtr usbdev = redirfilter->usbdevs[i];
if (usbdev->usbClass >= 0)
virBufferAsprintf(&buf, "0x%02X:", usbdev->usbClass);
else
virBufferAddLit(&buf, "-1:");
if (usbdev->vendor >= 0)
virBufferAsprintf(&buf, "0x%04X:", usbdev->vendor);
else
virBufferAddLit(&buf, "-1:");
if (usbdev->product >= 0)
virBufferAsprintf(&buf, "0x%04X:", usbdev->product);
else
virBufferAddLit(&buf, "-1:");
if (usbdev->version >= 0)
virBufferAsprintf(&buf, "0x%04X:", usbdev->version);
else
virBufferAddLit(&buf, "-1:");
virBufferAsprintf(&buf, "%u", usbdev->allow);
if (i < redirfilter->nusbdevs -1)
virBufferAddLit(&buf, "|");
}
}
if (dev->info.bootIndex) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_USB_REDIR_BOOTINDEX)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("USB redirection booting is not "
"supported by this version of QEMU"));
goto error;
}
virBufferAsprintf(&buf, ",bootindex=%d", dev->info.bootIndex);
}
if (qemuBuildDeviceAddressStr(&buf, def, &dev->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildUSBHostdevDevStr(virDomainDefPtr def,
virDomainHostdevDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (!dev->missing &&
!dev->source.subsys.u.usb.bus &&
!dev->source.subsys.u.usb.device) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("USB host device is missing bus/device information"));
return NULL;
}
virBufferAddLit(&buf, "usb-host");
if (!dev->missing) {
virBufferAsprintf(&buf, ",hostbus=%d,hostaddr=%d",
dev->source.subsys.u.usb.bus,
dev->source.subsys.u.usb.device);
}
virBufferAsprintf(&buf, ",id=%s", dev->info->alias);
if (dev->info->bootIndex)
virBufferAsprintf(&buf, ",bootindex=%d", dev->info->bootIndex);
if (qemuBuildDeviceAddressStr(&buf, def, dev->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildHubDevStr(virDomainDefPtr def,
virDomainHubDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (dev->type != VIR_DOMAIN_HUB_TYPE_USB) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("hub type %s not supported"),
virDomainHubTypeToString(dev->type));
goto error;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_USB_HUB)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("usb-hub not supported by QEMU binary"));
goto error;
}
virBufferAddLit(&buf, "usb-hub");
virBufferAsprintf(&buf, ",id=%s", dev->info.alias);
if (qemuBuildDeviceAddressStr(&buf, def, &dev->info, qemuCaps) < 0)
goto error;
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildUSBHostdevUsbDevStr(virDomainHostdevDefPtr dev)
{
char *ret = NULL;
if (dev->missing) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU doesn't not support missing USB devices"));
return NULL;
}
if (!dev->source.subsys.u.usb.bus &&
!dev->source.subsys.u.usb.device) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("USB host device is missing bus/device information"));
return NULL;
}
ignore_value(virAsprintf(&ret, "host:%d.%d",
dev->source.subsys.u.usb.bus,
dev->source.subsys.u.usb.device));
return ret;
}
char *
qemuBuildSCSIHostdevDrvStr(virDomainHostdevDefPtr dev,
virQEMUCapsPtr qemuCaps ATTRIBUTE_UNUSED,
qemuBuildCommandLineCallbacksPtr callbacks)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
char *sg = NULL;
sg = (callbacks->qemuGetSCSIDeviceSgName)(NULL,
dev->source.subsys.u.scsi.adapter,
dev->source.subsys.u.scsi.bus,
dev->source.subsys.u.scsi.target,
dev->source.subsys.u.scsi.unit);
if (!sg)
goto error;
virBufferAsprintf(&buf, "file=/dev/%s,if=none", sg);
virBufferAsprintf(&buf, ",id=%s-%s",
virDomainDeviceAddressTypeToString(dev->info->type),
dev->info->alias);
if (dev->readonly) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_READONLY)) {
virBufferAddLit(&buf, ",readonly=on");
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("this qemu doesn't support 'readonly' "
"for -drive"));
goto error;
}
}
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
VIR_FREE(sg);
return virBufferContentAndReset(&buf);
error:
VIR_FREE(sg);
virBufferFreeAndReset(&buf);
return NULL;
}
char *
qemuBuildSCSIHostdevDevStr(virDomainDefPtr def,
virDomainHostdevDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
int model = -1;
model = virDomainDeviceFindControllerModel(def, dev->info,
VIR_DOMAIN_CONTROLLER_TYPE_SCSI);
if (qemuSetScsiControllerModel(def, qemuCaps, &model) < 0)
goto error;
if (model == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC) {
if (dev->info->addr.drive.target != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("target must be 0 for scsi host device "
"if its controller model is 'lsilogic'"));
goto error;
}
if (dev->info->addr.drive.unit > 7) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("unit must be not more than 7 for scsi host "
"device if its controller model is 'lsilogic'"));
goto error;
}
}
virBufferAddLit(&buf, "scsi-generic");
if (model == VIR_DOMAIN_CONTROLLER_MODEL_SCSI_LSILOGIC) {
virBufferAsprintf(&buf, ",bus=scsi%d.%d,scsi-id=%d",
dev->info->addr.drive.controller,
dev->info->addr.drive.bus,
dev->info->addr.drive.unit);
} else {
virBufferAsprintf(&buf, ",bus=scsi%d.0,channel=%d,scsi-id=%d,lun=%d",
dev->info->addr.drive.controller,
dev->info->addr.drive.bus,
dev->info->addr.drive.target,
dev->info->addr.drive.unit);
}
virBufferAsprintf(&buf, ",drive=%s-%s,id=%s",
virDomainDeviceAddressTypeToString(dev->info->type),
dev->info->alias, dev->info->alias);
if (dev->info->bootIndex)
virBufferAsprintf(&buf, ",bootindex=%d", dev->info->bootIndex);
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
/* This function outputs a -chardev command line option which describes only the
* host side of the character device */
static char *
qemuBuildChrChardevStr(virDomainChrSourceDefPtr dev, const char *alias,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
bool telnet;
switch (dev->type) {
case VIR_DOMAIN_CHR_TYPE_NULL:
virBufferAsprintf(&buf, "null,id=char%s", alias);
break;
case VIR_DOMAIN_CHR_TYPE_VC:
virBufferAsprintf(&buf, "vc,id=char%s", alias);
break;
case VIR_DOMAIN_CHR_TYPE_PTY:
virBufferAsprintf(&buf, "pty,id=char%s", alias);
break;
case VIR_DOMAIN_CHR_TYPE_DEV:
virBufferAsprintf(&buf, "%s,id=char%s,path=%s",
STRPREFIX(alias, "parallel") ? "parport" : "tty",
alias, dev->data.file.path);
break;
case VIR_DOMAIN_CHR_TYPE_FILE:
virBufferAsprintf(&buf, "file,id=char%s,path=%s", alias,
dev->data.file.path);
break;
case VIR_DOMAIN_CHR_TYPE_PIPE:
virBufferAsprintf(&buf, "pipe,id=char%s,path=%s", alias,
dev->data.file.path);
break;
case VIR_DOMAIN_CHR_TYPE_STDIO:
virBufferAsprintf(&buf, "stdio,id=char%s", alias);
break;
case VIR_DOMAIN_CHR_TYPE_UDP: {
const char *connectHost = dev->data.udp.connectHost;
const char *bindHost = dev->data.udp.bindHost;
const char *bindService = dev->data.udp.bindService;
if (connectHost == NULL)
connectHost = "";
if (bindHost == NULL)
bindHost = "";
if (bindService == NULL)
bindService = "0";
virBufferAsprintf(&buf,
"udp,id=char%s,host=%s,port=%s,localaddr=%s,"
"localport=%s",
alias,
connectHost,
dev->data.udp.connectService,
bindHost, bindService);
break;
}
case VIR_DOMAIN_CHR_TYPE_TCP:
telnet = dev->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET;
virBufferAsprintf(&buf,
"socket,id=char%s,host=%s,port=%s%s%s",
alias,
dev->data.tcp.host,
dev->data.tcp.service,
telnet ? ",telnet" : "",
dev->data.tcp.listen ? ",server,nowait" : "");
break;
case VIR_DOMAIN_CHR_TYPE_UNIX:
virBufferAsprintf(&buf,
"socket,id=char%s,path=%s%s",
alias,
dev->data.nix.path,
dev->data.nix.listen ? ",server,nowait" : "");
break;
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV_SPICEVMC)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("spicevmc not supported in this QEMU binary"));
goto error;
}
virBufferAsprintf(&buf, "spicevmc,id=char%s,name=%s", alias,
virDomainChrSpicevmcTypeToString(dev->data.spicevmc));
break;
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV_SPICEPORT)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("spiceport not supported in this QEMU binary"));
goto error;
}
virBufferAsprintf(&buf, "spiceport,id=char%s,name=%s", alias,
dev->data.spiceport.channel);
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported chardev '%s'"),
virDomainChrTypeToString(dev->type));
goto error;
}
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *
qemuBuildChrArgStr(virDomainChrSourceDefPtr dev, const char *prefix)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (prefix)
virBufferAdd(&buf, prefix, strlen(prefix));
switch (dev->type) {
case VIR_DOMAIN_CHR_TYPE_NULL:
virBufferAddLit(&buf, "null");
break;
case VIR_DOMAIN_CHR_TYPE_VC:
virBufferAddLit(&buf, "vc");
break;
case VIR_DOMAIN_CHR_TYPE_PTY:
virBufferAddLit(&buf, "pty");
break;
case VIR_DOMAIN_CHR_TYPE_DEV:
virBufferStrcat(&buf, dev->data.file.path, NULL);
break;
case VIR_DOMAIN_CHR_TYPE_FILE:
virBufferAsprintf(&buf, "file:%s", dev->data.file.path);
break;
case VIR_DOMAIN_CHR_TYPE_PIPE:
virBufferAsprintf(&buf, "pipe:%s", dev->data.file.path);
break;
case VIR_DOMAIN_CHR_TYPE_STDIO:
virBufferAddLit(&buf, "stdio");
break;
case VIR_DOMAIN_CHR_TYPE_UDP: {
const char *connectHost = dev->data.udp.connectHost;
const char *bindHost = dev->data.udp.bindHost;
const char *bindService = dev->data.udp.bindService;
if (connectHost == NULL)
connectHost = "";
if (bindHost == NULL)
bindHost = "";
if (bindService == NULL)
bindService = "0";
virBufferAsprintf(&buf, "udp:%s:%s@%s:%s",
connectHost,
dev->data.udp.connectService,
bindHost,
bindService);
break;
}
case VIR_DOMAIN_CHR_TYPE_TCP:
if (dev->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET) {
virBufferAsprintf(&buf, "telnet:%s:%s%s",
dev->data.tcp.host,
dev->data.tcp.service,
dev->data.tcp.listen ? ",server,nowait" : "");
} else {
virBufferAsprintf(&buf, "tcp:%s:%s%s",
dev->data.tcp.host,
dev->data.tcp.service,
dev->data.tcp.listen ? ",server,nowait" : "");
}
break;
case VIR_DOMAIN_CHR_TYPE_UNIX:
virBufferAsprintf(&buf, "unix:%s%s",
dev->data.nix.path,
dev->data.nix.listen ? ",server,nowait" : "");
break;
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
break;
}
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *
qemuBuildVirtioSerialPortDevStr(virDomainChrDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
switch (dev->deviceType) {
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
virBufferAddLit(&buf, "virtconsole");
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
/* Legacy syntax '-device spicevmc' */
if (dev->source.type == VIR_DOMAIN_CHR_TYPE_SPICEVMC &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_SPICEVMC)) {
virBufferAddLit(&buf, "spicevmc");
} else {
virBufferAddLit(&buf, "virtserialport");
}
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Cannot use virtio serial for parallel/serial devices"));
return NULL;
}
if (dev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
dev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW &&
dev->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390) {
/* Check it's a virtio-serial address */
if (dev->info.type !=
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_SERIAL)
{
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("virtio serial device has invalid address type"));
goto error;
}
virBufferAsprintf(&buf,
",bus=" QEMU_VIRTIO_SERIAL_PREFIX "%d.%d",
dev->info.addr.vioserial.controller,
dev->info.addr.vioserial.bus);
virBufferAsprintf(&buf,
",nr=%d",
dev->info.addr.vioserial.port);
}
if (dev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL &&
dev->source.type == VIR_DOMAIN_CHR_TYPE_SPICEVMC &&
dev->target.name &&
STRNEQ(dev->target.name, "com.redhat.spice.0")) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported spicevmc target name '%s'"),
dev->target.name);
goto error;
}
if (!(dev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL &&
dev->source.type == VIR_DOMAIN_CHR_TYPE_SPICEVMC &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_SPICEVMC))) {
virBufferAsprintf(&buf, ",chardev=char%s,id=%s",
dev->info.alias, dev->info.alias);
if (dev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL) {
virBufferAsprintf(&buf, ",name=%s", dev->target.name
? dev->target.name : "com.redhat.spice.0");
}
} else {
virBufferAsprintf(&buf, ",id=%s", dev->info.alias);
}
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *
qemuBuildSclpDevStr(virDomainChrDefPtr dev)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (dev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE) {
switch (dev->targetType) {
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SCLP:
virBufferAddLit(&buf, "sclpconsole");
break;
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SCLPLM:
virBufferAddLit(&buf, "sclplmconsole");
break;
}
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Cannot use slcp with devices other than console"));
goto error;
}
virBufferAsprintf(&buf, ",chardev=char%s,id=%s",
dev->info.alias, dev->info.alias);
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static int
qemuBuildRNGBackendArgs(virCommandPtr cmd,
virDomainRNGDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
char *backend = NULL;
int ret = -1;
switch ((enum virDomainRNGBackend) dev->backend) {
case VIR_DOMAIN_RNG_BACKEND_RANDOM:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_RNG_RANDOM)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("this qemu doesn't support the rng-random "
"backend"));
goto cleanup;
}
virBufferAsprintf(&buf, "rng-random,id=%s", dev->info.alias);
if (dev->source.file)
virBufferAsprintf(&buf, ",filename=%s", dev->source.file);
virCommandAddArg(cmd, "-object");
virCommandAddArgBuffer(cmd, &buf);
break;
case VIR_DOMAIN_RNG_BACKEND_EGD:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_RNG_EGD)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("this qemu doesn't support the rng-egd "
"backend"));
goto cleanup;
}
if (!(backend = qemuBuildChrChardevStr(dev->source.chardev,
dev->info.alias, qemuCaps)))
goto cleanup;
virCommandAddArgList(cmd, "-chardev", backend, NULL);
virCommandAddArg(cmd, "-object");
virCommandAddArgFormat(cmd, "rng-egd,chardev=char%s,id=%s",
dev->info.alias, dev->info.alias);
break;
case VIR_DOMAIN_RNG_BACKEND_LAST:
break;
}
ret = 0;
cleanup:
virBufferFreeAndReset(&buf);
VIR_FREE(backend);
return ret;
}
static int
qemuBuildRNGDeviceArgs(virCommandPtr cmd,
virDomainDefPtr def,
virDomainRNGDefPtr dev,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
int ret = -1;
if (dev->model != VIR_DOMAIN_RNG_MODEL_VIRTIO ||
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VIRTIO_RNG)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("this qemu doesn't support RNG device type '%s'"),
virDomainRNGModelTypeToString(dev->model));
goto cleanup;
}
if (dev->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCW)
virBufferAsprintf(&buf, "virtio-rng-ccw,rng=%s", dev->info.alias);
else if (dev->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_S390)
virBufferAsprintf(&buf, "virtio-rng-s390,rng=%s", dev->info.alias);
else if (dev->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_VIRTIO_MMIO)
virBufferAsprintf(&buf, "virtio-rng-device,rng=%s", dev->info.alias);
else
virBufferAsprintf(&buf, "virtio-rng-pci,rng=%s", dev->info.alias);
if (dev->rate > 0) {
virBufferAsprintf(&buf, ",max-bytes=%u", dev->rate);
if (dev->period)
virBufferAsprintf(&buf, ",period=%u", dev->period);
else
virBufferAddLit(&buf, ",period=1000");
}
if (qemuBuildDeviceAddressStr(&buf, def, &dev->info, qemuCaps) < 0)
goto cleanup;
virCommandAddArg(cmd, "-device");
virCommandAddArgBuffer(cmd, &buf);
ret = 0;
cleanup:
virBufferFreeAndReset(&buf);
return ret;
}
static char *qemuBuildTPMBackendStr(const virDomainDef *def,
virQEMUCapsPtr qemuCaps,
const char *emulator)
{
const virDomainTPMDef *tpm = def->tpm;
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *type = virDomainTPMBackendTypeToString(tpm->type);
char *cancel_path;
const char *tpmdev;
virBufferAsprintf(&buf, "%s,id=tpm-%s", type, tpm->info.alias);
switch (tpm->type) {
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_TPM_PASSTHROUGH))
goto no_support;
tpmdev = tpm->data.passthrough.source.data.file.path;
if (!(cancel_path = virTPMCreateCancelPath(tpmdev)))
goto error;
virBufferAddLit(&buf, ",path=");
virBufferEscape(&buf, ',', ",", "%s", tpmdev);
virBufferAddLit(&buf, ",cancel-path=");
virBufferEscape(&buf, ',', ",", "%s", cancel_path);
VIR_FREE(cancel_path);
break;
case VIR_DOMAIN_TPM_TYPE_LAST:
goto error;
}
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
no_support:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("The QEMU executable %s does not support TPM "
"backend type %s"),
emulator, type);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *qemuBuildTPMDevStr(const virDomainDef *def,
virQEMUCapsPtr qemuCaps,
const char *emulator)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
const virDomainTPMDef *tpm = def->tpm;
const char *model = virDomainTPMModelTypeToString(tpm->model);
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_TPM_TIS)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("The QEMU executable %s does not support TPM "
"model %s"),
emulator, model);
goto error;
}
virBufferAsprintf(&buf, "%s,tpmdev=tpm-%s,id=%s",
model, tpm->info.alias, tpm->info.alias);
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *qemuBuildSmbiosBiosStr(virSysinfoDefPtr def)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
if ((def->bios_vendor == NULL) && (def->bios_version == NULL) &&
(def->bios_date == NULL) && (def->bios_release == NULL))
return NULL;
virBufferAddLit(&buf, "type=0");
/* 0:Vendor */
if (def->bios_vendor)
virBufferAsprintf(&buf, ",vendor=%s", def->bios_vendor);
/* 0:BIOS Version */
if (def->bios_version)
virBufferAsprintf(&buf, ",version=%s", def->bios_version);
/* 0:BIOS Release Date */
if (def->bios_date)
virBufferAsprintf(&buf, ",date=%s", def->bios_date);
/* 0:System BIOS Major Release and 0:System BIOS Minor Release */
if (def->bios_release)
virBufferAsprintf(&buf, ",release=%s", def->bios_release);
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *qemuBuildSmbiosSystemStr(virSysinfoDefPtr def, bool skip_uuid)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
if ((def->system_manufacturer == NULL) && (def->system_sku == NULL) &&
(def->system_product == NULL) && (def->system_version == NULL) &&
(def->system_serial == NULL) && (def->system_family == NULL) &&
(def->system_uuid == NULL || skip_uuid))
return NULL;
virBufferAddLit(&buf, "type=1");
/* 1:Manufacturer */
if (def->system_manufacturer)
virBufferAsprintf(&buf, ",manufacturer=%s",
def->system_manufacturer);
/* 1:Product Name */
if (def->system_product)
virBufferAsprintf(&buf, ",product=%s", def->system_product);
/* 1:Version */
if (def->system_version)
virBufferAsprintf(&buf, ",version=%s", def->system_version);
/* 1:Serial Number */
if (def->system_serial)
virBufferAsprintf(&buf, ",serial=%s", def->system_serial);
/* 1:UUID */
if (def->system_uuid && !skip_uuid)
virBufferAsprintf(&buf, ",uuid=%s", def->system_uuid);
/* 1:SKU Number */
if (def->system_sku)
virBufferAsprintf(&buf, ",sku=%s", def->system_sku);
/* 1:Family */
if (def->system_family)
virBufferAsprintf(&buf, ",family=%s", def->system_family);
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static char *
qemuBuildClockArgStr(virDomainClockDefPtr def)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
switch (def->offset) {
case VIR_DOMAIN_CLOCK_OFFSET_UTC:
virBufferAddLit(&buf, "base=utc");
break;
case VIR_DOMAIN_CLOCK_OFFSET_LOCALTIME:
case VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE:
virBufferAddLit(&buf, "base=localtime");
break;
case VIR_DOMAIN_CLOCK_OFFSET_VARIABLE: {
time_t now = time(NULL);
struct tm nowbits;
switch ((enum virDomainClockBasis) def->data.variable.basis) {
case VIR_DOMAIN_CLOCK_BASIS_UTC:
now += def->data.variable.adjustment;
gmtime_r(&now, &nowbits);
break;
case VIR_DOMAIN_CLOCK_BASIS_LOCALTIME:
now += def->data.variable.adjustment;
localtime_r(&now, &nowbits);
break;
case VIR_DOMAIN_CLOCK_BASIS_LAST:
break;
}
/* Store the guest's basedate */
def->data.variable.basedate = now;
virBufferAsprintf(&buf, "base=%d-%02d-%02dT%02d:%02d:%02d",
nowbits.tm_year + 1900,
nowbits.tm_mon + 1,
nowbits.tm_mday,
nowbits.tm_hour,
nowbits.tm_min,
nowbits.tm_sec);
} break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported clock offset '%s'"),
virDomainClockOffsetTypeToString(def->offset));
goto error;
}
/* Look for an 'rtc' timer element, and add in appropriate clock= and driftfix= */
size_t i;
for (i = 0; i < def->ntimers; i++) {
if (def->timers[i]->name == VIR_DOMAIN_TIMER_NAME_RTC) {
switch (def->timers[i]->track) {
case -1: /* unspecified - use hypervisor default */
break;
case VIR_DOMAIN_TIMER_TRACK_BOOT:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported rtc timer track '%s'"),
virDomainTimerTrackTypeToString(def->timers[i]->track));
goto error;
case VIR_DOMAIN_TIMER_TRACK_GUEST:
virBufferAddLit(&buf, ",clock=vm");
break;
case VIR_DOMAIN_TIMER_TRACK_WALL:
virBufferAddLit(&buf, ",clock=host");
break;
}
switch (def->timers[i]->tickpolicy) {
case -1:
case VIR_DOMAIN_TIMER_TICKPOLICY_DELAY:
/* This is the default - missed ticks delivered when
next scheduled, at normal rate */
break;
case VIR_DOMAIN_TIMER_TICKPOLICY_CATCHUP:
/* deliver ticks at a faster rate until caught up */
virBufferAddLit(&buf, ",driftfix=slew");
break;
case VIR_DOMAIN_TIMER_TICKPOLICY_MERGE:
case VIR_DOMAIN_TIMER_TICKPOLICY_DISCARD:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported rtc timer tickpolicy '%s'"),
virDomainTimerTickpolicyTypeToString(def->timers[i]->tickpolicy));
goto error;
}
break; /* no need to check other timers - there is only one rtc */
}
}
if (virBufferError(&buf)) {
virReportOOMError();
goto error;
}
return virBufferContentAndReset(&buf);
error:
virBufferFreeAndReset(&buf);
return NULL;
}
static int
qemuBuildCpuArgStr(virQEMUDriverPtr driver,
const virDomainDef *def,
const char *emulator,
virQEMUCapsPtr qemuCaps,
virArch hostarch,
char **opt,
bool *hasHwVirt,
bool migrating)
{
virCPUDefPtr host = NULL;
virCPUDefPtr guest = NULL;
virCPUDefPtr cpu = NULL;
size_t ncpus = 0;
char **cpus = NULL;
const char *default_model;
virCPUDataPtr data = NULL;
bool have_cpu = false;
char *compare_msg = NULL;
int ret = -1;
virBuffer buf = VIR_BUFFER_INITIALIZER;
size_t i;
virCapsPtr caps = NULL;
*hasHwVirt = false;
if (!(caps = virQEMUDriverGetCapabilities(driver, false)))
goto cleanup;
host = caps->host.cpu;
if (def->os.arch == VIR_ARCH_I686)
default_model = "qemu32";
else
default_model = "qemu64";
if (def->cpu &&
(def->cpu->mode != VIR_CPU_MODE_CUSTOM || def->cpu->model)) {
virCPUCompareResult cmp;
const char *preferred;
if (!host ||
!host->model ||
(ncpus = virQEMUCapsGetCPUDefinitions(qemuCaps, &cpus)) == 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("CPU specification not supported by hypervisor"));
goto cleanup;
}
if (!(cpu = virCPUDefCopy(def->cpu)))
goto cleanup;
if (cpu->mode != VIR_CPU_MODE_CUSTOM &&
!migrating &&
cpuUpdate(cpu, host) < 0)
goto cleanup;
cmp = cpuGuestData(host, cpu, &data, &compare_msg);
switch (cmp) {
case VIR_CPU_COMPARE_INCOMPATIBLE:
if (compare_msg) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("guest and host CPU are not compatible: %s"),
compare_msg);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("guest CPU is not compatible with host CPU"));
}
/* fall through */
case VIR_CPU_COMPARE_ERROR:
goto cleanup;
default:
break;
}
/* Only 'svm' requires --enable-nesting. The nested
* 'vmx' patches now simply hook off the CPU features
*/
if (def->os.arch == VIR_ARCH_X86_64 ||
def->os.arch == VIR_ARCH_I686) {
int hasSVM = cpuHasFeature(data, "svm");
if (hasSVM < 0)
goto cleanup;
*hasHwVirt = hasSVM > 0 ? true : false;
}
if (cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH) {
const char *mode = virCPUModeTypeToString(cpu->mode);
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CPU_HOST)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("CPU mode '%s' is not supported by QEMU"
" binary"), mode);
goto cleanup;
}
if (def->virtType != VIR_DOMAIN_VIRT_KVM) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("CPU mode '%s' is only supported with kvm"),
mode);
goto cleanup;
}
virBufferAddLit(&buf, "host");
} else {
if (VIR_ALLOC(guest) < 0)
goto cleanup;
if (VIR_STRDUP(guest->vendor_id, cpu->vendor_id) < 0)
goto cleanup;
guest->arch = host->arch;
if (cpu->match == VIR_CPU_MATCH_MINIMUM)
preferred = host->model;
else
preferred = cpu->model;
guest->type = VIR_CPU_TYPE_GUEST;
guest->fallback = cpu->fallback;
if (cpuDecode(guest, data, (const char **)cpus, ncpus, preferred) < 0)
goto cleanup;
virBufferAdd(&buf, guest->model, -1);
if (guest->vendor_id)
virBufferAsprintf(&buf, ",vendor=%s", guest->vendor_id);
for (i = 0; i < guest->nfeatures; i++) {
char sign;
if (guest->features[i].policy == VIR_CPU_FEATURE_DISABLE)
sign = '-';
else
sign = '+';
virBufferAsprintf(&buf, ",%c%s", sign, guest->features[i].name);
}
}
have_cpu = true;
} else {
/*
* Need to force a 32-bit guest CPU type if
*
* 1. guest OS is i686
* 2. host OS is x86_64
* 3. emulator is qemu-kvm or kvm
*
* Or
*
* 1. guest OS is i686
* 2. emulator is qemu-system-x86_64
*/
if (def->os.arch == VIR_ARCH_I686 &&
((hostarch == VIR_ARCH_X86_64 &&
strstr(emulator, "kvm")) ||
strstr(emulator, "x86_64"))) {
virBufferAdd(&buf, default_model, -1);
have_cpu = true;
}
}
/* Handle paravirtual timers */
for (i = 0; i < def->clock.ntimers; i++) {
virDomainTimerDefPtr timer = def->clock.timers[i];
if (timer->present == -1)
continue;
if (timer->name == VIR_DOMAIN_TIMER_NAME_KVMCLOCK) {
virBufferAsprintf(&buf, "%s,%ckvmclock",
have_cpu ? "" : default_model,
timer->present ? '+' : '-');
have_cpu = true;
} else if (timer->name == VIR_DOMAIN_TIMER_NAME_HYPERVCLOCK &&
timer->present) {
virBufferAsprintf(&buf, "%s,hv_time",
have_cpu ? "" : default_model);
have_cpu = true;
}
}
if (def->apic_eoi) {
char sign;
if (def->apic_eoi == VIR_DOMAIN_FEATURE_STATE_ON)
sign = '+';
else
sign = '-';
virBufferAsprintf(&buf, "%s,%ckvm_pv_eoi",
have_cpu ? "" : default_model,
sign);
have_cpu = true;
}
if (def->features[VIR_DOMAIN_FEATURE_PVSPINLOCK]) {
char sign;
if (def->features[VIR_DOMAIN_FEATURE_PVSPINLOCK] == VIR_DOMAIN_FEATURE_STATE_ON)
sign = '+';
else
sign = '-';
virBufferAsprintf(&buf, "%s,%ckvm_pv_unhalt",
have_cpu ? "" : default_model,
sign);
have_cpu = true;
}
if (def->features[VIR_DOMAIN_FEATURE_HYPERV] == VIR_DOMAIN_FEATURE_STATE_ON) {
if (!have_cpu) {
virBufferAdd(&buf, default_model, -1);
have_cpu = true;
}
for (i = 0; i < VIR_DOMAIN_HYPERV_LAST; i++) {
switch ((enum virDomainHyperv) i) {
case VIR_DOMAIN_HYPERV_RELAXED:
case VIR_DOMAIN_HYPERV_VAPIC:
if (def->hyperv_features[i] == VIR_DOMAIN_FEATURE_STATE_ON)
virBufferAsprintf(&buf, ",hv_%s",
virDomainHypervTypeToString(i));
break;
case VIR_DOMAIN_HYPERV_SPINLOCKS:
if (def->hyperv_features[i] == VIR_DOMAIN_FEATURE_STATE_ON)
virBufferAsprintf(&buf, ",hv_spinlocks=0x%x",
def->hyperv_spinlocks);
break;
case VIR_DOMAIN_HYPERV_LAST:
break;
}
}
}
if (virBufferError(&buf)) {
virReportOOMError();
goto cleanup;
}
*opt = virBufferContentAndReset(&buf);
ret = 0;
cleanup:
VIR_FREE(compare_msg);
cpuDataFree(data);
virCPUDefFree(guest);
virCPUDefFree(cpu);
virObjectUnref(caps);
return ret;
}
static int
qemuBuildObsoleteAccelArg(virCommandPtr cmd,
const virDomainDef *def,
virQEMUCapsPtr qemuCaps)
{
bool disableKQEMU = false;
bool enableKQEMU = false;
bool disableKVM = false;
bool enableKVM = false;
switch (def->virtType) {
case VIR_DOMAIN_VIRT_QEMU:
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_KQEMU))
disableKQEMU = true;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_KVM))
disableKVM = true;
break;
case VIR_DOMAIN_VIRT_KQEMU:
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_KVM))
disableKVM = true;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_ENABLE_KQEMU)) {
enableKQEMU = true;
} else if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_KQEMU)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("the QEMU binary does not support kqemu"));
return -1;
}
break;
case VIR_DOMAIN_VIRT_KVM:
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_KQEMU))
disableKQEMU = true;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_ENABLE_KVM)) {
enableKVM = true;
} else if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_KVM)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("the QEMU binary does not support kvm"));
return -1;
}
break;
case VIR_DOMAIN_VIRT_XEN:
/* XXX better check for xenner */
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("the QEMU binary does not support %s"),
virDomainVirtTypeToString(def->virtType));
return -1;
}
if (disableKQEMU)
virCommandAddArg(cmd, "-no-kqemu");
else if (enableKQEMU)
virCommandAddArgList(cmd, "-enable-kqemu", "-kernel-kqemu", NULL);
if (disableKVM)
virCommandAddArg(cmd, "-no-kvm");
if (enableKVM)
virCommandAddArg(cmd, "-enable-kvm");
return 0;
}
static int
qemuBuildMachineArgStr(virCommandPtr cmd,
const virDomainDef *def,
virQEMUCapsPtr qemuCaps)
{
bool obsoleteAccel = false;
/* This should *never* be NULL, since we always provide
* a machine in the capabilities data for QEMU. So this
* check is just here as a safety in case the unexpected
* happens */
if (!def->os.machine)
return 0;
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_OPT)) {
/* if no parameter to the machine type is needed, we still use
* '-M' to keep the most of the compatibility with older versions.
*/
virCommandAddArgList(cmd, "-M", def->os.machine, NULL);
if (def->mem.dump_core) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("dump-guest-core is not available "
"with this QEMU binary"));
return -1;
}
if (def->mem.nosharepages) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("disable shared memory is not available "
"with this QEMU binary"));
return -1;
}
obsoleteAccel = true;
} else {
virBuffer buf = VIR_BUFFER_INITIALIZER;
virCommandAddArg(cmd, "-machine");
virBufferAdd(&buf, def->os.machine, -1);
if (def->virtType == VIR_DOMAIN_VIRT_QEMU)
virBufferAddLit(&buf, ",accel=tcg");
else if (def->virtType == VIR_DOMAIN_VIRT_KVM)
virBufferAddLit(&buf, ",accel=kvm");
else
obsoleteAccel = true;
/* To avoid the collision of creating USB controllers when calling
* machine->init in QEMU, it needs to set usb=off
*/
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_USB_OPT))
virBufferAddLit(&buf, ",usb=off");
if (def->mem.dump_core) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DUMP_GUEST_CORE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("dump-guest-core is not available "
"with this QEMU binary"));
virBufferFreeAndReset(&buf);
return -1;
}
virBufferAsprintf(&buf, ",dump-guest-core=%s",
virDomainMemDumpTypeToString(def->mem.dump_core));
}
if (def->mem.nosharepages) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_MEM_MERGE)) {
virBufferAddLit(&buf, ",mem-merge=off");
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("disable shared memory is not available "
"with this QEMU binary"));
virBufferFreeAndReset(&buf);
return -1;
}
}
virCommandAddArgBuffer(cmd, &buf);
}
if (obsoleteAccel &&
qemuBuildObsoleteAccelArg(cmd, def, qemuCaps) < 0)
return -1;
return 0;
}
static char *
qemuBuildSmpArgStr(const virDomainDef *def,
virQEMUCapsPtr qemuCaps)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
virBufferAsprintf(&buf, "%u", def->vcpus);
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_SMP_TOPOLOGY)) {
if (def->vcpus != def->maxvcpus)
virBufferAsprintf(&buf, ",maxcpus=%u", def->maxvcpus);
/* sockets, cores, and threads are either all zero
* or all non-zero, thus checking one of them is enough */
if (def->cpu && def->cpu->sockets) {
virBufferAsprintf(&buf, ",sockets=%u", def->cpu->sockets);
virBufferAsprintf(&buf, ",cores=%u", def->cpu->cores);
virBufferAsprintf(&buf, ",threads=%u", def->cpu->threads);
}
else {
virBufferAsprintf(&buf, ",sockets=%u", def->maxvcpus);
virBufferAsprintf(&buf, ",cores=%u", 1);
virBufferAsprintf(&buf, ",threads=%u", 1);
}
} else if (def->vcpus != def->maxvcpus) {
virBufferFreeAndReset(&buf);
/* FIXME - consider hot-unplugging cpus after boot for older qemu */
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("setting current vcpu count less than maximum is "
"not supported with this QEMU binary"));
return NULL;
}
if (virBufferError(&buf)) {
virBufferFreeAndReset(&buf);
virReportOOMError();
return NULL;
}
return virBufferContentAndReset(&buf);
}
static int
qemuBuildNumaArgStr(const virDomainDef *def, virCommandPtr cmd)
{
size_t i;
virBuffer buf = VIR_BUFFER_INITIALIZER;
char *cpumask = NULL;
int ret = -1;
for (i = 0; i < def->cpu->ncells; i++) {
VIR_FREE(cpumask);
virCommandAddArg(cmd, "-numa");
virBufferAsprintf(&buf, "node,nodeid=%d", def->cpu->cells[i].cellid);
virBufferAddLit(&buf, ",cpus=");
if (!(cpumask = virBitmapFormat(def->cpu->cells[i].cpumask)))
goto cleanup;
/* Up through qemu 1.4, -numa does not accept a cpus
* argument any more complex than start-stop.
*
* XXX For qemu 1.5, the syntax has not yet been decided;
* but when it is, we need a capability bit and
* translation of our cpumask into the qemu syntax. */
if (strchr(cpumask, ',')) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("disjoint NUMA cpu ranges are not supported "
"with this QEMU"));
goto cleanup;
}
virBufferAdd(&buf, cpumask, -1);
def->cpu->cells[i].mem = VIR_DIV_UP(def->cpu->cells[i].mem,
1024) * 1024;
virBufferAsprintf(&buf, ",mem=%d", def->cpu->cells[i].mem / 1024);
if (virBufferError(&buf)) {
virReportOOMError();
goto cleanup;
}
virCommandAddArgBuffer(cmd, &buf);
}
ret = 0;
cleanup:
VIR_FREE(cpumask);
virBufferFreeAndReset(&buf);
return ret;
}
static int
qemuBuildGraphicsVNCCommandLine(virQEMUDriverConfigPtr cfg,
virCommandPtr cmd,
virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
virDomainGraphicsDefPtr graphics)
{
virBuffer opt = VIR_BUFFER_INITIALIZER;
const char *listenNetwork;
const char *listenAddr = NULL;
char *netAddr = NULL;
bool escapeAddr;
int ret;
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNC)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("vnc graphics are not supported with this QEMU"));
goto error;
}
if (graphics->data.vnc.socket || cfg->vncAutoUnixSocket) {
if (!graphics->data.vnc.socket &&
virAsprintf(&graphics->data.vnc.socket,
"%s/%s.vnc", cfg->libDir, def->name) == -1)
goto error;
virBufferAsprintf(&opt, "unix:%s", graphics->data.vnc.socket);
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNC_COLON)) {
switch (virDomainGraphicsListenGetType(graphics, 0)) {
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS:
listenAddr = virDomainGraphicsListenGetAddress(graphics, 0);
break;
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK:
listenNetwork = virDomainGraphicsListenGetNetwork(graphics, 0);
if (!listenNetwork)
break;
ret = networkGetNetworkAddress(listenNetwork, &netAddr);
if (ret <= -2) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("network-based listen not possible, "
"network driver not present"));
goto error;
}
if (ret < 0) {
virReportError(VIR_ERR_XML_ERROR,
_("listen network '%s' had no usable address"),
listenNetwork);
goto error;
}
listenAddr = netAddr;
/* store the address we found in the <graphics> element so it will
* show up in status. */
if (virDomainGraphicsListenSetAddress(graphics, 0,
listenAddr, -1, false) < 0)
goto error;
break;
}
if (!listenAddr)
listenAddr = cfg->vncListen;
escapeAddr = strchr(listenAddr, ':') != NULL;
if (escapeAddr)
virBufferAsprintf(&opt, "[%s]", listenAddr);
else
virBufferAdd(&opt, listenAddr, -1);
virBufferAsprintf(&opt, ":%d",
graphics->data.vnc.port - 5900);
VIR_FREE(netAddr);
} else {
virBufferAsprintf(&opt, "%d",
graphics->data.vnc.port - 5900);
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNC_COLON)) {
if (!graphics->data.vnc.socket &&
graphics->data.vnc.websocket) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNC_WEBSOCKET)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("VNC WebSockets are not supported "
"with this QEMU binary"));
goto error;
}
virBufferAsprintf(&opt, ",websocket=%d", graphics->data.vnc.websocket);
}
if (graphics->data.vnc.sharePolicy) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VNC_SHARE_POLICY)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("vnc display sharing policy is not "
"supported with this QEMU"));
goto error;
}
virBufferAsprintf(&opt, ",share=%s",
virDomainGraphicsVNCSharePolicyTypeToString(
graphics->data.vnc.sharePolicy));
}
if (graphics->data.vnc.auth.passwd || cfg->vncPassword)
virBufferAddLit(&opt, ",password");
if (cfg->vncTLS) {
virBufferAddLit(&opt, ",tls");
if (cfg->vncTLSx509verify)
virBufferAsprintf(&opt, ",x509verify=%s", cfg->vncTLSx509certdir);
else
virBufferAsprintf(&opt, ",x509=%s", cfg->vncTLSx509certdir);
}
if (cfg->vncSASL) {
virBufferAddLit(&opt, ",sasl");
if (cfg->vncSASLdir)
virCommandAddEnvPair(cmd, "SASL_CONF_DIR", cfg->vncSASLdir);
/* TODO: Support ACLs later */
}
}
virCommandAddArg(cmd, "-vnc");
virCommandAddArgBuffer(cmd, &opt);
if (graphics->data.vnc.keymap)
virCommandAddArgList(cmd, "-k", graphics->data.vnc.keymap, NULL);
/* Unless user requested it, set the audio backend to none, to
* prevent it opening the host OS audio devices, since that causes
* security issues and might not work when using VNC.
*/
if (cfg->vncAllowHostAudio)
virCommandAddEnvPassBlockSUID(cmd, "QEMU_AUDIO_DRV", NULL);
else
virCommandAddEnvString(cmd, "QEMU_AUDIO_DRV=none");
return 0;
error:
VIR_FREE(netAddr);
virBufferFreeAndReset(&opt);
return -1;
}
static int
qemuBuildGraphicsSPICECommandLine(virQEMUDriverConfigPtr cfg,
virCommandPtr cmd,
virQEMUCapsPtr qemuCaps,
virDomainGraphicsDefPtr graphics)
{
virBuffer opt = VIR_BUFFER_INITIALIZER;
const char *listenNetwork;
const char *listenAddr = NULL;
char *netAddr = NULL;
int ret;
int defaultMode = graphics->data.spice.defaultMode;
int port = graphics->data.spice.port;
int tlsPort = graphics->data.spice.tlsPort;
size_t i;
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPICE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("spice graphics are not supported with this QEMU"));
goto error;
}
if (port > 0 || tlsPort <= 0)
virBufferAsprintf(&opt, "port=%u", port);
if (tlsPort > 0) {
if (!cfg->spiceTLS) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("spice TLS port set in XML configuration,"
" but TLS is disabled in qemu.conf"));
goto error;
}
if (port > 0)
virBufferAddChar(&opt, ',');
virBufferAsprintf(&opt, "tls-port=%u", tlsPort);
}
if (cfg->spiceSASL) {
virBufferAddLit(&opt, ",sasl");
if (cfg->spiceSASLdir)
virCommandAddEnvPair(cmd, "SASL_CONF_PATH",
cfg->spiceSASLdir);
/* TODO: Support ACLs later */
}
switch (virDomainGraphicsListenGetType(graphics, 0)) {
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS:
listenAddr = virDomainGraphicsListenGetAddress(graphics, 0);
break;
case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK:
listenNetwork = virDomainGraphicsListenGetNetwork(graphics, 0);
if (!listenNetwork)
break;
ret = networkGetNetworkAddress(listenNetwork, &netAddr);
if (ret <= -2) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("network-based listen not possible, "
"network driver not present"));
goto error;
}
if (ret < 0) {
virReportError(VIR_ERR_XML_ERROR,
_("listen network '%s' had no usable address"),
listenNetwork);
goto error;
}
listenAddr = netAddr;
/* store the address we found in the <graphics> element so it will
* show up in status. */
if (virDomainGraphicsListenSetAddress(graphics, 0,
listenAddr, -1, false) < 0)
goto error;
break;
}
if (!listenAddr)
listenAddr = cfg->spiceListen;
if (listenAddr)
virBufferAsprintf(&opt, ",addr=%s", listenAddr);
VIR_FREE(netAddr);
if (graphics->data.spice.mousemode) {
switch (graphics->data.spice.mousemode) {
case VIR_DOMAIN_GRAPHICS_SPICE_MOUSE_MODE_SERVER:
virBufferAddLit(&opt, ",agent-mouse=off");
break;
case VIR_DOMAIN_GRAPHICS_SPICE_MOUSE_MODE_CLIENT:
virBufferAddLit(&opt, ",agent-mouse=on");
break;
default:
break;
}
}
/* In the password case we set it via monitor command, to avoid
* making it visible on CLI, so there's no use of password=XXX
* in this bit of the code */
if (!graphics->data.spice.auth.passwd &&
!cfg->spicePassword)
virBufferAddLit(&opt, ",disable-ticketing");
if (tlsPort > 0)
virBufferAsprintf(&opt, ",x509-dir=%s", cfg->spiceTLSx509certdir);
switch (defaultMode) {
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_SECURE:
virBufferAddLit(&opt, ",tls-channel=default");
break;
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_INSECURE:
virBufferAddLit(&opt, ",plaintext-channel=default");
break;
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_ANY:
/* nothing */
break;
}
for (i = 0; i < VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_LAST; i++) {
switch (graphics->data.spice.channels[i]) {
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_SECURE:
if (tlsPort <= 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("spice secure channels set in XML configuration, "
"but TLS port is not provided"));
goto error;
}
virBufferAsprintf(&opt, ",tls-channel=%s",
virDomainGraphicsSpiceChannelNameTypeToString(i));
break;
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_INSECURE:
if (port <= 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("spice insecure channels set in XML "
"configuration, but plain port is not provided"));
goto error;
}
virBufferAsprintf(&opt, ",plaintext-channel=%s",
virDomainGraphicsSpiceChannelNameTypeToString(i));
break;
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_ANY:
switch (defaultMode) {
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_SECURE:
if (tlsPort <= 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("spice defaultMode secure requested in XML "
"configuration but TLS port not provided"));
goto error;
}
break;
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_INSECURE:
if (port <= 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("spice defaultMode insecure requested in XML "
"configuration but plain port not provided"));
goto error;
}
break;
case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_ANY:
/* don't care */
break;
}
}
}
if (graphics->data.spice.image)
virBufferAsprintf(&opt, ",image-compression=%s",
virDomainGraphicsSpiceImageCompressionTypeToString(graphics->data.spice.image));
if (graphics->data.spice.jpeg)
virBufferAsprintf(&opt, ",jpeg-wan-compression=%s",
virDomainGraphicsSpiceJpegCompressionTypeToString(graphics->data.spice.jpeg));
if (graphics->data.spice.zlib)
virBufferAsprintf(&opt, ",zlib-glz-wan-compression=%s",
virDomainGraphicsSpiceZlibCompressionTypeToString(graphics->data.spice.zlib));
if (graphics->data.spice.playback)
virBufferAsprintf(&opt, ",playback-compression=%s",
virDomainGraphicsSpicePlaybackCompressionTypeToString(graphics->data.spice.playback));
if (graphics->data.spice.streaming)
virBufferAsprintf(&opt, ",streaming-video=%s",
virDomainGraphicsSpiceStreamingModeTypeToString(graphics->data.spice.streaming));
if (graphics->data.spice.copypaste == VIR_DOMAIN_GRAPHICS_SPICE_CLIPBOARD_COPYPASTE_NO)
virBufferAddLit(&opt, ",disable-copy-paste");
if (graphics->data.spice.filetransfer == VIR_DOMAIN_GRAPHICS_SPICE_AGENT_FILE_TRANSFER_NO) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SPICE_FILE_XFER_DISABLE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU can't disable file transfers through spice"));
goto error;
} else {
virBufferAddLit(&opt, ",disable-agent-file-xfer");
}
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_SEAMLESS_MIGRATION)) {
/* If qemu supports seamless migration turn it
* unconditionally on. If migration destination
* doesn't support it, it fallbacks to previous
* migration algorithm silently. */
virBufferAddLit(&opt, ",seamless-migration=on");
}
virCommandAddArg(cmd, "-spice");
virCommandAddArgBuffer(cmd, &opt);
if (graphics->data.spice.keymap)
virCommandAddArgList(cmd, "-k",
graphics->data.spice.keymap, NULL);
/* SPICE includes native support for tunnelling audio, so we
* set the audio backend to point at SPICE's own driver
*/
virCommandAddEnvString(cmd, "QEMU_AUDIO_DRV=spice");
return 0;
error:
VIR_FREE(netAddr);
virBufferFreeAndReset(&opt);
return -1;
}
static int
qemuBuildGraphicsCommandLine(virQEMUDriverConfigPtr cfg,
virCommandPtr cmd,
virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
virDomainGraphicsDefPtr graphics)
{
switch ((enum virDomainGraphicsType) graphics->type) {
case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_0_10) &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SDL)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("sdl not supported by '%s'"), def->emulator);
return -1;
}
if (graphics->data.sdl.xauth)
virCommandAddEnvPair(cmd, "XAUTHORITY", graphics->data.sdl.xauth);
if (graphics->data.sdl.display)
virCommandAddEnvPair(cmd, "DISPLAY", graphics->data.sdl.display);
if (graphics->data.sdl.fullscreen)
virCommandAddArg(cmd, "-full-screen");
/* If using SDL for video, then we should just let it
* use QEMU's host audio drivers, possibly SDL too
* User can set these two before starting libvirtd
*/
virCommandAddEnvPassBlockSUID(cmd, "QEMU_AUDIO_DRV", NULL);
virCommandAddEnvPassBlockSUID(cmd, "SDL_AUDIODRIVER", NULL);
/* New QEMU has this flag to let us explicitly ask for
* SDL graphics. This is better than relying on the
* default, since the default changes :-( */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_SDL))
virCommandAddArg(cmd, "-sdl");
break;
case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
return qemuBuildGraphicsVNCCommandLine(cfg, cmd, def, qemuCaps, graphics);
case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
return qemuBuildGraphicsSPICECommandLine(cfg, cmd, qemuCaps, graphics);
case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported graphics type '%s'"),
virDomainGraphicsTypeToString(graphics->type));
return -1;
}
return 0;
}
static int
qemuBuildInterfaceCommandLine(virCommandPtr cmd,
virQEMUDriverPtr driver,
virConnectPtr conn,
virDomainDefPtr def,
virDomainNetDefPtr net,
virQEMUCapsPtr qemuCaps,
int vlan,
int bootindex,
enum virNetDevVPortProfileOp vmop)
{
int ret = -1;
char *nic = NULL, *host = NULL;
int *tapfd = NULL;
int tapfdSize = 0;
int *vhostfd = NULL;
int vhostfdSize = 0;
char **tapfdName = NULL;
char **vhostfdName = NULL;
int actualType = virDomainNetGetActualType(net);
size_t i;
if (actualType == VIR_DOMAIN_NET_TYPE_HOSTDEV) {
/* NET_TYPE_HOSTDEV devices are really hostdev devices, so
* their commandlines are constructed with other hostdevs.
*/
return 0;
}
if (!bootindex)
bootindex = net->info.bootIndex;
/* Currently nothing besides TAP devices supports multiqueue. */
if (net->driver.virtio.queues > 0 &&
!(actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
actualType == VIR_DOMAIN_NET_TYPE_BRIDGE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Multiqueue network is not supported for: %s"),
virDomainNetTypeToString(actualType));
return -1;
}
if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
actualType == VIR_DOMAIN_NET_TYPE_BRIDGE) {
tapfdSize = net->driver.virtio.queues;
if (!tapfdSize)
tapfdSize = 1;
if (VIR_ALLOC_N(tapfd, tapfdSize) < 0 ||
VIR_ALLOC_N(tapfdName, tapfdSize) < 0)
goto cleanup;
memset(tapfd, -1, tapfdSize * sizeof(tapfd[0]));
if (qemuNetworkIfaceConnect(def, conn, driver, net,
qemuCaps, tapfd, &tapfdSize) < 0)
goto cleanup;
} else if (actualType == VIR_DOMAIN_NET_TYPE_DIRECT) {
if (VIR_ALLOC(tapfd) < 0 || VIR_ALLOC(tapfdName) < 0)
goto cleanup;
tapfdSize = 1;
tapfd[0] = qemuPhysIfaceConnect(def, driver, net,
qemuCaps, vmop);
if (tapfd[0] < 0)
goto cleanup;
}
if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
actualType == VIR_DOMAIN_NET_TYPE_BRIDGE ||
actualType == VIR_DOMAIN_NET_TYPE_ETHERNET ||
actualType == VIR_DOMAIN_NET_TYPE_DIRECT) {
/* Attempt to use vhost-net mode for these types of
network device */
vhostfdSize = net->driver.virtio.queues;
if (!vhostfdSize)
vhostfdSize = 1;
if (VIR_ALLOC_N(vhostfd, vhostfdSize) < 0 ||
VIR_ALLOC_N(vhostfdName, vhostfdSize))
goto cleanup;
memset(vhostfd, -1, vhostfdSize * sizeof(vhostfd[0]));
if (qemuOpenVhostNet(def, net, qemuCaps, vhostfd, &vhostfdSize) < 0)
goto cleanup;
}
for (i = 0; i < tapfdSize; i++) {
virCommandPassFD(cmd, tapfd[i],
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
if (virAsprintf(&tapfdName[i], "%d", tapfd[i]) < 0)
goto cleanup;
}
for (i = 0; i < vhostfdSize; i++) {
virCommandPassFD(cmd, vhostfd[i],
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
if (virAsprintf(&vhostfdName[i], "%d", vhostfd[i]) < 0)
goto cleanup;
}
/* Possible combinations:
*
* 1. Old way: -net nic,model=e1000,vlan=1 -net tap,vlan=1
* 2. Semi-new: -device e1000,vlan=1 -net tap,vlan=1
* 3. Best way: -netdev type=tap,id=netdev1 -device e1000,id=netdev1
*
* NB, no support for -netdev without use of -device
*/
if (qemuDomainSupportsNetdev(def, qemuCaps, net)) {
if (!(host = qemuBuildHostNetStr(net, driver,
',', vlan,
tapfdName, tapfdSize,
vhostfdName, vhostfdSize)))
goto cleanup;
virCommandAddArgList(cmd, "-netdev", host, NULL);
}
if (qemuDomainSupportsNicdev(def, qemuCaps, net)) {
if (!(nic = qemuBuildNicDevStr(def, net, vlan, bootindex,
vhostfdSize, qemuCaps)))
goto cleanup;
virCommandAddArgList(cmd, "-device", nic, NULL);
} else {
if (!(nic = qemuBuildNicStr(net, "nic,", vlan)))
goto cleanup;
virCommandAddArgList(cmd, "-net", nic, NULL);
}
if (!qemuDomainSupportsNetdev(def, qemuCaps, net)) {
if (!(host = qemuBuildHostNetStr(net, driver,
',', vlan,
tapfdName, tapfdSize,
vhostfdName, vhostfdSize)))
goto cleanup;
virCommandAddArgList(cmd, "-net", host, NULL);
}
ret = 0;
cleanup:
if (ret < 0)
virDomainConfNWFilterTeardown(net);
for (i = 0; tapfd && i < tapfdSize && tapfd[i] >= 0; i++) {
if (ret < 0)
VIR_FORCE_CLOSE(tapfd[i]);
if (tapfdName)
VIR_FREE(tapfdName[i]);
}
for (i = 0; vhostfd && i < vhostfdSize && vhostfd[i] >= 0; i++) {
if (ret < 0)
VIR_FORCE_CLOSE(vhostfd[i]);
if (vhostfdName)
VIR_FREE(vhostfdName[i]);
}
VIR_FREE(tapfd);
VIR_FREE(vhostfd);
VIR_FREE(nic);
VIR_FREE(host);
VIR_FREE(tapfdName);
VIR_FREE(vhostfdName);
return ret;
}
static int
qemuBuildChrDeviceCommandLine(virCommandPtr cmd,
virDomainDefPtr def,
virDomainChrDefPtr chr,
virQEMUCapsPtr qemuCaps)
{
char *devstr = NULL;
if (qemuBuildChrDeviceStr(&devstr, def, chr, qemuCaps) < 0)
return -1;
virCommandAddArgList(cmd, "-device", devstr, NULL);
VIR_FREE(devstr);
return 0;
}
qemuBuildCommandLineCallbacks buildCommandLineCallbacks = {
.qemuGetSCSIDeviceSgName = virSCSIDeviceGetSgName,
};
/*
* Constructs a argv suitable for launching qemu with config defined
* for a given virtual machine.
*
* XXX 'conn' is only required to resolve network -> bridge name
* figure out how to remove this requirement some day
*/
virCommandPtr
qemuBuildCommandLine(virConnectPtr conn,
virQEMUDriverPtr driver,
virDomainDefPtr def,
virDomainChrSourceDefPtr monitor_chr,
bool monitor_json,
virQEMUCapsPtr qemuCaps,
const char *migrateFrom,
int migrateFd,
virDomainSnapshotObjPtr snapshot,
enum virNetDevVPortProfileOp vmop,
qemuBuildCommandLineCallbacksPtr callbacks)
{
virErrorPtr originalError = NULL;
size_t i, j;
const char *emulator;
char uuid[VIR_UUID_STRING_BUFLEN];
char *cpu;
char *smp;
int last_good_net = -1;
bool hasHwVirt = false;
virCommandPtr cmd = NULL;
bool allowReboot = true;
bool emitBootindex = false;
int sdl = 0;
int vnc = 0;
int spice = 0;
int usbcontroller = 0;
int actualSerials = 0;
bool usblegacy = false;
bool mlock = false;
int contOrder[] = {
/* We don't add an explicit IDE or FD controller because the
* provided PIIX4 device already includes one. It isn't possible to
* remove the PIIX4.
*
* We don't add PCI root controller either, because it's implicit,
* but we do add PCI bridges. */
VIR_DOMAIN_CONTROLLER_TYPE_PCI,
VIR_DOMAIN_CONTROLLER_TYPE_USB,
VIR_DOMAIN_CONTROLLER_TYPE_SCSI,
VIR_DOMAIN_CONTROLLER_TYPE_SATA,
VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL,
VIR_DOMAIN_CONTROLLER_TYPE_CCID,
};
virArch hostarch = virArchFromHost();
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
VIR_DEBUG("conn=%p driver=%p def=%p mon=%p json=%d "
"qemuCaps=%p migrateFrom=%s migrateFD=%d "
"snapshot=%p vmop=%d",
conn, driver, def, monitor_chr, monitor_json,
qemuCaps, migrateFrom, migrateFd, snapshot, vmop);
virUUIDFormat(def->uuid, uuid);
emulator = def->emulator;
if (!cfg->privileged) {
/* If we have no cgroups than we can have no tunings that
* require them */
if (def->mem.hard_limit || def->mem.soft_limit ||
def->mem.min_guarantee || def->mem.swap_hard_limit) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Memory tuning is not available in session mode"));
goto error;
}
if (def->blkio.weight || def->blkio.ndevices) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Block I/O tuning is not available in session mode"));
goto error;
}
if (def->cputune.shares || def->cputune.period ||
def->cputune.quota || def->cputune.emulator_period ||
def->cputune.emulator_quota) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("CPU tuning is not available in session mode"));
goto error;
}
}
for (i = 0; i < def->ngraphics; ++i) {
switch (def->graphics[i]->type) {
case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
++sdl;
break;
case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
++vnc;
break;
case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
++spice;
break;
}
}
/*
* do not use boot=on for drives when not using KVM since this
* is not supported at all in upstream QEmu.
*/
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_KVM) &&
(def->virtType == VIR_DOMAIN_VIRT_QEMU))
virQEMUCapsClear(qemuCaps, QEMU_CAPS_DRIVE_BOOT);
cmd = virCommandNew(emulator);
virCommandAddEnvPassCommon(cmd);
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NAME)) {
virCommandAddArg(cmd, "-name");
if (cfg->setProcessName &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_NAME_PROCESS)) {
virCommandAddArgFormat(cmd, "%s,process=qemu:%s",
def->name, def->name);
} else {
virCommandAddArg(cmd, def->name);
}
}
virCommandAddArg(cmd, "-S"); /* freeze CPU */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_ENABLE_FIPS))
virCommandAddArg(cmd, "-enable-fips");
if (qemuBuildMachineArgStr(cmd, def, qemuCaps) < 0)
goto error;
if (qemuBuildCpuArgStr(driver, def, emulator, qemuCaps,
hostarch, &cpu, &hasHwVirt, !!migrateFrom) < 0)
goto error;
if (cpu) {
virCommandAddArgList(cmd, "-cpu", cpu, NULL);
VIR_FREE(cpu);
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NESTING) &&
hasHwVirt)
virCommandAddArg(cmd, "-enable-nesting");
}
if (def->os.loader) {
virCommandAddArg(cmd, "-bios");
virCommandAddArg(cmd, def->os.loader);
}
/* Set '-m MB' based on maxmem, because the lower 'memory' limit
* is set post-startup using the balloon driver. If balloon driver
* is not supported, then they're out of luck anyway. Update the
* XML to reflect our rounding.
*/
virCommandAddArg(cmd, "-m");
def->mem.max_balloon = VIR_DIV_UP(def->mem.max_balloon, 1024) * 1024;
virCommandAddArgFormat(cmd, "%llu", def->mem.max_balloon / 1024);
if (def->mem.hugepage_backed) {
if (!cfg->hugetlbfsMount) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("hugetlbfs filesystem is not mounted"));
goto error;
}
if (!cfg->hugepagePath) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("hugepages are disabled by administrator config"));
goto error;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MEM_PATH)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("hugepage backing not supported by '%s'"),
def->emulator);
goto error;
}
virCommandAddArgList(cmd, "-mem-prealloc", "-mem-path",
cfg->hugepagePath, NULL);
}
if (def->mem.locked && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_MLOCK)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("memory locking not supported by QEMU binary"));
goto error;
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_MLOCK)) {
virCommandAddArg(cmd, "-realtime");
virCommandAddArgFormat(cmd, "mlock=%s",
def->mem.locked ? "on" : "off");
}
mlock = def->mem.locked;
virCommandAddArg(cmd, "-smp");
if (!(smp = qemuBuildSmpArgStr(def, qemuCaps)))
goto error;
virCommandAddArg(cmd, smp);
VIR_FREE(smp);
if (def->cpu && def->cpu->ncells)
if (qemuBuildNumaArgStr(def, cmd) < 0)
goto error;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_UUID))
virCommandAddArgList(cmd, "-uuid", uuid, NULL);
if (def->virtType == VIR_DOMAIN_VIRT_XEN ||
STREQ(def->os.type, "xen") ||
STREQ(def->os.type, "linux")) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DOMID)) {
virCommandAddArg(cmd, "-domid");
virCommandAddArgFormat(cmd, "%d", def->id);
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_XEN_DOMID)) {
virCommandAddArg(cmd, "-xen-attach");
virCommandAddArg(cmd, "-xen-domid");
virCommandAddArgFormat(cmd, "%d", def->id);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("qemu emulator '%s' does not support xen"),
def->emulator);
goto error;
}
}
if ((def->os.smbios_mode != VIR_DOMAIN_SMBIOS_NONE) &&
(def->os.smbios_mode != VIR_DOMAIN_SMBIOS_EMULATE)) {
virSysinfoDefPtr source = NULL;
bool skip_uuid = false;
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SMBIOS_TYPE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("the QEMU binary %s does not support smbios settings"),
emulator);
goto error;
}
/* should we really error out or just warn in those cases ? */
if (def->os.smbios_mode == VIR_DOMAIN_SMBIOS_HOST) {
if (driver->hostsysinfo == NULL) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Host SMBIOS information is not available"));
goto error;
}
source = driver->hostsysinfo;
/* Host and guest uuid must differ, by definition of UUID. */
skip_uuid = true;
} else if (def->os.smbios_mode == VIR_DOMAIN_SMBIOS_SYSINFO) {
if (def->sysinfo == NULL) {
virReportError(VIR_ERR_XML_ERROR,
_("Domain '%s' sysinfo are not available"),
def->name);
goto error;
}
source = def->sysinfo;
/* domain_conf guaranteed that system_uuid matches guest uuid. */
}
if (source != NULL) {
char *smbioscmd;
smbioscmd = qemuBuildSmbiosBiosStr(source);
if (smbioscmd != NULL) {
virCommandAddArgList(cmd, "-smbios", smbioscmd, NULL);
VIR_FREE(smbioscmd);
}
smbioscmd = qemuBuildSmbiosSystemStr(source, skip_uuid);
if (smbioscmd != NULL) {
virCommandAddArgList(cmd, "-smbios", smbioscmd, NULL);
VIR_FREE(smbioscmd);
}
}
}
/*
* NB, -nographic *MUST* come before any serial, or monitor
* or parallel port flags due to QEMU craziness, where it
* decides to change the serial port & monitor to be on stdout
* if you ask for nographic. So we have to make sure we override
* these defaults ourselves...
*/
if (!def->graphics) {
virCommandAddArg(cmd, "-nographic");
if (cfg->nogfxAllowHostAudio)
virCommandAddEnvPassBlockSUID(cmd, "QEMU_AUDIO_DRV", NULL);
else
virCommandAddEnvString(cmd, "QEMU_AUDIO_DRV=none");
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
/* Disable global config files and default devices */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NO_USER_CONFIG))
virCommandAddArg(cmd, "-no-user-config");
else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NODEFCONFIG))
virCommandAddArg(cmd, "-nodefconfig");
virCommandAddArg(cmd, "-nodefaults");
}
/* Serial graphics adapter */
if (def->os.bios.useserial == VIR_DOMAIN_BIOS_USESERIAL_YES) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu does not support -device"));
goto error;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SGA)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("qemu does not support SGA"));
goto error;
}
if (!def->nserials) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("need at least one serial port to use SGA"));
goto error;
}
virCommandAddArgList(cmd, "-device", "sga", NULL);
}
if (monitor_chr) {
char *chrdev;
/* Use -chardev if it's available */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV)) {
virCommandAddArg(cmd, "-chardev");
if (!(chrdev = qemuBuildChrChardevStr(monitor_chr, "monitor",
qemuCaps)))
goto error;
virCommandAddArg(cmd, chrdev);
VIR_FREE(chrdev);
virCommandAddArg(cmd, "-mon");
virCommandAddArgFormat(cmd,
"chardev=charmonitor,id=monitor,mode=%s",
monitor_json ? "control" : "readline");
} else {
const char *prefix = NULL;
if (monitor_json)
prefix = "control,";
virCommandAddArg(cmd, "-monitor");
if (!(chrdev = qemuBuildChrArgStr(monitor_chr, prefix)))
goto error;
virCommandAddArg(cmd, chrdev);
VIR_FREE(chrdev);
}
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_RTC)) {
char *rtcopt;
virCommandAddArg(cmd, "-rtc");
if (!(rtcopt = qemuBuildClockArgStr(&def->clock)))
goto error;
virCommandAddArg(cmd, rtcopt);
VIR_FREE(rtcopt);
} else {
switch (def->clock.offset) {
case VIR_DOMAIN_CLOCK_OFFSET_LOCALTIME:
case VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE:
virCommandAddArg(cmd, "-localtime");
break;
case VIR_DOMAIN_CLOCK_OFFSET_UTC:
/* Nothing, its the default */
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported clock offset '%s'"),
virDomainClockOffsetTypeToString(def->clock.offset));
goto error;
}
}
if (def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE &&
def->clock.data.timezone) {
virCommandAddEnvPair(cmd, "TZ", def->clock.data.timezone);
}
for (i = 0; i < def->clock.ntimers; i++) {
switch ((enum virDomainTimerNameType) def->clock.timers[i]->name) {
case VIR_DOMAIN_TIMER_NAME_PLATFORM:
case VIR_DOMAIN_TIMER_NAME_TSC:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported timer type (name) '%s'"),
virDomainTimerNameTypeToString(def->clock.timers[i]->name));
goto error;
case VIR_DOMAIN_TIMER_NAME_KVMCLOCK:
case VIR_DOMAIN_TIMER_NAME_HYPERVCLOCK:
/* Timers above are handled when building -cpu. */
case VIR_DOMAIN_TIMER_NAME_LAST:
break;
case VIR_DOMAIN_TIMER_NAME_RTC:
/* This has already been taken care of (in qemuBuildClockArgStr)
if QEMU_CAPS_RTC is set (mutually exclusive with
QEMUD_FLAG_RTC_TD_HACK) */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_RTC_TD_HACK)) {
switch (def->clock.timers[i]->tickpolicy) {
case -1:
case VIR_DOMAIN_TIMER_TICKPOLICY_DELAY:
/* the default - do nothing */
break;
case VIR_DOMAIN_TIMER_TICKPOLICY_CATCHUP:
virCommandAddArg(cmd, "-rtc-td-hack");
break;
case VIR_DOMAIN_TIMER_TICKPOLICY_MERGE:
case VIR_DOMAIN_TIMER_TICKPOLICY_DISCARD:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported rtc tickpolicy '%s'"),
virDomainTimerTickpolicyTypeToString(def->clock.timers[i]->tickpolicy));
goto error;
}
} else if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_RTC)
&& (def->clock.timers[i]->tickpolicy
!= VIR_DOMAIN_TIMER_TICKPOLICY_DELAY)
&& (def->clock.timers[i]->tickpolicy != -1)) {
/* a non-default rtc policy was given, but there is no
way to implement it in this version of qemu */
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported rtc tickpolicy '%s'"),
virDomainTimerTickpolicyTypeToString(def->clock.timers[i]->tickpolicy));
goto error;
}
break;
case VIR_DOMAIN_TIMER_NAME_PIT:
switch (def->clock.timers[i]->tickpolicy) {
case -1:
case VIR_DOMAIN_TIMER_TICKPOLICY_DELAY:
/* delay is the default if we don't have kernel
(-no-kvm-pit), otherwise, the default is catchup. */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_KVM_PIT_TICK_POLICY))
virCommandAddArgList(cmd, "-global",
"kvm-pit.lost_tick_policy=discard", NULL);
else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NO_KVM_PIT))
virCommandAddArg(cmd, "-no-kvm-pit-reinjection");
break;
case VIR_DOMAIN_TIMER_TICKPOLICY_CATCHUP:
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NO_KVM_PIT) ||
virQEMUCapsGet(qemuCaps, QEMU_CAPS_KVM_PIT_TICK_POLICY)) {
/* do nothing - this is default for kvm-pit */
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_TDF)) {
/* -tdf switches to 'catchup' with userspace pit. */
virCommandAddArg(cmd, "-tdf");
} else {
/* can't catchup if we have neither pit mode */
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported pit tickpolicy '%s'"),
virDomainTimerTickpolicyTypeToString(def->clock.timers[i]->tickpolicy));
goto error;
}
break;
case VIR_DOMAIN_TIMER_TICKPOLICY_MERGE:
case VIR_DOMAIN_TIMER_TICKPOLICY_DISCARD:
/* no way to support these modes for pit in qemu */
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported pit tickpolicy '%s'"),
virDomainTimerTickpolicyTypeToString(def->clock.timers[i]->tickpolicy));
goto error;
}
break;
case VIR_DOMAIN_TIMER_NAME_HPET:
/* the only meaningful attribute for hpet is "present". If
* present is -1, that means it wasn't specified, and
* should be left at the default for the
* hypervisor. "default" when -no-hpet exists is "yes",
* and when -no-hpet doesn't exist is "no". "confusing"?
* "yes"! */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NO_HPET)) {
if (def->clock.timers[i]->present == 0)
virCommandAddArg(cmd, "-no-hpet");
} else {
/* no hpet timer available. The only possible action
is to raise an error if present="yes" */
if (def->clock.timers[i]->present == 1) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("pit timer is not supported"));
}
}
break;
}
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NO_REBOOT)) {
/* Only add -no-reboot option if each event destroys domain */
if (def->onReboot == VIR_DOMAIN_LIFECYCLE_DESTROY &&
def->onPoweroff == VIR_DOMAIN_LIFECYCLE_DESTROY &&
def->onCrash == VIR_DOMAIN_LIFECYCLE_DESTROY) {
allowReboot = false;
virCommandAddArg(cmd, "-no-reboot");
}
}
/* If JSON monitor is enabled, we can receive an event
* when QEMU stops. If we use no-shutdown, then we can
* watch for this event and do a soft/warm reboot.
*/
if (monitor_json && allowReboot &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_NO_SHUTDOWN)) {
virCommandAddArg(cmd, "-no-shutdown");
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_NO_ACPI)) {
if (def->features[VIR_DOMAIN_FEATURE_ACPI] != VIR_DOMAIN_FEATURE_STATE_ON)
virCommandAddArg(cmd, "-no-acpi");
}
if (def->pm.s3) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DISABLE_S3)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("setting ACPI S3 not supported"));
goto error;
}
virCommandAddArg(cmd, "-global");
virCommandAddArgFormat(cmd, "PIIX4_PM.disable_s3=%d",
def->pm.s3 == VIR_DOMAIN_PM_STATE_DISABLED);
}
if (def->pm.s4) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DISABLE_S4)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("setting ACPI S4 not supported"));
goto error;
}
virCommandAddArg(cmd, "-global");
virCommandAddArgFormat(cmd, "PIIX4_PM.disable_s4=%d",
def->pm.s4 == VIR_DOMAIN_PM_STATE_DISABLED);
}
if (!def->os.bootloader) {
int boot_nparams = 0;
virBuffer boot_buf = VIR_BUFFER_INITIALIZER;
/*
* We prefer using explicit bootindex=N parameters for predictable
* results even though domain XML doesn't use per device boot elements.
* However, we can't use bootindex if boot menu was requested.
*/
if (!def->os.nBootDevs) {
/* def->os.nBootDevs is guaranteed to be > 0 unless per-device boot
* configuration is used
*/
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_BOOTINDEX)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("hypervisor lacks deviceboot feature"));
goto error;
}
emitBootindex = true;
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_BOOTINDEX) &&
(def->os.bootmenu != VIR_DOMAIN_BOOT_MENU_ENABLED ||
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_BOOT_MENU))) {
emitBootindex = true;
}
if (!emitBootindex) {
char boot[VIR_DOMAIN_BOOT_LAST+1];
for (i = 0; i < def->os.nBootDevs; i++) {
switch (def->os.bootDevs[i]) {
case VIR_DOMAIN_BOOT_CDROM:
boot[i] = 'd';
break;
case VIR_DOMAIN_BOOT_FLOPPY:
boot[i] = 'a';
break;
case VIR_DOMAIN_BOOT_DISK:
boot[i] = 'c';
break;
case VIR_DOMAIN_BOOT_NET:
boot[i] = 'n';
break;
default:
boot[i] = 'c';
break;
}
}
boot[def->os.nBootDevs] = '\0';
virBufferAsprintf(&boot_buf, "%s", boot);
boot_nparams++;
}
if (def->os.bootmenu) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_BOOT_MENU)) {
if (boot_nparams++)
virBufferAddChar(&boot_buf, ',');
if (def->os.bootmenu == VIR_DOMAIN_BOOT_MENU_ENABLED)
virBufferAddLit(&boot_buf, "menu=on");
else
virBufferAddLit(&boot_buf, "menu=off");
} else {
/* We cannot emit an error when bootmenu is enabled but
* unsupported because of backward compatibility */
VIR_WARN("bootmenu is enabled but not "
"supported by this QEMU binary");
}
}
if (def->os.bios.rt_set) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_REBOOT_TIMEOUT)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("reboot timeout is not supported "
"by this QEMU binary"));
virBufferFreeAndReset(&boot_buf);
goto error;
}
if (boot_nparams++)
virBufferAddChar(&boot_buf, ',');
virBufferAsprintf(&boot_buf,
"reboot-timeout=%d",
def->os.bios.rt_delay);
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_BOOT_STRICT)) {
if (boot_nparams++)
virBufferAddChar(&boot_buf, ',');
virBufferAddLit(&boot_buf, "strict=on");
}
if (boot_nparams > 0) {
virCommandAddArg(cmd, "-boot");
if (virBufferError(&boot_buf)) {
virReportOOMError();
goto error;
}
if (boot_nparams < 2 || emitBootindex) {
virCommandAddArgBuffer(cmd, &boot_buf);
virBufferFreeAndReset(&boot_buf);
} else {
char *str = virBufferContentAndReset(&boot_buf);
virCommandAddArgFormat(cmd,
"order=%s",
str);
VIR_FREE(str);
}
}
if (def->os.kernel)
virCommandAddArgList(cmd, "-kernel", def->os.kernel, NULL);
if (def->os.initrd)
virCommandAddArgList(cmd, "-initrd", def->os.initrd, NULL);
if (def->os.cmdline)
virCommandAddArgList(cmd, "-append", def->os.cmdline, NULL);
if (def->os.dtb) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DTB)) {
virCommandAddArgList(cmd, "-dtb", def->os.dtb, NULL);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("dtb is not supported with this QEMU binary"));
goto error;
}
}
} else {
virCommandAddArgList(cmd, "-bootloader", def->os.bootloader, NULL);
}
for (i = 0; i < def->ncontrollers; i++) {
virDomainControllerDefPtr cont = def->controllers[i];
if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI &&
cont->opts.pciopts.pcihole64) {
const char *hoststr = NULL;
bool cap = false;
bool machine = false;
switch (cont->model) {
case VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT:
hoststr = "i440FX-pcihost";
cap = virQEMUCapsGet(qemuCaps, QEMU_CAPS_I440FX_PCI_HOLE64_SIZE);
machine = qemuDomainMachineIsI440FX(def);
break;
case VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT:
hoststr = "q35-pcihost";
cap = virQEMUCapsGet(qemuCaps, QEMU_CAPS_Q35_PCI_HOLE64_SIZE);
machine = qemuDomainMachineIsQ35(def);
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("64-bit PCI hole setting is only for root"
" PCI controllers"));
goto error;
}
if (!machine) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Setting the 64-bit PCI hole size is not "
"supported for machine '%s'"), def->os.machine);
goto error;
}
if (!cap) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("64-bit PCI hole size setting is not supported "
"with this QEMU binary"));
goto error;
}
virCommandAddArg(cmd, "-global");
virCommandAddArgFormat(cmd, "%s.pci-hole64-size=%luK", hoststr,
cont->opts.pciopts.pcihole64size);
}
}
for (i = 0; i < def->ndisks; i++) {
virDomainDiskDefPtr disk = def->disks[i];
if (disk->driverName != NULL &&
!STREQ(disk->driverName, "qemu")) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported driver name '%s' for disk '%s'"),
disk->driverName, disk->src);
goto error;
}
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
for (j = 0; j < ARRAY_CARDINALITY(contOrder); j++) {
for (i = 0; i < def->ncontrollers; i++) {
virDomainControllerDefPtr cont = def->controllers[i];
if (cont->type != contOrder[j])
continue;
/* Also, skip USB controllers with type none.*/
if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_USB &&
cont->model == VIR_DOMAIN_CONTROLLER_MODEL_USB_NONE) {
usbcontroller = -1; /* mark we don't want a controller */
continue;
}
/* Skip pci-root/pcie-root */
if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_PCI &&
(cont->model == VIR_DOMAIN_CONTROLLER_MODEL_PCI_ROOT ||
cont->model == VIR_DOMAIN_CONTROLLER_MODEL_PCIE_ROOT)) {
continue;
}
/* Only recent QEMU implements a SATA (AHCI) controller */
if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_SATA) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_ICH9_AHCI)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("SATA is not supported with this "
"QEMU binary"));
goto error;
} else if (cont->idx == 0 && qemuDomainMachineIsQ35(def)) {
/* first SATA controller on Q35 machines is implicit */
continue;
} else {
char *devstr;
virCommandAddArg(cmd, "-device");
if (!(devstr = qemuBuildControllerDevStr(def, cont,
qemuCaps, NULL)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
}
} else if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_USB &&
cont->model == -1 &&
!qemuDomainMachineIsQ35(def) &&
(!virQEMUCapsGet(qemuCaps, QEMU_CAPS_PIIX3_USB_UHCI) ||
def->os.arch == VIR_ARCH_PPC64)) {
if (usblegacy) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Multiple legacy USB controllers are "
"not supported"));
goto error;
}
usblegacy = true;
} else {
virCommandAddArg(cmd, "-device");
char *devstr;
if (!(devstr = qemuBuildControllerDevStr(def, cont, qemuCaps,
&usbcontroller)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
}
}
}
}
if (usbcontroller == 0 && !qemuDomainMachineIsQ35(def))
virCommandAddArg(cmd, "-usb");
for (i = 0; i < def->nhubs; i++) {
virDomainHubDefPtr hub = def->hubs[i];
char *optstr;
virCommandAddArg(cmd, "-device");
if (!(optstr = qemuBuildHubDevStr(def, hub, qemuCaps)))
goto error;
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
}
/* If QEMU supports -drive param instead of old -hda, -hdb, -cdrom .. */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE)) {
int bootCD = 0, bootFloppy = 0, bootDisk = 0;
if ((virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE_BOOT) || emitBootindex)) {
/* bootDevs will get translated into either bootindex=N or boot=on
* depending on what qemu supports */
for (i = 0; i < def->os.nBootDevs; i++) {
switch (def->os.bootDevs[i]) {
case VIR_DOMAIN_BOOT_CDROM:
bootCD = i + 1;
break;
case VIR_DOMAIN_BOOT_FLOPPY:
bootFloppy = i + 1;
break;
case VIR_DOMAIN_BOOT_DISK:
bootDisk = i + 1;
break;
}
}
}
for (i = 0; i < def->ndisks; i++) {
char *optstr;
int bootindex = 0;
virDomainDiskDefPtr disk = def->disks[i];
bool withDeviceArg = false;
bool deviceFlagMasked = false;
/* Unless we have -device, then USB disks need special
handling */
if ((disk->bus == VIR_DOMAIN_DISK_BUS_USB) &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK) {
virCommandAddArg(cmd, "-usbdevice");
virCommandAddArgFormat(cmd, "disk:%s", disk->src);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unsupported usb disk type for '%s'"),
disk->src);
goto error;
}
continue;
}
switch (disk->device) {
case VIR_DOMAIN_DISK_DEVICE_CDROM:
bootindex = bootCD;
bootCD = 0;
break;
case VIR_DOMAIN_DISK_DEVICE_FLOPPY:
bootindex = bootFloppy;
bootFloppy = 0;
break;
case VIR_DOMAIN_DISK_DEVICE_DISK:
case VIR_DOMAIN_DISK_DEVICE_LUN:
bootindex = bootDisk;
bootDisk = 0;
break;
}
virCommandAddArg(cmd, "-drive");
/* Unfortunately it is not possible to use
-device for floppies, xen PV, or SD
devices. Fortunately, those don't need
static PCI addresses, so we don't really
care that we can't use -device */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
if (disk->bus != VIR_DOMAIN_DISK_BUS_XEN &&
disk->bus != VIR_DOMAIN_DISK_BUS_SD) {
withDeviceArg = true;
} else {
virQEMUCapsClear(qemuCaps, QEMU_CAPS_DEVICE);
deviceFlagMasked = true;
}
}
optstr = qemuBuildDriveStr(conn, disk,
emitBootindex ? false : !!bootindex,
qemuCaps);
if (deviceFlagMasked)
virQEMUCapsSet(qemuCaps, QEMU_CAPS_DEVICE);
if (!optstr)
goto error;
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
if (!emitBootindex)
bootindex = 0;
else if (disk->info.bootIndex)
bootindex = disk->info.bootIndex;
if (withDeviceArg) {
if (disk->bus == VIR_DOMAIN_DISK_BUS_FDC) {
virCommandAddArg(cmd, "-global");
virCommandAddArgFormat(cmd, "isa-fdc.drive%c=drive-%s",
disk->info.addr.drive.unit
? 'B' : 'A',
disk->info.alias);
if (bootindex) {
virCommandAddArg(cmd, "-global");
virCommandAddArgFormat(cmd, "isa-fdc.bootindex%c=%d",
disk->info.addr.drive.unit
? 'B' : 'A',
bootindex);
}
} else {
virCommandAddArg(cmd, "-device");
if (!(optstr = qemuBuildDriveDevStr(def, disk, bootindex,
qemuCaps)))
goto error;
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
}
}
}
} else {
for (i = 0; i < def->ndisks; i++) {
char dev[NAME_MAX];
char *file;
const char *fmt;
virDomainDiskDefPtr disk = def->disks[i];
if ((disk->type == VIR_DOMAIN_DISK_TYPE_BLOCK) &&
(disk->tray_status == VIR_DOMAIN_DISK_TRAY_OPEN)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("tray status 'open' is invalid for "
"block type disk"));
goto error;
}
if (disk->bus == VIR_DOMAIN_DISK_BUS_USB) {
if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK) {
virCommandAddArg(cmd, "-usbdevice");
virCommandAddArgFormat(cmd, "disk:%s", disk->src);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unsupported usb disk type for '%s'"),
disk->src);
goto error;
}
continue;
}
if (STREQ(disk->dst, "hdc") &&
disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) {
if (disk->src) {
snprintf(dev, NAME_MAX, "-%s", "cdrom");
} else {
continue;
}
} else {
if (STRPREFIX(disk->dst, "hd") ||
STRPREFIX(disk->dst, "fd")) {
snprintf(dev, NAME_MAX, "-%s", disk->dst);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unsupported disk type '%s'"), disk->dst);
goto error;
}
}
if (disk->type == VIR_DOMAIN_DISK_TYPE_DIR) {
/* QEMU only supports magic FAT format for now */
if (disk->format > 0 && disk->format != VIR_STORAGE_FILE_FAT) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unsupported disk driver type for '%s'"),
virStorageFileFormatTypeToString(disk->format));
goto error;
}
if (!disk->readonly) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot create virtual FAT disks in read-write mode"));
goto error;
}
if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY)
fmt = "fat:floppy:%s";
else
fmt = "fat:%s";
if (virAsprintf(&file, fmt, disk->src) < 0)
goto error;
} else if (disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("network disks are only supported with -drive"));
} else {
if (VIR_STRDUP(file, disk->src) < 0) {
goto error;
}
}
/* Don't start with source if the tray is open for
* CDROM and Floppy device.
*/
if (!((disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY ||
disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) &&
disk->tray_status == VIR_DOMAIN_DISK_TRAY_OPEN))
virCommandAddArgList(cmd, dev, file, NULL);
VIR_FREE(file);
}
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_FSDEV)) {
for (i = 0; i < def->nfss; i++) {
char *optstr;
virDomainFSDefPtr fs = def->fss[i];
virCommandAddArg(cmd, "-fsdev");
if (!(optstr = qemuBuildFSStr(fs, qemuCaps)))
goto error;
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
virCommandAddArg(cmd, "-device");
if (!(optstr = qemuBuildFSDevStr(def, fs, qemuCaps)))
goto error;
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
}
} else {
if (def->nfss) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("filesystem passthrough not supported by this QEMU"));
goto error;
}
}
if (!def->nnets) {
/* If we have -device, then we set -nodefault already */
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
virCommandAddArgList(cmd, "-net", "none", NULL);
} else {
int bootNet = 0;
if (emitBootindex) {
/* convert <boot dev='network'/> to bootindex since we didn't emit
* -boot n
*/
for (i = 0; i < def->os.nBootDevs; i++) {
if (def->os.bootDevs[i] == VIR_DOMAIN_BOOT_NET) {
bootNet = i + 1;
break;
}
}
}
for (i = 0; i < def->nnets; i++) {
virDomainNetDefPtr net = def->nets[i];
int vlan;
/* VLANs are not used with -netdev, so don't record them */
if (qemuDomainSupportsNetdev(def, qemuCaps, net))
vlan = -1;
else
vlan = i;
if (qemuBuildInterfaceCommandLine(cmd, driver, conn, def, net,
qemuCaps, vlan, bootNet, vmop) < 0)
goto error;
last_good_net = i;
bootNet = 0;
}
}
if (def->nsmartcards) {
/* -device usb-ccid was already emitted along with other
* controllers. For now, qemu handles only one smartcard. */
virDomainSmartcardDefPtr smartcard = def->smartcards[0];
char *devstr;
virBuffer opt = VIR_BUFFER_INITIALIZER;
const char *database;
if (def->nsmartcards > 1 ||
smartcard->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_CCID ||
smartcard->info.addr.ccid.controller != 0 ||
smartcard->info.addr.ccid.slot != 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("this QEMU binary lacks multiple smartcard "
"support"));
virBufferFreeAndReset(&opt);
goto error;
}
switch (smartcard->type) {
case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV) ||
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CCID_EMULATED)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("this QEMU binary lacks smartcard host "
"mode support"));
goto error;
}
virBufferAddLit(&opt, "ccid-card-emulated,backend=nss-emulated");
break;
case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV) ||
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CCID_EMULATED)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("this QEMU binary lacks smartcard host "
"mode support"));
goto error;
}
virBufferAddLit(&opt, "ccid-card-emulated,backend=certificates");
for (j = 0; j < VIR_DOMAIN_SMARTCARD_NUM_CERTIFICATES; j++) {
if (strchr(smartcard->data.cert.file[j], ',')) {
virBufferFreeAndReset(&opt);
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("invalid certificate name: %s"),
smartcard->data.cert.file[j]);
goto error;
}
virBufferAsprintf(&opt, ",cert%zu=%s", j + 1,
smartcard->data.cert.file[j]);
}
if (smartcard->data.cert.database) {
if (strchr(smartcard->data.cert.database, ',')) {
virBufferFreeAndReset(&opt);
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("invalid database name: %s"),
smartcard->data.cert.database);
goto error;
}
database = smartcard->data.cert.database;
} else {
database = VIR_DOMAIN_SMARTCARD_DEFAULT_DATABASE;
}
virBufferAsprintf(&opt, ",db=%s", database);
break;
case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV) ||
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CCID_PASSTHRU)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("this QEMU binary lacks smartcard "
"passthrough mode support"));
goto error;
}
virCommandAddArg(cmd, "-chardev");
if (!(devstr = qemuBuildChrChardevStr(&smartcard->data.passthru,
smartcard->info.alias,
qemuCaps))) {
virBufferFreeAndReset(&opt);
goto error;
}
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
virBufferAsprintf(&opt, "ccid-card-passthru,chardev=char%s",
smartcard->info.alias);
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unexpected smartcard type %d"),
smartcard->type);
virBufferFreeAndReset(&opt);
goto error;
}
virCommandAddArg(cmd, "-device");
virBufferAsprintf(&opt, ",id=%s,bus=ccid0.0", smartcard->info.alias);
virCommandAddArgBuffer(cmd, &opt);
}
for (i = 0; i < def->nserials; i++) {
virDomainChrDefPtr serial = def->serials[i];
char *devstr;
if (serial->source.type == VIR_DOMAIN_CHR_TYPE_SPICEPORT && !spice)
continue;
/* Use -chardev with -device if they are available */
if (virQEMUCapsSupportsChardev(def, qemuCaps, serial)) {
virCommandAddArg(cmd, "-chardev");
if (!(devstr = qemuBuildChrChardevStr(&serial->source,
serial->info.alias,
qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
if (qemuBuildChrDeviceCommandLine(cmd, def, serial, qemuCaps) < 0)
goto error;
} else {
virCommandAddArg(cmd, "-serial");
if (!(devstr = qemuBuildChrArgStr(&serial->source, NULL)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
}
actualSerials++;
}
/* If we have -device, then we set -nodefault already */
if (!actualSerials && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
virCommandAddArgList(cmd, "-serial", "none", NULL);
if (!def->nparallels) {
/* If we have -device, then we set -nodefault already */
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
virCommandAddArgList(cmd, "-parallel", "none", NULL);
} else {
for (i = 0; i < def->nparallels; i++) {
virDomainChrDefPtr parallel = def->parallels[i];
char *devstr;
/* Use -chardev with -device if they are available */
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virCommandAddArg(cmd, "-chardev");
if (!(devstr = qemuBuildChrChardevStr(&parallel->source,
parallel->info.alias,
qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
if (qemuBuildChrDeviceCommandLine(cmd, def, parallel, qemuCaps) < 0)
goto error;
} else {
virCommandAddArg(cmd, "-parallel");
if (!(devstr = qemuBuildChrArgStr(&parallel->source, NULL)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
}
}
}
for (i = 0; i < def->nchannels; i++) {
virDomainChrDefPtr channel = def->channels[i];
char *devstr;
switch (channel->targetType) {
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV) ||
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("guestfwd requires QEMU to support -chardev & -device"));
goto error;
}
virCommandAddArg(cmd, "-chardev");
if (!(devstr = qemuBuildChrChardevStr(&channel->source,
channel->info.alias,
qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
if (qemuBuildChrDeviceStr(&devstr, def, channel, qemuCaps) < 0)
goto error;
virCommandAddArgList(cmd, "-netdev", devstr, NULL);
VIR_FREE(devstr);
break;
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("virtio channel requires QEMU to support -device"));
goto error;
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_SPICEVMC) &&
channel->source.type == VIR_DOMAIN_CHR_TYPE_SPICEVMC) {
/* spicevmc was originally introduced via a -device
* with a backend internal to qemu; although we prefer
* the newer -chardev interface. */
;
} else {
virCommandAddArg(cmd, "-chardev");
if (!(devstr = qemuBuildChrChardevStr(&channel->source,
channel->info.alias,
qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
}
if (qemuBuildChrDeviceCommandLine(cmd, def, channel, qemuCaps) < 0)
goto error;
break;
}
}
/* Explicit console devices */
for (i = 0; i < def->nconsoles; i++) {
virDomainChrDefPtr console = def->consoles[i];
char *devstr;
switch (console->targetType) {
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SCLP:
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SCLPLM:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("sclp console requires QEMU to support -device"));
goto error;
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SCLP_S390)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("sclp console requires QEMU to support s390-sclp"));
goto error;
}
virCommandAddArg(cmd, "-chardev");
if (!(devstr = qemuBuildChrChardevStr(&console->source,
console->info.alias,
qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
if (qemuBuildChrDeviceCommandLine(cmd, def, console, qemuCaps) < 0)
goto error;
break;
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_VIRTIO:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("virtio channel requires QEMU to support -device"));
goto error;
}
virCommandAddArg(cmd, "-chardev");
if (!(devstr = qemuBuildChrChardevStr(&console->source,
console->info.alias,
qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
if (qemuBuildChrDeviceCommandLine(cmd, def, console, qemuCaps) < 0)
goto error;
break;
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL:
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported console target type %s"),
NULLSTR(virDomainChrConsoleTargetTypeToString(console->targetType)));
goto error;
}
}
if (def->tpm) {
char *optstr;
if (!(optstr = qemuBuildTPMBackendStr(def, qemuCaps, emulator)))
goto error;
virCommandAddArgList(cmd, "-tpmdev", optstr, NULL);
VIR_FREE(optstr);
if (!(optstr = qemuBuildTPMDevStr(def, qemuCaps, emulator)))
goto error;
virCommandAddArgList(cmd, "-device", optstr, NULL);
VIR_FREE(optstr);
}
for (i = 0; i < def->ninputs; i++) {
virDomainInputDefPtr input = def->inputs[i];
if (input->bus == VIR_DOMAIN_INPUT_BUS_USB) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
char *optstr;
virCommandAddArg(cmd, "-device");
if (!(optstr = qemuBuildUSBInputDevStr(def, input, qemuCaps)))
goto error;
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
} else {
switch (input->type) {
case VIR_DOMAIN_INPUT_TYPE_MOUSE:
virCommandAddArgList(cmd, "-usbdevice", "mouse", NULL);
break;
case VIR_DOMAIN_INPUT_TYPE_TABLET:
virCommandAddArgList(cmd, "-usbdevice", "tablet", NULL);
break;
case VIR_DOMAIN_INPUT_TYPE_KBD:
virCommandAddArgList(cmd, "-usbdevice", "keyboard", NULL);
break;
}
}
}
}
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_0_10) && sdl + vnc + spice > 1) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("only 1 graphics device is supported"));
goto error;
}
if (sdl > 1 || vnc > 1 || spice > 1) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("only 1 graphics device of each type "
"(sdl, vnc, spice) is supported"));
goto error;
}
for (i = 0; i < def->ngraphics; ++i) {
if (qemuBuildGraphicsCommandLine(cfg, cmd, def, qemuCaps,
def->graphics[i]) < 0)
goto error;
}
if (def->nvideos > 0) {
int primaryVideoType = def->videos[0]->type;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VIDEO_PRIMARY) &&
((primaryVideoType == VIR_DOMAIN_VIDEO_TYPE_VGA &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VGA)) ||
(primaryVideoType == VIR_DOMAIN_VIDEO_TYPE_CIRRUS &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_CIRRUS_VGA)) ||
(primaryVideoType == VIR_DOMAIN_VIDEO_TYPE_VMVGA &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VMWARE_SVGA)) ||
(primaryVideoType == VIR_DOMAIN_VIDEO_TYPE_QXL &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_QXL_VGA)))
) {
for (i = 0; i < def->nvideos; i++) {
char *str;
virCommandAddArg(cmd, "-device");
if (!(str = qemuBuildDeviceVideoStr(def, def->videos[i], qemuCaps, !i)))
goto error;
virCommandAddArg(cmd, str);
VIR_FREE(str);
}
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VGA)) {
if (primaryVideoType == VIR_DOMAIN_VIDEO_TYPE_XEN) {
/* nothing - vga has no effect on Xen pvfb */
} else {
if ((primaryVideoType == VIR_DOMAIN_VIDEO_TYPE_QXL) &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VGA_QXL)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("This QEMU does not support QXL graphics adapters"));
goto error;
}
const char *vgastr = qemuVideoTypeToString(primaryVideoType);
if (!vgastr || STREQ(vgastr, "")) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("video type %s is not supported with QEMU"),
virDomainVideoTypeToString(primaryVideoType));
goto error;
}
virCommandAddArgList(cmd, "-vga", vgastr, NULL);
if (def->videos[0]->type == VIR_DOMAIN_VIDEO_TYPE_QXL &&
(def->videos[0]->vram || def->videos[0]->ram) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
const char *dev = (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_QXL_VGA)
? "qxl-vga" : "qxl");
int ram = def->videos[0]->ram;
int vram = def->videos[0]->vram;
if (vram > (UINT_MAX / 1024)) {
virReportError(VIR_ERR_OVERFLOW,
_("value for 'vram' must be less than '%u'"),
UINT_MAX / 1024);
goto error;
}
if (ram > (UINT_MAX / 1024)) {
virReportError(VIR_ERR_OVERFLOW,
_("value for 'ram' must be less than '%u'"),
UINT_MAX / 1024);
goto error;
}
if (ram) {
virCommandAddArg(cmd, "-global");
virCommandAddArgFormat(cmd, "%s.ram_size=%u",
dev, ram * 1024);
}
if (vram) {
virCommandAddArg(cmd, "-global");
virCommandAddArgFormat(cmd, "%s.vram_size=%u",
dev, vram * 1024);
}
}
}
if (def->nvideos > 1) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
for (i = 1; i < def->nvideos; i++) {
char *str;
if (def->videos[i]->type != VIR_DOMAIN_VIDEO_TYPE_QXL) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("video type %s is only valid as primary video card"),
virDomainVideoTypeToString(def->videos[0]->type));
goto error;
}
virCommandAddArg(cmd, "-device");
if (!(str = qemuBuildDeviceVideoStr(def, def->videos[i], qemuCaps, false)))
goto error;
virCommandAddArg(cmd, str);
VIR_FREE(str);
}
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("only one video card is currently supported"));
goto error;
}
}
} else {
switch (def->videos[0]->type) {
case VIR_DOMAIN_VIDEO_TYPE_VGA:
virCommandAddArg(cmd, "-std-vga");
break;
case VIR_DOMAIN_VIDEO_TYPE_VMVGA:
virCommandAddArg(cmd, "-vmwarevga");
break;
case VIR_DOMAIN_VIDEO_TYPE_XEN:
case VIR_DOMAIN_VIDEO_TYPE_CIRRUS:
/* No special args - this is the default */
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("video type %s is not supported with this QEMU"),
virDomainVideoTypeToString(def->videos[0]->type));
goto error;
}
if (def->nvideos > 1) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("only one video card is currently supported"));
goto error;
}
}
} else {
/* If we have -device, then we set -nodefault already */
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_VGA) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_VGA_NONE))
virCommandAddArgList(cmd, "-vga", "none", NULL);
}
/* Add sound hardware */
if (def->nsounds) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
for (i = 0; i < def->nsounds; i++) {
virDomainSoundDefPtr sound = def->sounds[i];
char *str = NULL;
/* Sadly pcspk device doesn't use -device syntax. Fortunately
* we don't need to set any PCI address on it, so we don't
* mind too much */
if (sound->model == VIR_DOMAIN_SOUND_MODEL_PCSPK) {
virCommandAddArgList(cmd, "-soundhw", "pcspk", NULL);
} else {
virCommandAddArg(cmd, "-device");
if (!(str = qemuBuildSoundDevStr(def, sound, qemuCaps)))
goto error;
virCommandAddArg(cmd, str);
VIR_FREE(str);
if (sound->model == VIR_DOMAIN_SOUND_MODEL_ICH6 ||
sound->model == VIR_DOMAIN_SOUND_MODEL_ICH9) {
char *codecstr = NULL;
for (j = 0; j < sound->ncodecs; j++) {
virCommandAddArg(cmd, "-device");
if (!(codecstr = qemuBuildSoundCodecStr(sound, sound->codecs[j], qemuCaps))) {
goto error;
}
virCommandAddArg(cmd, codecstr);
VIR_FREE(codecstr);
}
if (j == 0) {
virDomainSoundCodecDef codec = {
VIR_DOMAIN_SOUND_CODEC_TYPE_DUPLEX,
0
};
virCommandAddArg(cmd, "-device");
if (!(codecstr = qemuBuildSoundCodecStr(sound, &codec, qemuCaps))) {
goto error;
}
virCommandAddArg(cmd, codecstr);
VIR_FREE(codecstr);
}
}
}
}
} else {
int size = 100;
char *modstr;
if (VIR_ALLOC_N(modstr, size+1) < 0)
goto error;
for (i = 0; i < def->nsounds && size > 0; i++) {
virDomainSoundDefPtr sound = def->sounds[i];
const char *model = virDomainSoundModelTypeToString(sound->model);
if (!model) {
VIR_FREE(modstr);
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("invalid sound model"));
goto error;
}
if (sound->model == VIR_DOMAIN_SOUND_MODEL_ICH6 ||
sound->model == VIR_DOMAIN_SOUND_MODEL_ICH9) {
VIR_FREE(modstr);
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("this QEMU binary lacks hda support"));
goto error;
}
strncat(modstr, model, size);
size -= strlen(model);
if (i < (def->nsounds - 1))
strncat(modstr, ",", size--);
}
virCommandAddArgList(cmd, "-soundhw", modstr, NULL);
VIR_FREE(modstr);
}
}
/* Add watchdog hardware */
if (def->watchdog) {
virDomainWatchdogDefPtr watchdog = def->watchdog;
char *optstr;
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virCommandAddArg(cmd, "-device");
optstr = qemuBuildWatchdogDevStr(def, watchdog, qemuCaps);
if (!optstr)
goto error;
} else {
virCommandAddArg(cmd, "-watchdog");
const char *model = virDomainWatchdogModelTypeToString(watchdog->model);
if (!model) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("missing watchdog model"));
goto error;
}
if (VIR_STRDUP(optstr, model) < 0)
goto error;
}
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
int act = watchdog->action;
if (act == VIR_DOMAIN_WATCHDOG_ACTION_DUMP)
act = VIR_DOMAIN_WATCHDOG_ACTION_PAUSE;
const char *action = virDomainWatchdogActionTypeToString(act);
if (!action) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("invalid watchdog action"));
goto error;
}
virCommandAddArgList(cmd, "-watchdog-action", action, NULL);
}
/* Add redirected devices */
for (i = 0; i < def->nredirdevs; i++) {
virDomainRedirdevDefPtr redirdev = def->redirdevs[i];
char *devstr;
virCommandAddArg(cmd, "-chardev");
if (!(devstr = qemuBuildChrChardevStr(&redirdev->source.chr,
redirdev->info.alias,
qemuCaps))) {
goto error;
}
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))
goto error;
virCommandAddArg(cmd, "-device");
if (!(devstr = qemuBuildRedirdevDevStr(def, redirdev, qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
}
/* Add host passthrough hardware */
for (i = 0; i < def->nhostdevs; i++) {
virDomainHostdevDefPtr hostdev = def->hostdevs[i];
char *devstr;
if (hostdev->info->bootIndex) {
if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS ||
(hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI &&
hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB &&
hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("booting from assigned devices is only "
"supported for PCI, USB and SCSI devices"));
goto error;
} else {
if (hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) {
if (hostdev->source.subsys.u.pci.backend
== VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_VFIO_PCI_BOOTINDEX)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("booting from PCI devices assigned with VFIO "
"is not supported with this version of qemu"));
goto error;
}
} else {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_PCI_BOOTINDEX)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("booting from assigned PCI devices is not "
"supported with this version of qemu"));
goto error;
}
}
}
if (hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_USB_HOST_BOOTINDEX)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("booting from assigned USB devices is not "
"supported with this version of qemu"));
goto error;
}
if (hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI &&
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_SCSI_GENERIC_BOOTINDEX)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("booting from assigned SCSI devices is not"
" supported with this version of qemu"));
goto error;
}
}
}
/* USB */
if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
virCommandAddArg(cmd, "-device");
if (!(devstr = qemuBuildUSBHostdevDevStr(def, hostdev, qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
} else {
virCommandAddArg(cmd, "-usbdevice");
if (!(devstr = qemuBuildUSBHostdevUsbDevStr(hostdev)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
}
}
/* PCI */
if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) {
int backend = hostdev->source.subsys.u.pci.backend;
if (backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VFIO_PCI)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("VFIO PCI device assignment is not "
"supported by this version of qemu"));
goto error;
}
/* VFIO requires all of the guest's memory to be locked
* resident */
mlock = true;
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
char *configfd_name = NULL;
if ((backend != VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_PCI_CONFIGFD)) {
int configfd = qemuOpenPCIConfig(hostdev);
if (configfd >= 0) {
if (virAsprintf(&configfd_name, "%d", configfd) < 0) {
VIR_FORCE_CLOSE(configfd);
goto error;
}
virCommandPassFD(cmd, configfd,
VIR_COMMAND_PASS_FD_CLOSE_PARENT);
}
}
virCommandAddArg(cmd, "-device");
devstr = qemuBuildPCIHostdevDevStr(def, hostdev, configfd_name, qemuCaps);
VIR_FREE(configfd_name);
if (!devstr)
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_PCIDEVICE)) {
virCommandAddArg(cmd, "-pcidevice");
if (!(devstr = qemuBuildPCIHostdevPCIDevStr(hostdev)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("PCI device assignment is not supported by this version of qemu"));
goto error;
}
}
/* SCSI */
if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS &&
hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_SCSI_GENERIC)) {
char *drvstr;
virCommandAddArg(cmd, "-drive");
if (!(drvstr = qemuBuildSCSIHostdevDrvStr(hostdev, qemuCaps, callbacks)))
goto error;
virCommandAddArg(cmd, drvstr);
VIR_FREE(drvstr);
virCommandAddArg(cmd, "-device");
if (!(devstr = qemuBuildSCSIHostdevDevStr(def, hostdev, qemuCaps)))
goto error;
virCommandAddArg(cmd, devstr);
VIR_FREE(devstr);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("SCSI passthrough is not supported by this version of qemu"));
goto error;
}
}
}
/* Migration is very annoying due to wildly varying syntax &
* capabilities over time of KVM / QEMU codebases.
*/
if (migrateFrom) {
virCommandAddArg(cmd, "-incoming");
if (STRPREFIX(migrateFrom, "tcp")) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_TCP)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("TCP migration is not supported with "
"this QEMU binary"));
goto error;
}
virCommandAddArg(cmd, migrateFrom);
} else if (STREQ(migrateFrom, "stdio")) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_FD)) {
virCommandAddArgFormat(cmd, "fd:%d", migrateFd);
virCommandPassFD(cmd, migrateFd, 0);
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_EXEC)) {
virCommandAddArg(cmd, "exec:cat");
virCommandSetInputFD(cmd, migrateFd);
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_KVM_STDIO)) {
virCommandAddArg(cmd, migrateFrom);
virCommandSetInputFD(cmd, migrateFd);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("STDIO migration is not supported "
"with this QEMU binary"));
goto error;
}
} else if (STRPREFIX(migrateFrom, "exec")) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_EXEC)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("EXEC migration is not supported "
"with this QEMU binary"));
goto error;
}
virCommandAddArg(cmd, migrateFrom);
} else if (STRPREFIX(migrateFrom, "fd")) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_FD)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("FD migration is not supported "
"with this QEMU binary"));
goto error;
}
virCommandAddArg(cmd, migrateFrom);
virCommandPassFD(cmd, migrateFd, 0);
} else if (STRPREFIX(migrateFrom, "unix")) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_UNIX)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
"%s", _("UNIX migration is not supported "
"with this QEMU binary"));
goto error;
}
virCommandAddArg(cmd, migrateFrom);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("unknown migration protocol"));
goto error;
}
}
/* QEMU changed its default behavior to not include the virtio balloon
* device. Explicitly request it to ensure it will be present.
*
* NB: Earlier we declared that VirtIO balloon will always be in
* slot 0x3 on bus 0x0
*/
if (STREQLEN(def->os.machine, "s390-virtio", 10) &&
virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_S390) && def->memballoon)
def->memballoon->model = VIR_DOMAIN_MEMBALLOON_MODEL_NONE;
if (def->memballoon &&
def->memballoon->model != VIR_DOMAIN_MEMBALLOON_MODEL_NONE) {
if (def->memballoon->model != VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Memory balloon device type '%s' is not supported by this version of qemu"),
virDomainMemballoonModelTypeToString(def->memballoon->model));
goto error;
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE)) {
char *optstr;
virCommandAddArg(cmd, "-device");
optstr = qemuBuildMemballoonDevStr(def, def->memballoon, qemuCaps);
if (!optstr)
goto error;
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
} else if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_BALLOON)) {
virCommandAddArgList(cmd, "-balloon", "virtio", NULL);
}
}
if (def->rng) {
/* add the RNG source backend */
if (qemuBuildRNGBackendArgs(cmd, def->rng, qemuCaps) < 0)
goto error;
/* add the device */
if (qemuBuildRNGDeviceArgs(cmd, def, def->rng, qemuCaps) < 0)
goto error;
}
if (def->nvram) {
if (def->os.arch == VIR_ARCH_PPC64 &&
STREQ(def->os.machine, "pseries")) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_NVRAM)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("nvram device is not supported by "
"this QEMU binary"));
goto error;
}
char *optstr;
virCommandAddArg(cmd, "-global");
optstr = qemuBuildNVRAMDevStr(def->nvram);
if (!optstr)
goto error;
if (optstr)
virCommandAddArg(cmd, optstr);
VIR_FREE(optstr);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("nvram device is only supported for PPC64"));
goto error;
}
}
if (snapshot)
virCommandAddArgList(cmd, "-loadvm", snapshot->def->name, NULL);
if (def->namespaceData) {
qemuDomainCmdlineDefPtr qemucmd;
qemucmd = def->namespaceData;
for (i = 0; i < qemucmd->num_args; i++)
virCommandAddArg(cmd, qemucmd->args[i]);
for (i = 0; i < qemucmd->num_env; i++)
virCommandAddEnvPair(cmd, qemucmd->env_name[i],
qemucmd->env_value[i]
? qemucmd->env_value[i] : "");
}
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_SECCOMP_SANDBOX)) {
if (cfg->seccompSandbox == 0)
virCommandAddArgList(cmd, "-sandbox", "off", NULL);
else if (cfg->seccompSandbox > 0)
virCommandAddArgList(cmd, "-sandbox", "on", NULL);
} else if (cfg->seccompSandbox > 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("QEMU does not support seccomp sandboxes"));
goto error;
}
if (def->panic) {
if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_PANIC)) {
if (def->panic->info.addr.isa.iobase > 0) {
virCommandAddArg(cmd, "-device");
virCommandAddArgFormat(cmd, "pvpanic,ioport=%d",
def->panic->info.addr.isa.iobase);
} else {
virCommandAddArgList(cmd, "-device", "pvpanic", NULL);
}
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("your QEMU is too old to support pvpanic"));
goto error;
}
}
if (mlock) {
unsigned long long memKB;
/* VFIO requires all of the guest's memory to be
* locked resident, plus some amount for IO
* space. Alex Williamson suggested adding 1GiB for IO
* space just to be safe (some finer tuning might be
* nice, though).
*/
memKB = def->mem.hard_limit ?
def->mem.hard_limit : def->mem.max_balloon + 1024 * 1024;
virCommandSetMaxMemLock(cmd, memKB * 1024);
}
virObjectUnref(cfg);
return cmd;
error:
virObjectUnref(cfg);
/* free up any resources in the network driver
* but don't overwrite the original error */
originalError = virSaveLastError();
for (i = 0; last_good_net != -1 && i <= last_good_net; i++)
virDomainConfNWFilterTeardown(def->nets[i]);
virSetError(originalError);
virFreeError(originalError);
virCommandFree(cmd);
return NULL;
}
/* This function generates the correct '-device' string for character
* devices of each architecture.
*/
static int
qemuBuildSerialChrDeviceStr(char **deviceStr,
virDomainDefPtr def,
virDomainChrDefPtr serial,
virQEMUCapsPtr qemuCaps,
virArch arch,
char *machine)
{
virBuffer cmd = VIR_BUFFER_INITIALIZER;
if ((arch == VIR_ARCH_PPC64) && STREQ(machine, "pseries")) {
if (serial->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL &&
serial->info.type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO) {
virBufferAsprintf(&cmd, "spapr-vty,chardev=char%s",
serial->info.alias);
if (qemuBuildDeviceAddressStr(&cmd, def, &serial->info, qemuCaps) < 0)
goto error;
}
} else {
virBufferAsprintf(&cmd, "%s,chardev=char%s,id=%s",
virDomainChrSerialTargetTypeToString(serial->targetType),
serial->info.alias, serial->info.alias);
if (serial->targetType == VIR_DOMAIN_CHR_SERIAL_TARGET_TYPE_USB) {
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_USB_SERIAL)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("usb-serial is not supported in this QEMU binary"));
goto error;
}
if (serial->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
serial->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_USB) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("usb-serial requires address of usb type"));
goto error;
}
if (qemuBuildDeviceAddressStr(&cmd, def, &serial->info, qemuCaps) < 0)
goto error;
}
}
if (virBufferError(&cmd)) {
virReportOOMError();
goto error;
}
*deviceStr = virBufferContentAndReset(&cmd);
return 0;
error:
virBufferFreeAndReset(&cmd);
return -1;
}
static int
qemuBuildParallelChrDeviceStr(char **deviceStr,
virDomainChrDefPtr chr)
{
if (virAsprintf(deviceStr, "isa-parallel,chardev=char%s,id=%s",
chr->info.alias, chr->info.alias) < 0) {
virReportOOMError();
return -1;
}
return 0;
}
static int
qemuBuildChannelChrDeviceStr(char **deviceStr,
virDomainChrDefPtr chr,
virQEMUCapsPtr qemuCaps)
{
int ret = -1;
char *addr = NULL;
int port;
switch ((enum virDomainChrChannelTargetType) chr->targetType) {
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD:
addr = virSocketAddrFormat(chr->target.addr);
if (!addr)
return ret;
port = virSocketAddrGetPort(chr->target.addr);
if (virAsprintf(deviceStr,
"user,guestfwd=tcp:%s:%i,chardev=char%s,id=user-%s",
addr, port, chr->info.alias, chr->info.alias) < 0) {
virReportOOMError();
goto cleanup;
}
break;
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO:
if (!(*deviceStr = qemuBuildVirtioSerialPortDevStr(chr, qemuCaps)))
goto cleanup;
break;
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_NONE:
case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_LAST:
return ret;
}
ret = 0;
cleanup:
VIR_FREE(addr);
return ret;
}
static int
qemuBuildConsoleChrDeviceStr(char **deviceStr,
virDomainChrDefPtr chr,
virQEMUCapsPtr qemuCaps)
{
int ret = -1;
switch ((enum virDomainChrConsoleTargetType) chr->targetType) {
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SCLP:
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SCLPLM:
if (!(*deviceStr = qemuBuildSclpDevStr(chr)))
goto cleanup;
break;
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_VIRTIO:
if (!(*deviceStr = qemuBuildVirtioSerialPortDevStr(chr, qemuCaps)))
goto cleanup;
break;
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL:
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_NONE:
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_XEN:
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_UML:
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_LXC:
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_OPENVZ:
case VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_LAST:
break;
}
ret = 0;
cleanup:
return ret;
}
int
qemuBuildChrDeviceStr(char **deviceStr,
virDomainDefPtr vmdef,
virDomainChrDefPtr chr,
virQEMUCapsPtr qemuCaps)
{
int ret = -1;
switch ((enum virDomainChrDeviceType) chr->deviceType) {
case VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL:
ret = qemuBuildSerialChrDeviceStr(deviceStr, vmdef, chr, qemuCaps,
vmdef->os.arch,
vmdef->os.machine);
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL:
ret = qemuBuildParallelChrDeviceStr(deviceStr, chr);
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL:
ret = qemuBuildChannelChrDeviceStr(deviceStr, chr, qemuCaps);
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE:
ret = qemuBuildConsoleChrDeviceStr(deviceStr, chr, qemuCaps);
break;
case VIR_DOMAIN_CHR_DEVICE_TYPE_LAST:
return ret;
}
return ret;
}
/*
* This method takes a string representing a QEMU command line ARGV set
* optionally prefixed by a list of environment variables. It then tries
* to split it up into a NULL terminated list of env & argv, splitting
* on space
*/
static int qemuStringToArgvEnv(const char *args,
char ***retenv,
char ***retargv)
{
char **arglist = NULL;
size_t argcount = 0;
size_t argalloc = 0;
size_t envend;
size_t i;
const char *curr = args;
const char *start;
char **progenv = NULL;
char **progargv = NULL;
/* Iterate over string, splitting on sequences of ' ' */
while (curr && *curr != '\0') {
char *arg;
const char *next;
start = curr;
/* accept a space in CEPH_ARGS */
if (STRPREFIX(curr, "CEPH_ARGS=-m ")) {
start += strlen("CEPH_ARGS=-m ");
}
if (*start == '\'') {
if (start == curr)
curr++;
next = strchr(start + 1, '\'');
} else if (*start == '"') {
if (start == curr)
curr++;
next = strchr(start + 1, '"');
} else {
next = strchr(start, ' ');
}
if (!next)
next = strchr(curr, '\n');
if (VIR_STRNDUP(arg, curr, next ? next - curr : -1) < 0)
goto error;
if (next && (*next == '\'' || *next == '"'))
next++;
if (VIR_RESIZE_N(arglist, argalloc, argcount, 2) < 0) {
VIR_FREE(arg);
goto error;
}
arglist[argcount++] = arg;
arglist[argcount] = NULL;
while (next && c_isspace(*next))
next++;
curr = next;
}
/* Iterate over list of args, finding first arg not containing
* the '=' character (eg, skip over env vars FOO=bar) */
for (envend = 0; ((envend < argcount) &&
(strchr(arglist[envend], '=') != NULL));
envend++)
; /* nada */
/* Copy the list of env vars */
if (envend > 0) {
if (VIR_REALLOC_N(progenv, envend+1) < 0)
goto error;
for (i = 0; i < envend; i++)
progenv[i] = arglist[i];
progenv[i] = NULL;
}
/* Copy the list of argv */
if (VIR_REALLOC_N(progargv, argcount-envend + 1) < 0)
goto error;
for (i = envend; i < argcount; i++)
progargv[i-envend] = arglist[i];
progargv[i-envend] = NULL;
VIR_FREE(arglist);
*retenv = progenv;
*retargv = progargv;
return 0;
error:
VIR_FREE(progenv);
VIR_FREE(progargv);
virStringFreeList(arglist);
return -1;
}
/*
* Search for a named env variable, and return the value part
*/
static const char *qemuFindEnv(char **progenv,
const char *name)
{
size_t i;
int len = strlen(name);
for (i = 0; progenv && progenv[i]; i++) {
if (STREQLEN(progenv[i], name, len) &&
progenv[i][len] == '=')
return progenv[i] + len + 1;
}
return NULL;
}
/*
* Takes a string containing a set of key=value,key=value,key...
* parameters and splits them up, returning two arrays with
* the individual keys and values. If allowEmptyValue is nonzero,
* the "=value" part is optional and if a key with no value is found,
* NULL is be placed into corresponding place in retvalues.
*/
int
qemuParseKeywords(const char *str,
char ***retkeywords,
char ***retvalues,
int *retnkeywords,
int allowEmptyValue)
{
int keywordCount = 0;
int keywordAlloc = 0;
char **keywords = NULL;
char **values = NULL;
const char *start = str;
const char *end;
size_t i;
*retkeywords = NULL;
*retvalues = NULL;
*retnkeywords = 0;
end = start + strlen(str);
while (start) {
const char *separator;
const char *endmark;
char *keyword;
char *value = NULL;
endmark = start;
do {
/* Qemu accepts ',,' as an escape for a literal comma;
* skip past those here while searching for the end of the
* value, then strip them down below */
endmark = strchr(endmark, ',');
} while (endmark && endmark[1] == ',' && (endmark += 2));
if (!endmark)
endmark = end;
if (!(separator = strchr(start, '=')))
separator = end;
if (separator >= endmark) {
if (!allowEmptyValue) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("malformed keyword arguments in '%s'"), str);
goto error;
}
separator = endmark;
}
if (VIR_STRNDUP(keyword, start, separator - start) < 0)
goto error;
if (separator < endmark) {
separator++;
if (VIR_STRNDUP(value, separator, endmark - separator) < 0) {
VIR_FREE(keyword);
goto error;
}
if (strchr(value, ',')) {
char *p = strchr(value, ',') + 1;
char *q = p + 1;
while (*q) {
if (*q == ',')
q++;
*p++ = *q++;
}
*p = '\0';
}
}
if (keywordAlloc == keywordCount) {
if (VIR_REALLOC_N(keywords, keywordAlloc + 10) < 0 ||
VIR_REALLOC_N(values, keywordAlloc + 10) < 0) {
VIR_FREE(keyword);
VIR_FREE(value);
goto error;
}
keywordAlloc += 10;
}
keywords[keywordCount] = keyword;
values[keywordCount] = value;
keywordCount++;
start = endmark < end ? endmark + 1 : NULL;
}
*retkeywords = keywords;
*retvalues = values;
*retnkeywords = keywordCount;
return 0;
error:
for (i = 0; i < keywordCount; i++) {
VIR_FREE(keywords[i]);
VIR_FREE(values[i]);
}
VIR_FREE(keywords);
VIR_FREE(values);
return -1;
}
/*
* Tries to parse new style QEMU -drive args.
*
* eg -drive file=/dev/HostVG/VirtData1,if=ide,index=1
*
* Will fail if not using the 'index' keyword
*/
static virDomainDiskDefPtr
qemuParseCommandLineDisk(virDomainXMLOptionPtr xmlopt,
const char *val,
virDomainDefPtr dom,
int nvirtiodisk,
bool old_style_ceph_args)
{
virDomainDiskDefPtr def = NULL;
char **keywords;
char **values;
int nkeywords;
size_t i;
int idx = -1;
int busid = -1;
int unitid = -1;
int trans = VIR_DOMAIN_DISK_TRANS_DEFAULT;
if (qemuParseKeywords(val,
&keywords,
&values,
&nkeywords,
0) < 0)
return NULL;
if (VIR_ALLOC(def) < 0)
goto cleanup;
if (((dom->os.arch == VIR_ARCH_PPC64) &&
dom->os.machine && STREQ(dom->os.machine, "pseries")))
def->bus = VIR_DOMAIN_DISK_BUS_SCSI;
else
def->bus = VIR_DOMAIN_DISK_BUS_IDE;
def->device = VIR_DOMAIN_DISK_DEVICE_DISK;
def->type = VIR_DOMAIN_DISK_TYPE_FILE;
for (i = 0; i < nkeywords; i++) {
if (STREQ(keywords[i], "file")) {
if (values[i] && STRNEQ(values[i], "")) {
def->src = values[i];
values[i] = NULL;
if (STRPREFIX(def->src, "/dev/"))
def->type = VIR_DOMAIN_DISK_TYPE_BLOCK;
else if (STRPREFIX(def->src, "nbd:") ||
STRPREFIX(def->src, "nbd+")) {
def->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
def->protocol = VIR_DOMAIN_DISK_PROTOCOL_NBD;
if (qemuParseNBDString(def) < 0)
goto error;
} else if (STRPREFIX(def->src, "rbd:")) {
char *p = def->src;
def->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
def->protocol = VIR_DOMAIN_DISK_PROTOCOL_RBD;
if (VIR_STRDUP(def->src, p + strlen("rbd:")) < 0)
goto error;
/* old-style CEPH_ARGS env variable is parsed later */
if (!old_style_ceph_args && qemuParseRBDString(def) < 0) {
VIR_FREE(p);
goto error;
}
VIR_FREE(p);
} else if (STRPREFIX(def->src, "gluster:") ||
STRPREFIX(def->src, "gluster+")) {
def->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
def->protocol = VIR_DOMAIN_DISK_PROTOCOL_GLUSTER;
if (qemuParseGlusterString(def) < 0)
goto error;
} else if (STRPREFIX(def->src, "iscsi:")) {
def->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
def->protocol = VIR_DOMAIN_DISK_PROTOCOL_ISCSI;
if (qemuParseISCSIString(def) < 0)
goto error;
} else if (STRPREFIX(def->src, "sheepdog:")) {
char *p = def->src;
char *port, *vdi;
def->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
def->protocol = VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG;
if (VIR_STRDUP(def->src, p + strlen("sheepdog:")) < 0)
goto error;
VIR_FREE(p);
/* def->src must be [vdiname] or [host]:[port]:[vdiname] */
port = strchr(def->src, ':');
if (port) {
*port = '\0';
vdi = strchr(port + 1, ':');
if (!vdi) {
*port = ':';
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse sheepdog filename '%s'"), def->src);
goto error;
}
port++;
*vdi++ = '\0';
if (VIR_ALLOC(def->hosts) < 0)
goto error;
def->nhosts = 1;
def->hosts->name = def->src;
if (VIR_STRDUP(def->hosts->port, port) < 0)
goto error;
def->hosts->transport = VIR_DOMAIN_DISK_PROTO_TRANS_TCP;
def->hosts->socket = NULL;
if (VIR_STRDUP(def->src, vdi) < 0)
goto error;
}
} else
def->type = VIR_DOMAIN_DISK_TYPE_FILE;
} else {
def->type = VIR_DOMAIN_DISK_TYPE_FILE;
}
} else if (STREQ(keywords[i], "if")) {
if (STREQ(values[i], "ide")) {
def->bus = VIR_DOMAIN_DISK_BUS_IDE;
if (((dom->os.arch == VIR_ARCH_PPC64) &&
dom->os.machine && STREQ(dom->os.machine, "pseries"))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("pseries systems do not support ide devices '%s'"), val);
goto error;
}
} else if (STREQ(values[i], "scsi"))
def->bus = VIR_DOMAIN_DISK_BUS_SCSI;
else if (STREQ(values[i], "virtio"))
def->bus = VIR_DOMAIN_DISK_BUS_VIRTIO;
else if (STREQ(values[i], "xen"))
def->bus = VIR_DOMAIN_DISK_BUS_XEN;
else if (STREQ(values[i], "sd"))
def->bus = VIR_DOMAIN_DISK_BUS_SD;
} else if (STREQ(keywords[i], "media")) {
if (STREQ(values[i], "cdrom")) {
def->device = VIR_DOMAIN_DISK_DEVICE_CDROM;
def->readonly = true;
} else if (STREQ(values[i], "floppy"))
def->device = VIR_DOMAIN_DISK_DEVICE_FLOPPY;
} else if (STREQ(keywords[i], "format")) {
if (VIR_STRDUP(def->driverName, "qemu") < 0)
goto error;
def->format = virStorageFileFormatTypeFromString(values[i]);
} else if (STREQ(keywords[i], "cache")) {
if (STREQ(values[i], "off") ||
STREQ(values[i], "none"))
def->cachemode = VIR_DOMAIN_DISK_CACHE_DISABLE;
else if (STREQ(values[i], "writeback") ||
STREQ(values[i], "on"))
def->cachemode = VIR_DOMAIN_DISK_CACHE_WRITEBACK;
else if (STREQ(values[i], "writethrough"))
def->cachemode = VIR_DOMAIN_DISK_CACHE_WRITETHRU;
else if (STREQ(values[i], "directsync"))
def->cachemode = VIR_DOMAIN_DISK_CACHE_DIRECTSYNC;
else if (STREQ(values[i], "unsafe"))
def->cachemode = VIR_DOMAIN_DISK_CACHE_UNSAFE;
} else if (STREQ(keywords[i], "werror")) {
if (STREQ(values[i], "stop"))
def->error_policy = VIR_DOMAIN_DISK_ERROR_POLICY_STOP;
else if (STREQ(values[i], "report"))
def->error_policy = VIR_DOMAIN_DISK_ERROR_POLICY_REPORT;
else if (STREQ(values[i], "ignore"))
def->error_policy = VIR_DOMAIN_DISK_ERROR_POLICY_IGNORE;
else if (STREQ(values[i], "enospc"))
def->error_policy = VIR_DOMAIN_DISK_ERROR_POLICY_ENOSPACE;
} else if (STREQ(keywords[i], "rerror")) {
if (STREQ(values[i], "stop"))
def->rerror_policy = VIR_DOMAIN_DISK_ERROR_POLICY_STOP;
else if (STREQ(values[i], "report"))
def->rerror_policy = VIR_DOMAIN_DISK_ERROR_POLICY_REPORT;
else if (STREQ(values[i], "ignore"))
def->rerror_policy = VIR_DOMAIN_DISK_ERROR_POLICY_IGNORE;
} else if (STREQ(keywords[i], "index")) {
if (virStrToLong_i(values[i], NULL, 10, &idx) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse drive index '%s'"), val);
goto error;
}
} else if (STREQ(keywords[i], "bus")) {
if (virStrToLong_i(values[i], NULL, 10, &busid) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse drive bus '%s'"), val);
goto error;
}
} else if (STREQ(keywords[i], "unit")) {
if (virStrToLong_i(values[i], NULL, 10, &unitid) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse drive unit '%s'"), val);
goto error;
}
} else if (STREQ(keywords[i], "readonly")) {
if ((values[i] == NULL) || STREQ(values[i], "on"))
def->readonly = true;
} else if (STREQ(keywords[i], "aio")) {
if ((def->iomode = virDomainDiskIoTypeFromString(values[i])) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse io mode '%s'"), values[i]);
goto error;
}
} else if (STREQ(keywords[i], "cyls")) {
if (virStrToLong_ui(values[i], NULL, 10,
&(def->geometry.cylinders)) < 0) {
virDomainDiskDefFree(def);
def = NULL;
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse cylinders value'%s'"),
values[i]);
goto error;
}
} else if (STREQ(keywords[i], "heads")) {
if (virStrToLong_ui(values[i], NULL, 10,
&(def->geometry.heads)) < 0) {
virDomainDiskDefFree(def);
def = NULL;
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse heads value'%s'"),
values[i]);
goto error;
}
} else if (STREQ(keywords[i], "secs")) {
if (virStrToLong_ui(values[i], NULL, 10,
&(def->geometry.sectors)) < 0) {
virDomainDiskDefFree(def);
def = NULL;
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse sectors value'%s'"),
values[i]);
goto error;
}
} else if (STREQ(keywords[i], "trans")) {
def->geometry.trans =
virDomainDiskGeometryTransTypeFromString(values[i]);
if ((trans < VIR_DOMAIN_DISK_TRANS_DEFAULT) ||
(trans >= VIR_DOMAIN_DISK_TRANS_LAST)) {
virDomainDiskDefFree(def);
def = NULL;
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse translation value'%s'"),
values[i]);
goto error;
}
}
}
if (def->rerror_policy == def->error_policy)
def->rerror_policy = 0;
if (!def->src &&
def->device == VIR_DOMAIN_DISK_DEVICE_DISK &&
def->type != VIR_DOMAIN_DISK_TYPE_NETWORK) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing file parameter in drive '%s'"), val);
goto error;
}
if (idx == -1 &&
def->bus == VIR_DOMAIN_DISK_BUS_VIRTIO)
idx = nvirtiodisk;
if (idx == -1 &&
unitid == -1 &&
busid == -1) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing index/unit/bus parameter in drive '%s'"),
val);
goto error;
}
if (idx == -1) {
if (unitid == -1)
unitid = 0;
if (busid == -1)
busid = 0;
switch (def->bus) {
case VIR_DOMAIN_DISK_BUS_IDE:
idx = (busid * 2) + unitid;
break;
case VIR_DOMAIN_DISK_BUS_SCSI:
idx = (busid * 7) + unitid;
break;
default:
idx = unitid;
break;
}
}
if (def->bus == VIR_DOMAIN_DISK_BUS_IDE) {
ignore_value(VIR_STRDUP(def->dst, "hda"));
} else if (def->bus == VIR_DOMAIN_DISK_BUS_SCSI ||
def->bus == VIR_DOMAIN_DISK_BUS_SD) {
ignore_value(VIR_STRDUP(def->dst, "sda"));
} else if (def->bus == VIR_DOMAIN_DISK_BUS_VIRTIO) {
ignore_value(VIR_STRDUP(def->dst, "vda"));
} else if (def->bus == VIR_DOMAIN_DISK_BUS_XEN) {
ignore_value(VIR_STRDUP(def->dst, "xvda"));
} else {
ignore_value(VIR_STRDUP(def->dst, "hda"));
}
if (!def->dst)
goto error;
if (STREQ(def->dst, "xvda"))
def->dst[3] = 'a' + idx;
else
def->dst[2] = 'a' + idx;
if (virDomainDiskDefAssignAddress(xmlopt, def) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid device name '%s'"), def->dst);
virDomainDiskDefFree(def);
def = NULL;
/* fall through to "cleanup" */
}
cleanup:
for (i = 0; i < nkeywords; i++) {
VIR_FREE(keywords[i]);
VIR_FREE(values[i]);
}
VIR_FREE(keywords);
VIR_FREE(values);
return def;
error:
virDomainDiskDefFree(def);
def = NULL;
goto cleanup;
}
/*
* Tries to find a NIC definition matching a vlan we want
*/
static const char *
qemuFindNICForVLAN(int nnics,
const char **nics,
int wantvlan)
{
size_t i;
for (i = 0; i < nnics; i++) {
int gotvlan;
const char *tmp = strstr(nics[i], "vlan=");
char *end;
if (!tmp)
continue;
tmp += strlen("vlan=");
if (virStrToLong_i(tmp, &end, 10, &gotvlan) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse NIC vlan in '%s'"), nics[i]);
return NULL;
}
if (gotvlan == wantvlan)
return nics[i];
}
if (wantvlan == 0 && nnics > 0)
return nics[0];
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot find NIC definition for vlan %d"), wantvlan);
return NULL;
}
/*
* Tries to parse a QEMU -net backend argument. Gets given
* a list of all known -net frontend arguments to try and
* match up against. Horribly complicated stuff
*/
static virDomainNetDefPtr
qemuParseCommandLineNet(virDomainXMLOptionPtr xmlopt,
const char *val,
int nnics,
const char **nics)
{
virDomainNetDefPtr def = NULL;
char **keywords = NULL;
char **values = NULL;
int nkeywords;
const char *nic;
int wantvlan = 0;
const char *tmp;
bool genmac = true;
size_t i;
tmp = strchr(val, ',');
if (tmp) {
if (qemuParseKeywords(tmp+1,
&keywords,
&values,
&nkeywords,
0) < 0)
return NULL;
} else {
nkeywords = 0;
}
if (VIR_ALLOC(def) < 0)
goto cleanup;
/* 'tap' could turn into libvirt type=ethernet, type=bridge or
* type=network, but we can't tell, so use the generic config */
if (STRPREFIX(val, "tap,"))
def->type = VIR_DOMAIN_NET_TYPE_ETHERNET;
else if (STRPREFIX(val, "socket"))
def->type = VIR_DOMAIN_NET_TYPE_CLIENT;
else if (STRPREFIX(val, "user"))
def->type = VIR_DOMAIN_NET_TYPE_USER;
else
def->type = VIR_DOMAIN_NET_TYPE_ETHERNET;
for (i = 0; i < nkeywords; i++) {
if (STREQ(keywords[i], "vlan")) {
if (virStrToLong_i(values[i], NULL, 10, &wantvlan) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse vlan in '%s'"), val);
virDomainNetDefFree(def);
def = NULL;
goto cleanup;
}
} else if (def->type == VIR_DOMAIN_NET_TYPE_ETHERNET &&
STREQ(keywords[i], "script") && STRNEQ(values[i], "")) {
def->script = values[i];
values[i] = NULL;
} else if (def->type == VIR_DOMAIN_NET_TYPE_ETHERNET &&
STREQ(keywords[i], "ifname")) {
def->ifname = values[i];
values[i] = NULL;
}
}
/* Done parsing the nic backend. Now to try and find corresponding
* frontend, based off vlan number. NB this assumes a 1-1 mapping
*/
nic = qemuFindNICForVLAN(nnics, nics, wantvlan);
if (!nic) {
virDomainNetDefFree(def);
def = NULL;
goto cleanup;
}
if (!STRPREFIX(nic, "nic")) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse NIC definition '%s'"), nic);
virDomainNetDefFree(def);
def = NULL;
goto cleanup;
}
for (i = 0; i < nkeywords; i++) {
VIR_FREE(keywords[i]);
VIR_FREE(values[i]);
}
VIR_FREE(keywords);
VIR_FREE(values);
if (STRPREFIX(nic, "nic,")) {
if (qemuParseKeywords(nic + strlen("nic,"),
&keywords,
&values,
&nkeywords,
0) < 0) {
virDomainNetDefFree(def);
def = NULL;
goto cleanup;
}
} else {
nkeywords = 0;
}
for (i = 0; i < nkeywords; i++) {
if (STREQ(keywords[i], "macaddr")) {
genmac = false;
if (virMacAddrParse(values[i], &def->mac) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unable to parse mac address '%s'"),
values[i]);
virDomainNetDefFree(def);
def = NULL;
goto cleanup;
}
} else if (STREQ(keywords[i], "model")) {
def->model = values[i];
values[i] = NULL;
} else if (STREQ(keywords[i], "vhost")) {
if ((values[i] == NULL) || STREQ(values[i], "on")) {
def->driver.virtio.name = VIR_DOMAIN_NET_BACKEND_TYPE_VHOST;
} else if (STREQ(keywords[i], "off")) {
def->driver.virtio.name = VIR_DOMAIN_NET_BACKEND_TYPE_QEMU;
}
} else if (STREQ(keywords[i], "sndbuf") && values[i]) {
if (virStrToLong_ul(values[i], NULL, 10, &def->tune.sndbuf) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse sndbuf size in '%s'"), val);
virDomainNetDefFree(def);
def = NULL;
goto cleanup;
}
def->tune.sndbuf_specified = true;
}
}
if (genmac)
virDomainNetGenerateMAC(xmlopt, &def->mac);
cleanup:
for (i = 0; i < nkeywords; i++) {
VIR_FREE(keywords[i]);
VIR_FREE(values[i]);
}
VIR_FREE(keywords);
VIR_FREE(values);
return def;
}
/*
* Tries to parse a QEMU PCI device
*/
static virDomainHostdevDefPtr
qemuParseCommandLinePCI(const char *val)
{
int bus = 0, slot = 0, func = 0;
const char *start;
char *end;
virDomainHostdevDefPtr def = virDomainHostdevDefAlloc();
if (!def)
goto error;
if (!STRPREFIX(val, "host=")) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown PCI device syntax '%s'"), val);
goto error;
}
start = val + strlen("host=");
if (virStrToLong_i(start, &end, 16, &bus) < 0 || *end != ':') {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot extract PCI device bus '%s'"), val);
goto error;
}
start = end + 1;
if (virStrToLong_i(start, &end, 16, &slot) < 0 || *end != '.') {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot extract PCI device slot '%s'"), val);
goto error;
}
start = end + 1;
if (virStrToLong_i(start, NULL, 16, &func) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot extract PCI device function '%s'"), val);
goto error;
}
def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
def->managed = true;
def->source.subsys.type = VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI;
def->source.subsys.u.pci.addr.bus = bus;
def->source.subsys.u.pci.addr.slot = slot;
def->source.subsys.u.pci.addr.function = func;
return def;
error:
virDomainHostdevDefFree(def);
return NULL;
}
/*
* Tries to parse a QEMU USB device
*/
static virDomainHostdevDefPtr
qemuParseCommandLineUSB(const char *val)
{
virDomainHostdevDefPtr def = virDomainHostdevDefAlloc();
int first = 0, second = 0;
const char *start;
char *end;
if (!def)
goto error;
if (!STRPREFIX(val, "host:")) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown USB device syntax '%s'"), val);
goto error;
}
start = val + strlen("host:");
if (strchr(start, ':')) {
if (virStrToLong_i(start, &end, 16, &first) < 0 || *end != ':') {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot extract USB device vendor '%s'"), val);
goto error;
}
start = end + 1;
if (virStrToLong_i(start, NULL, 16, &second) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot extract USB device product '%s'"), val);
goto error;
}
} else {
if (virStrToLong_i(start, &end, 10, &first) < 0 || *end != '.') {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot extract USB device bus '%s'"), val);
goto error;
}
start = end + 1;
if (virStrToLong_i(start, NULL, 10, &second) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot extract USB device address '%s'"), val);
goto error;
}
}
def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
def->managed = false;
def->source.subsys.type = VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB;
if (*end == '.') {
def->source.subsys.u.usb.bus = first;
def->source.subsys.u.usb.device = second;
} else {
def->source.subsys.u.usb.vendor = first;
def->source.subsys.u.usb.product = second;
}
return def;
error:
virDomainHostdevDefFree(def);
return NULL;
}
/*
* Tries to parse a QEMU serial/parallel device
*/
static int
qemuParseCommandLineChr(virDomainChrSourceDefPtr source,
const char *val)
{
if (STREQ(val, "null")) {
source->type = VIR_DOMAIN_CHR_TYPE_NULL;
} else if (STREQ(val, "vc")) {
source->type = VIR_DOMAIN_CHR_TYPE_VC;
} else if (STREQ(val, "pty")) {
source->type = VIR_DOMAIN_CHR_TYPE_PTY;
} else if (STRPREFIX(val, "file:")) {
source->type = VIR_DOMAIN_CHR_TYPE_FILE;
if (VIR_STRDUP(source->data.file.path, val + strlen("file:")) < 0)
goto error;
} else if (STRPREFIX(val, "pipe:")) {
source->type = VIR_DOMAIN_CHR_TYPE_PIPE;
if (VIR_STRDUP(source->data.file.path, val + strlen("pipe:")) < 0)
goto error;
} else if (STREQ(val, "stdio")) {
source->type = VIR_DOMAIN_CHR_TYPE_STDIO;
} else if (STRPREFIX(val, "udp:")) {
const char *svc1, *host2, *svc2;
source->type = VIR_DOMAIN_CHR_TYPE_UDP;
val += strlen("udp:");
svc1 = strchr(val, ':');
host2 = svc1 ? strchr(svc1, '@') : NULL;
svc2 = host2 ? strchr(host2, ':') : NULL;
if (svc1 && svc1 != val &&
VIR_STRNDUP(source->data.udp.connectHost, val, svc1 - val) < 0)
goto error;
if (svc1) {
svc1++;
if (VIR_STRNDUP(source->data.udp.connectService, svc1,
host2 ? host2 - svc1 : strlen(svc1)) < 0)
goto error;
}
if (host2) {
host2++;
if (svc2 && svc2 != host2 &&
VIR_STRNDUP(source->data.udp.bindHost, host2, svc2 - host2) < 0)
goto error;
}
if (svc2) {
svc2++;
if (STRNEQ(svc2, "0")) {
if (VIR_STRDUP(source->data.udp.bindService, svc2) < 0)
goto error;
}
}
} else if (STRPREFIX(val, "tcp:") ||
STRPREFIX(val, "telnet:")) {
const char *opt, *svc;
source->type = VIR_DOMAIN_CHR_TYPE_TCP;
if (STRPREFIX(val, "tcp:")) {
val += strlen("tcp:");
} else {
val += strlen("telnet:");
source->data.tcp.protocol = VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET;
}
svc = strchr(val, ':');
if (!svc) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot find port number in character device %s"), val);
goto error;
}
opt = strchr(svc, ',');
if (opt && strstr(opt, "server"))
source->data.tcp.listen = true;
if (VIR_STRNDUP(source->data.tcp.host, val, svc - val) < 0)
goto error;
svc++;
if (VIR_STRNDUP(source->data.tcp.service, svc, opt ? opt - svc : -1) < 0)
goto error;
} else if (STRPREFIX(val, "unix:")) {
const char *opt;
val += strlen("unix:");
opt = strchr(val, ',');
source->type = VIR_DOMAIN_CHR_TYPE_UNIX;
if (VIR_STRNDUP(source->data.nix.path, val, opt ? opt - val : -1) < 0)
goto error;
} else if (STRPREFIX(val, "/dev")) {
source->type = VIR_DOMAIN_CHR_TYPE_DEV;
if (VIR_STRDUP(source->data.file.path, val) < 0)
goto error;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown character device syntax %s"), val);
goto error;
}
return 0;
error:
return -1;
}
static virCPUDefPtr
qemuInitGuestCPU(virDomainDefPtr dom)
{
if (!dom->cpu) {
virCPUDefPtr cpu;
if (VIR_ALLOC(cpu) < 0)
return NULL;
cpu->type = VIR_CPU_TYPE_GUEST;
cpu->match = VIR_CPU_MATCH_EXACT;
dom->cpu = cpu;
}
return dom->cpu;
}
static int
qemuParseCommandLineCPU(virDomainDefPtr dom,
const char *val)
{
virCPUDefPtr cpu = NULL;
char **tokens;
char **hv_tokens = NULL;
char *model = NULL;
int ret = -1;
size_t i;
if (!(tokens = virStringSplit(val, ",", 0)))
goto cleanup;
if (tokens[0] == NULL)
goto syntax;
for (i = 0; tokens[i] != NULL; i++) {
if (*tokens[i] == '\0')
goto syntax;
if (i == 0) {
if (VIR_STRDUP(model, tokens[i]) < 0)
goto cleanup;
if (!STREQ(model, "qemu32") && !STREQ(model, "qemu64")) {
if (!(cpu = qemuInitGuestCPU(dom)))
goto cleanup;
cpu->model = model;
model = NULL;
}
} else if (*tokens[i] == '+' || *tokens[i] == '-') {
const char *feature = tokens[i] + 1; /* '+' or '-' */
int policy;
if (*tokens[i] == '+')
policy = VIR_CPU_FEATURE_REQUIRE;
else
policy = VIR_CPU_FEATURE_DISABLE;
if (*feature == '\0')
goto syntax;
if (dom->os.arch != VIR_ARCH_X86_64 &&
dom->os.arch != VIR_ARCH_I686) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("%s platform doesn't support CPU features'"),
virArchToString(dom->os.arch));
goto cleanup;
}
if (STREQ(feature, "kvmclock")) {
bool present = (policy == VIR_CPU_FEATURE_REQUIRE);
size_t j;
for (j = 0; j < dom->clock.ntimers; j++) {
if (dom->clock.timers[j]->name == VIR_DOMAIN_TIMER_NAME_KVMCLOCK)
break;
}
if (j == dom->clock.ntimers) {
virDomainTimerDefPtr timer;
if (VIR_ALLOC(timer) < 0 ||
VIR_APPEND_ELEMENT_COPY(dom->clock.timers,
dom->clock.ntimers, timer) < 0) {
VIR_FREE(timer);
goto cleanup;
}
timer->name = VIR_DOMAIN_TIMER_NAME_KVMCLOCK;
timer->present = present;
timer->tickpolicy = -1;
timer->track = -1;
timer->mode = -1;
} else if (dom->clock.timers[j]->present != -1 &&
dom->clock.timers[j]->present != present) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("conflicting occurrences of kvmclock feature"));
goto cleanup;
}
} else if (STREQ(feature, "kvm_pv_eoi")) {
if (policy == VIR_CPU_FEATURE_REQUIRE)
dom->apic_eoi = VIR_DOMAIN_FEATURE_STATE_ON;
else
dom->apic_eoi = VIR_DOMAIN_FEATURE_STATE_OFF;
} else {
if (!cpu) {
if (!(cpu = qemuInitGuestCPU(dom)))
goto cleanup;
cpu->model = model;
model = NULL;
}
if (virCPUDefAddFeature(cpu, feature, policy) < 0)
goto cleanup;
}
} else if (STRPREFIX(tokens[i], "hv_")) {
const char *token = tokens[i] + 3; /* "hv_" */
const char *feature, *value;
int f;
if (*token == '\0')
goto syntax;
if (!(hv_tokens = virStringSplit(token, "=", 2)))
goto cleanup;
feature = hv_tokens[0];
value = hv_tokens[1];
if (*feature == '\0')
goto syntax;
dom->features[VIR_DOMAIN_FEATURE_HYPERV] = VIR_DOMAIN_FEATURE_STATE_ON;
if ((f = virDomainHypervTypeFromString(feature)) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unsupported HyperV Enlightenment feature "
"'%s'"), feature);
goto cleanup;
}
switch ((enum virDomainHyperv) f) {
case VIR_DOMAIN_HYPERV_RELAXED:
case VIR_DOMAIN_HYPERV_VAPIC:
if (value) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("HyperV feature '%s' should not "
"have a value"), feature);
goto cleanup;
}
dom->hyperv_features[f] = VIR_DOMAIN_FEATURE_STATE_ON;
break;
case VIR_DOMAIN_HYPERV_SPINLOCKS:
dom->hyperv_features[f] = VIR_DOMAIN_FEATURE_STATE_ON;
if (!value) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("missing HyperV spinlock retry count"));
goto cleanup;
}
if (virStrToLong_ui(value, NULL, 0, &dom->hyperv_spinlocks) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("cannot parse HyperV spinlock retry count"));
goto cleanup;
}
if (dom->hyperv_spinlocks < 0xFFF)
dom->hyperv_spinlocks = 0xFFF;
break;
case VIR_DOMAIN_HYPERV_LAST:
break;
}
virStringFreeList(hv_tokens);
hv_tokens = NULL;
}
}
if (dom->os.arch == VIR_ARCH_X86_64) {
bool is_32bit = false;
if (cpu) {
virCPUDataPtr cpuData = NULL;
if (cpuEncode(VIR_ARCH_X86_64, cpu, NULL, &cpuData,
NULL, NULL, NULL, NULL) < 0)
goto cleanup;
is_32bit = (cpuHasFeature(cpuData, "lm") != 1);
cpuDataFree(cpuData);
} else if (model) {
is_32bit = STREQ(model, "qemu32");
}
if (is_32bit)
dom->os.arch = VIR_ARCH_I686;
}
ret = 0;
cleanup:
VIR_FREE(model);
virStringFreeList(tokens);
virStringFreeList(hv_tokens);
return ret;
syntax:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown CPU syntax '%s'"), val);
goto cleanup;
}
static int
qemuParseCommandLineSmp(virDomainDefPtr dom,
const char *val)
{
unsigned int sockets = 0;
unsigned int cores = 0;
unsigned int threads = 0;
unsigned int maxcpus = 0;
size_t i;
int nkws;
char **kws;
char **vals;
int n;
char *end;
int ret;
if (qemuParseKeywords(val, &kws, &vals, &nkws, 1) < 0)
return -1;
for (i = 0; i < nkws; i++) {
if (vals[i] == NULL) {
if (i > 0 ||
virStrToLong_i(kws[i], &end, 10, &n) < 0 || *end != '\0')
goto syntax;
dom->vcpus = n;
} else {
if (virStrToLong_i(vals[i], &end, 10, &n) < 0 || *end != '\0')
goto syntax;
if (STREQ(kws[i], "sockets"))
sockets = n;
else if (STREQ(kws[i], "cores"))
cores = n;
else if (STREQ(kws[i], "threads"))
threads = n;
else if (STREQ(kws[i], "maxcpus"))
maxcpus = n;
else
goto syntax;
}
}
dom->maxvcpus = maxcpus ? maxcpus : dom->vcpus;
if (sockets && cores && threads) {
virCPUDefPtr cpu;
if (!(cpu = qemuInitGuestCPU(dom)))
goto error;
cpu->sockets = sockets;
cpu->cores = cores;
cpu->threads = threads;
} else if (sockets || cores || threads)
goto syntax;
ret = 0;
cleanup:
for (i = 0; i < nkws; i++) {
VIR_FREE(kws[i]);
VIR_FREE(vals[i]);
}
VIR_FREE(kws);
VIR_FREE(vals);
return ret;
syntax:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse CPU topology '%s'"), val);
error:
ret = -1;
goto cleanup;
}
static void
qemuParseCommandLineBootDevs(virDomainDefPtr def, const char *str) {
int n, b = 0;
for (n = 0; str[n] && b < VIR_DOMAIN_BOOT_LAST; n++) {
if (str[n] == 'a')
def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_FLOPPY;
else if (str[n] == 'c')
def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_DISK;
else if (str[n] == 'd')
def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_CDROM;
else if (str[n] == 'n')
def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_NET;
else if (str[n] == ',')
break;
}
def->os.nBootDevs = b;
}
/*
* Analyse the env and argv settings and reconstruct a
* virDomainDefPtr representing these settings as closely
* as is practical. This is not an exact science....
*/
static virDomainDefPtr
qemuParseCommandLine(virCapsPtr qemuCaps,
virDomainXMLOptionPtr xmlopt,
char **progenv,
char **progargv,
char **pidfile,
virDomainChrSourceDefPtr *monConfig,
bool *monJSON)
{
virDomainDefPtr def;
size_t i;
bool nographics = false;
bool fullscreen = false;
char *path;
size_t nnics = 0;
const char **nics = NULL;
int video = VIR_DOMAIN_VIDEO_TYPE_CIRRUS;
int nvirtiodisk = 0;
qemuDomainCmdlineDefPtr cmd = NULL;
virDomainDiskDefPtr disk = NULL;
const char *ceph_args = qemuFindEnv(progenv, "CEPH_ARGS");
if (pidfile)
*pidfile = NULL;
if (monConfig)
*monConfig = NULL;
if (monJSON)
*monJSON = false;
if (!progargv[0]) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("no emulator path found"));
return NULL;
}
if (VIR_ALLOC(def) < 0)
goto error;
/* allocate the cmdlinedef up-front; if it's unused, we'll free it later */
if (VIR_ALLOC(cmd) < 0)
goto error;
if (virUUIDGenerate(def->uuid) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("failed to generate uuid"));
goto error;
}
def->id = -1;
def->mem.cur_balloon = def->mem.max_balloon = 64 * 1024;
def->maxvcpus = 1;
def->vcpus = 1;
def->clock.offset = VIR_DOMAIN_CLOCK_OFFSET_UTC;
def->onReboot = VIR_DOMAIN_LIFECYCLE_RESTART;
def->onCrash = VIR_DOMAIN_LIFECYCLE_DESTROY;
def->onPoweroff = VIR_DOMAIN_LIFECYCLE_DESTROY;
def->virtType = VIR_DOMAIN_VIRT_QEMU;
if (VIR_STRDUP(def->emulator, progargv[0]) < 0)
goto error;
if (!(path = last_component(def->emulator)))
goto error;
if (strstr(path, "xenner")) {
def->virtType = VIR_DOMAIN_VIRT_KVM;
if (VIR_STRDUP(def->os.type, "xen") < 0)
goto error;
} else {
if (VIR_STRDUP(def->os.type, "hvm") < 0)
goto error;
if (strstr(path, "kvm")) {
def->virtType = VIR_DOMAIN_VIRT_KVM;
def->features[VIR_DOMAIN_FEATURE_PAE] = VIR_DOMAIN_FEATURE_STATE_ON;
}
}
if (def->virtType == VIR_DOMAIN_VIRT_KVM)
def->os.arch = qemuCaps->host.arch;
else if (STRPREFIX(path, "qemu-system-"))
def->os.arch = virArchFromString(path + strlen("qemu-system-"));
else
def->os.arch = VIR_ARCH_I686;
if ((def->os.arch == VIR_ARCH_I686) ||
(def->os.arch == VIR_ARCH_X86_64))
def->features[VIR_DOMAIN_FEATURE_ACPI] = VIR_DOMAIN_FEATURE_STATE_ON;
#define WANT_VALUE() \
const char *val = progargv[++i]; \
if (!val) { \
virReportError(VIR_ERR_INTERNAL_ERROR, \
_("missing value for %s argument"), arg); \
goto error; \
}
/* One initial loop to get list of NICs, so we
* can correlate them later */
for (i = 1; progargv[i]; i++) {
const char *arg = progargv[i];
/* Make sure we have a single - for all options to
simplify next logic */
if (STRPREFIX(arg, "--"))
arg++;
if (STREQ(arg, "-net")) {
WANT_VALUE();
if (STRPREFIX(val, "nic") &&
VIR_APPEND_ELEMENT(nics, nnics, val) < 0)
goto error;
}
}
/* Now the real processing loop */
for (i = 1; progargv[i]; i++) {
const char *arg = progargv[i];
/* Make sure we have a single - for all options to
simplify next logic */
if (STRPREFIX(arg, "--"))
arg++;
if (STREQ(arg, "-vnc")) {
virDomainGraphicsDefPtr vnc;
char *tmp;
WANT_VALUE();
if (VIR_ALLOC(vnc) < 0)
goto error;
vnc->type = VIR_DOMAIN_GRAPHICS_TYPE_VNC;
if (STRPREFIX(val, "unix:")) {
/* -vnc unix:/some/big/path */
if (VIR_STRDUP(vnc->data.vnc.socket, val + 5) < 0) {
virDomainGraphicsDefFree(vnc);
goto error;
}
} else {
/*
* -vnc 127.0.0.1:4
* -vnc [2001:1:2:3:4:5:1234:1234]:4
* -vnc some.host.name:4
*/
char *opts;
char *port;
const char *sep = ":";
if (val[0] == '[')
sep = "]:";
tmp = strstr(val, sep);
if (!tmp) {
virDomainGraphicsDefFree(vnc);
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing VNC port number in '%s'"), val);
goto error;
}
port = tmp + strlen(sep);
if (virStrToLong_i(port, &opts, 10,
&vnc->data.vnc.port) < 0) {
virDomainGraphicsDefFree(vnc);
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse VNC port '%s'"), port);
goto error;
}
if (val[0] == '[')
virDomainGraphicsListenSetAddress(vnc, 0,
val+1, tmp-(val+1), true);
else
virDomainGraphicsListenSetAddress(vnc, 0,
val, tmp-val, true);
if (!virDomainGraphicsListenGetAddress(vnc, 0)) {
virDomainGraphicsDefFree(vnc);
goto error;
}
if (*opts == ',') {
char *orig_opts;
if (VIR_STRDUP(orig_opts, opts + 1) < 0) {
virDomainGraphicsDefFree(vnc);
goto error;
}
opts = orig_opts;
while (opts && *opts) {
char *nextopt = strchr(opts, ',');
if (nextopt)
*(nextopt++) = '\0';
if (STRPREFIX(opts, "websocket")) {
char *websocket = opts + strlen("websocket");
if (*(websocket++) == '=' &&
*websocket) {
/* If the websocket continues with
* '=<something>', we'll parse it */
if (virStrToLong_i(websocket,
NULL, 0,
&vnc->data.vnc.websocket) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse VNC "
"WebSocket port '%s'"),
websocket);
virDomainGraphicsDefFree(vnc);
VIR_FREE(orig_opts);
goto error;
}
} else {
/* Otherwise, we'll compute the port the same
* way QEMU does, by adding a 5700 to the
* display value. */
vnc->data.vnc.websocket =
vnc->data.vnc.port + 5700;
}
} else if (STRPREFIX(opts, "share=")) {
char *sharePolicy = opts + strlen("share=");
if (sharePolicy && *sharePolicy) {
int policy =
virDomainGraphicsVNCSharePolicyTypeFromString(sharePolicy);
if (policy < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown vnc display sharing policy '%s'"),
sharePolicy);
virDomainGraphicsDefFree(vnc);
VIR_FREE(orig_opts);
goto error;
} else {
vnc->data.vnc.sharePolicy = policy;
}
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("missing vnc sharing policy"));
virDomainGraphicsDefFree(vnc);
VIR_FREE(orig_opts);
goto error;
}
}
opts = nextopt;
}
VIR_FREE(orig_opts);
}
vnc->data.vnc.port += 5900;
vnc->data.vnc.autoport = false;
}
if (VIR_APPEND_ELEMENT(def->graphics, def->ngraphics, vnc) < 0) {
virDomainGraphicsDefFree(vnc);
goto error;
}
} else if (STREQ(arg, "-m")) {
int mem;
WANT_VALUE();
if (virStrToLong_i(val, NULL, 10, &mem) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, \
_("cannot parse memory level '%s'"), val);
goto error;
}
def->mem.cur_balloon = def->mem.max_balloon = mem * 1024;
} else if (STREQ(arg, "-smp")) {
WANT_VALUE();
if (qemuParseCommandLineSmp(def, val) < 0)
goto error;
} else if (STREQ(arg, "-uuid")) {
WANT_VALUE();
if (virUUIDParse(val, def->uuid) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, \
_("cannot parse UUID '%s'"), val);
goto error;
}
} else if (STRPREFIX(arg, "-hd") ||
STRPREFIX(arg, "-sd") ||
STRPREFIX(arg, "-fd") ||
STREQ(arg, "-cdrom")) {
WANT_VALUE();
if (VIR_ALLOC(disk) < 0)
goto error;
if (STRPREFIX(val, "/dev/"))
disk->type = VIR_DOMAIN_DISK_TYPE_BLOCK;
else if (STRPREFIX(val, "nbd:")) {
disk->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
disk->protocol = VIR_DOMAIN_DISK_PROTOCOL_NBD;
} else if (STRPREFIX(val, "rbd:")) {
disk->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
disk->protocol = VIR_DOMAIN_DISK_PROTOCOL_RBD;
val += strlen("rbd:");
} else if (STRPREFIX(val, "gluster")) {
disk->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
disk->protocol = VIR_DOMAIN_DISK_PROTOCOL_GLUSTER;
} else if (STRPREFIX(val, "sheepdog:")) {
disk->type = VIR_DOMAIN_DISK_TYPE_NETWORK;
disk->protocol = VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG;
val += strlen("sheepdog:");
} else
disk->type = VIR_DOMAIN_DISK_TYPE_FILE;
if (STREQ(arg, "-cdrom")) {
disk->device = VIR_DOMAIN_DISK_DEVICE_CDROM;
if (((def->os.arch == VIR_ARCH_PPC64) &&
def->os.machine && STREQ(def->os.machine, "pseries")))
disk->bus = VIR_DOMAIN_DISK_BUS_SCSI;
if (VIR_STRDUP(disk->dst, "hdc") < 0)
goto error;
disk->readonly = true;
} else {
if (STRPREFIX(arg, "-fd")) {
disk->device = VIR_DOMAIN_DISK_DEVICE_FLOPPY;
disk->bus = VIR_DOMAIN_DISK_BUS_FDC;
} else {
disk->device = VIR_DOMAIN_DISK_DEVICE_DISK;
if (STRPREFIX(arg, "-hd"))
disk->bus = VIR_DOMAIN_DISK_BUS_IDE;
else
disk->bus = VIR_DOMAIN_DISK_BUS_SCSI;
if (((def->os.arch == VIR_ARCH_PPC64) &&
def->os.machine && STREQ(def->os.machine, "pseries")))
disk->bus = VIR_DOMAIN_DISK_BUS_SCSI;
}
if (VIR_STRDUP(disk->dst, arg + 1) < 0)
goto error;
}
if (VIR_STRDUP(disk->src, val) < 0)
goto error;
if (disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK) {
char *port;
switch (disk->protocol) {
case VIR_DOMAIN_DISK_PROTOCOL_NBD:
if (qemuParseNBDString(disk) < 0)
goto error;
break;
case VIR_DOMAIN_DISK_PROTOCOL_RBD:
/* old-style CEPH_ARGS env variable is parsed later */
if (!ceph_args && qemuParseRBDString(disk) < 0)
goto error;
break;
case VIR_DOMAIN_DISK_PROTOCOL_SHEEPDOG:
/* disk->src must be [vdiname] or [host]:[port]:[vdiname] */
port = strchr(disk->src, ':');
if (port) {
char *vdi;
*port++ = '\0';
vdi = strchr(port, ':');
if (!vdi) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse sheepdog filename '%s'"), val);
goto error;
}
*vdi++ = '\0';
if (VIR_ALLOC(disk->hosts) < 0)
goto error;
disk->nhosts = 1;
disk->hosts->name = disk->src;
if (VIR_STRDUP(disk->hosts->port, port) < 0)
goto error;
if (VIR_STRDUP(disk->src, vdi) < 0)
goto error;
}
break;
case VIR_DOMAIN_DISK_PROTOCOL_GLUSTER:
if (qemuParseGlusterString(disk) < 0)
goto error;
break;
case VIR_DOMAIN_DISK_PROTOCOL_ISCSI:
if (qemuParseISCSIString(disk) < 0)
goto error;
break;
}
}
if (virDomainDiskDefAssignAddress(xmlopt, disk) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot assign address for device name '%s'"),
disk->dst);
goto error;
}
if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
goto error;
} else if (STREQ(arg, "-no-acpi")) {
def->features[VIR_DOMAIN_FEATURE_ACPI] = VIR_DOMAIN_FEATURE_STATE_DEFAULT;
} else if (STREQ(arg, "-no-reboot")) {
def->onReboot = VIR_DOMAIN_LIFECYCLE_DESTROY;
} else if (STREQ(arg, "-no-kvm")) {
def->virtType = VIR_DOMAIN_VIRT_QEMU;
} else if (STREQ(arg, "-enable-kvm")) {
def->virtType = VIR_DOMAIN_VIRT_KVM;
} else if (STREQ(arg, "-nographic")) {
nographics = true;
} else if (STREQ(arg, "-full-screen")) {
fullscreen = true;
} else if (STREQ(arg, "-localtime")) {
def->clock.offset = VIR_DOMAIN_CLOCK_OFFSET_LOCALTIME;
} else if (STREQ(arg, "-kernel")) {
WANT_VALUE();
if (VIR_STRDUP(def->os.kernel, val) < 0)
goto error;
} else if (STREQ(arg, "-bios")) {
WANT_VALUE();
if (VIR_STRDUP(def->os.loader, val) < 0)
goto error;
} else if (STREQ(arg, "-initrd")) {
WANT_VALUE();
if (VIR_STRDUP(def->os.initrd, val) < 0)
goto error;
} else if (STREQ(arg, "-append")) {
WANT_VALUE();
if (VIR_STRDUP(def->os.cmdline, val) < 0)
goto error;
} else if (STREQ(arg, "-dtb")) {
WANT_VALUE();
if (VIR_STRDUP(def->os.dtb, val) < 0)
goto error;
} else if (STREQ(arg, "-boot")) {
const char *token = NULL;
WANT_VALUE();
if (!strchr(val, ','))
qemuParseCommandLineBootDevs(def, val);
else {
token = val;
while (token && *token) {
if (STRPREFIX(token, "order=")) {
token += strlen("order=");
qemuParseCommandLineBootDevs(def, token);
} else if (STRPREFIX(token, "menu=on")) {
def->os.bootmenu = 1;
} else if (STRPREFIX(token, "reboot-timeout=")) {
int num;
char *endptr;
if (virStrToLong_i(token + strlen("reboot-timeout="),
&endptr, 10, &num) < 0 ||
(*endptr != '\0' && endptr != strchr(token, ','))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot parse reboot-timeout value"));
goto error;
}
if (num > 65535)
num = 65535;
else if (num < -1)
num = -1;
def->os.bios.rt_delay = num;
def->os.bios.rt_set = true;
}
token = strchr(token, ',');
/* This incrementation has to be done here in order to make it
* possible to pass the token pointer properly into the loop */
if (token)
token++;
}
}
} else if (STREQ(arg, "-name")) {
char *process;
WANT_VALUE();
process = strstr(val, ",process=");
if (process == NULL) {
if (VIR_STRDUP(def->name, val) < 0)
goto error;
} else {
if (VIR_STRNDUP(def->name, val, process - val) < 0)
goto error;
}
if (STREQ(def->name, ""))
VIR_FREE(def->name);
} else if (STREQ(arg, "-M") ||
STREQ(arg, "-machine")) {
char **list;
char *param;
size_t j = 0;
/* -machine [type=]name[,prop[=value][,...]]
* Set os.machine only if first parameter lacks '=' or
* contains explicit type='...' */
WANT_VALUE();
if (!(list = virStringSplit(val, ",", 0)))
goto error;
param = list[0];
if (STRPREFIX(param, "type="))
param += strlen("type=");
if (!strchr(param, '=')) {
if (VIR_STRDUP(def->os.machine, param) < 0) {
virStringFreeList(list);
goto error;
}
j++;
}
/* handle all remaining "-machine" parameters */
while ((param = list[j++])) {
if (STRPREFIX(param, "dump-guest-core=")) {
param += strlen("dump-guest-core=");
def->mem.dump_core = virDomainMemDumpTypeFromString(param);
if (def->mem.dump_core <= 0)
def->mem.dump_core = VIR_DOMAIN_MEM_DUMP_DEFAULT;
} else if (STRPREFIX(param, "mem-merge=off")) {
def->mem.nosharepages = true;
} else if (STRPREFIX(param, "accel=kvm")) {
def->virtType = VIR_DOMAIN_VIRT_KVM;
def->features[VIR_DOMAIN_FEATURE_PAE] = VIR_DOMAIN_FEATURE_STATE_ON;
}
}
virStringFreeList(list);
} else if (STREQ(arg, "-serial")) {
WANT_VALUE();
if (STRNEQ(val, "none")) {
virDomainChrDefPtr chr;
if (!(chr = virDomainChrDefNew()))
goto error;
if (qemuParseCommandLineChr(&chr->source, val) < 0) {
virDomainChrDefFree(chr);
goto error;
}
chr->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL;
chr->target.port = def->nserials;
if (VIR_APPEND_ELEMENT(def->serials, def->nserials, chr) < 0) {
virDomainChrDefFree(chr);
goto error;
}
}
} else if (STREQ(arg, "-parallel")) {
WANT_VALUE();
if (STRNEQ(val, "none")) {
virDomainChrDefPtr chr;
if (!(chr = virDomainChrDefNew()))
goto error;
if (qemuParseCommandLineChr(&chr->source, val) < 0) {
virDomainChrDefFree(chr);
goto error;
}
chr->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_PARALLEL;
chr->target.port = def->nparallels;
if (VIR_APPEND_ELEMENT(def->parallels, def->nparallels, chr) < 0) {
virDomainChrDefFree(chr);
goto error;
}
}
} else if (STREQ(arg, "-usbdevice")) {
WANT_VALUE();
if (STREQ(val, "tablet") ||
STREQ(val, "mouse") ||
STREQ(val, "keyboard")) {
virDomainInputDefPtr input;
if (VIR_ALLOC(input) < 0)
goto error;
input->bus = VIR_DOMAIN_INPUT_BUS_USB;
if (STREQ(val, "tablet"))
input->type = VIR_DOMAIN_INPUT_TYPE_TABLET;
else if (STREQ(val, "mouse"))
input->type = VIR_DOMAIN_INPUT_TYPE_MOUSE;
else
input->type = VIR_DOMAIN_INPUT_TYPE_KBD;
if (VIR_APPEND_ELEMENT(def->inputs, def->ninputs, input) < 0) {
virDomainInputDefFree(input);
goto error;
}
} else if (STRPREFIX(val, "disk:")) {
if (VIR_ALLOC(disk) < 0)
goto error;
if (VIR_STRDUP(disk->src, val + strlen("disk:")) < 0)
goto error;
if (STRPREFIX(disk->src, "/dev/"))
disk->type = VIR_DOMAIN_DISK_TYPE_BLOCK;
else
disk->type = VIR_DOMAIN_DISK_TYPE_FILE;
disk->device = VIR_DOMAIN_DISK_DEVICE_DISK;
disk->bus = VIR_DOMAIN_DISK_BUS_USB;
disk->removable = VIR_DOMAIN_FEATURE_STATE_DEFAULT;
if (VIR_STRDUP(disk->dst, "sda") < 0)
goto error;
if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
goto error;
} else {
virDomainHostdevDefPtr hostdev;
if (!(hostdev = qemuParseCommandLineUSB(val)))
goto error;
if (VIR_APPEND_ELEMENT(def->hostdevs, def->nhostdevs, hostdev) < 0) {
virDomainHostdevDefFree(hostdev);
goto error;
}
}
} else if (STREQ(arg, "-net")) {
WANT_VALUE();
if (!STRPREFIX(val, "nic") && STRNEQ(val, "none")) {
virDomainNetDefPtr net;
if (!(net = qemuParseCommandLineNet(xmlopt, val, nnics, nics)))
goto error;
if (VIR_APPEND_ELEMENT(def->nets, def->nnets, net) < 0) {
virDomainNetDefFree(net);
goto error;
}
}
} else if (STREQ(arg, "-drive")) {
WANT_VALUE();
if (!(disk = qemuParseCommandLineDisk(xmlopt, val, def,
nvirtiodisk,
ceph_args != NULL)))
goto error;
if (disk->bus == VIR_DOMAIN_DISK_BUS_VIRTIO)
nvirtiodisk++;
if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
goto error;
} else if (STREQ(arg, "-pcidevice")) {
virDomainHostdevDefPtr hostdev;
WANT_VALUE();
if (!(hostdev = qemuParseCommandLinePCI(val)))
goto error;
if (VIR_APPEND_ELEMENT(def->hostdevs, def->nhostdevs, hostdev) < 0) {
virDomainHostdevDefFree(hostdev);
goto error;
}
} else if (STREQ(arg, "-soundhw")) {
const char *start;
WANT_VALUE();
start = val;
while (start) {
const char *tmp = strchr(start, ',');
int type = -1;
if (STRPREFIX(start, "pcspk")) {
type = VIR_DOMAIN_SOUND_MODEL_PCSPK;
} else if (STRPREFIX(start, "sb16")) {
type = VIR_DOMAIN_SOUND_MODEL_SB16;
} else if (STRPREFIX(start, "es1370")) {
type = VIR_DOMAIN_SOUND_MODEL_ES1370;
} else if (STRPREFIX(start, "ac97")) {
type = VIR_DOMAIN_SOUND_MODEL_AC97;
} else if (STRPREFIX(start, "hda")) {
type = VIR_DOMAIN_SOUND_MODEL_ICH6;
}
if (type != -1) {
virDomainSoundDefPtr snd;
if (VIR_ALLOC(snd) < 0)
goto error;
snd->model = type;
if (VIR_APPEND_ELEMENT(def->sounds, def->nsounds, snd) < 0) {
VIR_FREE(snd);
goto error;
}
}
start = tmp ? tmp + 1 : NULL;
}
} else if (STREQ(arg, "-watchdog")) {
WANT_VALUE();
int model = virDomainWatchdogModelTypeFromString(val);
if (model != -1) {
virDomainWatchdogDefPtr wd;
if (VIR_ALLOC(wd) < 0)
goto error;
wd->model = model;
wd->action = VIR_DOMAIN_WATCHDOG_ACTION_RESET;
def->watchdog = wd;
}
} else if (STREQ(arg, "-watchdog-action") && def->watchdog) {
WANT_VALUE();
int action = virDomainWatchdogActionTypeFromString(val);
if (action != -1)
def->watchdog->action = action;
} else if (STREQ(arg, "-bootloader")) {
WANT_VALUE();
if (VIR_STRDUP(def->os.bootloader, val) < 0)
goto error;
} else if (STREQ(arg, "-vmwarevga")) {
video = VIR_DOMAIN_VIDEO_TYPE_VMVGA;
} else if (STREQ(arg, "-std-vga")) {
video = VIR_DOMAIN_VIDEO_TYPE_VGA;
} else if (STREQ(arg, "-vga")) {
WANT_VALUE();
if (STRNEQ(val, "none")) {
video = qemuVideoTypeFromString(val);
if (video < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown video adapter type '%s'"), val);
goto error;
}
}
} else if (STREQ(arg, "-cpu")) {
WANT_VALUE();
if (qemuParseCommandLineCPU(def, val) < 0)
goto error;
} else if (STREQ(arg, "-domid")) {
WANT_VALUE();
/* ignore, generted on the fly */
} else if (STREQ(arg, "-usb")) {
virDomainControllerDefPtr ctldef;
if (VIR_ALLOC(ctldef) < 0)
goto error;
ctldef->type = VIR_DOMAIN_CONTROLLER_TYPE_USB;
ctldef->idx = 0;
ctldef->model = -1;
if (virDomainControllerInsert(def, ctldef) < 0) {
VIR_FREE(ctldef);
goto error;
}
} else if (STREQ(arg, "-pidfile")) {
WANT_VALUE();
if (pidfile)
if (VIR_STRDUP(*pidfile, val) < 0)
goto error;
} else if (STREQ(arg, "-incoming")) {
WANT_VALUE();
/* ignore, used via restore/migrate APIs */
} else if (STREQ(arg, "-monitor")) {
WANT_VALUE();
if (monConfig) {
virDomainChrSourceDefPtr chr;
if (VIR_ALLOC(chr) < 0)
goto error;
if (qemuParseCommandLineChr(chr, val) < 0) {
virDomainChrSourceDefFree(chr);
goto error;
}
*monConfig = chr;
}
} else if (STREQ(arg, "-global") &&
STRPREFIX(progargv[i + 1], "PIIX4_PM.disable_s3=")) {
/* We want to parse only the known "-global" parameters,
* so the ones that we don't know are still added to the
* namespace */
WANT_VALUE();
val += strlen("PIIX4_PM.disable_s3=");
if (STREQ(val, "0"))
def->pm.s3 = VIR_DOMAIN_PM_STATE_ENABLED;
else if (STREQ(val, "1"))
def->pm.s3 = VIR_DOMAIN_PM_STATE_DISABLED;
else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("invalid value for disable_s3 parameter: "
"'%s'"), val);
goto error;
}
} else if (STREQ(arg, "-global") &&
STRPREFIX(progargv[i + 1], "PIIX4_PM.disable_s4=")) {
WANT_VALUE();
val += strlen("PIIX4_PM.disable_s4=");
if (STREQ(val, "0"))
def->pm.s4 = VIR_DOMAIN_PM_STATE_ENABLED;
else if (STREQ(val, "1"))
def->pm.s4 = VIR_DOMAIN_PM_STATE_DISABLED;
else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("invalid value for disable_s4 parameter: "
"'%s'"), val);
goto error;
}
} else if (STREQ(arg, "-global") &&
STRPREFIX(progargv[i + 1], "spapr-nvram.reg=")) {
WANT_VALUE();
if (VIR_ALLOC(def->nvram) < 0)
goto error;
def->nvram->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_SPAPRVIO;
def->nvram->info.addr.spaprvio.has_reg = true;
val += strlen("spapr-nvram.reg=");
if (virStrToLong_ull(val, NULL, 16,
&def->nvram->info.addr.spaprvio.reg) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot parse nvram's address '%s'"), val);
goto error;
}
} else if (STREQ(arg, "-S")) {
/* ignore, always added by libvirt */
} else {
char *tmp = NULL;
/* something we can't yet parse. Add it to the qemu namespace
* cmdline/environment advanced options and hope for the best
*/
VIR_WARN("unknown QEMU argument '%s', adding to the qemu namespace",
arg);
if (VIR_STRDUP(tmp, arg) < 0 ||
VIR_APPEND_ELEMENT(cmd->args, cmd->num_args, tmp) < 0) {
VIR_FREE(tmp);
goto error;
}
}
}
#undef WANT_VALUE
if (def->ndisks > 0 && ceph_args) {
char *hosts, *port, *saveptr = NULL, *token;
virDomainDiskDefPtr first_rbd_disk = NULL;
for (i = 0; i < def->ndisks; i++) {
if (def->disks[i]->type == VIR_DOMAIN_DISK_TYPE_NETWORK &&
def->disks[i]->protocol == VIR_DOMAIN_DISK_PROTOCOL_RBD) {
first_rbd_disk = def->disks[i];
break;
}
}
if (!first_rbd_disk) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("CEPH_ARGS was set without an rbd disk"));
goto error;
}
/* CEPH_ARGS should be: -m host1[:port1][,host2[:port2]]... */
if (!STRPREFIX(ceph_args, "-m ")) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("could not parse CEPH_ARGS '%s'"), ceph_args);
goto error;
}
if (VIR_STRDUP(hosts, strchr(ceph_args, ' ') + 1) < 0)
goto error;
first_rbd_disk->nhosts = 0;
token = strtok_r(hosts, ",", &saveptr);
while (token != NULL) {
if (VIR_REALLOC_N(first_rbd_disk->hosts, first_rbd_disk->nhosts + 1) < 0) {
VIR_FREE(hosts);
goto error;
}
port = strchr(token, ':');
if (port) {
*port++ = '\0';
if (VIR_STRDUP(port, port) < 0) {
VIR_FREE(hosts);
goto error;
}
}
first_rbd_disk->hosts[first_rbd_disk->nhosts].port = port;
if (VIR_STRDUP(first_rbd_disk->hosts[first_rbd_disk->nhosts].name,
token) < 0) {
VIR_FREE(hosts);
goto error;
}
first_rbd_disk->hosts[first_rbd_disk->nhosts].transport = VIR_DOMAIN_DISK_PROTO_TRANS_TCP;
first_rbd_disk->hosts[first_rbd_disk->nhosts].socket = NULL;
first_rbd_disk->nhosts++;
token = strtok_r(NULL, ",", &saveptr);
}
VIR_FREE(hosts);
if (first_rbd_disk->nhosts == 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("found no rbd hosts in CEPH_ARGS '%s'"), ceph_args);
goto error;
}
}
if (!def->os.machine) {
const char *defaultMachine =
virCapabilitiesDefaultGuestMachine(qemuCaps,
def->os.type,
def->os.arch,
virDomainVirtTypeToString(def->virtType));
if (defaultMachine != NULL)
if (VIR_STRDUP(def->os.machine, defaultMachine) < 0)
goto error;
}
if (!nographics && def->ngraphics == 0) {
virDomainGraphicsDefPtr sdl;
const char *display = qemuFindEnv(progenv, "DISPLAY");
const char *xauth = qemuFindEnv(progenv, "XAUTHORITY");
if (VIR_ALLOC(sdl) < 0)
goto error;
sdl->type = VIR_DOMAIN_GRAPHICS_TYPE_SDL;
sdl->data.sdl.fullscreen = fullscreen;
if (VIR_STRDUP(sdl->data.sdl.display, display) < 0) {
VIR_FREE(sdl);
goto error;
}
if (VIR_STRDUP(sdl->data.sdl.xauth, xauth) < 0) {
VIR_FREE(sdl);
goto error;
}
if (VIR_APPEND_ELEMENT(def->graphics, def->ngraphics, sdl) < 0) {
virDomainGraphicsDefFree(sdl);
goto error;
}
}
if (def->ngraphics) {
virDomainVideoDefPtr vid;
if (VIR_ALLOC(vid) < 0)
goto error;
if (def->virtType == VIR_DOMAIN_VIRT_XEN)
vid->type = VIR_DOMAIN_VIDEO_TYPE_XEN;
else
vid->type = video;
vid->vram = virDomainVideoDefaultRAM(def, vid->type);
vid->ram = vid->type == VIR_DOMAIN_VIDEO_TYPE_QXL ?
virDomainVideoDefaultRAM(def, vid->type) : 0;
vid->heads = 1;
if (VIR_APPEND_ELEMENT(def->videos, def->nvideos, vid) < 0) {
virDomainVideoDefFree(vid);
goto error;
}
}
/*
* having a balloon is the default, define one with type="none" to avoid it
*/
if (!def->memballoon) {
virDomainMemballoonDefPtr memballoon;
if (VIR_ALLOC(memballoon) < 0)
goto error;
memballoon->model = VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO;
def->memballoon = memballoon;
}
VIR_FREE(nics);
if (virDomainDefAddImplicitControllers(def) < 0)
goto error;
if (virDomainDefPostParse(def, qemuCaps, xmlopt) < 0)
goto error;
if (cmd->num_args || cmd->num_env) {
def->ns = *virDomainXMLOptionGetNamespace(xmlopt);
def->namespaceData = cmd;
}
else
qemuDomainCmdlineDefFree(cmd);
return def;
error:
virDomainDiskDefFree(disk);
qemuDomainCmdlineDefFree(cmd);
virDomainDefFree(def);
VIR_FREE(nics);
if (monConfig) {
virDomainChrSourceDefFree(*monConfig);
*monConfig = NULL;
}
if (pidfile)
VIR_FREE(*pidfile);
return NULL;
}
virDomainDefPtr qemuParseCommandLineString(virCapsPtr qemuCaps,
virDomainXMLOptionPtr xmlopt,
const char *args,
char **pidfile,
virDomainChrSourceDefPtr *monConfig,
bool *monJSON)
{
char **progenv = NULL;
char **progargv = NULL;
virDomainDefPtr def = NULL;
if (qemuStringToArgvEnv(args, &progenv, &progargv) < 0)
goto cleanup;
def = qemuParseCommandLine(qemuCaps, xmlopt, progenv, progargv,
pidfile, monConfig, monJSON);
cleanup:
virStringFreeList(progargv);
virStringFreeList(progenv);
return def;
}
static int qemuParseProcFileStrings(int pid_value,
const char *name,
char ***list)
{
char *path = NULL;
int ret = -1;
char *data = NULL;
ssize_t len;
char *tmp;
size_t nstr = 0;
char **str = NULL;
if (virAsprintf(&path, "/proc/%d/%s", pid_value, name) < 0)
goto cleanup;
if ((len = virFileReadAll(path, 1024*128, &data)) < 0)
goto cleanup;
tmp = data;
while (tmp < (data + len)) {
if (VIR_EXPAND_N(str, nstr, 1) < 0)
goto cleanup;
if (VIR_STRDUP(str[nstr-1], tmp) < 0)
goto cleanup;
/* Skip arg */
tmp += strlen(tmp);
/* Skip \0 separator */
tmp++;
}
if (VIR_EXPAND_N(str, nstr, 1) < 0)
goto cleanup;
str[nstr-1] = NULL;
ret = nstr-1;
*list = str;
cleanup:
if (ret < 0)
virStringFreeList(str);
VIR_FREE(data);
VIR_FREE(path);
return ret;
}
virDomainDefPtr qemuParseCommandLinePid(virCapsPtr qemuCaps,
virDomainXMLOptionPtr xmlopt,
pid_t pid,
char **pidfile,
virDomainChrSourceDefPtr *monConfig,
bool *monJSON)
{
virDomainDefPtr def = NULL;
char **progargv = NULL;
char **progenv = NULL;
char *exepath = NULL;
char *emulator;
/* The parser requires /proc/pid, which only exists on platforms
* like Linux where pid_t fits in int. */
if ((int) pid != pid ||
qemuParseProcFileStrings(pid, "cmdline", &progargv) < 0 ||
qemuParseProcFileStrings(pid, "environ", &progenv) < 0)
goto cleanup;
if (!(def = qemuParseCommandLine(qemuCaps, xmlopt, progenv, progargv,
pidfile, monConfig, monJSON)))
goto cleanup;
if (virAsprintf(&exepath, "/proc/%d/exe", (int) pid) < 0)
goto cleanup;
if (virFileResolveLink(exepath, &emulator) < 0) {
virReportSystemError(errno,
_("Unable to resolve %s for pid %u"),
exepath, (int) pid);
goto cleanup;
}
VIR_FREE(def->emulator);
def->emulator = emulator;
cleanup:
VIR_FREE(exepath);
virStringFreeList(progargv);
virStringFreeList(progenv);
return def;
}