/* // Copyright contributors to the Virtual Machine Manager for Go project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ // Package qemu provides methods and types for launching and managing QEMU // instances. Instances can be launched with the LaunchQemu function and // managed thereafter via QMPStart and the QMP object that this function // returns. To manage a qemu instance after it has been launched you need // to pass the -qmp option during launch requesting the qemu instance to create // a QMP unix domain manageent socket, e.g., // -qmp unix:/tmp/qmp-socket,server,nowait. For more information see the // example below. package qemu import ( "bytes" "fmt" "log" "os" "os/exec" "runtime" "strconv" "strings" "syscall" "context" ) // Machine describes the machine type qemu will emulate. type Machine struct { // Type is the machine type to be used by qemu. Type string // Acceleration are the machine acceleration options to be used by qemu. Acceleration string // Options are options for the machine type // For example gic-version=host and usb=off Options string } const ( // MachineTypeMicrovm is the QEMU microvm machine type for amd64 MachineTypeMicrovm string = "microvm" ) // Device is the qemu device interface. type Device interface { Valid() bool QemuParams(config *Config) []string } // DeviceDriver is the device driver string. type DeviceDriver string const ( // NVDIMM is the Non Volatile DIMM device driver. NVDIMM DeviceDriver = "nvdimm" // VirtioNet is the virtio networking device driver. VirtioNet DeviceDriver = "virtio-net" // VirtioNetPCI is the virt-io pci networking device driver. VirtioNetPCI DeviceDriver = "virtio-net-pci" // VirtioNetCCW is the virt-io ccw networking device driver. VirtioNetCCW DeviceDriver = "virtio-net-ccw" // VirtioBlock is the block device driver. VirtioBlock DeviceDriver = "virtio-blk" // Console is the console device driver. Console DeviceDriver = "virtconsole" // Virtio9P is the 9pfs device driver. Virtio9P DeviceDriver = "virtio-9p" // VirtioSerial is the serial device driver. VirtioSerial DeviceDriver = "virtio-serial" // VirtioSerialPort is the serial port device driver. VirtioSerialPort DeviceDriver = "virtserialport" // VirtioRng is the paravirtualized RNG device driver. VirtioRng DeviceDriver = "virtio-rng" // VirtioBalloon is the memory balloon device driver. VirtioBalloon DeviceDriver = "virtio-balloon" //VhostUserSCSI represents a SCSI vhostuser device type. VhostUserSCSI DeviceDriver = "vhost-user-scsi" //VhostUserNet represents a net vhostuser device type. VhostUserNet DeviceDriver = "virtio-net" //VhostUserBlk represents a block vhostuser device type. VhostUserBlk DeviceDriver = "vhost-user-blk" //VhostUserFS represents a virtio-fs vhostuser device type VhostUserFS DeviceDriver = "vhost-user-fs" // PCIBridgeDriver represents a PCI bridge device type. PCIBridgeDriver DeviceDriver = "pci-bridge" // PCIePCIBridgeDriver represents a PCIe to PCI bridge device type. PCIePCIBridgeDriver DeviceDriver = "pcie-pci-bridge" // VfioPCI is the vfio driver with PCI transport. VfioPCI DeviceDriver = "vfio-pci" // VfioCCW is the vfio driver with CCW transport. VfioCCW DeviceDriver = "vfio-ccw" // VfioAP is the vfio driver with AP transport. VfioAP DeviceDriver = "vfio-ap" // VHostVSockPCI is a generic Vsock vhost device with PCI transport. VHostVSockPCI DeviceDriver = "vhost-vsock-pci" // PCIeRootPort is a PCIe Root Port, the PCIe device should be hotplugged to this port. PCIeRootPort DeviceDriver = "pcie-root-port" ) func isDimmSupported(config *Config) bool { switch runtime.GOARCH { case "amd64", "386", "ppc64le": if config != nil && config.Machine.Type == MachineTypeMicrovm { // microvm does not support NUMA return false } return true default: return false } } // VirtioTransport is the transport in use for a virtio device. type VirtioTransport string const ( // TransportPCI is the PCI transport for virtio device. TransportPCI VirtioTransport = "pci" // TransportCCW is the CCW transport for virtio devices. TransportCCW VirtioTransport = "ccw" // TransportMMIO is the MMIO transport for virtio devices. TransportMMIO VirtioTransport = "mmio" ) // defaultTransport returns the default transport for the current combination // of host's architecture and QEMU machine type. func (transport VirtioTransport) defaultTransport(config *Config) VirtioTransport { switch runtime.GOARCH { case "amd64", "386": if config != nil && config.Machine.Type == MachineTypeMicrovm { return TransportMMIO } return TransportPCI case "s390x": return TransportCCW default: return TransportPCI } } // isVirtioPCI returns true if the transport is PCI. func (transport VirtioTransport) isVirtioPCI(config *Config) bool { if transport == "" { transport = transport.defaultTransport(config) } return transport == TransportPCI } // isVirtioCCW returns true if the transport is CCW. func (transport VirtioTransport) isVirtioCCW(config *Config) bool { if transport == "" { transport = transport.defaultTransport(config) } return transport == TransportCCW } // getName returns the name of the current transport. func (transport VirtioTransport) getName(config *Config) string { if transport == "" { transport = transport.defaultTransport(config) } return string(transport) } // disableModern returns the parameters with the disable-modern option. // In case the device driver is not a PCI device and it doesn't have the option // an empty string is returned. func (transport VirtioTransport) disableModern(config *Config, disable bool) string { if !transport.isVirtioPCI(config) { return "" } if disable { return "disable-modern=true" } return "disable-modern=false" } // ObjectType is a string representing a qemu object type. type ObjectType string const ( // MemoryBackendFile represents a guest memory mapped file. MemoryBackendFile ObjectType = "memory-backend-file" ) // Object is a qemu object representation. type Object struct { // Driver is the qemu device driver Driver DeviceDriver // Type is the qemu object type. Type ObjectType // ID is the user defined object ID. ID string // DeviceID is the user defined device ID. DeviceID string // MemPath is the object's memory path. // This is only relevant for memory objects MemPath string // Size is the object size in bytes Size uint64 } // Valid returns true if the Object structure is valid and complete. func (object Object) Valid() bool { switch object.Type { case MemoryBackendFile: if object.ID == "" || object.MemPath == "" || object.Size == 0 { return false } default: return false } return true } // QemuParams returns the qemu parameters built out of this Object device. func (object Object) QemuParams(config *Config) []string { var objectParams []string var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, string(object.Driver)) deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", object.DeviceID)) switch object.Type { case MemoryBackendFile: objectParams = append(objectParams, string(object.Type)) objectParams = append(objectParams, fmt.Sprintf(",id=%s", object.ID)) objectParams = append(objectParams, fmt.Sprintf(",mem-path=%s", object.MemPath)) objectParams = append(objectParams, fmt.Sprintf(",size=%d", object.Size)) deviceParams = append(deviceParams, fmt.Sprintf(",memdev=%s", object.ID)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) qemuParams = append(qemuParams, "-object") qemuParams = append(qemuParams, strings.Join(objectParams, "")) return qemuParams } // Virtio9PMultidev filesystem behaviour to deal // with multiple devices being shared with a 9p export. type Virtio9PMultidev string const ( // Remap shares multiple devices with only one export. Remap Virtio9PMultidev = "remap" // Warn assumes that only one device is shared by the same export. // Only a warning message is logged (once) by qemu on host side. // This is the default behaviour. Warn Virtio9PMultidev = "warn" // Forbid like "warn" but also deny access to additional devices on guest. Forbid Virtio9PMultidev = "forbid" ) // FSDriver represents a qemu filesystem driver. type FSDriver string // SecurityModelType is a qemu filesystem security model type. type SecurityModelType string const ( // Local is the local qemu filesystem driver. Local FSDriver = "local" // Handle is the handle qemu filesystem driver. Handle FSDriver = "handle" // Proxy is the proxy qemu filesystem driver. Proxy FSDriver = "proxy" ) const ( // None is like passthrough without failure reports. None SecurityModelType = "none" // PassThrough uses the same credentials on both the host and guest. PassThrough SecurityModelType = "passthrough" // MappedXattr stores some files attributes as extended attributes. MappedXattr SecurityModelType = "mapped-xattr" // MappedFile stores some files attributes in the .virtfs directory. MappedFile SecurityModelType = "mapped-file" ) // FSDevice represents a qemu filesystem configuration. type FSDevice struct { // Driver is the qemu device driver Driver DeviceDriver // FSDriver is the filesystem driver backend. FSDriver FSDriver // ID is the filesystem identifier. ID string // Path is the host root path for this filesystem. Path string // MountTag is the device filesystem mount point tag. MountTag string // SecurityModel is the security model for this filesystem device. SecurityModel SecurityModelType // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // Transport is the virtio transport for this device. Transport VirtioTransport // Multidev is the filesystem behaviour to deal // with multiple devices being shared with a 9p export Multidev Virtio9PMultidev } // Virtio9PTransport is a map of the virtio-9p device name that corresponds // to each transport. var Virtio9PTransport = map[VirtioTransport]string{ TransportPCI: "virtio-9p-pci", TransportCCW: "virtio-9p-ccw", TransportMMIO: "virtio-9p-device", } // Valid returns true if the FSDevice structure is valid and complete. func (fsdev FSDevice) Valid() bool { if fsdev.ID == "" || fsdev.Path == "" || fsdev.MountTag == "" { return false } return true } // QemuParams returns the qemu parameters built out of this filesystem device. func (fsdev FSDevice) QemuParams(config *Config) []string { var fsParams []string var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, fsdev.deviceName(config)) if s := fsdev.Transport.disableModern(config, fsdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",fsdev=%s", fsdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mount_tag=%s", fsdev.MountTag)) if fsdev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", fsdev.ROMFile)) } if fsdev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, ",iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", fsdev.DevNo)) } fsParams = append(fsParams, string(fsdev.FSDriver)) fsParams = append(fsParams, fmt.Sprintf(",id=%s", fsdev.ID)) fsParams = append(fsParams, fmt.Sprintf(",path=%s", fsdev.Path)) fsParams = append(fsParams, fmt.Sprintf(",security_model=%s", fsdev.SecurityModel)) if fsdev.Multidev != "" { fsParams = append(fsParams, fmt.Sprintf(",multidevs=%s", fsdev.Multidev)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) qemuParams = append(qemuParams, "-fsdev") qemuParams = append(qemuParams, strings.Join(fsParams, "")) return qemuParams } // deviceName returns the QEMU shared filesystem device name for the current // combination of driver and transport. func (fsdev FSDevice) deviceName(config *Config) string { if fsdev.Transport == "" { fsdev.Transport = fsdev.Transport.defaultTransport(config) } switch fsdev.Driver { case Virtio9P: return Virtio9PTransport[fsdev.Transport] } return string(fsdev.Driver) } // CharDeviceBackend is the character device backend for qemu type CharDeviceBackend string const ( // Pipe creates a 2 way connection to the guest. Pipe CharDeviceBackend = "pipe" // Socket creates a 2 way stream socket (TCP or Unix). Socket CharDeviceBackend = "socket" // CharConsole sends traffic from the guest to QEMU's standard output. CharConsole CharDeviceBackend = "console" // Serial sends traffic from the guest to a serial device on the host. Serial CharDeviceBackend = "serial" // TTY is an alias for Serial. TTY CharDeviceBackend = "tty" // PTY creates a new pseudo-terminal on the host and connect to it. PTY CharDeviceBackend = "pty" ) // CharDevice represents a qemu character device. type CharDevice struct { Backend CharDeviceBackend // Driver is the qemu device driver Driver DeviceDriver // Bus is the serial bus associated to this device. Bus string // DeviceID is the user defined device ID. DeviceID string ID string Path string Name string // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // Transport is the virtio transport for this device. Transport VirtioTransport } // VirtioSerialTransport is a map of the virtio-serial device name that // corresponds to each transport. var VirtioSerialTransport = map[VirtioTransport]string{ TransportPCI: "virtio-serial-pci", TransportCCW: "virtio-serial-ccw", TransportMMIO: "virtio-serial-device", } // Valid returns true if the CharDevice structure is valid and complete. func (cdev CharDevice) Valid() bool { if cdev.ID == "" || cdev.Path == "" { return false } return true } // QemuParams returns the qemu parameters built out of this character device. func (cdev CharDevice) QemuParams(config *Config) []string { var cdevParams []string var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, cdev.deviceName(config)) if cdev.Driver == VirtioSerial { if s := cdev.Transport.disableModern(config, cdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } } if cdev.Bus != "" { deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", cdev.Bus)) } deviceParams = append(deviceParams, fmt.Sprintf(",chardev=%s", cdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", cdev.DeviceID)) if cdev.Name != "" { deviceParams = append(deviceParams, fmt.Sprintf(",name=%s", cdev.Name)) } if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", cdev.ROMFile)) } if cdev.Driver == VirtioSerial && cdev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, ",iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", cdev.DevNo)) } cdevParams = append(cdevParams, string(cdev.Backend)) cdevParams = append(cdevParams, fmt.Sprintf(",id=%s", cdev.ID)) if cdev.Backend == Socket { cdevParams = append(cdevParams, fmt.Sprintf(",path=%s,server,nowait", cdev.Path)) } else { cdevParams = append(cdevParams, fmt.Sprintf(",path=%s", cdev.Path)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) qemuParams = append(qemuParams, "-chardev") qemuParams = append(qemuParams, strings.Join(cdevParams, "")) return qemuParams } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (cdev CharDevice) deviceName(config *Config) string { if cdev.Transport == "" { cdev.Transport = cdev.Transport.defaultTransport(config) } switch cdev.Driver { case VirtioSerial: return VirtioSerialTransport[cdev.Transport] } return string(cdev.Driver) } // NetDeviceType is a qemu networking device type. type NetDeviceType string const ( // TAP is a TAP networking device type. TAP NetDeviceType = "tap" // MACVTAP is a macvtap networking device type. MACVTAP NetDeviceType = "macvtap" // IPVTAP is a ipvtap virtual networking device type. IPVTAP NetDeviceType = "ipvtap" // VETHTAP is a veth-tap virtual networking device type. VETHTAP NetDeviceType = "vethtap" // VFIO is a direct assigned PCI device or PCI VF VFIO NetDeviceType = "VFIO" // VHOSTUSER is a vhost-user port (socket) VHOSTUSER NetDeviceType = "vhostuser" ) // QemuNetdevParam converts to the QEMU -netdev parameter notation func (n NetDeviceType) QemuNetdevParam(netdev *NetDevice, config *Config) string { if netdev.Transport == "" { netdev.Transport = netdev.Transport.defaultTransport(config) } switch n { case TAP: return "tap" case MACVTAP: return "tap" case IPVTAP: return "tap" case VETHTAP: return "tap" // -netdev type=tap -device virtio-net-pci case VFIO: if netdev.Transport == TransportMMIO { log.Fatal("vfio devices are not support with the MMIO transport") } return "" // -device vfio-pci (no netdev) case VHOSTUSER: if netdev.Transport == TransportCCW { log.Fatal("vhost-user devices are not supported on IBM Z") } return "vhost-user" // -netdev type=vhost-user (no device) default: return "" } } // QemuDeviceParam converts to the QEMU -device parameter notation func (n NetDeviceType) QemuDeviceParam(netdev *NetDevice, config *Config) DeviceDriver { if netdev.Transport == "" { netdev.Transport = netdev.Transport.defaultTransport(config) } var device string switch n { case TAP: device = "virtio-net" case MACVTAP: device = "virtio-net" case IPVTAP: device = "virtio-net" case VETHTAP: device = "virtio-net" // -netdev type=tap -device virtio-net-pci case VFIO: if netdev.Transport == TransportMMIO { log.Fatal("vfio devices are not support with the MMIO transport") } device = "vfio" // -device vfio-pci (no netdev) case VHOSTUSER: if netdev.Transport == TransportCCW { log.Fatal("vhost-user devices are not supported on IBM Z") } return "" // -netdev type=vhost-user (no device) default: return "" } switch netdev.Transport { case TransportPCI: return DeviceDriver(device + "-pci") case TransportCCW: return DeviceDriver(device + "-ccw") case TransportMMIO: return DeviceDriver(device + "-device") default: return "" } } // NetDevice represents a guest networking device type NetDevice struct { // Type is the netdev type (e.g. tap). Type NetDeviceType // Driver is the qemu device driver Driver DeviceDriver // ID is the netdevice identifier. ID string // IfName is the interface name, IFName string // Bus is the bus path name of a PCI device. Bus string // Addr is the address offset of a PCI device. Addr string // DownScript is the tap interface deconfiguration script. DownScript string // Script is the tap interface configuration script. Script string // FDs represents the list of already existing file descriptors to be used. // This is mostly useful for mq support. FDs []*os.File VhostFDs []*os.File // VHost enables virtio device emulation from the host kernel instead of from qemu. VHost bool // MACAddress is the networking device interface MAC address. MACAddress string // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // Transport is the virtio transport for this device. Transport VirtioTransport } // VirtioNetTransport is a map of the virtio-net device name that corresponds // to each transport. var VirtioNetTransport = map[VirtioTransport]string{ TransportPCI: "virtio-net-pci", TransportCCW: "virtio-net-ccw", TransportMMIO: "virtio-net-device", } // Valid returns true if the NetDevice structure is valid and complete. func (netdev NetDevice) Valid() bool { if netdev.ID == "" || netdev.IFName == "" { return false } switch netdev.Type { case TAP: return true case MACVTAP: return true default: return false } } // mqParameter returns the parameters for multi-queue driver. If the driver is a PCI device then the // vector flag is required. If the driver is a CCW type than the vector flag is not implemented and only // multi-queue option mq needs to be activated. See comment in libvirt code at // https://github.com/libvirt/libvirt/blob/6e7e965dcd3d885739129b1454ce19e819b54c25/src/qemu/qemu_command.c#L3633 func (netdev NetDevice) mqParameter(config *Config) string { p := []string{",mq=on"} if netdev.Transport.isVirtioPCI(config) { // https://www.linux-kvm.org/page/Multiqueue // -netdev tap,vhost=on,queues=N // enable mq and specify msix vectors in qemu cmdline // (2N+2 vectors, N for tx queues, N for rx queues, 1 for config, and one for possible control vq) // -device virtio-net-pci,mq=on,vectors=2N+2... // enable mq in guest by 'ethtool -L eth0 combined $queue_num' // Clearlinux automatically sets up the queues properly // The agent implementation should do this to ensure that it is // always set vectors := len(netdev.FDs)*2 + 2 p = append(p, fmt.Sprintf(",vectors=%d", vectors)) } return strings.Join(p, "") } // QemuDeviceParams returns the -device parameters for this network device func (netdev NetDevice) QemuDeviceParams(config *Config) []string { var deviceParams []string driver := netdev.Type.QemuDeviceParam(&netdev, config) if driver == "" { return nil } deviceParams = append(deviceParams, fmt.Sprintf("driver=%s", driver)) deviceParams = append(deviceParams, fmt.Sprintf(",netdev=%s", netdev.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",mac=%s", netdev.MACAddress)) if netdev.Bus != "" { deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", netdev.Bus)) } if netdev.Addr != "" { addr, err := strconv.Atoi(netdev.Addr) if err == nil && addr >= 0 { deviceParams = append(deviceParams, fmt.Sprintf(",addr=%x", addr)) } } if s := netdev.Transport.disableModern(config, netdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } if len(netdev.FDs) > 0 { // Note: We are appending to the device params here deviceParams = append(deviceParams, netdev.mqParameter(config)) } if netdev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", netdev.ROMFile)) } if netdev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, ",iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", netdev.DevNo)) } return deviceParams } // QemuNetdevParams returns the -netdev parameters for this network device func (netdev NetDevice) QemuNetdevParams(config *Config) []string { var netdevParams []string netdevType := netdev.Type.QemuNetdevParam(&netdev, config) if netdevType == "" { return nil } netdevParams = append(netdevParams, netdevType) netdevParams = append(netdevParams, fmt.Sprintf(",id=%s", netdev.ID)) if netdev.VHost { netdevParams = append(netdevParams, ",vhost=on") if len(netdev.VhostFDs) > 0 { var fdParams []string qemuFDs := config.appendFDs(netdev.VhostFDs) for _, fd := range qemuFDs { fdParams = append(fdParams, fmt.Sprintf("%d", fd)) } netdevParams = append(netdevParams, fmt.Sprintf(",vhostfds=%s", strings.Join(fdParams, ":"))) } } if len(netdev.FDs) > 0 { var fdParams []string qemuFDs := config.appendFDs(netdev.FDs) for _, fd := range qemuFDs { fdParams = append(fdParams, fmt.Sprintf("%d", fd)) } netdevParams = append(netdevParams, fmt.Sprintf(",fds=%s", strings.Join(fdParams, ":"))) } else { netdevParams = append(netdevParams, fmt.Sprintf(",ifname=%s", netdev.IFName)) if netdev.DownScript != "" { netdevParams = append(netdevParams, fmt.Sprintf(",downscript=%s", netdev.DownScript)) } if netdev.Script != "" { netdevParams = append(netdevParams, fmt.Sprintf(",script=%s", netdev.Script)) } } return netdevParams } // QemuParams returns the qemu parameters built out of this network device. func (netdev NetDevice) QemuParams(config *Config) []string { var netdevParams []string var deviceParams []string var qemuParams []string // Macvtap can only be connected via fds if (netdev.Type == MACVTAP) && (len(netdev.FDs) == 0) { return nil // implicit error } if netdev.Type.QemuNetdevParam(&netdev, config) != "" { netdevParams = netdev.QemuNetdevParams(config) if netdevParams != nil { qemuParams = append(qemuParams, "-netdev") qemuParams = append(qemuParams, strings.Join(netdevParams, "")) } } if netdev.Type.QemuDeviceParam(&netdev, config) != "" { deviceParams = netdev.QemuDeviceParams(config) if deviceParams != nil { qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) } } return qemuParams } // SerialDevice represents a qemu serial device. type SerialDevice struct { // Driver is the qemu device driver Driver DeviceDriver // ID is the serial device identifier. ID string // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // Transport is the virtio transport for this device. Transport VirtioTransport // MaxPorts is the maximum number of ports for this device. MaxPorts uint } // Valid returns true if the SerialDevice structure is valid and complete. func (dev SerialDevice) Valid() bool { if dev.Driver == "" || dev.ID == "" { return false } return true } // QemuParams returns the qemu parameters built out of this serial device. func (dev SerialDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, dev.deviceName(config)) if s := dev.Transport.disableModern(config, dev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", dev.ID)) if dev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", dev.ROMFile)) if dev.Driver == VirtioSerial && dev.MaxPorts != 0 { deviceParams = append(deviceParams, fmt.Sprintf(",max_ports=%d", dev.MaxPorts)) } } if dev.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, ",iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", dev.DevNo)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) return qemuParams } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (dev SerialDevice) deviceName(config *Config) string { if dev.Transport == "" { dev.Transport = dev.Transport.defaultTransport(config) } switch dev.Driver { case VirtioSerial: return VirtioSerialTransport[dev.Transport] } return string(dev.Driver) } // BlockDeviceInterface defines the type of interface the device is connected to. type BlockDeviceInterface string // BlockDeviceAIO defines the type of asynchronous I/O the block device should use. type BlockDeviceAIO string // BlockDeviceFormat defines the image format used on a block device. type BlockDeviceFormat string const ( // NoInterface for block devices with no interfaces. NoInterface BlockDeviceInterface = "none" // SCSI represents a SCSI block device interface. SCSI BlockDeviceInterface = "scsi" ) const ( // Threads is the pthread asynchronous I/O implementation. Threads BlockDeviceAIO = "threads" // Native is the pthread asynchronous I/O implementation. Native BlockDeviceAIO = "native" ) const ( // QCOW2 is the Qemu Copy On Write v2 image format. QCOW2 BlockDeviceFormat = "qcow2" ) // BlockDevice represents a qemu block device. type BlockDevice struct { Driver DeviceDriver ID string File string Interface BlockDeviceInterface AIO BlockDeviceAIO Format BlockDeviceFormat SCSI bool WCE bool // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // ShareRW enables multiple qemu instances to share the File ShareRW bool // ReadOnly sets the block device in readonly mode ReadOnly bool // Transport is the virtio transport for this device. Transport VirtioTransport } // VirtioBlockTransport is a map of the virtio-blk device name that corresponds // to each transport. var VirtioBlockTransport = map[VirtioTransport]string{ TransportPCI: "virtio-blk-pci", TransportCCW: "virtio-blk-ccw", TransportMMIO: "virtio-blk-device", } // Valid returns true if the BlockDevice structure is valid and complete. func (blkdev BlockDevice) Valid() bool { if blkdev.Driver == "" || blkdev.ID == "" || blkdev.File == "" { return false } return true } // QemuParams returns the qemu parameters built out of this block device. func (blkdev BlockDevice) QemuParams(config *Config) []string { var blkParams []string var deviceParams []string var qemuParams []string deviceParams = append(deviceParams, blkdev.deviceName(config)) if s := blkdev.Transport.disableModern(config, blkdev.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } deviceParams = append(deviceParams, fmt.Sprintf(",drive=%s", blkdev.ID)) if !blkdev.SCSI { deviceParams = append(deviceParams, ",scsi=off") } if !blkdev.WCE { deviceParams = append(deviceParams, ",config-wce=off") } if blkdev.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", blkdev.ROMFile)) } if blkdev.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", blkdev.DevNo)) } if blkdev.ShareRW { deviceParams = append(deviceParams, fmt.Sprintf(",share-rw=on")) } blkParams = append(blkParams, fmt.Sprintf("id=%s", blkdev.ID)) blkParams = append(blkParams, fmt.Sprintf(",file=%s", blkdev.File)) blkParams = append(blkParams, fmt.Sprintf(",aio=%s", blkdev.AIO)) blkParams = append(blkParams, fmt.Sprintf(",format=%s", blkdev.Format)) blkParams = append(blkParams, fmt.Sprintf(",if=%s", blkdev.Interface)) if blkdev.ReadOnly { blkParams = append(blkParams, ",readonly") } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) qemuParams = append(qemuParams, "-drive") qemuParams = append(qemuParams, strings.Join(blkParams, "")) return qemuParams } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (blkdev BlockDevice) deviceName(config *Config) string { if blkdev.Transport == "" { blkdev.Transport = blkdev.Transport.defaultTransport(config) } switch blkdev.Driver { case VirtioBlock: return VirtioBlockTransport[blkdev.Transport] } return string(blkdev.Driver) } // PVPanicDevice represents a qemu pvpanic device. type PVPanicDevice struct { NoShutdown bool } // Valid always returns true for pvpanic device func (dev PVPanicDevice) Valid() bool { return true } // QemuParams returns the qemu parameters built out of this serial device. func (dev PVPanicDevice) QemuParams(config *Config) []string { if dev.NoShutdown { return []string{"-device", "pvpanic", "-no-shutdown"} } return []string{"-device", "pvpanic"} } // VhostUserDevice represents a qemu vhost-user device meant to be passed // in to the guest type VhostUserDevice struct { SocketPath string //path to vhostuser socket on host CharDevID string TypeDevID string //variable QEMU parameter based on value of VhostUserType Address string //used for MAC address in net case Tag string //virtio-fs volume id for mounting inside guest CacheSize uint32 //virtio-fs DAX cache size in MiB SharedVersions bool //enable virtio-fs shared version metadata VhostUserType DeviceDriver // ROMFile specifies the ROM file being used for this device. ROMFile string // Transport is the virtio transport for this device. Transport VirtioTransport } // VhostUserNetTransport is a map of the virtio-net device name that // corresponds to each transport. var VhostUserNetTransport = map[VirtioTransport]string{ TransportPCI: "virtio-net-pci", TransportCCW: "virtio-net-ccw", TransportMMIO: "virtio-net-device", } // VhostUserSCSITransport is a map of the vhost-user-scsi device name that // corresponds to each transport. var VhostUserSCSITransport = map[VirtioTransport]string{ TransportPCI: "vhost-user-scsi-pci", TransportCCW: "vhost-user-scsi-ccw", TransportMMIO: "vhost-user-scsi-device", } // VhostUserBlkTransport is a map of the vhost-user-blk device name that // corresponds to each transport. var VhostUserBlkTransport = map[VirtioTransport]string{ TransportPCI: "vhost-user-blk-pci", TransportCCW: "vhost-user-blk-ccw", TransportMMIO: "vhost-user-blk-device", } // VhostUserFSTransport is a map of the vhost-user-fs device name that // corresponds to each transport. var VhostUserFSTransport = map[VirtioTransport]string{ TransportPCI: "vhost-user-fs-pci", TransportCCW: "vhost-user-fs-ccw", TransportMMIO: "vhost-user-fs-device", } // Valid returns true if there is a valid structure defined for VhostUserDevice func (vhostuserDev VhostUserDevice) Valid() bool { if vhostuserDev.SocketPath == "" || vhostuserDev.CharDevID == "" { return false } switch vhostuserDev.VhostUserType { case VhostUserNet: if vhostuserDev.TypeDevID == "" || vhostuserDev.Address == "" { return false } case VhostUserSCSI: if vhostuserDev.TypeDevID == "" { return false } case VhostUserBlk: case VhostUserFS: if vhostuserDev.Tag == "" { return false } default: return false } return true } // QemuParams returns the qemu parameters built out of this vhostuser device. func (vhostuserDev VhostUserDevice) QemuParams(config *Config) []string { var qemuParams []string var charParams []string var netParams []string var devParams []string var driver string charParams = append(charParams, "socket") charParams = append(charParams, fmt.Sprintf("id=%s", vhostuserDev.CharDevID)) charParams = append(charParams, fmt.Sprintf("path=%s", vhostuserDev.SocketPath)) switch vhostuserDev.VhostUserType { // if network based vhost device: case VhostUserNet: driver = vhostuserDev.deviceName(config) if driver == "" { return nil } netParams = append(netParams, "type=vhost-user") netParams = append(netParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) netParams = append(netParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) netParams = append(netParams, "vhostforce") devParams = append(devParams, driver) devParams = append(devParams, fmt.Sprintf("netdev=%s", vhostuserDev.TypeDevID)) devParams = append(devParams, fmt.Sprintf("mac=%s", vhostuserDev.Address)) case VhostUserSCSI: driver = vhostuserDev.deviceName(config) if driver == "" { return nil } devParams = append(devParams, driver) devParams = append(devParams, fmt.Sprintf("id=%s", vhostuserDev.TypeDevID)) devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) case VhostUserBlk: driver = vhostuserDev.deviceName(config) if driver == "" { return nil } devParams = append(devParams, driver) devParams = append(devParams, "logical_block_size=4096") devParams = append(devParams, "size=512M") devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) case VhostUserFS: driver = vhostuserDev.deviceName(config) if driver == "" { return nil } devParams = append(devParams, driver) devParams = append(devParams, fmt.Sprintf("chardev=%s", vhostuserDev.CharDevID)) devParams = append(devParams, fmt.Sprintf("tag=%s", vhostuserDev.Tag)) if vhostuserDev.CacheSize != 0 { devParams = append(devParams, fmt.Sprintf("cache-size=%dM", vhostuserDev.CacheSize)) } if vhostuserDev.SharedVersions { devParams = append(devParams, "versiontable=/dev/shm/fuse_shared_versions") } default: return nil } if vhostuserDev.Transport.isVirtioPCI(config) { devParams = append(devParams, fmt.Sprintf("romfile=%s", vhostuserDev.ROMFile)) } qemuParams = append(qemuParams, "-chardev") qemuParams = append(qemuParams, strings.Join(charParams, ",")) // if network based vhost device: if vhostuserDev.VhostUserType == VhostUserNet { qemuParams = append(qemuParams, "-netdev") qemuParams = append(qemuParams, strings.Join(netParams, ",")) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(devParams, ",")) return qemuParams } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (vhostuserDev VhostUserDevice) deviceName(config *Config) string { if vhostuserDev.Transport == "" { vhostuserDev.Transport = vhostuserDev.Transport.defaultTransport(config) } switch vhostuserDev.VhostUserType { case VhostUserNet: return VhostUserNetTransport[vhostuserDev.Transport] case VhostUserSCSI: return VhostUserSCSITransport[vhostuserDev.Transport] case VhostUserBlk: return VhostUserBlkTransport[vhostuserDev.Transport] case VhostUserFS: return VhostUserFSTransport[vhostuserDev.Transport] default: return "" } } // PCIeRootPortDevice represents a memory balloon device. type PCIeRootPortDevice struct { ID string // format: rp{n}, n>=0 Bus string // default is pcie.0 Chassis string // (slot, chassis) pair is mandatory and must be unique for each pcie-root-port, >=0, default is 0x00 Slot string // >=0, default is 0x00 Multifunction bool // true => "on", false => "off", default is off Addr string // >=0, default is 0x00 // The PCIE-PCI bridge can be hot-plugged only into pcie-root-port that has 'bus-reserve' property value to // provide secondary bus for the hot-plugged bridge. BusReserve string Pref64Reserve string // reserve prefetched MMIO aperture, 64-bit Pref32Reserve string // reserve prefetched MMIO aperture, 32-bit MemReserve string // reserve non-prefetched MMIO aperture, 32-bit *only* IOReserve string // IO reservation ROMFile string // ROMFile specifies the ROM file being used for this device. // Transport is the virtio transport for this device. Transport VirtioTransport } // QemuParams returns the qemu parameters built out of the PCIeRootPortDevice. func (b PCIeRootPortDevice) QemuParams(config *Config) []string { var qemuParams []string var deviceParams []string driver := PCIeRootPort deviceParams = append(deviceParams, fmt.Sprintf("%s,id=%s", driver, b.ID)) if b.Bus == "" { b.Bus = "pcie.0" } deviceParams = append(deviceParams, fmt.Sprintf("bus=%s", b.Bus)) if b.Chassis == "" { b.Chassis = "0x00" } deviceParams = append(deviceParams, fmt.Sprintf("chassis=%s", b.Chassis)) if b.Slot == "" { b.Slot = "0x00" } deviceParams = append(deviceParams, fmt.Sprintf("slot=%s", b.Slot)) multifunction := "off" if b.Multifunction { multifunction = "on" if b.Addr == "" { b.Addr = "0x00" } deviceParams = append(deviceParams, fmt.Sprintf("addr=%s", b.Addr)) } deviceParams = append(deviceParams, fmt.Sprintf("multifunction=%v", multifunction)) if b.BusReserve != "" { deviceParams = append(deviceParams, fmt.Sprintf("bus-reserve=%s", b.BusReserve)) } if b.Pref64Reserve != "" { deviceParams = append(deviceParams, fmt.Sprintf("pref64-reserve=%s", b.Pref64Reserve)) } if b.Pref32Reserve != "" { deviceParams = append(deviceParams, fmt.Sprintf("pref32-reserve=%s", b.Pref32Reserve)) } if b.MemReserve != "" { deviceParams = append(deviceParams, fmt.Sprintf("mem-reserve=%s", b.MemReserve)) } if b.IOReserve != "" { deviceParams = append(deviceParams, fmt.Sprintf("io-reserve=%s", b.IOReserve)) } if b.Transport.isVirtioPCI(config) && b.ROMFile != "" { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } // Valid returns true if the PCIeRootPortDevice structure is valid and complete. func (b PCIeRootPortDevice) Valid() bool { // the "pref32-reserve" and "pref64-reserve" hints are mutually exclusive. if b.Pref64Reserve != "" && b.Pref32Reserve != "" { return false } if b.ID == "" { return false } return true } // VFIODevice represents a qemu vfio device meant for direct access by guest OS. type VFIODevice struct { // Bus-Device-Function of device BDF string // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // VendorID specifies vendor id VendorID string // DeviceID specifies device id DeviceID string // Bus specifies device bus Bus string // Transport is the virtio transport for this device. Transport VirtioTransport } // VFIODeviceTransport is a map of the vfio device name that corresponds to // each transport. var VFIODeviceTransport = map[VirtioTransport]string{ TransportPCI: "vfio-pci", TransportCCW: "vfio-ccw", TransportMMIO: "vfio-device", } // Valid returns true if the VFIODevice structure is valid and complete. func (vfioDev VFIODevice) Valid() bool { return vfioDev.BDF != "" } // QemuParams returns the qemu parameters built out of this vfio device. func (vfioDev VFIODevice) QemuParams(config *Config) []string { var qemuParams []string var deviceParams []string driver := vfioDev.deviceName(config) deviceParams = append(deviceParams, fmt.Sprintf("%s,host=%s", driver, vfioDev.BDF)) if vfioDev.Transport.isVirtioPCI(config) { if vfioDev.VendorID != "" { deviceParams = append(deviceParams, fmt.Sprintf(",x-pci-vendor-id=%s", vfioDev.VendorID)) } if vfioDev.DeviceID != "" { deviceParams = append(deviceParams, fmt.Sprintf(",x-pci-device-id=%s", vfioDev.DeviceID)) } deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vfioDev.ROMFile)) } if vfioDev.Bus != "" { deviceParams = append(deviceParams, fmt.Sprintf(",bus=%s", vfioDev.Bus)) } if vfioDev.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vfioDev.DevNo)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) return qemuParams } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (vfioDev VFIODevice) deviceName(config *Config) string { if vfioDev.Transport == "" { vfioDev.Transport = vfioDev.Transport.defaultTransport(config) } return VFIODeviceTransport[vfioDev.Transport] } // SCSIController represents a SCSI controller device. type SCSIController struct { ID string // Bus on which the SCSI controller is attached, this is optional Bus string // Addr is the PCI address offset, this is optional Addr string // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // IOThread is the IO thread on which IO will be handled IOThread string // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // Transport is the virtio transport for this device. Transport VirtioTransport } // SCSIControllerTransport is a map of the virtio-scsi device name that // corresponds to each transport. var SCSIControllerTransport = map[VirtioTransport]string{ TransportPCI: "virtio-scsi-pci", TransportCCW: "virtio-scsi-ccw", TransportMMIO: "virtio-scsi-device", } // Valid returns true if the SCSIController structure is valid and complete. func (scsiCon SCSIController) Valid() bool { return scsiCon.ID != "" } // QemuParams returns the qemu parameters built out of this SCSIController device. func (scsiCon SCSIController) QemuParams(config *Config) []string { var qemuParams []string var devParams []string driver := scsiCon.deviceName(config) devParams = append(devParams, fmt.Sprintf("%s,id=%s", driver, scsiCon.ID)) if scsiCon.Bus != "" { devParams = append(devParams, fmt.Sprintf("bus=%s", scsiCon.Bus)) } if scsiCon.Addr != "" { devParams = append(devParams, fmt.Sprintf("addr=%s", scsiCon.Addr)) } if s := scsiCon.Transport.disableModern(config, scsiCon.DisableModern); s != "" { devParams = append(devParams, s) } if scsiCon.IOThread != "" { devParams = append(devParams, fmt.Sprintf("iothread=%s", scsiCon.IOThread)) } if scsiCon.Transport.isVirtioPCI(config) { devParams = append(devParams, fmt.Sprintf("romfile=%s", scsiCon.ROMFile)) } if scsiCon.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { devParams = append(devParams, ",iommu_platform=on") } devParams = append(devParams, fmt.Sprintf("devno=%s", scsiCon.DevNo)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(devParams, ",")) return qemuParams } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (scsiCon SCSIController) deviceName(config *Config) string { if scsiCon.Transport == "" { scsiCon.Transport = scsiCon.Transport.defaultTransport(config) } return SCSIControllerTransport[scsiCon.Transport] } // BridgeType is the type of the bridge type BridgeType uint const ( // PCIBridge is a pci bridge PCIBridge BridgeType = iota // PCIEBridge is a pcie bridge PCIEBridge ) // BridgeDevice represents a qemu bridge device like pci-bridge, pxb, etc. type BridgeDevice struct { // Type of the bridge Type BridgeType // Bus number where the bridge is plugged, typically pci.0 or pcie.0 Bus string // ID is used to identify the bridge in qemu ID string // Chassis number Chassis int // SHPC is used to enable or disable the standard hot plug controller SHPC bool // PCI Slot Addr string // ROMFile specifies the ROM file being used for this device. ROMFile string } // Valid returns true if the BridgeDevice structure is valid and complete. func (bridgeDev BridgeDevice) Valid() bool { if bridgeDev.Type != PCIBridge && bridgeDev.Type != PCIEBridge { return false } if bridgeDev.Bus == "" { return false } if bridgeDev.ID == "" { return false } return true } // QemuParams returns the qemu parameters built out of this bridge device. func (bridgeDev BridgeDevice) QemuParams(config *Config) []string { var qemuParams []string var deviceParam []string var driver DeviceDriver switch bridgeDev.Type { case PCIEBridge: driver = PCIePCIBridgeDriver deviceParam = append(deviceParam, fmt.Sprintf("%s,bus=%s,id=%s", driver, bridgeDev.Bus, bridgeDev.ID)) default: driver = PCIBridgeDriver shpc := "off" if bridgeDev.SHPC { shpc = "on" } deviceParam = append(deviceParam, fmt.Sprintf("%s,bus=%s,id=%s,chassis_nr=%d,shpc=%s", driver, bridgeDev.Bus, bridgeDev.ID, bridgeDev.Chassis, shpc)) } if bridgeDev.Addr != "" { addr, err := strconv.Atoi(bridgeDev.Addr) if err == nil && addr >= 0 { deviceParam = append(deviceParam, fmt.Sprintf(",addr=%x", addr)) } } var transport VirtioTransport if transport.isVirtioPCI(config) { deviceParam = append(deviceParam, fmt.Sprintf(",romfile=%s", bridgeDev.ROMFile)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParam, "")) return qemuParams } // VSOCKDevice represents a AF_VSOCK socket. type VSOCKDevice struct { ID string ContextID uint64 // VHostFD vhost file descriptor that holds the ContextID VHostFD *os.File // DisableModern prevents qemu from relying on fast MMIO. DisableModern bool // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // Transport is the virtio transport for this device. Transport VirtioTransport } // VSOCKDeviceTransport is a map of the vhost-vsock device name that // corresponds to each transport. var VSOCKDeviceTransport = map[VirtioTransport]string{ TransportPCI: "vhost-vsock-pci", TransportCCW: "vhost-vsock-ccw", TransportMMIO: "vhost-vsock-device", } const ( // MinimalGuestCID is the smallest valid context ID for a guest. MinimalGuestCID uint64 = 3 // MaxGuestCID is the largest valid context ID for a guest. MaxGuestCID uint64 = 1<<32 - 1 ) const ( // VSOCKGuestCID is the VSOCK guest CID parameter. VSOCKGuestCID = "guest-cid" ) // Valid returns true if the VSOCKDevice structure is valid and complete. func (vsock VSOCKDevice) Valid() bool { if vsock.ID == "" || vsock.ContextID < MinimalGuestCID || vsock.ContextID > MaxGuestCID { return false } return true } // QemuParams returns the qemu parameters built out of the VSOCK device. func (vsock VSOCKDevice) QemuParams(config *Config) []string { var deviceParams []string var qemuParams []string driver := vsock.deviceName(config) deviceParams = append(deviceParams, string(driver)) if s := vsock.Transport.disableModern(config, vsock.DisableModern); s != "" { deviceParams = append(deviceParams, fmt.Sprintf(",%s", s)) } if vsock.VHostFD != nil { qemuFDs := config.appendFDs([]*os.File{vsock.VHostFD}) deviceParams = append(deviceParams, fmt.Sprintf(",vhostfd=%d", qemuFDs[0])) } deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", vsock.ID)) deviceParams = append(deviceParams, fmt.Sprintf(",%s=%d", VSOCKGuestCID, vsock.ContextID)) if vsock.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf(",romfile=%s", vsock.ROMFile)) } if vsock.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, ",iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf(",devno=%s", vsock.DevNo)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, "")) return qemuParams } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (vsock VSOCKDevice) deviceName(config *Config) string { if vsock.Transport == "" { vsock.Transport = vsock.Transport.defaultTransport(config) } return VSOCKDeviceTransport[vsock.Transport] } // RngDevice represents a random number generator device. type RngDevice struct { // ID is the device ID ID string // Filename is entropy source on the host Filename string // MaxBytes is the bytes allowed to guest to get from the host’s entropy per period MaxBytes uint // Period is duration of a read period in seconds Period uint // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // Transport is the virtio transport for this device. Transport VirtioTransport } // RngDeviceTransport is a map of the virtio-rng device name that corresponds // to each transport. var RngDeviceTransport = map[VirtioTransport]string{ TransportPCI: "virtio-rng-pci", TransportCCW: "virtio-rng-ccw", TransportMMIO: "virtio-rng-device", } // Valid returns true if the RngDevice structure is valid and complete. func (v RngDevice) Valid() bool { return v.ID != "" } // QemuParams returns the qemu parameters built out of the RngDevice. func (v RngDevice) QemuParams(config *Config) []string { var qemuParams []string //-object rng-random,filename=/dev/hwrng,id=rng0 var objectParams []string //-device virtio-rng-pci,rng=rng0,max-bytes=1024,period=1000 var deviceParams []string objectParams = append(objectParams, "rng-random") objectParams = append(objectParams, "id="+v.ID) deviceParams = append(deviceParams, v.deviceName(config)) deviceParams = append(deviceParams, "rng="+v.ID) if v.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", v.ROMFile)) } if v.Transport.isVirtioCCW(config) { if config.Knobs.IOMMUPlatform { deviceParams = append(deviceParams, ",iommu_platform=on") } deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", v.DevNo)) } if v.Filename != "" { objectParams = append(objectParams, "filename="+v.Filename) } if v.MaxBytes > 0 { deviceParams = append(deviceParams, fmt.Sprintf("max-bytes=%d", v.MaxBytes)) } if v.Period > 0 { deviceParams = append(deviceParams, fmt.Sprintf("period=%d", v.Period)) } qemuParams = append(qemuParams, "-object") qemuParams = append(qemuParams, strings.Join(objectParams, ",")) qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (v RngDevice) deviceName(config *Config) string { if v.Transport == "" { v.Transport = v.Transport.defaultTransport(config) } return RngDeviceTransport[v.Transport] } // BalloonDevice represents a memory balloon device. type BalloonDevice struct { DeflateOnOOM bool DisableModern bool ID string // ROMFile specifies the ROM file being used for this device. ROMFile string // DevNo identifies the ccw devices for s390x architecture DevNo string // Transport is the virtio transport for this device. Transport VirtioTransport } // BalloonDeviceTransport is a map of the virtio-balloon device name that // corresponds to each transport. var BalloonDeviceTransport = map[VirtioTransport]string{ TransportPCI: "virtio-balloon-pci", TransportCCW: "virtio-balloon-ccw", TransportMMIO: "virtio-balloon-device", } // QemuParams returns the qemu parameters built out of the BalloonDevice. func (b BalloonDevice) QemuParams(config *Config) []string { var qemuParams []string var deviceParams []string deviceParams = append(deviceParams, b.deviceName(config)) if b.ID != "" { deviceParams = append(deviceParams, "id="+b.ID) } if b.Transport.isVirtioPCI(config) { deviceParams = append(deviceParams, fmt.Sprintf("romfile=%s", b.ROMFile)) } if b.Transport.isVirtioCCW(config) { deviceParams = append(deviceParams, fmt.Sprintf("devno=%s", b.DevNo)) } if b.DeflateOnOOM { deviceParams = append(deviceParams, "deflate-on-oom=on") } else { deviceParams = append(deviceParams, "deflate-on-oom=off") } if s := b.Transport.disableModern(config, b.DisableModern); s != "" { deviceParams = append(deviceParams, string(s)) } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } // Valid returns true if the balloonDevice structure is valid and complete. func (b BalloonDevice) Valid() bool { return b.ID != "" } // deviceName returns the QEMU device name for the current combination of // driver and transport. func (b BalloonDevice) deviceName(config *Config) string { if b.Transport == "" { b.Transport = b.Transport.defaultTransport(config) } return BalloonDeviceTransport[b.Transport] } // IommuDev represents a Intel IOMMU Device type IommuDev struct { Intremap bool DeviceIotlb bool CachingMode bool } // Valid returns true if the IommuDev is valid func (dev IommuDev) Valid() bool { return true } // deviceName the qemu device name func (dev IommuDev) deviceName() string { return "intel-iommu" } // QemuParams returns the qemu parameters built out of the IommuDev. func (dev IommuDev) QemuParams(_ *Config) []string { var qemuParams []string var deviceParams []string deviceParams = append(deviceParams, dev.deviceName()) if dev.Intremap { deviceParams = append(deviceParams, "intremap=on") } else { deviceParams = append(deviceParams, "intremap=off") } if dev.DeviceIotlb { deviceParams = append(deviceParams, "device-iotlb=on") } else { deviceParams = append(deviceParams, "device-iotlb=off") } if dev.CachingMode { deviceParams = append(deviceParams, "caching-mode=on") } else { deviceParams = append(deviceParams, "caching-mode=off") } qemuParams = append(qemuParams, "-device") qemuParams = append(qemuParams, strings.Join(deviceParams, ",")) return qemuParams } // RTCBaseType is the qemu RTC base time type. type RTCBaseType string // RTCClock is the qemu RTC clock type. type RTCClock string // RTCDriftFix is the qemu RTC drift fix type. type RTCDriftFix string const ( // UTC is the UTC base time for qemu RTC. UTC RTCBaseType = "utc" // LocalTime is the local base time for qemu RTC. LocalTime RTCBaseType = "localtime" ) const ( // Host is for using the host clock as a reference. Host RTCClock = "host" // RT is for using the host monotonic clock as a reference. RT RTCClock = "rt" // VM is for using the guest clock as a reference VM RTCClock = "vm" ) const ( // Slew is the qemu RTC Drift fix mechanism. Slew RTCDriftFix = "slew" // NoDriftFix means we don't want/need to fix qemu's RTC drift. NoDriftFix RTCDriftFix = "none" ) // RTC represents a qemu Real Time Clock configuration. type RTC struct { // Base is the RTC start time. Base RTCBaseType // Clock is the is the RTC clock driver. Clock RTCClock // DriftFix is the drift fixing mechanism. DriftFix RTCDriftFix } // Valid returns true if the RTC structure is valid and complete. func (rtc RTC) Valid() bool { if rtc.Clock != Host && rtc.Clock != RT && rtc.Clock != VM { return false } if rtc.DriftFix != Slew && rtc.DriftFix != NoDriftFix { return false } return true } // QMPSocketType is the type of socket used for QMP communication. type QMPSocketType string const ( // Unix socket for QMP. Unix QMPSocketType = "unix" ) // QMPSocket represents a qemu QMP socket configuration. type QMPSocket struct { // Type is the socket type (e.g. "unix"). Type QMPSocketType // Name is the socket name. Name string // Server tells if this is a server socket. Server bool // NoWait tells if qemu should block waiting for a client to connect. NoWait bool } // Valid returns true if the QMPSocket structure is valid and complete. func (qmp QMPSocket) Valid() bool { if qmp.Type == "" || qmp.Name == "" { return false } if qmp.Type != Unix { return false } return true } // SMP is the multi processors configuration structure. type SMP struct { // CPUs is the number of VCPUs made available to qemu. CPUs uint32 // Cores is the number of cores made available to qemu. Cores uint32 // Threads is the number of threads made available to qemu. Threads uint32 // Sockets is the number of sockets made available to qemu. Sockets uint32 // MaxCPUs is the maximum number of VCPUs that a VM can have. // This value, if non-zero, MUST BE equal to or greater than CPUs MaxCPUs uint32 } // Memory is the guest memory configuration structure. type Memory struct { // Size is the amount of memory made available to the guest. // It should be suffixed with M or G for sizes in megabytes or // gigabytes respectively. Size string // Slots is the amount of memory slots made available to the guest. Slots uint8 // MaxMem is the maximum amount of memory that can be made available // to the guest through e.g. hot pluggable memory. MaxMem string // Path is the file path of the memory device. It points to a local // file path used by FileBackedMem. Path string } // Kernel is the guest kernel configuration structure. type Kernel struct { // Path is the guest kernel path on the host filesystem. Path string // InitrdPath is the guest initrd path on the host filesystem. InitrdPath string // Params is the kernel parameters string. Params string } // FwCfg allows QEMU to pass entries to the guest // File and Str are mutually exclusive type FwCfg struct { Name string File string Str string } // Valid returns true if the FwCfg structure is valid and complete. func (fwcfg FwCfg) Valid() bool { if fwcfg.Name == "" { return false } if fwcfg.File != "" && fwcfg.Str != "" { return false } if fwcfg.File == "" && fwcfg.Str == "" { return false } return true } // QemuParams returns the qemu parameters built out of the FwCfg object func (fwcfg FwCfg) QemuParams(config *Config) []string { var fwcfgParams []string var qemuParams []string if config.FwCfg.Name != "" { fwcfgParams = append(fwcfgParams, fmt.Sprintf("name=%q", config.FwCfg.Name)) if config.FwCfg.File != "" { fwcfgParams = append(fwcfgParams, fmt.Sprintf(",file=%q", config.FwCfg.File)) } if config.FwCfg.Str != "" { fwcfgParams = append(fwcfgParams, fmt.Sprintf(",string=%q", config.FwCfg.Str)) } } qemuParams = append(qemuParams, "-fw_cfg") qemuParams = append(qemuParams, strings.Join(fwcfgParams, "")) return qemuParams } // Knobs regroups a set of qemu boolean settings type Knobs struct { // NoUserConfig prevents qemu from loading user config files. NoUserConfig bool // NoDefaults prevents qemu from creating default devices. NoDefaults bool // NoGraphic completely disables graphic output. NoGraphic bool // Daemonize will turn the qemu process into a daemon Daemonize bool // Both HugePages and MemPrealloc require the Memory.Size of the VM // to be set, as they need to reserve the memory upfront in order // for the VM to boot without errors. // // HugePages always results in memory pre-allocation. // However the setup is different from normal pre-allocation. // Hence HugePages has precedence over MemPrealloc // HugePages will pre-allocate all the RAM from huge pages HugePages bool // MemPrealloc will allocate all the RAM upfront MemPrealloc bool // FileBackedMem requires Memory.Size and Memory.Path of the VM to // be set. FileBackedMem bool // MemShared will set the memory device as shared. MemShared bool // Mlock will control locking of memory // Only active when Realtime is set to true Mlock bool // Stopped will not start guest CPU at startup Stopped bool // Realtime will enable realtime QEMU Realtime bool // Exit instead of rebooting NoReboot bool // IOMMUPlatform will enable IOMMU for supported devices IOMMUPlatform bool } // IOThread allows IO to be performed on a separate thread. type IOThread struct { ID string } const ( // MigrationFD is the migration incoming type based on open file descriptor. // Skip default 0 so that it must be set on purpose. MigrationFD = 1 // MigrationExec is the migration incoming type based on commands. MigrationExec = 2 // MigrationDefer is the defer incoming type MigrationDefer = 3 ) // Incoming controls migration source preparation type Incoming struct { // Possible values are MigrationFD, MigrationExec MigrationType int // Only valid if MigrationType == MigrationFD FD *os.File // Only valid if MigrationType == MigrationExec Exec string } // Config is the qemu configuration structure. // It allows for passing custom settings and parameters to the qemu API. type Config struct { // Path is the qemu binary path. Path string // Ctx is the context used when launching qemu. Ctx context.Context // Name is the qemu guest name Name string // UUID is the qemu process UUID. UUID string // CPUModel is the CPU model to be used by qemu. CPUModel string // Machine Machine Machine // QMPSockets is a slice of QMP socket description. QMPSockets []QMPSocket // Devices is a list of devices for qemu to create and drive. Devices []Device // RTC is the qemu Real Time Clock configuration RTC RTC // VGA is the qemu VGA mode. VGA string // Kernel is the guest kernel configuration. Kernel Kernel // Memory is the guest memory configuration. Memory Memory // SMP is the quest multi processors configuration. SMP SMP // GlobalParam is the -global parameter. GlobalParam string // Knobs is a set of qemu boolean settings. Knobs Knobs // Bios is the -bios parameter Bios string // Incoming controls migration source preparation Incoming Incoming // fds is a list of open file descriptors to be passed to the spawned qemu process fds []*os.File // FwCfg is the -fw_cfg parameter FwCfg FwCfg IOThreads []IOThread // PidFile is the -pidfile parameter PidFile string // LogFile is the -D parameter LogFile string qemuParams []string } // appendFDs append a list of file descriptors to the qemu configuration and // returns a slice of offset file descriptors that will be seen by the qemu process. func (config *Config) appendFDs(fds []*os.File) []int { var fdInts []int oldLen := len(config.fds) config.fds = append(config.fds, fds...) // The magic 3 offset comes from https://golang.org/src/os/exec/exec.go: // ExtraFiles specifies additional open files to be inherited by the // new process. It does not include standard input, standard output, or // standard error. If non-nil, entry i becomes file descriptor 3+i. for i := range fds { fdInts = append(fdInts, oldLen+3+i) } return fdInts } func (config *Config) appendName() { if config.Name != "" { config.qemuParams = append(config.qemuParams, "-name") config.qemuParams = append(config.qemuParams, config.Name) } } func (config *Config) appendMachine() { if config.Machine.Type != "" { var machineParams []string machineParams = append(machineParams, config.Machine.Type) if config.Machine.Acceleration != "" { machineParams = append(machineParams, fmt.Sprintf(",accel=%s", config.Machine.Acceleration)) } if config.Machine.Options != "" { machineParams = append(machineParams, fmt.Sprintf(",%s", config.Machine.Options)) } config.qemuParams = append(config.qemuParams, "-machine") config.qemuParams = append(config.qemuParams, strings.Join(machineParams, "")) } } func (config *Config) appendCPUModel() { if config.CPUModel != "" { config.qemuParams = append(config.qemuParams, "-cpu") config.qemuParams = append(config.qemuParams, config.CPUModel) } } func (config *Config) appendQMPSockets() { for _, q := range config.QMPSockets { if !q.Valid() { continue } qmpParams := append([]string{}, fmt.Sprintf("%s:", q.Type)) qmpParams = append(qmpParams, q.Name) if q.Server { qmpParams = append(qmpParams, ",server") if q.NoWait { qmpParams = append(qmpParams, ",nowait") } } config.qemuParams = append(config.qemuParams, "-qmp") config.qemuParams = append(config.qemuParams, strings.Join(qmpParams, "")) } } func (config *Config) appendDevices() { for _, d := range config.Devices { if !d.Valid() { continue } config.qemuParams = append(config.qemuParams, d.QemuParams(config)...) } } func (config *Config) appendUUID() { if config.UUID != "" { config.qemuParams = append(config.qemuParams, "-uuid") config.qemuParams = append(config.qemuParams, config.UUID) } } func (config *Config) appendMemory() { if config.Memory.Size != "" { var memoryParams []string memoryParams = append(memoryParams, config.Memory.Size) if config.Memory.Slots > 0 { memoryParams = append(memoryParams, fmt.Sprintf(",slots=%d", config.Memory.Slots)) } if config.Memory.MaxMem != "" { memoryParams = append(memoryParams, fmt.Sprintf(",maxmem=%s", config.Memory.MaxMem)) } config.qemuParams = append(config.qemuParams, "-m") config.qemuParams = append(config.qemuParams, strings.Join(memoryParams, "")) } } func (config *Config) appendCPUs() error { if config.SMP.CPUs > 0 { var SMPParams []string SMPParams = append(SMPParams, fmt.Sprintf("%d", config.SMP.CPUs)) if config.SMP.Cores > 0 { SMPParams = append(SMPParams, fmt.Sprintf(",cores=%d", config.SMP.Cores)) } if config.SMP.Threads > 0 { SMPParams = append(SMPParams, fmt.Sprintf(",threads=%d", config.SMP.Threads)) } if config.SMP.Sockets > 0 { SMPParams = append(SMPParams, fmt.Sprintf(",sockets=%d", config.SMP.Sockets)) } if config.SMP.MaxCPUs > 0 { if config.SMP.MaxCPUs < config.SMP.CPUs { return fmt.Errorf("MaxCPUs %d must be equal to or greater than CPUs %d", config.SMP.MaxCPUs, config.SMP.CPUs) } SMPParams = append(SMPParams, fmt.Sprintf(",maxcpus=%d", config.SMP.MaxCPUs)) } config.qemuParams = append(config.qemuParams, "-smp") config.qemuParams = append(config.qemuParams, strings.Join(SMPParams, "")) } return nil } func (config *Config) appendRTC() { if !config.RTC.Valid() { return } var RTCParams []string RTCParams = append(RTCParams, fmt.Sprintf("base=%s", string(config.RTC.Base))) if config.RTC.DriftFix != "" { RTCParams = append(RTCParams, fmt.Sprintf(",driftfix=%s", config.RTC.DriftFix)) } if config.RTC.Clock != "" { RTCParams = append(RTCParams, fmt.Sprintf(",clock=%s", config.RTC.Clock)) } config.qemuParams = append(config.qemuParams, "-rtc") config.qemuParams = append(config.qemuParams, strings.Join(RTCParams, "")) } func (config *Config) appendGlobalParam() { if config.GlobalParam != "" { config.qemuParams = append(config.qemuParams, "-global") config.qemuParams = append(config.qemuParams, config.GlobalParam) } } func (config *Config) appendVGA() { if config.VGA != "" { config.qemuParams = append(config.qemuParams, "-vga") config.qemuParams = append(config.qemuParams, config.VGA) } } func (config *Config) appendKernel() { if config.Kernel.Path != "" { config.qemuParams = append(config.qemuParams, "-kernel") config.qemuParams = append(config.qemuParams, config.Kernel.Path) if config.Kernel.InitrdPath != "" { config.qemuParams = append(config.qemuParams, "-initrd") config.qemuParams = append(config.qemuParams, config.Kernel.InitrdPath) } if config.Kernel.Params != "" { config.qemuParams = append(config.qemuParams, "-append") config.qemuParams = append(config.qemuParams, config.Kernel.Params) } } } func (config *Config) appendMemoryKnobs() { if config.Memory.Size == "" { return } if !isDimmSupported(config) { return } var objMemParam, numaMemParam string dimmName := "dimm1" if config.Knobs.HugePages { objMemParam = "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=/dev/hugepages" numaMemParam = "node,memdev=" + dimmName } else if config.Knobs.FileBackedMem && config.Memory.Path != "" { objMemParam = "memory-backend-file,id=" + dimmName + ",size=" + config.Memory.Size + ",mem-path=" + config.Memory.Path numaMemParam = "node,memdev=" + dimmName } else { objMemParam = "memory-backend-ram,id=" + dimmName + ",size=" + config.Memory.Size numaMemParam = "node,memdev=" + dimmName } if config.Knobs.MemShared { objMemParam += ",share=on" } if config.Knobs.MemPrealloc { objMemParam += ",prealloc=on" } config.qemuParams = append(config.qemuParams, "-object") config.qemuParams = append(config.qemuParams, objMemParam) config.qemuParams = append(config.qemuParams, "-numa") config.qemuParams = append(config.qemuParams, numaMemParam) } func (config *Config) appendKnobs() { if config.Knobs.NoUserConfig { config.qemuParams = append(config.qemuParams, "-no-user-config") } if config.Knobs.NoDefaults { config.qemuParams = append(config.qemuParams, "-nodefaults") } if config.Knobs.NoGraphic { config.qemuParams = append(config.qemuParams, "-nographic") } if config.Knobs.NoReboot { config.qemuParams = append(config.qemuParams, "--no-reboot") } if config.Knobs.Daemonize { config.qemuParams = append(config.qemuParams, "-daemonize") } config.appendMemoryKnobs() if config.Knobs.Realtime { config.qemuParams = append(config.qemuParams, "-realtime") // This path is redundant as the default behaviour is locked memory // Realtime today does not control any other feature even though // other features may be added in the future // https://lists.gnu.org/archive/html/qemu-devel/2012-12/msg03330.html if config.Knobs.Mlock { config.qemuParams = append(config.qemuParams, "mlock=on") } else { config.qemuParams = append(config.qemuParams, "mlock=off") } } else { // In order to turn mlock off we need the -realtime option as well if !config.Knobs.Mlock { //Enable realtime anyway just to get the right swapping behaviour config.qemuParams = append(config.qemuParams, "-realtime") config.qemuParams = append(config.qemuParams, "mlock=off") } } if config.Knobs.Stopped { config.qemuParams = append(config.qemuParams, "-S") } } func (config *Config) appendBios() { if config.Bios != "" { config.qemuParams = append(config.qemuParams, "-bios") config.qemuParams = append(config.qemuParams, config.Bios) } } func (config *Config) appendIOThreads() { for _, t := range config.IOThreads { if t.ID != "" { config.qemuParams = append(config.qemuParams, "-object") config.qemuParams = append(config.qemuParams, fmt.Sprintf("iothread,id=%s", t.ID)) } } } func (config *Config) appendIncoming() { var uri string switch config.Incoming.MigrationType { case MigrationExec: uri = fmt.Sprintf("exec:%s", config.Incoming.Exec) case MigrationFD: chFDs := config.appendFDs([]*os.File{config.Incoming.FD}) uri = fmt.Sprintf("fd:%d", chFDs[0]) case MigrationDefer: uri = "defer" default: return } config.qemuParams = append(config.qemuParams, "-S", "-incoming", uri) } func (config *Config) appendPidFile() { if config.PidFile != "" { config.qemuParams = append(config.qemuParams, "-pidfile") config.qemuParams = append(config.qemuParams, config.PidFile) } } func (config *Config) appendLogFile() { if config.LogFile != "" { config.qemuParams = append(config.qemuParams, "-D") config.qemuParams = append(config.qemuParams, config.LogFile) } } func (config *Config) appendFwCfg(logger QMPLog) { if logger == nil { logger = qmpNullLogger{} } if !config.FwCfg.Valid() { logger.Errorf("fw_cfg is not valid: %+v", config.FwCfg) return } config.qemuParams = append(config.qemuParams, config.FwCfg.QemuParams(config)...) } // LaunchQemu can be used to launch a new qemu instance. // // The Config parameter contains a set of qemu parameters and settings. // // This function writes its log output via logger parameter. // // The function will block until the launched qemu process exits. "", nil // will be returned if the launch succeeds. Otherwise a string containing // the contents of stderr + a Go error object will be returned. func LaunchQemu(config Config, logger QMPLog) (string, error) { config.appendName() config.appendUUID() config.appendMachine() config.appendCPUModel() config.appendQMPSockets() config.appendMemory() config.appendDevices() config.appendRTC() config.appendGlobalParam() config.appendVGA() config.appendKnobs() config.appendKernel() config.appendBios() config.appendIOThreads() config.appendIncoming() config.appendPidFile() config.appendLogFile() config.appendFwCfg(logger) if err := config.appendCPUs(); err != nil { return "", err } ctx := config.Ctx if ctx == nil { ctx = context.Background() } return LaunchCustomQemu(ctx, config.Path, config.qemuParams, config.fds, nil, logger) } // LaunchCustomQemu can be used to launch a new qemu instance. // // The path parameter is used to pass the qemu executable path. // // params is a slice of options to pass to qemu-system-x86_64 and fds is a // list of open file descriptors that are to be passed to the spawned qemu // process. The attrs parameter can be used to control aspects of the // newly created qemu process, such as the user and group under which it // runs. It may be nil. // // This function writes its log output via logger parameter. // // The function will block until the launched qemu process exits. "", nil // will be returned if the launch succeeds. Otherwise a string containing // the contents of stderr + a Go error object will be returned. func LaunchCustomQemu(ctx context.Context, path string, params []string, fds []*os.File, attr *syscall.SysProcAttr, logger QMPLog) (string, error) { if logger == nil { logger = qmpNullLogger{} } errStr := "" if path == "" { path = "qemu-system-x86_64" } /* #nosec */ cmd := exec.CommandContext(ctx, path, params...) if len(fds) > 0 { logger.Infof("Adding extra file %v", fds) cmd.ExtraFiles = fds } cmd.SysProcAttr = attr var stderr bytes.Buffer cmd.Stderr = &stderr logger.Infof("launching %s with: %v", path, params) err := cmd.Run() if err != nil { logger.Errorf("Unable to launch %s: %v", path, err) errStr = stderr.String() logger.Errorf("%s", errStr) } return errStr, err }