// Copyright contributors to the Virtual Machine Manager for Go project
//
// SPDX-License-Identifier: Apache-2.0
//

package qemu

import (
	"bufio"
	"container/list"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"strconv"
	"syscall"
	"time"

	"context"
	"strings"
)

// QMPLog is a logging interface used by the qemu package to log various
// interesting pieces of information.  Rather than introduce a dependency
// on a given logging package, qemu presents this interface that allows
// clients to provide their own logging type which they can use to
// seamlessly integrate qemu's logs into their own logs.  A QMPLog
// implementation can be specified in the QMPConfig structure.
type QMPLog interface {
	// V returns true if the given argument is less than or equal
	// to the implementation's defined verbosity level.
	V(int32) bool

	// Infof writes informational output to the log.  A newline will be
	// added to the output if one is not provided.
	Infof(string, ...interface{})

	// Warningf writes warning output to the log.  A newline will be
	// added to the output if one is not provided.
	Warningf(string, ...interface{})

	// Errorf writes error output to the log.  A newline will be
	// added to the output if one is not provided.
	Errorf(string, ...interface{})
}

type qmpNullLogger struct{}

func (l qmpNullLogger) V(level int32) bool {
	return false
}

func (l qmpNullLogger) Infof(format string, v ...interface{}) {
}

func (l qmpNullLogger) Warningf(format string, v ...interface{}) {
}

func (l qmpNullLogger) Errorf(format string, v ...interface{}) {
}

// QMPConfig is a configuration structure that can be used to specify a
// logger and a channel to which logs and  QMP events are to be sent.  If
// neither of these fields are specified, or are set to nil, no logs will be
// written and no QMP events will be reported to the client.
type QMPConfig struct {
	// eventCh can be specified by clients who wish to receive QMP
	// events.
	EventCh chan<- QMPEvent

	// logger is used by the qmpStart function and all the go routines
	// it spawns to log information.
	Logger QMPLog

	// specify the capacity of buffer used by receive QMP response.
	MaxCapacity int
}

type qmpEventFilter struct {
	eventName string
	dataKey   string
	dataValue string
}

// QMPEvent contains a single QMP event, sent on the QMPConfig.EventCh channel.
// nolint: govet
type QMPEvent struct {
	// The name of the event, e.g., DEVICE_DELETED
	Name string

	// The data associated with the event.  The contents of this map are
	// unprocessed by the qemu package.  It is simply the result of
	// unmarshalling the QMP json event.  Here's an example map
	// map[string]interface{}{
	//	"driver": "virtio-blk-pci",
	//	"drive":  "drive_3437843748734873483",
	// }
	Data map[string]interface{}

	// The event's timestamp converted to a time.Time object.
	Timestamp time.Time
}

type qmpResult struct {
	response interface{}
	err      error
}

// nolint: govet
type qmpCommand struct {
	ctx            context.Context
	res            chan qmpResult
	name           string
	args           map[string]interface{}
	filter         *qmpEventFilter
	resultReceived bool
	oob            []byte
}

// QMP is a structure that contains the internal state used by startQMPLoop and
// the go routines it spwans.  All the contents of this structure are private.
// nolint: govet
type QMP struct {
	cmdCh          chan qmpCommand
	conn           io.ReadWriteCloser
	cfg            QMPConfig
	connectedCh    chan<- *QMPVersion
	disconnectedCh chan struct{}
	version        *QMPVersion
}

// QMPVersion contains the version number and the capabailities of a QEMU
// instance, as reported in the QMP greeting message.
// nolint: govet
type QMPVersion struct {
	Major        int
	Minor        int
	Micro        int
	Capabilities []string
}

// CPUProperties contains the properties of a CPU instance
type CPUProperties struct {
	Node   int `json:"node-id"`
	Socket int `json:"socket-id"`
	Die    int `json:"die-id"`
	Core   int `json:"core-id"`
	Thread int `json:"thread-id"`
}

// HotpluggableCPU represents a hotpluggable CPU
// nolint: govet
type HotpluggableCPU struct {
	Type       string        `json:"type"`
	VcpusCount int           `json:"vcpus-count"`
	Properties CPUProperties `json:"props"`
	QOMPath    string        `json:"qom-path"`
}

// MemoryDevicesData cotains the data describes a memory device
// nolint: govet
type MemoryDevicesData struct {
	Slot         int    `json:"slot"`
	Node         int    `json:"node"`
	Addr         uint64 `json:"addr"`
	Memdev       string `json:"memdev"`
	ID           string `json:"id"`
	Hotpluggable bool   `json:"hotpluggable"`
	Hotplugged   bool   `json:"hotplugged"`
	Size         uint64 `json:"size"`
}

// MemoryDevices represents memory devices of vm
// nolint: govet
type MemoryDevices struct {
	Data MemoryDevicesData `json:"data"`
	Type string            `json:"type"`
}

// CPUInfo represents information about each virtual CPU
// nolint: govet
type CPUInfo struct {
	CPU      int           `json:"CPU"`
	Current  bool          `json:"current"`
	Halted   bool          `json:"halted"`
	QomPath  string        `json:"qom_path"`
	Arch     string        `json:"arch"`
	Pc       int           `json:"pc"`
	ThreadID int           `json:"thread_id"`
	Props    CPUProperties `json:"props"`
}

// CPUInfoFast represents information about each virtual CPU
// nolint: govet
type CPUInfoFast struct {
	CPUIndex int           `json:"cpu-index"`
	QomPath  string        `json:"qom-path"`
	Arch     string        `json:"arch"`
	ThreadID int           `json:"thread-id"`
	Target   string        `json:"target"`
	Props    CPUProperties `json:"props"`
}

// MigrationRAM represents migration ram status
type MigrationRAM struct {
	Total            int64 `json:"total"`
	Remaining        int64 `json:"remaining"`
	Transferred      int64 `json:"transferred"`
	TotalTime        int64 `json:"total-time"`
	SetupTime        int64 `json:"setup-time"`
	ExpectedDowntime int64 `json:"expected-downtime"`
	Duplicate        int64 `json:"duplicate"`
	Normal           int64 `json:"normal"`
	NormalBytes      int64 `json:"normal-bytes"`
	DirtySyncCount   int64 `json:"dirty-sync-count"`
}

// MigrationDisk represents migration disk status
type MigrationDisk struct {
	Total       int64 `json:"total"`
	Remaining   int64 `json:"remaining"`
	Transferred int64 `json:"transferred"`
}

// MigrationXbzrleCache represents migration XbzrleCache status
type MigrationXbzrleCache struct {
	CacheSize     int64 `json:"cache-size"`
	Bytes         int64 `json:"bytes"`
	Pages         int64 `json:"pages"`
	CacheMiss     int64 `json:"cache-miss"`
	CacheMissRate int64 `json:"cache-miss-rate"`
	Overflow      int64 `json:"overflow"`
}

// MigrationStatus represents migration status of a vm
type MigrationStatus struct {
	Status       string                   `json:"status"`
	Capabilities []map[string]interface{} `json:"capabilities,omitempty"`
	RAM          MigrationRAM             `json:"ram,omitempty"`
	Disk         MigrationDisk            `json:"disk,omitempty"`
	XbzrleCache  MigrationXbzrleCache     `json:"xbzrle-cache,omitempty"`
}

// SchemaInfo represents all QMP wire ABI
type SchemaInfo struct {
	MetaType string `json:"meta-type"`
	Name     string `json:"name"`
}

// StatusInfo represents guest running status
// nolint: govet
type StatusInfo struct {
	Running    bool   `json:"running"`
	SingleStep bool   `json:"singlestep"`
	Status     string `json:"status"`
}

func (q *QMP) readLoop(fromVMCh chan<- []byte) {
	scanner := bufio.NewScanner(q.conn)
	if q.cfg.MaxCapacity > 0 {
		buffer := make([]byte, q.cfg.MaxCapacity)
		scanner.Buffer(buffer, q.cfg.MaxCapacity)
	}

	for scanner.Scan() {
		line := scanner.Bytes()
		// Since []byte channel type transfer slice info(include slice underlying array pointer, len, cap)
		// between channel sender and receiver. scanner.Bytes() returned slice's underlying array
		// may point to data that will be overwritten by a subsequent call to Scan(reference from:
		// https://golang.org/pkg/bufio/#Scanner.Bytes), which may make receiver read mixed data,
		// so we need to copy line to new allocated space and then send to channel receiver
		sendLine := make([]byte, len(line))
		copy(sendLine, line)

		fromVMCh <- sendLine
	}
	q.cfg.Logger.Infof("scanner return error: %v", scanner.Err())
	close(fromVMCh)
}

func (q *QMP) processQMPEvent(cmdQueue *list.List, name interface{}, data interface{},
	timestamp interface{}) {

	strname, ok := name.(string)
	if !ok {
		return
	}

	var eventData map[string]interface{}
	if data != nil {
		eventData, _ = data.(map[string]interface{})
	}

	cmdEl := cmdQueue.Front()
	if cmdEl != nil {
		cmd := cmdEl.Value.(*qmpCommand)
		filter := cmd.filter
		if filter != nil {
			if filter.eventName == strname {
				match := filter.dataKey == ""
				if !match && eventData != nil {
					match = eventData[filter.dataKey] == filter.dataValue
				}
				if match {
					if cmd.resultReceived {
						q.finaliseCommand(cmdEl, cmdQueue, true)
					} else {
						cmd.filter = nil
					}
				}
			}
		}
	}

	if q.cfg.EventCh != nil {
		ev := QMPEvent{
			Name: strname,
			Data: eventData,
		}
		if timestamp != nil {
			timestamp, ok := timestamp.(map[string]interface{})
			if ok {
				seconds, _ := timestamp["seconds"].(float64)
				microseconds, _ := timestamp["microseconds"].(float64)
				ev.Timestamp = time.Unix(int64(seconds), int64(microseconds))
			}
		}

		q.cfg.EventCh <- ev
	}
}

func (q *QMP) finaliseCommandWithResponse(cmdEl *list.Element, cmdQueue *list.List, succeeded bool, response interface{}) {
	cmd := cmdEl.Value.(*qmpCommand)
	cmdQueue.Remove(cmdEl)
	select {
	case <-cmd.ctx.Done():
	default:
		if succeeded {
			cmd.res <- qmpResult{response: response}
		} else {
			cmd.res <- qmpResult{err: fmt.Errorf("QMP command failed: %v", response)}
		}
	}
	if cmdQueue.Len() > 0 {
		q.writeNextQMPCommand(cmdQueue)
	}
}

func (q *QMP) finaliseCommand(cmdEl *list.Element, cmdQueue *list.List, succeeded bool) {
	q.finaliseCommandWithResponse(cmdEl, cmdQueue, succeeded, nil)
}

func (q *QMP) errorDesc(errorData interface{}) (string, error) {
	// convert error to json
	data, err := json.Marshal(errorData)
	if err != nil {
		return "", fmt.Errorf("unable to extract error information: %v", err)
	}

	// see: https://github.com/qemu/qemu/blob/stable-2.12/qapi/qmp-dispatch.c#L125
	var qmpErr map[string]string
	// convert json to qmpError
	if err = json.Unmarshal(data, &qmpErr); err != nil {
		return "", fmt.Errorf("unable to convert json to qmpError: %v", err)
	}

	return qmpErr["desc"], nil
}

func (q *QMP) processQMPInput(line []byte, cmdQueue *list.List) {
	var vmData map[string]interface{}
	err := json.Unmarshal(line, &vmData)
	if err != nil {
		q.cfg.Logger.Warningf("Unable to decode response [%s] from VM: %v",
			string(line), err)
		return
	}
	if evname, found := vmData["event"]; found {
		q.processQMPEvent(cmdQueue, evname, vmData["data"], vmData["timestamp"])
		return
	}

	response, succeeded := vmData["return"]
	errData, failed := vmData["error"]

	if !succeeded && !failed {
		return
	}

	cmdEl := cmdQueue.Front()
	if cmdEl == nil {
		q.cfg.Logger.Warningf("Unexpected command response received [%s] from VM",
			string(line))
		return
	}
	cmd := cmdEl.Value.(*qmpCommand)
	if failed || cmd.filter == nil {
		if errData != nil {
			desc, err := q.errorDesc(errData)
			if err != nil {
				q.cfg.Logger.Infof("Get error description failed: %v", err)
			} else {
				response = desc
			}
		}
		q.finaliseCommandWithResponse(cmdEl, cmdQueue, succeeded, response)
	} else {
		cmd.resultReceived = true
	}
}

func currentCommandDoneCh(cmdQueue *list.List) <-chan struct{} {
	cmdEl := cmdQueue.Front()
	if cmdEl == nil {
		return nil
	}
	cmd := cmdEl.Value.(*qmpCommand)
	return cmd.ctx.Done()
}

func (q *QMP) writeNextQMPCommand(cmdQueue *list.List) {
	cmdEl := cmdQueue.Front()
	cmd := cmdEl.Value.(*qmpCommand)
	cmdData := make(map[string]interface{})
	cmdData["execute"] = cmd.name
	if cmd.args != nil {
		cmdData["arguments"] = cmd.args
	}
	encodedCmd, err := json.Marshal(&cmdData)
	if err != nil {
		cmd.res <- qmpResult{
			err: fmt.Errorf("unable to marhsall command %s: %v",
				cmd.name, err),
		}
		cmdQueue.Remove(cmdEl)
	}
	encodedCmd = append(encodedCmd, '\n')
	if unixConn, ok := q.conn.(*net.UnixConn); ok && len(cmd.oob) > 0 {
		_, _, err = unixConn.WriteMsgUnix(encodedCmd, cmd.oob, nil)
	} else {
		_, err = q.conn.Write(encodedCmd)
	}

	if err != nil {
		cmd.res <- qmpResult{
			err: fmt.Errorf("unable to write command to qmp socket %v", err),
		}
		cmdQueue.Remove(cmdEl)
	}
}

func failOutstandingCommands(cmdQueue *list.List) {
	for e := cmdQueue.Front(); e != nil; e = e.Next() {
		cmd := e.Value.(*qmpCommand)
		select {
		case cmd.res <- qmpResult{
			err: errors.New("exitting QMP loop, command cancelled"),
		}:
		case <-cmd.ctx.Done():
		}
	}
}

func (q *QMP) cancelCurrentCommand(cmdQueue *list.List) {
	cmdEl := cmdQueue.Front()
	cmd := cmdEl.Value.(*qmpCommand)
	if cmd.resultReceived {
		q.finaliseCommand(cmdEl, cmdQueue, false)
	} else {
		cmd.filter = nil
	}
}

func (q *QMP) parseVersion(version []byte) *QMPVersion {
	var qmp map[string]interface{}
	err := json.Unmarshal(version, &qmp)
	if err != nil {
		q.cfg.Logger.Errorf("Invalid QMP greeting: %s", string(version))
		return nil
	}

	versionMap := qmp
	for _, k := range []string{"QMP", "version", "qemu"} {
		versionMap, _ = versionMap[k].(map[string]interface{})
		if versionMap == nil {
			return nil
		}
	}

	micro, _ := versionMap["micro"].(float64)
	minor, _ := versionMap["minor"].(float64)
	major, _ := versionMap["major"].(float64)
	capabilities, _ := qmp["QMP"].(map[string]interface{})["capabilities"].([]interface{})
	stringcaps := make([]string, 0, len(capabilities))
	for _, c := range capabilities {
		if cap, ok := c.(string); ok {
			stringcaps = append(stringcaps, cap)
		}
	}
	return &QMPVersion{Major: int(major),
		Minor:        int(minor),
		Micro:        int(micro),
		Capabilities: stringcaps,
	}
}

// The qemu package allows multiple QMP commands to be submitted concurrently
// from different Go routines.  Unfortunately, QMP doesn't really support parallel
// commands as there is no way reliable way to associate a command response
// with a request.  For this reason we need to submit our commands to
// QMP serially.  The qemu package performs this serialisation using a
// queue (cmdQueue owned by mainLoop).  We use a queue rather than a simple
// mutex so we can support cancelling of commands (see below) and ordered
// execution of commands, i.e., if command B is issued before command C,
// it should be executed before command C even if both commands are initially
// blocked waiting for command A to finish.  This would be hard to achieve with
// a simple mutex.
//
// Cancelling is a little tricky.  Commands such as ExecuteQMPCapabilities
// can be cancelled by cancelling or timing out their contexts.  When a
// command is cancelled the calling function, e.g., ExecuteQMPCapabilities,
// will return but we may not be able to remove the command's entry from
// the command queue or issue the next command.  There are two scenarios
// here.
//
// 1. The command has been processed by QMP, i.e., we have received a
// return or an error, but is still blocking as it is waiting for
// an event.  For example, the ExecuteDeviceDel blocks until a DEVICE_DELETED
// event is received.  When such a command is cancelled we can remove it
// from the queue and start issuing the next command.  When the DEVICE_DELETED
// event eventually arrives it will just be ignored.
//
// 2. The command has not been processed by QMP.  In this case the command
// needs to remain on the cmdQueue until the response to this command is
// received from QMP.  During this time no new commands can be issued.  When the
// response is received, it is discarded (as no one is interested in the result
// any more), the entry is removed from the cmdQueue and we can proceed to
// execute the next command.

func (q *QMP) mainLoop() {
	cmdQueue := list.New().Init()
	fromVMCh := make(chan []byte)
	go q.readLoop(fromVMCh)

	defer func() {
		if q.cfg.EventCh != nil {
			close(q.cfg.EventCh)
		}
		/* #nosec */
		_ = q.conn.Close()
		<-fromVMCh
		failOutstandingCommands(cmdQueue)
		close(q.disconnectedCh)
	}()

	var cmdDoneCh <-chan struct{}
	var version *QMPVersion
	ready := false

	for {
		select {
		case cmd, ok := <-q.cmdCh:
			if !ok {
				return
			}
			_ = cmdQueue.PushBack(&cmd)

			// We only want to execute the new cmd if QMP is
			// ready and there are no other commands pending.
			// If there are commands pending our new command
			// will get run when the pending commands complete.
			if ready && cmdQueue.Len() == 1 {
				q.writeNextQMPCommand(cmdQueue)
				cmdDoneCh = currentCommandDoneCh(cmdQueue)
			}

		case line, ok := <-fromVMCh:
			if !ok {
				return
			}

			if !ready {
				// Not ready yet. Check if line is the QMP version.
				// Sometimes QMP events are thrown before the QMP version,
				// hence it's not a guarantee that the first data read from
				// the channel is the QMP version.
				version = q.parseVersion(line)
				if version != nil {
					q.connectedCh <- version
					ready = true
				}
				// Do not process QMP input to avoid deadlocks.
				break
			}

			q.processQMPInput(line, cmdQueue)
			cmdDoneCh = currentCommandDoneCh(cmdQueue)

		case <-cmdDoneCh:
			q.cancelCurrentCommand(cmdQueue)
			cmdDoneCh = currentCommandDoneCh(cmdQueue)
		}
	}
}

func startQMPLoop(conn io.ReadWriteCloser, cfg QMPConfig,
	connectedCh chan<- *QMPVersion, disconnectedCh chan struct{}) *QMP {
	q := &QMP{
		cmdCh:          make(chan qmpCommand),
		conn:           conn,
		cfg:            cfg,
		connectedCh:    connectedCh,
		disconnectedCh: disconnectedCh,
	}
	go q.mainLoop()
	return q
}

func (q *QMP) executeCommandWithResponse(ctx context.Context, name string, args map[string]interface{},
	oob []byte, filter *qmpEventFilter) (interface{}, error) {
	var err error
	var response interface{}
	resCh := make(chan qmpResult)
	select {
	case <-q.disconnectedCh:
		err = errors.New("exitting QMP loop, command cancelled")
	case q.cmdCh <- qmpCommand{
		ctx:    ctx,
		res:    resCh,
		name:   name,
		args:   args,
		filter: filter,
		oob:    oob,
	}:
	}

	if err != nil {
		return response, err
	}

	select {
	case res := <-resCh:
		err = res.err
		response = res.response
	case <-ctx.Done():
		err = ctx.Err()
	}

	return response, err
}

func (q *QMP) executeCommand(ctx context.Context, name string, args map[string]interface{},
	filter *qmpEventFilter) error {

	_, err := q.executeCommandWithResponse(ctx, name, args, nil, filter)
	return err
}

// QMPStart connects to a unix domain socket maintained by a QMP instance.  It
// waits to receive the QMP welcome message via the socket and spawns some go
// routines to manage the socket.  The function returns a *QMP which can be
// used by callers to send commands to the QEMU instance or to close the
// socket and all the go routines that have been spawned to monitor it.  A
// *QMPVersion is also returned.  This structure contains the version and
// capabilities information returned by the QEMU instance in its welcome
// message.
//
// socket contains the path to the domain socket. cfg contains some options
// that can be specified by the caller, namely where the qemu package should
// send logs and QMP events.  disconnectedCh is a channel that must be supplied
// by the caller.  It is closed when an error occurs openning or writing to
// or reading from the unix domain socket.  This implies that the QEMU instance
// that opened the socket has closed.
//
// If this function returns without error, callers should call QMP.Shutdown
// when they wish to stop monitoring the QMP instance.  This is not strictly
// necessary if the QEMU instance exits and the disconnectedCh is closed, but
// doing so will not cause any problems.
//
// Commands can be sent to the QEMU instance via the QMP.Execute methods.
// These commands are executed serially, even if the QMP.Execute methods
// are called from different go routines.  The QMP.Execute methods will
// block until they have received a success or failure message from QMP,
// i.e., {"return": {}} or {"error":{}}, and in some cases certain events
// are received.
//
// QEMU currently requires that the "qmp_capabilties" command is sent before any
// other command. Therefore you must call qmp.ExecuteQMPCapabilities() before
// you execute any other command.
func QMPStart(ctx context.Context, socket string, cfg QMPConfig, disconnectedCh chan struct{}) (*QMP, *QMPVersion, error) {
	if cfg.Logger == nil {
		cfg.Logger = qmpNullLogger{}
	}
	dialer := net.Dialer{Cancel: ctx.Done()}
	conn, err := dialer.Dial("unix", socket)
	if err != nil {
		cfg.Logger.Warningf("Unable to connect to unix socket (%s): %v", socket, err)
		close(disconnectedCh)
		return nil, nil, err
	}

	connectedCh := make(chan *QMPVersion)

	q := startQMPLoop(conn, cfg, connectedCh, disconnectedCh)
	select {
	case <-ctx.Done():
		q.Shutdown()
		<-disconnectedCh
		return nil, nil, fmt.Errorf("canceled by caller")
	case <-disconnectedCh:
		return nil, nil, fmt.Errorf("lost connection to VM")
	case q.version = <-connectedCh:
		if q.version == nil {
			return nil, nil, fmt.Errorf("failed to find QMP version information")
		}
	}

	if q.version.Major < 5 {
		return nil, nil, fmt.Errorf("govmm requires qemu version 5.0 or later, this is qemu (%d.%d)", q.version.Major, q.version.Minor)
	}

	return q, q.version, nil
}

// Shutdown closes the domain socket used to monitor a QEMU instance and
// terminates all the go routines spawned by QMPStart to manage that instance.
// QMP.Shutdown does not shut down the running instance.  Calling QMP.Shutdown
// will result in the disconnectedCh channel being closed, indicating that we
// have lost connection to the QMP instance.  In this case it does not indicate
// that the instance has quit.
//
// QMP.Shutdown should not be called concurrently with other QMP methods.  It
// should not be called twice on the same QMP instance.
//
// Calling QMP.Shutdown after the disconnectedCh channel is closed is permitted but
// will not have any effect.
func (q *QMP) Shutdown() {
	close(q.cmdCh)
}

// ExecuteQMPCapabilities executes the qmp_capabilities command on the instance.
func (q *QMP) ExecuteQMPCapabilities(ctx context.Context) error {
	return q.executeCommand(ctx, "qmp_capabilities", nil, nil)
}

// ExecuteStop sends the stop command to the instance.
func (q *QMP) ExecuteStop(ctx context.Context) error {
	return q.executeCommand(ctx, "stop", nil, nil)
}

// ExecuteCont sends the cont command to the instance.
func (q *QMP) ExecuteCont(ctx context.Context) error {
	return q.executeCommand(ctx, "cont", nil, nil)
}

// ExecuteSystemPowerdown sends the system_powerdown command to the instance.
// This function will block until the SHUTDOWN event is received.
func (q *QMP) ExecuteSystemPowerdown(ctx context.Context) error {
	filter := &qmpEventFilter{
		eventName: "POWERDOWN",
	}
	return q.executeCommand(ctx, "system_powerdown", nil, filter)
}

// ExecuteQuit sends the quit command to the instance, terminating
// the QMP instance immediately.
func (q *QMP) ExecuteQuit(ctx context.Context) error {
	return q.executeCommand(ctx, "quit", nil, nil)
}

func (q *QMP) blockdevAddBaseArgs(driver string, blockDevice *BlockDevice) map[string]interface{} {
	blockdevArgs := map[string]interface{}{
		"driver":    "raw",
		"read-only": blockDevice.ReadOnly,
		"file": map[string]interface{}{
			"driver":   driver,
			"filename": blockDevice.File,
			"aio":      string(blockDevice.AIO),
		},
	}

	blockdevArgs["node-name"] = blockDevice.ID

	return blockdevArgs
}

// ExecuteBlockdevAdd sends a blockdev-add to the QEMU instance.  device is the
// path of the device to add, e.g., /dev/rdb0, and blockdevID is an identifier
// used to name the device.  As this identifier will be passed directly to QMP,
// it must obey QMP's naming rules, e,g., it must start with a letter.
func (q *QMP) ExecuteBlockdevAdd(ctx context.Context, blockDevice *BlockDevice) error {
	args := q.blockdevAddBaseArgs("host_device", blockDevice)

	return q.executeCommand(ctx, "blockdev-add", args, nil)
}

// ExecuteBlockdevAddWithCache has two more parameters direct and noFlush
// than ExecuteBlockdevAdd.
// They are cache-related options for block devices that are described in
// https://github.com/qemu/qemu/blob/master/qapi/block-core.json.
// direct denotes whether use of O_DIRECT (bypass the host page cache)
// is enabled.  noFlush denotes whether flush requests for the device are
// ignored.
func (q *QMP) ExecuteBlockdevAddWithCache(ctx context.Context, blockDevice *BlockDevice, direct, noFlush bool) error {
	blockdevArgs := q.blockdevAddBaseArgs("host_device", blockDevice)

	blockdevArgs["cache"] = map[string]interface{}{
		"direct":   direct,
		"no-flush": noFlush,
	}

	return q.executeCommand(ctx, "blockdev-add", blockdevArgs, nil)
}

// ExecuteBlockdevAddWithDriverCache has three one parameter driver
// than ExecuteBlockdevAddWithCache.
// Parameter driver can set the driver of block device.
func (q *QMP) ExecuteBlockdevAddWithDriverCache(ctx context.Context, driver string, blockDevice *BlockDevice, direct, noFlush bool) error {
	blockdevArgs := q.blockdevAddBaseArgs(driver, blockDevice)

	blockdevArgs["cache"] = map[string]interface{}{
		"direct":   direct,
		"no-flush": noFlush,
	}

	return q.executeCommand(ctx, "blockdev-add", blockdevArgs, nil)
}

// ExecuteDeviceAdd adds the guest portion of a device to a QEMU instance
// using the device_add command.  blockdevID should match the blockdevID passed
// to a previous call to ExecuteBlockdevAdd.  devID is the id of the device to
// add.  Both strings must be valid QMP identifiers.  driver is the name of the
// driver,e.g., virtio-blk-pci, and bus is the name of the bus.  bus is optional.
// shared denotes if the drive can be shared allowing it to be passed more than once.
// disableModern indicates if virtio version 1.0 should be replaced by the
// former version 0.9, as there is a KVM bug that occurs when using virtio
// 1.0 in nested environments.
func (q *QMP) ExecuteDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, shared, disableModern bool) error {
	args := map[string]interface{}{
		"id":     devID,
		"driver": driver,
		"drive":  blockdevID,
	}

	var transport VirtioTransport

	if transport.isVirtioCCW(nil) {
		args["devno"] = bus
	} else if bus != "" {
		args["bus"] = bus
	}

	if shared {
		args["share-rw"] = "on"
	}
	if transport.isVirtioPCI(nil) {
		args["romfile"] = romfile

		if disableModern {
			args["disable-modern"] = disableModern
		}
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteSCSIDeviceAdd adds the guest portion of a block device to a QEMU instance
// using a SCSI driver with the device_add command.  blockdevID should match the
// blockdevID passed to a previous call to ExecuteBlockdevAdd.  devID is the id of
// the device to add.  Both strings must be valid QMP identifiers.  driver is the name of the
// scsi driver,e.g., scsi-hd, and bus is the name of a SCSI controller bus.
// scsiID is the SCSI id, lun is logical unit number. scsiID and lun are optional, a negative value
// for scsiID and lun is ignored. shared denotes if the drive can be shared allowing it
// to be passed more than once.
// disableModern indicates if virtio version 1.0 should be replaced by the
// former version 0.9, as there is a KVM bug that occurs when using virtio
// 1.0 in nested environments.
func (q *QMP) ExecuteSCSIDeviceAdd(ctx context.Context, blockdevID, devID, driver, bus, romfile string, scsiID, lun int, shared, disableModern bool) error {
	// TBD: Add drivers for scsi passthrough like scsi-generic and scsi-block
	drivers := []string{"scsi-hd", "scsi-cd", "scsi-disk"}

	isSCSIDriver := false
	for _, d := range drivers {
		if driver == d {
			isSCSIDriver = true
			break
		}
	}

	if !isSCSIDriver {
		return fmt.Errorf("invalid SCSI driver provided %s", driver)
	}

	args := map[string]interface{}{
		"id":     devID,
		"driver": driver,
		"drive":  blockdevID,
		"bus":    bus,
	}

	if scsiID >= 0 {
		args["scsi-id"] = scsiID
	}
	if lun >= 0 {
		args["lun"] = lun
	}
	if shared {
		args["share-rw"] = "on"
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteBlockdevDel deletes a block device by sending blockdev-del
// command.  blockdevID is the id of the block device to be deleted.
// Typically, this will match the id passed to ExecuteBlockdevAdd.  It
// must be a valid QMP id.
func (q *QMP) ExecuteBlockdevDel(ctx context.Context, blockdevID string) error {
	args := map[string]interface{}{}

	args["node-name"] = blockdevID
	return q.executeCommand(ctx, "blockdev-del", args, nil)
}

// ExecuteChardevDel deletes a char device by sending a chardev-remove command.
// chardevID is the id of the char device to be deleted. Typically, this will
// match the id passed to ExecuteCharDevUnixSocketAdd. It must be a valid QMP id.
func (q *QMP) ExecuteChardevDel(ctx context.Context, chardevID string) error {
	args := map[string]interface{}{
		"id": chardevID,
	}

	return q.executeCommand(ctx, "chardev-remove", args, nil)
}

// ExecuteNetdevAdd adds a Net device to a QEMU instance
// using the netdev_add command. netdevID is the id of the device to add.
// Must be valid QMP identifier.
func (q *QMP) ExecuteNetdevAdd(ctx context.Context, netdevType, netdevID, ifname, downscript, script string, queues int) error {
	args := map[string]interface{}{
		"type":       netdevType,
		"id":         netdevID,
		"ifname":     ifname,
		"downscript": downscript,
		"script":     script,
	}
	if queues > 1 {
		args["queues"] = queues
	}

	return q.executeCommand(ctx, "netdev_add", args, nil)
}

// ExecuteNetdevChardevAdd adds a Net device to a QEMU instance
// using the netdev_add command. netdevID is the id of the device to add.
// Must be valid QMP identifier.
func (q *QMP) ExecuteNetdevChardevAdd(ctx context.Context, netdevType, netdevID, chardev string, queues int) error {
	args := map[string]interface{}{
		"type":    netdevType,
		"id":      netdevID,
		"chardev": chardev,
	}
	if queues > 1 {
		args["queues"] = queues
	}

	return q.executeCommand(ctx, "netdev_add", args, nil)
}

// ExecuteNetdevAddByFds adds a Net device to a QEMU instance
// using the netdev_add command by fds and vhostfds. netdevID is the id of the device to add.
// Must be valid QMP identifier.
func (q *QMP) ExecuteNetdevAddByFds(ctx context.Context, netdevType, netdevID string, fdNames, vhostFdNames []string) error {
	fdNameStr := strings.Join(fdNames, ":")
	args := map[string]interface{}{
		"type": netdevType,
		"id":   netdevID,
		"fds":  fdNameStr,
	}
	if len(vhostFdNames) > 0 {
		vhostFdNameStr := strings.Join(vhostFdNames, ":")
		args["vhost"] = true
		args["vhostfds"] = vhostFdNameStr
	}

	return q.executeCommand(ctx, "netdev_add", args, nil)
}

// ExecuteNetdevDel deletes a Net device from a QEMU instance
// using the netdev_del command. netdevID is the id of the device to delete.
func (q *QMP) ExecuteNetdevDel(ctx context.Context, netdevID string) error {
	args := map[string]interface{}{
		"id": netdevID,
	}
	return q.executeCommand(ctx, "netdev_del", args, nil)
}

// ExecuteNetPCIDeviceAdd adds a Net PCI device to a QEMU instance
// using the device_add command. devID is the id of the device to add.
// Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add.
// queues is the number of queues of a nic.
// disableModern indicates if virtio version 1.0 should be replaced by the
// former version 0.9, as there is a KVM bug that occurs when using virtio
// 1.0 in nested environments.
func (q *QMP) ExecuteNetPCIDeviceAdd(ctx context.Context, netdevID, devID, macAddr, addr, bus, romfile string, queues int, disableModern bool) error {
	args := map[string]interface{}{
		"id":      devID,
		"driver":  VirtioNetPCI,
		"romfile": romfile,
	}

	if bus != "" {
		args["bus"] = bus
	}
	if addr != "" {
		args["addr"] = addr
	}
	if macAddr != "" {
		args["mac"] = macAddr
	}
	if netdevID != "" {
		args["netdev"] = netdevID
	}
	if disableModern {
		args["disable-modern"] = disableModern
	}

	if queues > 0 {
		// (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
		args["mq"] = "on"
		args["vectors"] = 2*queues + 2
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteNetCCWDeviceAdd adds a Net CCW device to a QEMU instance
// using the device_add command. devID is the id of the device to add.
// Must be valid QMP identifier. netdevID is the id of nic added by previous netdev_add.
// queues is the number of queues of a nic.
func (q *QMP) ExecuteNetCCWDeviceAdd(ctx context.Context, netdevID, devID, macAddr, bus string, queues int) error {
	args := map[string]interface{}{
		"id":     devID,
		"driver": VirtioNetCCW,
		"netdev": netdevID,
		"mac":    macAddr,
		"devno":  bus,
	}

	if queues > 0 {
		args["mq"] = "on"
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteDeviceDel deletes guest portion of a QEMU device by sending a
// device_del command.   devId is the identifier of the device to delete.
// Typically it would match the devID parameter passed to an earlier call
// to ExecuteDeviceAdd.  It must be a valid QMP identidier.
//
// This method blocks until a DEVICE_DELETED event is received for devID.
func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error {
	args := map[string]interface{}{
		"id": devID,
	}
	filter := &qmpEventFilter{
		eventName: "DEVICE_DELETED",
		dataKey:   "device",
		dataValue: devID,
	}
	return q.executeCommand(ctx, "device_del", args, filter)
}

// ExecutePCIDeviceAdd is the PCI version of ExecuteDeviceAdd. This function can be used
// to hot plug PCI devices on PCI(E) bridges, unlike ExecuteDeviceAdd this function receive the
// device address on its parent bus. bus is optional. queues specifies the number of queues of
// a block device. shared denotes if the drive can be shared allowing it to be passed more than once.
// disableModern indicates if virtio version 1.0 should be replaced by the
// former version 0.9, as there is a KVM bug that occurs when using virtio
// 1.0 in nested environments.
func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus, romfile string, queues int, shared, disableModern bool) error {
	args := map[string]interface{}{
		"id":     devID,
		"driver": driver,
		"drive":  blockdevID,
		"addr":   addr,
	}
	if bus != "" {
		args["bus"] = bus
	}
	if shared {
		args["share-rw"] = "on"
	}
	if queues > 0 {
		args["num-queues"] = strconv.Itoa(queues)
	}

	var transport VirtioTransport

	if transport.isVirtioPCI(nil) {
		args["romfile"] = romfile

		if disableModern {
			args["disable-modern"] = disableModern
		}
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecutePCIVhostUserDevAdd adds a vhost-user device to a QEMU instance using the device_add command.
// This function can be used to hot plug vhost-user devices on PCI(E) bridges.
// It receives the bus and the device address on its parent bus. bus is optional.
// devID is the id of the device to add.Must be valid QMP identifier. chardevID
// is the QMP identifier of character device using a unix socket as backend.
// driver is the name of vhost-user driver, like vhost-user-blk-pci.
func (q *QMP) ExecutePCIVhostUserDevAdd(ctx context.Context, driver, devID, chardevID, addr, bus string) error {
	args := map[string]interface{}{
		"driver":  driver,
		"id":      devID,
		"chardev": chardevID,
		"addr":    addr,
	}

	if bus != "" {
		args["bus"] = bus
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteVFIODeviceAdd adds a VFIO device to a QEMU instance using the device_add command.
// devID is the id of the device to add. Must be valid QMP identifier.
// bdf is the PCI bus-device-function of the pci device.
// bus is optional. When hot plugging a PCIe device, the bus can be the ID of the pcie-root-port.
func (q *QMP) ExecuteVFIODeviceAdd(ctx context.Context, devID, bdf, bus, romfile string) error {
	var driver string
	var transport VirtioTransport

	if transport.isVirtioCCW(nil) {
		driver = string(VfioCCW)
	} else {
		driver = string(VfioPCI)
	}

	args := map[string]interface{}{
		"id":      devID,
		"driver":  driver,
		"host":    bdf,
		"romfile": romfile,
	}
	if bus != "" {
		args["bus"] = bus
	}
	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecutePCIVFIODeviceAdd adds a VFIO device to a QEMU instance using the device_add command.
// This function can be used to hot plug VFIO devices on PCI(E) bridges, unlike
// ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus.
// bus is optional. devID is the id of the device to add.Must be valid QMP identifier. bdf is the
// PCI bus-device-function of the pci device.
func (q *QMP) ExecutePCIVFIODeviceAdd(ctx context.Context, devID, bdf, addr, bus, romfile string) error {
	args := map[string]interface{}{
		"id":      devID,
		"driver":  VfioPCI,
		"host":    bdf,
		"addr":    addr,
		"romfile": romfile,
	}

	if bus != "" {
		args["bus"] = bus
	}
	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecutePCIVFIOMediatedDeviceAdd adds a VFIO mediated device to a QEMU instance using the device_add command.
// This function can be used to hot plug VFIO mediated devices on PCI(E) bridges or root bus, unlike
// ExecuteVFIODeviceAdd this function receives the bus and the device address on its parent bus.
// devID is the id of the device to add. Must be valid QMP identifier. sysfsdev is the VFIO mediated device.
// Both bus and addr are optional. If they are both set to be empty, the system will pick up an empty slot on root bus.
func (q *QMP) ExecutePCIVFIOMediatedDeviceAdd(ctx context.Context, devID, sysfsdev, addr, bus, romfile string) error {
	args := map[string]interface{}{
		"id":       devID,
		"driver":   VfioPCI,
		"sysfsdev": sysfsdev,
		"romfile":  romfile,
	}

	if bus != "" {
		args["bus"] = bus
	}
	if addr != "" {
		args["addr"] = addr
	}
	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteAPVFIOMediatedDeviceAdd adds a VFIO mediated AP device to a QEMU instance using the device_add command.
func (q *QMP) ExecuteAPVFIOMediatedDeviceAdd(ctx context.Context, sysfsdev string) error {
	args := map[string]interface{}{
		"driver":   VfioAP,
		"sysfsdev": sysfsdev,
	}
	return q.executeCommand(ctx, "device_add", args, nil)
}

// isSocketIDSupported returns if the cpu driver supports the socket id option
func isSocketIDSupported(driver string) bool {
	if driver == "host-s390x-cpu" || driver == "host-powerpc64-cpu" {
		return false
	}
	return true
}

// isThreadIDSupported returns if the cpu driver supports the thread id option
func isThreadIDSupported(driver string) bool {
	if driver == "host-s390x-cpu" || driver == "host-powerpc64-cpu" {
		return false
	}
	return true
}

// isDieIDSupported returns if the cpu driver and the qemu version support the die id option
func (q *QMP) isDieIDSupported(driver string) bool {
	return driver == "host-x86_64-cpu"
}

// ExecuteCPUDeviceAdd adds a CPU to a QEMU instance using the device_add command.
// driver is the CPU model, cpuID must be a unique ID to identify the CPU, socketID is the socket number within
// node/board the CPU belongs to, coreID is the core number within socket the CPU belongs to, threadID is the
// thread number within core the CPU belongs to. Note that socketID and threadID are not a requirement for
// architecures like ppc64le.
func (q *QMP) ExecuteCPUDeviceAdd(ctx context.Context, driver, cpuID, socketID, dieID, coreID, threadID, romfile string) error {
	args := map[string]interface{}{
		"driver":  driver,
		"id":      cpuID,
		"core-id": coreID,
	}

	if socketID != "" && isSocketIDSupported(driver) {
		args["socket-id"] = socketID
	}

	if threadID != "" && isThreadIDSupported(driver) {
		args["thread-id"] = threadID
	}

	if q.isDieIDSupported(driver) {
		if dieID != "" {
			args["die-id"] = dieID
		}
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteQueryHotpluggableCPUs returns a slice with the list of hotpluggable CPUs
func (q *QMP) ExecuteQueryHotpluggableCPUs(ctx context.Context) ([]HotpluggableCPU, error) {
	response, err := q.executeCommandWithResponse(ctx, "query-hotpluggable-cpus", nil, nil, nil)
	if err != nil {
		return nil, err
	}

	// convert response to json
	data, err := json.Marshal(response)
	if err != nil {
		return nil, fmt.Errorf("unable to extract CPU information: %v", err)
	}

	var cpus []HotpluggableCPU
	// convert json to []HotpluggableCPU
	if err = json.Unmarshal(data, &cpus); err != nil {
		return nil, fmt.Errorf("unable to convert json to hotpluggable CPU: %v", err)
	}

	return cpus, nil
}

// ExecSetMigrationCaps sets migration capabilities
func (q *QMP) ExecSetMigrationCaps(ctx context.Context, caps []map[string]interface{}) error {
	args := map[string]interface{}{
		"capabilities": caps,
	}

	return q.executeCommand(ctx, "migrate-set-capabilities", args, nil)
}

// ExecSetMigrateArguments sets the command line used for migration
func (q *QMP) ExecSetMigrateArguments(ctx context.Context, url string) error {
	args := map[string]interface{}{
		"uri": url,
	}

	return q.executeCommand(ctx, "migrate", args, nil)
}

// ExecQueryMemoryDevices returns a slice with the list of memory devices
func (q *QMP) ExecQueryMemoryDevices(ctx context.Context) ([]MemoryDevices, error) {
	response, err := q.executeCommandWithResponse(ctx, "query-memory-devices", nil, nil, nil)
	if err != nil {
		return nil, err
	}

	// convert response to json
	data, err := json.Marshal(response)
	if err != nil {
		return nil, fmt.Errorf("unable to extract memory devices information: %v", err)
	}

	var memoryDevices []MemoryDevices
	// convert json to []MemoryDevices
	if err = json.Unmarshal(data, &memoryDevices); err != nil {
		return nil, fmt.Errorf("unable to convert json to memory devices: %v", err)
	}

	return memoryDevices, nil
}

// ExecQueryCpus returns a slice with the list of `CpuInfo`
// Since qemu 2.12, we have `query-cpus-fast` as a better choice in production
// we can still choose `ExecQueryCpus` for compatibility though not recommended.
func (q *QMP) ExecQueryCpus(ctx context.Context) ([]CPUInfo, error) {
	response, err := q.executeCommandWithResponse(ctx, "query-cpus", nil, nil, nil)
	if err != nil {
		return nil, err
	}

	// convert response to json
	data, err := json.Marshal(response)
	if err != nil {
		return nil, fmt.Errorf("unable to extract memory devices information: %v", err)
	}

	var cpuInfo []CPUInfo
	// convert json to []CPUInfo
	if err = json.Unmarshal(data, &cpuInfo); err != nil {
		return nil, fmt.Errorf("unable to convert json to CPUInfo: %v", err)
	}

	return cpuInfo, nil
}

// ExecQueryCpusFast returns a slice with the list of `CpuInfoFast`
// This is introduced since 2.12, it does not incur a performance penalty and
// should be used in production instead of query-cpus.
func (q *QMP) ExecQueryCpusFast(ctx context.Context) ([]CPUInfoFast, error) {
	response, err := q.executeCommandWithResponse(ctx, "query-cpus-fast", nil, nil, nil)
	if err != nil {
		return nil, err
	}

	// convert response to json
	data, err := json.Marshal(response)
	if err != nil {
		return nil, fmt.Errorf("unable to extract memory devices information: %v", err)
	}

	var cpuInfoFast []CPUInfoFast
	// convert json to []CPUInfoFast
	if err = json.Unmarshal(data, &cpuInfoFast); err != nil {
		return nil, fmt.Errorf("unable to convert json to CPUInfoFast: %v", err)
	}

	return cpuInfoFast, nil
}

// ExecMemdevAdd adds size of MiB memory device to the guest
func (q *QMP) ExecMemdevAdd(ctx context.Context, qomtype, id, mempath string, size int, share bool, driver, driverID, addr, bus string) error {
	args := map[string]interface{}{
		"qom-type": qomtype,
		"id":       id,
		"size":     uint64(size) << 20,
	}
	if mempath != "" {
		args["mem-path"] = mempath
	}
	if share {
		args["share"] = true
	}
	err := q.executeCommand(ctx, "object-add", args, nil)
	if err != nil {
		return err
	}

	defer func() {
		if err != nil {
			q.cfg.Logger.Errorf("Unable to add memory device %s: %v", id, err)
			err = q.executeCommand(ctx, "object-del", map[string]interface{}{"id": id}, nil)
			if err != nil {
				q.cfg.Logger.Warningf("Unable to clean up memory object %s: %v", id, err)
			}
		}
	}()

	args = map[string]interface{}{
		"driver": driver,
		"id":     driverID,
		"memdev": id,
	}

	if bus != "" {
		args["bus"] = bus
	}
	if addr != "" {
		args["addr"] = addr
	}

	err = q.executeCommand(ctx, "device_add", args, nil)

	return err
}

// ExecHotplugMemory adds size of MiB memory to the guest
func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string, size int, share bool) error {
	return q.ExecMemdevAdd(ctx, qomtype, id, mempath, size, share, "pc-dimm", "dimm"+id, "", "")
}

// ExecuteNVDIMMDeviceAdd adds a block device to a QEMU instance using
// a NVDIMM driver with the device_add command.
// id is the id of the device to add.  It must be a valid QMP identifier.
// mempath is the path of the device to add, e.g., /dev/rdb0.  size is
// the data size of the device. pmem is to guarantee the persistence of QEMU writes
// to the vNVDIMM backend.
func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64, pmem *bool) error {
	args := map[string]interface{}{
		"qom-type": "memory-backend-file",
		"id":       "nvdimmbackmem" + id,
		"mem-path": mempath,
		"size":     size,
		"share":    true,
	}

	if pmem != nil {
		args["pmem"] = *pmem
	}

	err := q.executeCommand(ctx, "object-add", args, nil)
	if err != nil {
		return err
	}

	args = map[string]interface{}{
		"driver": "nvdimm",
		"id":     "nvdimm" + id,
		"memdev": "nvdimmbackmem" + id,
	}
	if err = q.executeCommand(ctx, "device_add", args, nil); err != nil {
		q.cfg.Logger.Errorf("Unable to hotplug NVDIMM device: %v", err)
		err2 := q.executeCommand(ctx, "object-del", map[string]interface{}{"id": "nvdimmbackmem" + id}, nil)
		if err2 != nil {
			q.cfg.Logger.Warningf("Unable to clean up memory object: %v", err2)
		}
	}

	return err
}

// ExecuteBalloon sets the size of the balloon, hence updates the memory
// allocated for the VM.
func (q *QMP) ExecuteBalloon(ctx context.Context, bytes uint64) error {
	args := map[string]interface{}{
		"value": bytes,
	}
	return q.executeCommand(ctx, "balloon", args, nil)
}

// ExecutePCIVSockAdd adds a vhost-vsock-pci bus
// disableModern indicates if virtio version 1.0 should be replaced by the
// former version 0.9, as there is a KVM bug that occurs when using virtio
// 1.0 in nested environments.
func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID, vhostfd, addr, bus, romfile string, disableModern bool) error {
	args := map[string]interface{}{
		"driver":    VHostVSockPCI,
		"id":        id,
		"guest-cid": guestCID,
		"vhostfd":   vhostfd,
		"addr":      addr,
		"romfile":   romfile,
	}

	if bus != "" {
		args["bus"] = bus
	}

	if disableModern {
		args["disable-modern"] = disableModern
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteGetFD sends a file descriptor via SCM rights and assigns it a name
func (q *QMP) ExecuteGetFD(ctx context.Context, fdname string, fd *os.File) error {
	oob := syscall.UnixRights(int(fd.Fd()))
	args := map[string]interface{}{
		"fdname": fdname,
	}

	_, err := q.executeCommandWithResponse(ctx, "getfd", args, oob, nil)
	return err
}

// ExecuteCharDevUnixSocketAdd adds a character device using as backend a unix socket,
// id is an identifier for the device, path specifies the local path of the unix socket,
// wait is to block waiting for a client to connect, server specifies that the socket is a listening socket.
func (q *QMP) ExecuteCharDevUnixSocketAdd(ctx context.Context, id, path string, wait, server bool) error {
	data := map[string]interface{}{
		"server": server,
		"addr": map[string]interface{}{
			"type": "unix",
			"data": map[string]interface{}{
				"path": path,
			},
		},
	}

	// wait is only valid for server mode
	if server {
		data["wait"] = wait
	}

	args := map[string]interface{}{
		"id": id,
		"backend": map[string]interface{}{
			"type": "socket",
			"data": data,
		},
	}
	return q.executeCommand(ctx, "chardev-add", args, nil)
}

// ExecuteVirtSerialPortAdd adds a virtserialport.
// id is an identifier for the virtserialport, name is a name for the virtserialport and
// it will be visible in the VM, chardev is the character device id previously added.
func (q *QMP) ExecuteVirtSerialPortAdd(ctx context.Context, id, name, chardev string) error {
	args := map[string]interface{}{
		"driver":  VirtioSerialPort,
		"id":      id,
		"name":    name,
		"chardev": chardev,
	}

	return q.executeCommand(ctx, "device_add", args, nil)
}

// ExecuteQueryMigration queries migration progress.
func (q *QMP) ExecuteQueryMigration(ctx context.Context) (MigrationStatus, error) {
	response, err := q.executeCommandWithResponse(ctx, "query-migrate", nil, nil, nil)
	if err != nil {
		return MigrationStatus{}, err
	}

	data, err := json.Marshal(response)
	if err != nil {
		return MigrationStatus{}, fmt.Errorf("unable to extract migrate status information: %v", err)
	}

	var status MigrationStatus
	if err = json.Unmarshal(data, &status); err != nil {
		return MigrationStatus{}, fmt.Errorf("unable to convert migrate status information: %v", err)
	}

	return status, nil
}

// ExecuteMigrationIncoming start migration from incoming uri.
func (q *QMP) ExecuteMigrationIncoming(ctx context.Context, uri string) error {
	args := map[string]interface{}{
		"uri": uri,
	}
	return q.executeCommand(ctx, "migrate-incoming", args, nil)
}

// ExecQueryQmpSchema query all QMP wire ABI and returns a slice
func (q *QMP) ExecQueryQmpSchema(ctx context.Context) ([]SchemaInfo, error) {
	response, err := q.executeCommandWithResponse(ctx, "query-qmp-schema", nil, nil, nil)
	if err != nil {
		return nil, err
	}

	// convert response to json
	data, err := json.Marshal(response)
	if err != nil {
		return nil, fmt.Errorf("unable to extract memory devices information: %v", err)
	}

	var schemaInfo []SchemaInfo
	if err = json.Unmarshal(data, &schemaInfo); err != nil {
		return nil, fmt.Errorf("unable to convert json to schemaInfo: %v", err)
	}

	return schemaInfo, nil
}

// ExecuteQueryStatus queries guest status
func (q *QMP) ExecuteQueryStatus(ctx context.Context) (StatusInfo, error) {
	response, err := q.executeCommandWithResponse(ctx, "query-status", nil, nil, nil)
	if err != nil {
		return StatusInfo{}, err
	}

	data, err := json.Marshal(response)
	if err != nil {
		return StatusInfo{}, fmt.Errorf("unable to extract migrate status information: %v", err)
	}

	var status StatusInfo
	if err = json.Unmarshal(data, &status); err != nil {
		return StatusInfo{}, fmt.Errorf("unable to convert migrate status information: %v", err)
	}

	return status, nil
}

// ExecQomSet qom-set path property value
func (q *QMP) ExecQomSet(ctx context.Context, path, property string, value uint64) error {
	args := map[string]interface{}{
		"path":     path,
		"property": property,
		"value":    value,
	}

	return q.executeCommand(ctx, "qom-set", args, nil)
}

// ExecQomGet qom-get path property
func (q *QMP) ExecQomGet(ctx context.Context, path, property string) (interface{}, error) {
	args := map[string]interface{}{
		"path":     path,
		"property": property,
	}

	response, err := q.executeCommandWithResponse(ctx, "qom-get", args, nil, nil)
	if err != nil {
		return "", err
	}

	return response, nil
}

// ExecuteDumpGuestMemory dump guest memory to host
func (q *QMP) ExecuteDumpGuestMemory(ctx context.Context, protocol string, paging bool, format string) error {
	args := map[string]interface{}{
		"protocol": protocol,
		"paging":   paging,
		"format":   format,
	}

	return q.executeCommand(ctx, "dump-guest-memory", args, nil)
}
