/*
 * qemu_monitor_json.c: interaction with QEMU monitor console
 *
 * Copyright (C) 2006-2016 Red Hat, Inc.
 * Copyright (C) 2006 Daniel P. Berrange
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <poll.h>
#include <unistd.h>
#include <sys/time.h>

#include "qemu_monitor_json.h"
#include "qemu_alias.h"
#include "qemu_capabilities.h"
#include "viralloc.h"
#include "virlog.h"
#include "driver.h"
#include "datatypes.h"
#include "virerror.h"
#include "virjson.h"
#include "virprobe.h"
#include "virstring.h"
#include "cpu/cpu_x86.h"
#include "virenum.h"
#include "virsocket.h"

#ifdef WITH_DTRACE_PROBES
# include "libvirt_qemu_probes.h"
#endif

#define LIBVIRT_QEMU_MONITOR_PRIV_H_ALLOW
#include "qemu_monitor_priv.h"

#define VIR_FROM_THIS VIR_FROM_QEMU

VIR_LOG_INIT("qemu.qemu_monitor_json");

#define LINE_ENDING "\r\n"

static void qemuMonitorJSONHandleShutdown(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleReset(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleStop(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleResume(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleRTCChange(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleWatchdog(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleIOError(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleVNCConnect(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleVNCInitialize(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleSPICEConnect(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleSPICEInitialize(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleSPICEDisconnect(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleTrayChange(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandlePMWakeup(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandlePMSuspend(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleBlockJobCompleted(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleBlockJobCanceled(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleBlockJobReady(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleJobStatusChange(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleBalloonChange(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandlePMSuspendDisk(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleGuestCrashloaded(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleGuestPanic(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleDeviceDeleted(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleNicRxFilterChanged(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleSerialChange(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleSpiceMigrated(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleMigrationStatus(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleMigrationPass(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleAcpiOstInfo(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleBlockThreshold(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleDumpCompleted(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandlePRManagerStatusChanged(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleRdmaGidStatusChanged(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleMemoryFailure(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleMemoryDeviceSizeChange(qemuMonitor *mon, virJSONValue *data);
static void qemuMonitorJSONHandleDeviceUnplugErr(qemuMonitor *mon, virJSONValue *data);

typedef struct {
    const char *type;
    void (*handler)(qemuMonitor *mon, virJSONValue *data);
} qemuEventHandler;

static qemuEventHandler eventHandlers[] = {
    { "ACPI_DEVICE_OST", qemuMonitorJSONHandleAcpiOstInfo, },
    { "BALLOON_CHANGE", qemuMonitorJSONHandleBalloonChange, },
    { "BLOCK_IO_ERROR", qemuMonitorJSONHandleIOError, },
    { "BLOCK_JOB_CANCELLED", qemuMonitorJSONHandleBlockJobCanceled, },
    { "BLOCK_JOB_COMPLETED", qemuMonitorJSONHandleBlockJobCompleted, },
    { "BLOCK_JOB_READY", qemuMonitorJSONHandleBlockJobReady, },
    { "BLOCK_WRITE_THRESHOLD", qemuMonitorJSONHandleBlockThreshold, },
    { "DEVICE_DELETED", qemuMonitorJSONHandleDeviceDeleted, },
    { "DEVICE_TRAY_MOVED", qemuMonitorJSONHandleTrayChange, },
    { "DEVICE_UNPLUG_GUEST_ERROR", qemuMonitorJSONHandleDeviceUnplugErr, },
    { "DUMP_COMPLETED", qemuMonitorJSONHandleDumpCompleted, },
    { "GUEST_CRASHLOADED", qemuMonitorJSONHandleGuestCrashloaded, },
    { "GUEST_PANICKED", qemuMonitorJSONHandleGuestPanic, },
    { "JOB_STATUS_CHANGE", qemuMonitorJSONHandleJobStatusChange, },
    { "MEMORY_DEVICE_SIZE_CHANGE", qemuMonitorJSONHandleMemoryDeviceSizeChange, },
    { "MEMORY_FAILURE", qemuMonitorJSONHandleMemoryFailure, },
    { "MIGRATION", qemuMonitorJSONHandleMigrationStatus, },
    { "MIGRATION_PASS", qemuMonitorJSONHandleMigrationPass, },
    { "NIC_RX_FILTER_CHANGED", qemuMonitorJSONHandleNicRxFilterChanged, },
    { "PR_MANAGER_STATUS_CHANGED", qemuMonitorJSONHandlePRManagerStatusChanged, },
    { "RDMA_GID_STATUS_CHANGED", qemuMonitorJSONHandleRdmaGidStatusChanged, },
    { "RESET", qemuMonitorJSONHandleReset, },
    { "RESUME", qemuMonitorJSONHandleResume, },
    { "RTC_CHANGE", qemuMonitorJSONHandleRTCChange, },
    { "SHUTDOWN", qemuMonitorJSONHandleShutdown, },
    { "SPICE_CONNECTED", qemuMonitorJSONHandleSPICEConnect, },
    { "SPICE_DISCONNECTED", qemuMonitorJSONHandleSPICEDisconnect, },
    { "SPICE_INITIALIZED", qemuMonitorJSONHandleSPICEInitialize, },
    { "SPICE_MIGRATE_COMPLETED", qemuMonitorJSONHandleSpiceMigrated, },
    { "STOP", qemuMonitorJSONHandleStop, },
    { "SUSPEND", qemuMonitorJSONHandlePMSuspend, },
    { "SUSPEND_DISK", qemuMonitorJSONHandlePMSuspendDisk, },
    { "VNC_CONNECTED", qemuMonitorJSONHandleVNCConnect, },
    { "VNC_DISCONNECTED", qemuMonitorJSONHandleVNCDisconnect, },
    { "VNC_INITIALIZED", qemuMonitorJSONHandleVNCInitialize, },
    { "VSERPORT_CHANGE", qemuMonitorJSONHandleSerialChange, },
    { "WAKEUP", qemuMonitorJSONHandlePMWakeup, },
    { "WATCHDOG", qemuMonitorJSONHandleWatchdog, },
    /* We use bsearch, so keep this list sorted.  */
};

static int
qemuMonitorEventCompare(const void *key, const void *elt)
{
    const char *type = key;
    const qemuEventHandler *handler = elt;
    return strcmp(type, handler->type);
}

static int
qemuMonitorJSONIOProcessEvent(qemuMonitor *mon,
                              virJSONValue *obj)
{
    const char *type;
    qemuEventHandler *handler;
    virJSONValue *data;
    g_autofree char *details = NULL;
    virJSONValue *timestamp;
    long long seconds = -1;
    unsigned int micros = 0;

    VIR_DEBUG("mon=%p obj=%p", mon, obj);

    type = virJSONValueObjectGetString(obj, "event");
    if (!type) {
        VIR_WARN("missing event type in message");
        errno = EINVAL;
        return -1;
    }

    /* Not all events have data; and event reporting is best-effort only */
    if ((data = virJSONValueObjectGet(obj, "data")))
        details = virJSONValueToString(data, false);
    if ((timestamp = virJSONValueObjectGet(obj, "timestamp"))) {
        ignore_value(virJSONValueObjectGetNumberLong(timestamp, "seconds",
                                                     &seconds));
        ignore_value(virJSONValueObjectGetNumberUint(timestamp, "microseconds",
                                                     &micros));
    }
    qemuMonitorEmitEvent(mon, type, seconds, micros, details);

    handler = bsearch(type, eventHandlers, G_N_ELEMENTS(eventHandlers),
                      sizeof(eventHandlers[0]), qemuMonitorEventCompare);
    if (handler) {
        VIR_DEBUG("handle %s handler=%p data=%p", type,
                  handler->handler, data);
        (handler->handler)(mon, data);
    }
    return 0;
}

int
qemuMonitorJSONIOProcessLine(qemuMonitor *mon,
                             const char *line,
                             qemuMonitorMessage *msg)
{
    g_autoptr(virJSONValue) obj = NULL;

    VIR_DEBUG("Line [%s]", line);

    if (!(obj = virJSONValueFromString(line)))
        return -1;

    if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Parsed JSON reply '%s' isn't an object"), line);
        return -1;
    }

    if (virJSONValueObjectHasKey(obj, "QMP") == 1) {
        return 0;
    } else if (virJSONValueObjectHasKey(obj, "event") == 1) {
        PROBE(QEMU_MONITOR_RECV_EVENT,
              "mon=%p event=%s", mon, line);
        return qemuMonitorJSONIOProcessEvent(mon, obj);
    } else if (virJSONValueObjectHasKey(obj, "error") == 1 ||
               virJSONValueObjectHasKey(obj, "return") == 1) {
        PROBE(QEMU_MONITOR_RECV_REPLY,
              "mon=%p reply=%s", mon, line);
        if (msg) {
            msg->rxObject = g_steal_pointer(&obj);
            msg->finished = 1;
            return 0;
        } else {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Unexpected JSON reply '%s'"), line);
        }
    } else {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown JSON reply '%s'"), line);
    }

    return -1;
}

int qemuMonitorJSONIOProcess(qemuMonitor *mon,
                             const char *data,
                             size_t len,
                             qemuMonitorMessage *msg)
{
    int used = 0;
    /*VIR_DEBUG("Data %d bytes [%s]", len, data);*/

    while (used < len) {
        char *nl = strstr(data + used, LINE_ENDING);

        if (nl) {
            int got = nl - (data + used);
            g_autofree char *line = g_strndup(data + used, got);

            used += got + strlen(LINE_ENDING);
            line[got] = '\0'; /* kill \n */
            if (qemuMonitorJSONIOProcessLine(mon, line, msg) < 0)
                return -1;
        } else {
            break;
        }
    }

    return used;
}

static int
qemuMonitorJSONCommandWithFd(qemuMonitor *mon,
                             virJSONValue *cmd,
                             int scm_fd,
                             virJSONValue **reply)
{
    int ret = -1;
    qemuMonitorMessage msg;
    g_auto(virBuffer) cmdbuf = VIR_BUFFER_INITIALIZER;

    *reply = NULL;

    memset(&msg, 0, sizeof(msg));

    if (virJSONValueObjectHasKey(cmd, "execute") == 1) {
        g_autofree char *id = qemuMonitorNextCommandID(mon);

        if (virJSONValueObjectAppendString(cmd, "id", id) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Unable to append command 'id' string"));
            return -1;
        }
    }

    if (virJSONValueToBuffer(cmd, &cmdbuf, false) < 0)
        return -1;
    virBufferAddLit(&cmdbuf, "\r\n");

    msg.txLength = virBufferUse(&cmdbuf);
    msg.txBuffer = virBufferCurrentContent(&cmdbuf);
    msg.txFD = scm_fd;

    ret = qemuMonitorSend(mon, &msg);

    if (ret == 0) {
        if (!msg.rxObject) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Missing monitor reply object"));
            ret = -1;
        } else {
            *reply = msg.rxObject;
        }
    }

    return ret;
}


static int
qemuMonitorJSONCommand(qemuMonitor *mon,
                       virJSONValue *cmd,
                       virJSONValue **reply)
{
    return qemuMonitorJSONCommandWithFd(mon, cmd, -1, reply);
}

/* Ignoring OOM in this method, since we're already reporting
 * a more important error
 *
 * XXX see qerror.h for different klasses & fill out useful params
 */
static const char *
qemuMonitorJSONStringifyError(virJSONValue *error)
{
    const char *klass = virJSONValueObjectGetString(error, "class");
    const char *detail = NULL;

    /* The QMP 'desc' field is usually sufficient for our generic
     * error reporting needs.
     */
    if (klass)
        detail = virJSONValueObjectGetString(error, "desc");


    if (!detail)
        detail = "unknown QEMU command error";

    return detail;
}

static const char *
qemuMonitorJSONCommandName(virJSONValue *cmd)
{
    const char *name = virJSONValueObjectGetString(cmd, "execute");
    if (name)
        return name;
    else
        return "<unknown>";
}

static int
qemuMonitorJSONCheckErrorFull(virJSONValue *cmd,
                              virJSONValue *reply,
                              bool report)
{
    if (virJSONValueObjectHasKey(reply, "error")) {
        virJSONValue *error = virJSONValueObjectGet(reply, "error");
        g_autofree char *cmdstr = virJSONValueToString(cmd, false);
        g_autofree char *replystr = virJSONValueToString(reply, false);

        /* Log the full JSON formatted command & error */
        VIR_DEBUG("unable to execute QEMU command %s: %s",
                  NULLSTR(cmdstr), NULLSTR(replystr));

        if (!report)
            return -1;

        /* Only send the user the command name + friendly error */
        if (!error)
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("unable to execute QEMU command '%s'"),
                           qemuMonitorJSONCommandName(cmd));
        else
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("unable to execute QEMU command '%s': %s"),
                           qemuMonitorJSONCommandName(cmd),
                           qemuMonitorJSONStringifyError(error));

        return -1;
    } else if (!virJSONValueObjectHasKey(reply, "return")) {
        g_autofree char *cmdstr = virJSONValueToString(cmd, false);
        g_autofree char *replystr = virJSONValueToString(reply, false);

        VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %s: %s",
                  NULLSTR(cmdstr), NULLSTR(replystr));

        if (!report)
            return -1;

        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unable to execute QEMU command '%s'"),
                       qemuMonitorJSONCommandName(cmd));
        return -1;
    }
    return 0;
}


static int
qemuMonitorJSONCheckError(virJSONValue *cmd,
                          virJSONValue *reply)
{
    return qemuMonitorJSONCheckErrorFull(cmd, reply, true);
}


static int
qemuMonitorJSONCheckReply(virJSONValue *cmd,
                          virJSONValue *reply,
                          virJSONType type)
{
    virJSONValue *data;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    data = virJSONValueObjectGet(reply, "return");
    if (virJSONValueGetType(data) != type) {
        g_autofree char *cmdstr = virJSONValueToString(cmd, false);
        g_autofree char *retstr = virJSONValueToString(data, false);

        VIR_DEBUG("Unexpected return type %d (expecting %d) for command %s: %s",
                  virJSONValueGetType(data), type, cmdstr, retstr);
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unexpected type returned by QEMU command '%s'"),
                       qemuMonitorJSONCommandName(cmd));

        return -1;
    }

    return 0;
}


static bool
qemuMonitorJSONErrorIsClass(virJSONValue *error,
                            const char *klass)
{
    return STREQ_NULLABLE(virJSONValueObjectGetString(error, "class"), klass);
}


static bool
qemuMonitorJSONHasError(virJSONValue *reply,
                        const char *klass)
{
    virJSONValue *error;

    if (!(error = virJSONValueObjectGet(reply, "error")))
        return false;

    return qemuMonitorJSONErrorIsClass(error, klass);
}


/**
 * qemuMonitorJSONTransactionAdd:
 * @actions: array of actions for the 'transaction' command
 * @cmdname: command to add to @actions
 * @...: arguments for @cmdname (see virJSONValueObjectAddVArgs for formatting)
 *
 * Add a new command with arguments to the existing ones. The resulting array
 * is intended to be used as argument for the 'transaction' command.
 *
 * Returns 0 on success and -1 on error.
 */
static int
qemuMonitorJSONTransactionAdd(virJSONValue *actions,
                              const char *cmdname,
                              ...)
{
    g_autoptr(virJSONValue) entry = NULL;
    g_autoptr(virJSONValue) data = NULL;
    va_list args;

    va_start(args, cmdname);

    if (virJSONValueObjectAddVArgs(&data, args) < 0) {
        va_end(args);
        return -1;
    }

    va_end(args);

    if (virJSONValueObjectAdd(&entry,
                              "s:type", cmdname,
                              "A:data", &data, NULL) < 0)
        return -1;

    if (virJSONValueArrayAppend(actions, &entry) < 0)
        return -1;

    return 0;
}


/**
 * qemuMonitorJSONMakeCommandInternal:
 * @cmdname: QMP command name
 * @arguments: a JSON object containing command arguments or NULL
 *
 * Create a JSON object used on the QMP monitor to call a command.
 *
 * Note that @arguments is consumed and cleared.
 */
static virJSONValue *
qemuMonitorJSONMakeCommandInternal(const char *cmdname,
                                   virJSONValue **arguments)
{
    virJSONValue *ret = NULL;

    ignore_value(virJSONValueObjectAdd(&ret,
                                       "s:execute", cmdname,
                                       "A:arguments", arguments, NULL));

    return ret;
}


static virJSONValue *G_GNUC_NULL_TERMINATED
qemuMonitorJSONMakeCommand(const char *cmdname,
                           ...)
{
    virJSONValue *obj = NULL;
    g_autoptr(virJSONValue) jargs = NULL;
    va_list args;

    va_start(args, cmdname);

    if (virJSONValueObjectAddVArgs(&jargs, args) < 0)
        goto cleanup;

    obj = qemuMonitorJSONMakeCommandInternal(cmdname, &jargs);

 cleanup:
    va_end(args);

    return obj;
}


static void qemuMonitorJSONHandleShutdown(qemuMonitor *mon, virJSONValue *data)
{
    bool guest = false;
    virTristateBool guest_initiated = VIR_TRISTATE_BOOL_ABSENT;

    if (data && virJSONValueObjectGetBoolean(data, "guest", &guest) == 0)
        guest_initiated = virTristateBoolFromBool(guest);

    qemuMonitorEmitShutdown(mon, guest_initiated);
}

static void qemuMonitorJSONHandleReset(qemuMonitor *mon, virJSONValue *data G_GNUC_UNUSED)
{
    qemuMonitorEmitReset(mon);
}

static void qemuMonitorJSONHandleStop(qemuMonitor *mon, virJSONValue *data G_GNUC_UNUSED)
{
    qemuMonitorEmitStop(mon);
}

static void qemuMonitorJSONHandleResume(qemuMonitor *mon, virJSONValue *data G_GNUC_UNUSED)
{
    qemuMonitorEmitResume(mon);
}


static qemuMonitorEventPanicInfo *
qemuMonitorJSONGuestPanicExtractInfoHyperv(virJSONValue *data)
{
    g_autoptr(qemuMonitorEventPanicInfo) ret = g_new0(qemuMonitorEventPanicInfo, 1);

    ret->type = QEMU_MONITOR_EVENT_PANIC_INFO_TYPE_HYPERV;

    if (virJSONValueObjectGetNumberUlong(data, "arg1", &ret->data.hyperv.arg1) < 0 ||
        virJSONValueObjectGetNumberUlong(data, "arg2", &ret->data.hyperv.arg2) < 0 ||
        virJSONValueObjectGetNumberUlong(data, "arg3", &ret->data.hyperv.arg3) < 0 ||
        virJSONValueObjectGetNumberUlong(data, "arg4", &ret->data.hyperv.arg4) < 0 ||
        virJSONValueObjectGetNumberUlong(data, "arg5", &ret->data.hyperv.arg5) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("malformed hyperv panic data"));
        return NULL;
    }

    return g_steal_pointer(&ret);
}

static qemuMonitorEventPanicInfo *
qemuMonitorJSONGuestPanicExtractInfoS390(virJSONValue *data)
{
    g_autoptr(qemuMonitorEventPanicInfo) ret = NULL;
    int core;
    unsigned long long psw_mask, psw_addr;
    const char *reason = NULL;

    ret = g_new0(qemuMonitorEventPanicInfo, 1);

    ret->type = QEMU_MONITOR_EVENT_PANIC_INFO_TYPE_S390;

    if (virJSONValueObjectGetNumberInt(data, "core", &core) < 0 ||
        virJSONValueObjectGetNumberUlong(data, "psw-mask", &psw_mask) < 0 ||
        virJSONValueObjectGetNumberUlong(data, "psw-addr", &psw_addr) < 0 ||
        !(reason = virJSONValueObjectGetString(data, "reason"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed s390 panic data"));
        return NULL;
    }

    ret->data.s390.core = core;
    ret->data.s390.psw_mask = psw_mask;
    ret->data.s390.psw_addr = psw_addr;

    ret->data.s390.reason = g_strdup(reason);

    return g_steal_pointer(&ret);
}

static qemuMonitorEventPanicInfo *
qemuMonitorJSONGuestPanicExtractInfo(virJSONValue *data)
{
    const char *type = virJSONValueObjectGetString(data, "type");

    if (STREQ_NULLABLE(type, "hyper-v"))
        return qemuMonitorJSONGuestPanicExtractInfoHyperv(data);
    else if (STREQ_NULLABLE(type, "s390"))
        return qemuMonitorJSONGuestPanicExtractInfoS390(data);

    virReportError(VIR_ERR_INTERNAL_ERROR,
                   _("unknown panic info type '%s'"), NULLSTR(type));
    return NULL;
}


static void
qemuMonitorJSONHandleGuestPanic(qemuMonitor *mon,
                                virJSONValue *data)
{
    virJSONValue *infojson = virJSONValueObjectGetObject(data, "info");
    qemuMonitorEventPanicInfo *info = NULL;

    if (infojson)
        info = qemuMonitorJSONGuestPanicExtractInfo(infojson);

    qemuMonitorEmitGuestPanic(mon, info);
}


static void qemuMonitorJSONHandleRTCChange(qemuMonitor *mon, virJSONValue *data)
{
    long long offset = 0;
    if (virJSONValueObjectGetNumberLong(data, "offset", &offset) < 0) {
        VIR_WARN("missing offset in RTC change event");
        offset = 0;
    }
    qemuMonitorEmitRTCChange(mon, offset);
}

VIR_ENUM_DECL(qemuMonitorWatchdogAction);
VIR_ENUM_IMPL(qemuMonitorWatchdogAction,
              VIR_DOMAIN_EVENT_WATCHDOG_LAST,
              "none", "pause", "reset", "poweroff", "shutdown", "debug", "inject-nmi",
);

static void qemuMonitorJSONHandleWatchdog(qemuMonitor *mon, virJSONValue *data)
{
    const char *action;
    int actionID;
    if (!(action = virJSONValueObjectGetString(data, "action")))
        VIR_WARN("missing action in watchdog event");
    if (action) {
        if ((actionID = qemuMonitorWatchdogActionTypeFromString(action)) < 0) {
            VIR_WARN("unknown action %s in watchdog event", action);
            actionID = VIR_DOMAIN_EVENT_WATCHDOG_NONE;
        }
    } else {
            actionID = VIR_DOMAIN_EVENT_WATCHDOG_NONE;
    }
    qemuMonitorEmitWatchdog(mon, actionID);
}

VIR_ENUM_DECL(qemuMonitorIOErrorAction);
VIR_ENUM_IMPL(qemuMonitorIOErrorAction,
              VIR_DOMAIN_EVENT_IO_ERROR_LAST,
              "ignore", "stop", "report",
);


static void
qemuMonitorJSONHandleIOError(qemuMonitor *mon, virJSONValue *data)
{
    const char *device;
    const char *nodename;
    const char *action;
    const char *reason = "";
    bool nospc = false;
    int actionID;

    /* Throughout here we try our best to carry on upon errors,
       since it's important to get as much info as possible out
       to the application */

    if ((action = virJSONValueObjectGetString(data, "action")) == NULL) {
        VIR_WARN("Missing action in disk io error event");
        action = "ignore";
    }

    if ((device = virJSONValueObjectGetString(data, "device")) == NULL)
        VIR_WARN("missing device in disk io error event");

    nodename = virJSONValueObjectGetString(data, "node-name");

    if (virJSONValueObjectGetBoolean(data, "nospace", &nospc) == 0 && nospc)
        reason = "enospc";

    if ((actionID = qemuMonitorIOErrorActionTypeFromString(action)) < 0) {
        VIR_WARN("unknown disk io error action '%s'", action);
        actionID = VIR_DOMAIN_EVENT_IO_ERROR_NONE;
    }

    qemuMonitorEmitIOError(mon, device, nodename, actionID, reason);
}


VIR_ENUM_DECL(qemuMonitorGraphicsAddressFamily);
VIR_ENUM_IMPL(qemuMonitorGraphicsAddressFamily,
              VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_LAST,
              "ipv4", "ipv6", "unix",
);

static void
qemuMonitorJSONHandleGraphicsVNC(qemuMonitor *mon,
                                 virJSONValue *data,
                                 int phase)
{
    const char *localNode, *localService, *localFamily;
    const char *remoteNode, *remoteService, *remoteFamily;
    const char *authScheme, *saslUsername, *x509dname;
    int localFamilyID, remoteFamilyID;
    virJSONValue *client;
    virJSONValue *server;

    if (!(client = virJSONValueObjectGetObject(data, "client"))) {
        VIR_WARN("missing client info in VNC event");
        return;
    }
    if (!(server = virJSONValueObjectGetObject(data, "server"))) {
        VIR_WARN("missing server info in VNC event");
        return;
    }

    if (!(authScheme = virJSONValueObjectGetString(server, "auth"))) {
        /* not all events are required to contain auth scheme */
        VIR_DEBUG("missing auth scheme in VNC event");
        authScheme = "";
    }

    if (!(localFamily = virJSONValueObjectGetString(server, "family"))) {
        VIR_WARN("missing local address family in VNC event");
        return;
    }
    if (!(localNode = virJSONValueObjectGetString(server, "host"))) {
        VIR_WARN("missing local hostname in VNC event");
        return;
    }
    if (!(localService = virJSONValueObjectGetString(server, "service"))) {
        VIR_WARN("missing local service in VNC event");
        return;
    }

    if (!(remoteFamily = virJSONValueObjectGetString(client, "family"))) {
        VIR_WARN("missing remote address family in VNC event");
        return;
    }
    if (!(remoteNode = virJSONValueObjectGetString(client, "host"))) {
        VIR_WARN("missing remote hostname in VNC event");
        return;
    }
    if (!(remoteService = virJSONValueObjectGetString(client, "service"))) {
        VIR_WARN("missing remote service in VNC event");
        return;
    }

    saslUsername = virJSONValueObjectGetString(client, "sasl_username");
    x509dname = virJSONValueObjectGetString(client, "x509_dname");

    if ((localFamilyID = qemuMonitorGraphicsAddressFamilyTypeFromString(localFamily)) < 0) {
        VIR_WARN("unknown address family '%s'", localFamily);
        localFamilyID = VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4;
    }
    if ((remoteFamilyID = qemuMonitorGraphicsAddressFamilyTypeFromString(remoteFamily)) < 0) {
        VIR_WARN("unknown address family '%s'", remoteFamily);
        remoteFamilyID = VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4;
    }

    qemuMonitorEmitGraphics(mon, phase,
                            localFamilyID, localNode, localService,
                            remoteFamilyID, remoteNode, remoteService,
                            authScheme, x509dname, saslUsername);
}

static void qemuMonitorJSONHandleVNCConnect(qemuMonitor *mon, virJSONValue *data)
{
    qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_CONNECT);
}


static void qemuMonitorJSONHandleVNCInitialize(qemuMonitor *mon, virJSONValue *data)
{
    qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE);
}


static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitor *mon, virJSONValue *data)
{
    qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT);
}


static void
qemuMonitorJSONHandleGraphicsSPICE(qemuMonitor *mon,
                                   virJSONValue *data,
                                   int phase)
{
    const char *lhost, *lport, *lfamily;
    const char *rhost, *rport, *rfamily;
    const char *auth = "";
    int lfamilyID, rfamilyID;
    virJSONValue *client;
    virJSONValue *server;

    if (!(client = virJSONValueObjectGetObject(data, "client")) ||
        !(server = virJSONValueObjectGetObject(data, "server"))) {
        VIR_WARN("missing server or client info in SPICE event");
        return;
    }

    if (phase == VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE &&
        !(auth = virJSONValueObjectGetString(server, "auth"))) {
        VIR_DEBUG("missing auth scheme in SPICE event");
        auth = "";
    }

    if (!(lfamily = virJSONValueObjectGetString(server, "family"))) {
        VIR_WARN("missing local address family in SPICE event");
        return;
    }
    if (!(lhost = virJSONValueObjectGetString(server, "host"))) {
        VIR_WARN("missing local hostname in SPICE event");
        return;
    }
    if (!(lport = virJSONValueObjectGetString(server, "port"))) {
        VIR_WARN("missing local port in SPICE event");
        return;
    }

    if (!(rfamily = virJSONValueObjectGetString(client, "family"))) {
        VIR_WARN("missing remote address family in SPICE event");
        return;
    }
    if (!(rhost = virJSONValueObjectGetString(client, "host"))) {
        VIR_WARN("missing remote hostname in SPICE event");
        return;
    }
    if (!(rport = virJSONValueObjectGetString(client, "port"))) {
        VIR_WARN("missing remote service in SPICE event");
        return;
    }

    if ((lfamilyID = qemuMonitorGraphicsAddressFamilyTypeFromString(lfamily)) < 0) {
        VIR_WARN("unknown address family '%s'", lfamily);
        lfamilyID = VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4;
    }
    if ((rfamilyID = qemuMonitorGraphicsAddressFamilyTypeFromString(rfamily)) < 0) {
        VIR_WARN("unknown address family '%s'", rfamily);
        rfamilyID = VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4;
    }

    qemuMonitorEmitGraphics(mon, phase, lfamilyID, lhost, lport, rfamilyID,
                            rhost, rport, auth, NULL, NULL);
}


static void qemuMonitorJSONHandleSPICEConnect(qemuMonitor *mon, virJSONValue *data)
{
    qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_CONNECT);
}


static void qemuMonitorJSONHandleSPICEInitialize(qemuMonitor *mon, virJSONValue *data)
{
    qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE);
}


static void qemuMonitorJSONHandleSPICEDisconnect(qemuMonitor *mon, virJSONValue *data)
{
    qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT);
}

static void
qemuMonitorJSONHandleBlockJobImpl(qemuMonitor *mon,
                                  virJSONValue *data,
                                  int event)
{
    const char *device;
    const char *type_str;
    const char *error = NULL;
    int type = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
    unsigned long long offset, len;

    if ((device = virJSONValueObjectGetString(data, "device")) == NULL) {
        VIR_WARN("missing device in block job event");
        goto out;
    }

    if (virJSONValueObjectGetNumberUlong(data, "offset", &offset) < 0) {
        VIR_WARN("missing offset in block job event");
        goto out;
    }

    if (virJSONValueObjectGetNumberUlong(data, "len", &len) < 0) {
        VIR_WARN("missing len in block job event");
        goto out;
    }

    if ((type_str = virJSONValueObjectGetString(data, "type")) == NULL) {
        VIR_WARN("missing type in block job event");
        goto out;
    }

    if (STREQ(type_str, "stream"))
        type = VIR_DOMAIN_BLOCK_JOB_TYPE_PULL;
    else if (STREQ(type_str, "commit"))
        type = VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT;
    else if (STREQ(type_str, "mirror"))
        type = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY;
    else if (STREQ(type_str, "backup"))
        type = VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP;

    switch ((virConnectDomainEventBlockJobStatus) event) {
    case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
        error = virJSONValueObjectGetString(data, "error");
        /* Make sure the whole device has been processed */
        if (offset != len || error)
            event = VIR_DOMAIN_BLOCK_JOB_FAILED;
        break;
    case VIR_DOMAIN_BLOCK_JOB_CANCELED:
    case VIR_DOMAIN_BLOCK_JOB_READY:
        break;
    case VIR_DOMAIN_BLOCK_JOB_FAILED:
    case VIR_DOMAIN_BLOCK_JOB_LAST:
        VIR_DEBUG("should not get here");
        break;
    }

 out:
    qemuMonitorEmitBlockJob(mon, device, type, event, error);
}


static void
qemuMonitorJSONHandleJobStatusChange(qemuMonitor *mon,
                                     virJSONValue *data)
{
    const char *jobname = virJSONValueObjectGetString(data, "id");
    const char *statusstr = virJSONValueObjectGetString(data, "status");
    int status;

    if (!jobname) {
        VIR_WARN("missing job name in JOB_STATUS_CHANGE event");
        return;
    }

    if ((status = qemuMonitorJobStatusTypeFromString(statusstr)) < 0) {
        VIR_WARN("unknown job status '%s' for job '%s' in JOB_STATUS_CHANGE event",
                 statusstr, jobname);
        return;
    }

    qemuMonitorEmitJobStatusChange(mon, jobname, status);
}


static void
qemuMonitorJSONHandleTrayChange(qemuMonitor *mon,
                                virJSONValue *data)
{
    const char *devAlias = virJSONValueObjectGetString(data, "device");
    const char *devid = virJSONValueObjectGetString(data, "id");
    bool trayOpened;
    int reason;

    /* drive alias is always reported but empty for -blockdev */
    if (*devAlias == '\0')
        devAlias = NULL;

    if (!devAlias && !devid) {
        VIR_WARN("missing device in tray change event");
        return;
    }

    if (virJSONValueObjectGetBoolean(data, "tray-open", &trayOpened) < 0) {
        VIR_WARN("missing tray-open in tray change event");
        return;
    }

    if (trayOpened)
        reason = VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN;
    else
        reason = VIR_DOMAIN_EVENT_TRAY_CHANGE_CLOSE;

    qemuMonitorEmitTrayChange(mon, devAlias, devid, reason);
}

static void
qemuMonitorJSONHandlePMWakeup(qemuMonitor *mon,
                              virJSONValue *data G_GNUC_UNUSED)
{
    qemuMonitorEmitPMWakeup(mon);
}

static void
qemuMonitorJSONHandlePMSuspend(qemuMonitor *mon,
                               virJSONValue *data G_GNUC_UNUSED)
{
    qemuMonitorEmitPMSuspend(mon);
}

static void
qemuMonitorJSONHandleBlockJobCompleted(qemuMonitor *mon,
                                       virJSONValue *data)
{
    qemuMonitorJSONHandleBlockJobImpl(mon, data,
                                      VIR_DOMAIN_BLOCK_JOB_COMPLETED);
}

static void
qemuMonitorJSONHandleBlockJobCanceled(qemuMonitor *mon,
                                       virJSONValue *data)
{
    qemuMonitorJSONHandleBlockJobImpl(mon, data,
                                      VIR_DOMAIN_BLOCK_JOB_CANCELED);
}

static void
qemuMonitorJSONHandleBlockJobReady(qemuMonitor *mon,
                                   virJSONValue *data)
{
    qemuMonitorJSONHandleBlockJobImpl(mon, data,
                                      VIR_DOMAIN_BLOCK_JOB_READY);
}

static void
qemuMonitorJSONHandleBalloonChange(qemuMonitor *mon,
                                   virJSONValue *data)
{
    unsigned long long actual = 0;
    if (virJSONValueObjectGetNumberUlong(data, "actual", &actual) < 0) {
        VIR_WARN("missing actual in balloon change event");
        return;
    }
    actual = VIR_DIV_UP(actual, 1024);
    qemuMonitorEmitBalloonChange(mon, actual);
}

static void
qemuMonitorJSONHandlePMSuspendDisk(qemuMonitor *mon,
                                   virJSONValue *data G_GNUC_UNUSED)
{
    qemuMonitorEmitPMSuspendDisk(mon);
}

static void
qemuMonitorJSONHandleDeviceDeleted(qemuMonitor *mon, virJSONValue *data)
{
    const char *device;

    if (!(device = virJSONValueObjectGetString(data, "device"))) {
        VIR_DEBUG("missing device in device deleted event");
        return;
    }

    qemuMonitorEmitDeviceDeleted(mon, device);
}


static void
qemuMonitorJSONHandleDeviceUnplugErr(qemuMonitor *mon, virJSONValue *data)
{
    const char *device;
    const char *path;

    if (!(path = virJSONValueObjectGetString(data, "path"))) {
        VIR_DEBUG("missing path in device unplug guest error event");
        return;
    }

    device = virJSONValueObjectGetString(data, "device");

    qemuMonitorEmitDeviceUnplugErr(mon, path, device);
}


static void
qemuMonitorJSONHandleNicRxFilterChanged(qemuMonitor *mon, virJSONValue *data)
{
    const char *name;

    if (!(name = virJSONValueObjectGetString(data, "name"))) {
        VIR_WARN("missing device in NIC_RX_FILTER_CHANGED event");
        return;
    }

    qemuMonitorEmitNicRxFilterChanged(mon, name);
}


static void
qemuMonitorJSONHandleSerialChange(qemuMonitor *mon,
                                  virJSONValue *data)
{
    const char *name;
    bool connected;

    if (!(name = virJSONValueObjectGetString(data, "id"))) {
        VIR_WARN("missing device alias in VSERPORT_CHANGE event");
        return;
    }

    if (virJSONValueObjectGetBoolean(data, "open", &connected) < 0) {
        VIR_WARN("missing port state for '%s' in VSERPORT_CHANGE event", name);
        return;
    }

    qemuMonitorEmitSerialChange(mon, name, connected);
}


static void
qemuMonitorJSONHandleSpiceMigrated(qemuMonitor *mon,
                                   virJSONValue *data G_GNUC_UNUSED)
{
    qemuMonitorEmitSpiceMigrated(mon);
}


static void
qemuMonitorJSONHandleMemoryDeviceSizeChange(qemuMonitor *mon,
                                            virJSONValue *data)
{
    const char *name;
    unsigned long long size;

    if (!(name = virJSONValueObjectGetString(data, "id"))) {
        VIR_WARN("missing device alias in MEMORY_DEVICE_SIZE_CHANGE event");
        return;
    }

    if (virJSONValueObjectGetNumberUlong(data, "size", &size) < 0) {
        VIR_WARN("missing new size for '%s' in MEMORY_DEVICE_SIZE_CHANGE event", name);
        return;
    }


    qemuMonitorEmitMemoryDeviceSizeChange(mon, name, size);
}


static void
qemuMonitorJSONHandleMemoryFailure(qemuMonitor *mon,
                                   virJSONValue *data)
{
    virJSONValue *flagsjson = virJSONValueObjectGetObject(data, "flags");
    const char *str;
    int recipient;
    int action;
    bool ar = false;
    bool recursive = false;
    qemuMonitorEventMemoryFailure mf = {0};

    if (!(str = virJSONValueObjectGetString(data, "recipient"))) {
        VIR_WARN("missing recipient in memory failure event");
        return;
    }

    recipient = qemuMonitorMemoryFailureRecipientTypeFromString(str);
    if (recipient < 0) {
        VIR_WARN("unknown recipient '%s' in memory_failure event", str);
        return;
    }

    if (!(str = virJSONValueObjectGetString(data, "action"))) {
        VIR_WARN("missing action in memory failure event");
        return;
    }

    action = qemuMonitorMemoryFailureActionTypeFromString(str);
    if (action < 0) {
        VIR_WARN("unknown action '%s' in memory_failure event", str);
        return;
    }

    if (flagsjson) {
        virJSONValueObjectGetBoolean(flagsjson, "action-required", &ar);
        virJSONValueObjectGetBoolean(flagsjson, "recursive", &recursive);
    }

    mf.recipient = recipient;
    mf.action = action;
    mf.action_required = ar;
    mf.recursive = recursive;
    qemuMonitorEmitMemoryFailure(mon, &mf);
}


static void
qemuMonitorJSONHandleMigrationStatus(qemuMonitor *mon,
                                     virJSONValue *data)
{
    const char *str;
    int status;

    if (!(str = virJSONValueObjectGetString(data, "status"))) {
        VIR_WARN("missing status in migration event");
        return;
    }

    if ((status = qemuMonitorMigrationStatusTypeFromString(str)) == -1) {
        VIR_WARN("unknown status '%s' in migration event", str);
        return;
    }

    qemuMonitorEmitMigrationStatus(mon, status);
}


static void
qemuMonitorJSONHandleMigrationPass(qemuMonitor *mon,
                                   virJSONValue *data)
{
    int pass;

    if (virJSONValueObjectGetNumberInt(data, "pass", &pass) < 0) {
        VIR_WARN("missing dirty-sync-count in migration-pass event");
        return;
    }

    qemuMonitorEmitMigrationPass(mon, pass);
}


static void
qemuMonitorJSONHandleAcpiOstInfo(qemuMonitor *mon, virJSONValue *data)
{
    virJSONValue *info;
    const char *alias;
    const char *slotType;
    const char *slot;
    unsigned int source;
    unsigned int status;

    if (!(info = virJSONValueObjectGetObject(data, "info")))
        goto error;

    /* optional */
    alias = virJSONValueObjectGetString(info, "device");

    if (!(slotType = virJSONValueObjectGetString(info, "slot-type")))
        goto error;

    if (!(slot = virJSONValueObjectGetString(info, "slot")))
        goto error;

    if (virJSONValueObjectGetNumberUint(info, "source", &source) < 0)
        goto error;

    if (virJSONValueObjectGetNumberUint(info, "status", &status) < 0)
        goto error;

    qemuMonitorEmitAcpiOstInfo(mon, alias, slotType, slot, source, status);
    return;

 error:
    VIR_WARN("malformed ACPI_DEVICE_OST event");
    return;
}


static void
qemuMonitorJSONHandleBlockThreshold(qemuMonitor *mon, virJSONValue *data)
{
    const char *nodename;
    unsigned long long threshold;
    unsigned long long excess;

    if (!(nodename = virJSONValueObjectGetString(data, "node-name")))
        goto error;

    if (virJSONValueObjectGetNumberUlong(data, "write-threshold", &threshold) < 0)
        goto error;

    if (virJSONValueObjectGetNumberUlong(data, "amount-exceeded", &excess) < 0)
        goto error;

    qemuMonitorEmitBlockThreshold(mon, nodename, threshold, excess);
    return;

 error:
    VIR_WARN("malformed 'BLOCK_WRITE_THRESHOLD' event");
}


static int
qemuMonitorJSONExtractDumpStats(virJSONValue *result,
                                qemuMonitorDumpStats *ret)
{
    const char *statusstr;

    if (!(statusstr = virJSONValueObjectGetString(result, "status"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("incomplete result, failed to get status"));
        return -1;
    }

    ret->status = qemuMonitorDumpStatusTypeFromString(statusstr);
    if (ret->status < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("incomplete result, unknown status string '%s'"),
                       statusstr);
        return -1;
    }

    if (virJSONValueObjectGetNumberUlong(result, "completed", &ret->completed) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("incomplete result, failed to get completed"));
        return -1;
    }

    if (virJSONValueObjectGetNumberUlong(result, "total", &ret->total) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("incomplete result, failed to get total"));
        return -1;
    }

    return 0;
}


static void
qemuMonitorJSONHandleDumpCompleted(qemuMonitor *mon,
                                   virJSONValue *data)
{
    virJSONValue *result;
    int status;
    qemuMonitorDumpStats stats = { 0 };
    const char *error = NULL;

    if (!(result = virJSONValueObjectGetObject(data, "result"))) {
        VIR_WARN("missing result in dump completed event");
        return;
    }

    status = qemuMonitorJSONExtractDumpStats(result, &stats);

    error = virJSONValueObjectGetString(data, "error");

    qemuMonitorEmitDumpCompleted(mon, status, &stats, error);
}


static void qemuMonitorJSONHandlePRManagerStatusChanged(qemuMonitor *mon,
                                                        virJSONValue *data)
{
    const char *name;
    bool connected;

    if (!(name = virJSONValueObjectGetString(data, "id"))) {
        VIR_WARN("missing pr-manager alias in PR_MANAGER_STATUS_CHANGED event");
        return;
    }

    if (virJSONValueObjectGetBoolean(data, "connected", &connected) < 0) {
        VIR_WARN("missing connected state for %s "
                 "in PR_MANAGER_STATUS_CHANGED event", name);
        return;
    }

    qemuMonitorEmitPRManagerStatusChanged(mon, name, connected);
}


static void qemuMonitorJSONHandleRdmaGidStatusChanged(qemuMonitor *mon,
                                                      virJSONValue *data)
{
    const char *netdev;
    bool gid_status;
    unsigned long long subnet_prefix, interface_id;

    if (!(netdev = virJSONValueObjectGetString(data, "netdev"))) {
        VIR_WARN("missing netdev in GID_STATUS_CHANGED event");
        return;
    }

    if (virJSONValueObjectGetBoolean(data, "gid-status", &gid_status)) {
        VIR_WARN("missing gid-status in GID_STATUS_CHANGED event");
        return;
    }

    if (virJSONValueObjectGetNumberUlong(data, "subnet-prefix",
                                         &subnet_prefix)) {
        VIR_WARN("missing subnet-prefix in GID_STATUS_CHANGED event");
        return;
    }

    if (virJSONValueObjectGetNumberUlong(data, "interface-id",
                                         &interface_id)) {
        VIR_WARN("missing interface-id in GID_STATUS_CHANGED event");
        return;
    }

    qemuMonitorEmitRdmaGidStatusChanged(mon, netdev, gid_status, subnet_prefix,
                                        interface_id);
}


static void
qemuMonitorJSONHandleGuestCrashloaded(qemuMonitor *mon,
                                      virJSONValue *data)
{
    VIR_DEBUG("qemuMonitorJSONHandleGuestCrashloaded event, mon %p, data %p", mon, data);

    qemuMonitorEmitGuestCrashloaded(mon);
}


int
qemuMonitorJSONHumanCommand(qemuMonitor *mon,
                            const char *cmd_str,
                            char **reply_str)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *obj;
    const char *data;

    cmd = qemuMonitorJSONMakeCommand("human-monitor-command",
                                     "s:command-line", cmd_str,
                                     NULL);

    if (!cmd || qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                       _("Human monitor command is not available to run %s"),
                       cmd_str);
        return -1;
    }

    if (qemuMonitorJSONCheckError(cmd, reply))
        return -1;

    obj = virJSONValueObjectGet(reply, "return");
    data = virJSONValueGetString(obj);
    *reply_str = g_strdup(NULLSTR_EMPTY(data));

    return 0;
}


int
qemuMonitorJSONSetCapabilities(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qmp_capabilities",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONStartCPUs(qemuMonitor *mon)
{
    int ret;
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("cont", NULL);
    size_t i = 0;
    int timeout = 3;
    if (!cmd)
        return -1;

    do {
        g_autoptr(virJSONValue) reply = NULL;

        ret = qemuMonitorJSONCommand(mon, cmd, &reply);

        if (ret != 0)
            break;

        /* If no error, we're done */
        if ((ret = qemuMonitorJSONCheckError(cmd, reply)) == 0)
            break;

        /* If error class is not MigrationExpected, we're done.
         * Otherwise try 'cont' cmd again */
        if (!qemuMonitorJSONHasError(reply, "MigrationExpected"))
            break;

        g_usleep(250000);
    } while (++i <= timeout);

    return ret;
}


int
qemuMonitorJSONStopCPUs(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("stop", NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONGetStatus(qemuMonitor *mon,
                         bool *running,
                         virDomainPausedReason *reason)
{
    const char *status;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;

    if (reason)
        *reason = VIR_DOMAIN_PAUSED_UNKNOWN;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-status", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    data = virJSONValueObjectGetObject(reply, "return");

    if (virJSONValueObjectGetBoolean(data, "running", running) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-status reply was missing running state"));
        return -1;
    }

    if ((status = virJSONValueObjectGetString(data, "status"))) {
        if (!*running && reason)
            *reason = qemuMonitorVMStatusToPausedReason(status);
    } else if (!*running) {
        VIR_DEBUG("query-status reply was missing status details");
    }

    return 0;
}


int qemuMonitorJSONSystemPowerdown(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("system_powerdown", NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONSetLink(qemuMonitor *mon,
                           const char *name,
                           virDomainNetInterfaceLinkState state)
{
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("set_link",
                                                             "s:name", name,
                                                             "b:up", state != VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN,
                                                             NULL);

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONSystemReset(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("system_reset", NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


/**
 * qemuMonitorJSONExtractCPUS390Info:
 * @jsoncpu: pointer to a single JSON cpu entry
 * @cpu: pointer to a single cpu entry
 *
 * Derive the legacy cpu info 'halted' information
 * from the more accurate s390 cpu state. @cpu is
 * modified only on success.
 *
 * Note: the 'uninitialized' s390 cpu state can't be
 *       mapped to halted yes/no.
 *
 * A s390 cpu entry could look like this
 *  { "arch": "s390",
 *    "cpu-index": 0,
 *    "qom-path": "/machine/unattached/device[0]",
 *    "thread_id": 3081,
 *    "cpu-state": "operating" }
 *
 */
static void
qemuMonitorJSONExtractCPUS390Info(virJSONValue *jsoncpu,
                                  struct qemuMonitorQueryCpusEntry *cpu)
{
    const char *cpu_state = virJSONValueObjectGetString(jsoncpu, "cpu-state");

    if (STREQ_NULLABLE(cpu_state, "operating") ||
        STREQ_NULLABLE(cpu_state, "load"))
        cpu->halted = false;
    else if (STREQ_NULLABLE(cpu_state, "stopped") ||
             STREQ_NULLABLE(cpu_state, "check-stop"))
        cpu->halted = true;
}


/**
 * qemuMonitorJSONExtractCPUInfo:
 * @data: JSON response data
 * @entries: filled with detected cpu entries on success
 * @nentries: number of entries returned
 * @fast: true if this is a response from query-cpus-fast
 *
 * The JSON response @data will have the following format
 * in case @fast == false
 * [{ "arch": "x86",
 *    "current": true,
 *    "CPU": 0,
 *    "qom_path": "/machine/unattached/device[0]",
 *    "pc": -2130415978,
 *    "halted": true,
 *    "thread_id": 2631237,
 *    ...},
 *    {...}
 *  ]
 * and for @fast == true
 * [{ "arch": "x86",
 *    "cpu-index": 0,
 *    "props": {
 *       "core-id": 0,
 *       "thread-id": 0,
 *       "socket-id": 0
 *    },
 *    "qom-path": "/machine/unattached/device[0]",
 *    "thread-id": 2631237,
 *    ...},
 *    {...}
 *  ]
 * or for s390
 * [{ "arch": "s390",
 *    "cpu-index": 0,
 *    "props": {
 *       "core-id": 0
 *    },
 *    "qom-path": "/machine/unattached/device[0]",
 *    "thread-id": 1237,
 *    "cpu-state": "operating",
 *    ...},
 *    {...}
 *  ]
 *
 *  Note that since QEMU 2.13.0 the "arch" output member of the
 *  "query-cpus-fast" command is replaced by "target".
 */
static int
qemuMonitorJSONExtractCPUInfo(virJSONValue *data,
                              struct qemuMonitorQueryCpusEntry **entries,
                              size_t *nentries,
                              bool fast)
{
    const char *arch = NULL;
    struct qemuMonitorQueryCpusEntry *cpus = NULL;
    int ret = -1;
    size_t i;
    size_t ncpus;

    if ((ncpus = virJSONValueArraySize(data)) == 0)
        return -2;

    cpus = g_new0(struct qemuMonitorQueryCpusEntry, ncpus);

    for (i = 0; i < ncpus; i++) {
        virJSONValue *entry = virJSONValueArrayGet(data, i);
        int cpuid = -1;
        int thread = 0;
        bool halted = false;
        const char *qom_path;
        if (!entry) {
            ret = -2;
            goto cleanup;
        }

        /* Some older qemu versions don't report the thread_id so treat this as
         * non-fatal, simply returning no data.
         * The return data of query-cpus-fast has different field names
         */
        if (fast) {
            if (!(arch = virJSONValueObjectGetString(entry, "target")))
                arch = virJSONValueObjectGetString(entry, "arch");
            ignore_value(virJSONValueObjectGetNumberInt(entry, "cpu-index", &cpuid));
            ignore_value(virJSONValueObjectGetNumberInt(entry, "thread-id", &thread));
            qom_path = virJSONValueObjectGetString(entry, "qom-path");
        } else {
            arch = virJSONValueObjectGetString(entry, "arch");
            ignore_value(virJSONValueObjectGetNumberInt(entry, "CPU", &cpuid));
            ignore_value(virJSONValueObjectGetNumberInt(entry, "thread_id", &thread));
            ignore_value(virJSONValueObjectGetBoolean(entry, "halted", &halted));
            qom_path = virJSONValueObjectGetString(entry, "qom_path");
        }

        cpus[i].qemu_id = cpuid;
        cpus[i].tid = thread;
        cpus[i].halted = halted;
        cpus[i].qom_path = g_strdup(qom_path);

        /* process optional architecture-specific data */
        if (STREQ_NULLABLE(arch, "s390") || STREQ_NULLABLE(arch, "s390x"))
            qemuMonitorJSONExtractCPUS390Info(entry, cpus + i);
    }

    *entries = g_steal_pointer(&cpus);
    *nentries = ncpus;
    ret = 0;

 cleanup:
    qemuMonitorQueryCpusFree(cpus, ncpus);
    return ret;
}


/**
 * qemuMonitorJSONQueryCPUs:
 *
 * @mon: monitor object
 * @entries: filled with detected entries on success
 * @nentries: number of entries returned
 * @force: force exit on error
 * @fast: use query-cpus-fast
 *
 * Queries qemu for cpu-related information. Failure to execute the command or
 * extract results does not produce an error as libvirt can continue without
 * this information, unless the caller has specified @force == true.
 *
 * Returns 0 on success, -1 on a fatal error (oom ...) and -2 if the
 * query failed gracefully.
 */
int
qemuMonitorJSONQueryCPUs(qemuMonitor *mon,
                         struct qemuMonitorQueryCpusEntry **entries,
                         size_t *nentries,
                         bool force,
                         bool fast)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;

    if (fast)
        cmd = qemuMonitorJSONMakeCommand("query-cpus-fast", NULL);
    else
        cmd = qemuMonitorJSONMakeCommand("query-cpus", NULL);

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (force && qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    if (!(data = virJSONValueObjectGetArray(reply, "return")))
        return -2;

    return qemuMonitorJSONExtractCPUInfo(data, entries, nentries, fast);
}


/**
 * Loads correct video memory size values from QEMU and update the video
 * definition.
 *
 * Return 0 on success, -1 on failure and set proper error message.
 */
int
qemuMonitorJSONUpdateVideoMemorySize(qemuMonitor *mon,
                                     virDomainVideoDef *video,
                                     char *path)
{
    qemuMonitorJSONObjectProperty prop = {
        QEMU_MONITOR_OBJECT_PROPERTY_ULONG,
        {0}
    };

    switch (video->type) {
    case VIR_DOMAIN_VIDEO_TYPE_VGA:
        if (qemuMonitorJSONGetObjectProperty(mon, path, "vgamem_mb", &prop) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("QOM Object '%s' has no property 'vgamem_mb'"),
                           path);
            return -1;
        }
        video->vram = prop.val.ul * 1024;
        break;
    case VIR_DOMAIN_VIDEO_TYPE_QXL:
        if (qemuMonitorJSONGetObjectProperty(mon, path, "vram_size", &prop) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("QOM Object '%s' has no property 'vram_size'"),
                           path);
            return -1;
        }
        video->vram = prop.val.ul / 1024;

        if (qemuMonitorJSONGetObjectProperty(mon, path, "ram_size", &prop) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("QOM Object '%s' has no property 'ram_size'"),
                           path);
            return -1;
        }
        video->ram = prop.val.ul / 1024;
        if (qemuMonitorJSONGetObjectProperty(mon, path, "vgamem_mb", &prop) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("QOM Object '%s' has no property 'vgamem_mb'"),
                           path);
            return -1;
        }
        video->vgamem = prop.val.ul * 1024;
        break;
    case VIR_DOMAIN_VIDEO_TYPE_VMVGA:
        if (qemuMonitorJSONGetObjectProperty(mon, path, "vgamem_mb", &prop) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("QOM Object '%s' has no property 'vgamem_mb'"),
                           path);
            return -1;
        }
        video->vram = prop.val.ul * 1024;
        break;
    case VIR_DOMAIN_VIDEO_TYPE_CIRRUS:
    case VIR_DOMAIN_VIDEO_TYPE_XEN:
    case VIR_DOMAIN_VIDEO_TYPE_VBOX:
    case VIR_DOMAIN_VIDEO_TYPE_LAST:
        break;
    }

    return 0;
}


/**
 * Loads correct video vram64 size value from QEMU and update the video
 * definition.
 *
 * Return 0 on success, -1 on failure and set proper error message.
 */
int
qemuMonitorJSONUpdateVideoVram64Size(qemuMonitor *mon,
                                     virDomainVideoDef *video,
                                     char *path)
{
    qemuMonitorJSONObjectProperty prop = {
        QEMU_MONITOR_OBJECT_PROPERTY_ULONG,
        {0}
    };

    switch (video->type) {
    case VIR_DOMAIN_VIDEO_TYPE_QXL:
        if (video->vram64 != 0) {
            if (qemuMonitorJSONGetObjectProperty(mon, path,
                                                 "vram64_size_mb", &prop) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("QOM Object '%s' has no property 'vram64_size_mb'"),
                               path);
                return -1;
            }
            video->vram64 = prop.val.ul * 1024;
        }
        break;
    case VIR_DOMAIN_VIDEO_TYPE_VGA:
    case VIR_DOMAIN_VIDEO_TYPE_VMVGA:
    case VIR_DOMAIN_VIDEO_TYPE_CIRRUS:
    case VIR_DOMAIN_VIDEO_TYPE_XEN:
    case VIR_DOMAIN_VIDEO_TYPE_VBOX:
    case VIR_DOMAIN_VIDEO_TYPE_LAST:
        break;
    }

    return 0;
}


int
qemuMonitorJSONGetBalloonInfo(qemuMonitor *mon,
                              unsigned long long *currmem)
{
    virJSONValue *data;
    unsigned long long mem;
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-balloon",
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    *currmem = 0;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    /* See if balloon soft-failed */
    if (qemuMonitorJSONHasError(reply, "DeviceNotActive") ||
        qemuMonitorJSONHasError(reply, "KVMMissingCap")) {
        return 0;
    }

    /* See if any other fatal error occurred */
    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    data = virJSONValueObjectGetObject(reply, "return");

    if (virJSONValueObjectGetNumberUlong(data, "actual", &mem) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("info balloon reply was missing balloon data"));
        return -1;
    }

    *currmem = (mem/1024);
    return 1;
}


/* Process the balloon driver statistics.  The request and data returned
 * will be as follows (although the 'child[#]' entry will differ based on
 * where it's run).
 *
 * { "execute": "qom-get","arguments": \
 *    { "path": "/machine/i440fx/pci.0/child[7]","property": "guest-stats"} }
 *
 * {"return": {"stats": \
 *               {"stat-swap-out": 0,
 *                "stat-free-memory": 686350336,
 *                "stat-minor-faults": 697283,
 *                "stat-major-faults": 951,
 *                "stat-total-memory": 1019924480,
 *                "stat-swap-in": 0},
 *            "last-update": 1371221540}}
 *
 * A value in "stats" can be -1 indicating it's never been collected/stored.
 * The 'last-update' value could be used in the future in order to determine
 * rates and/or whether data has been collected since a previous cycle.
 * It's currently unused.
 */
#define GET_BALLOON_STATS(OBJECT, FIELD, TAG, DIVISOR) \
    if (virJSONValueObjectHasKey(OBJECT, FIELD) && \
       (got < nr_stats)) { \
        if (virJSONValueObjectGetNumberUlong(OBJECT, FIELD, &mem) < 0) { \
            VIR_DEBUG("Failed to get '%s' value", FIELD); \
        } else { \
            /* Not being collected? No point in providing bad data */ \
            if (mem != -1UL) { \
                stats[got].tag = TAG; \
                stats[got].val = mem / DIVISOR; \
                got++; \
            } \
        } \
    }


int
qemuMonitorJSONGetMemoryStats(qemuMonitor *mon,
                              char *balloonpath,
                              virDomainMemoryStatPtr stats,
                              unsigned int nr_stats)
{
    int ret = -1;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    virJSONValue *statsdata;
    unsigned long long mem;
    int got = 0;

    ret = qemuMonitorJSONGetBalloonInfo(mon, &mem);
    if (ret == 1 && (got < nr_stats)) {
        stats[got].tag = VIR_DOMAIN_MEMORY_STAT_ACTUAL_BALLOON;
        stats[got].val = mem;
        got++;
    }

    if (!balloonpath)
        return got;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", balloonpath,
                                           "s:property", "guest-stats",
                                           NULL)))
        return got;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return got;

    if ((data = virJSONValueObjectGetObject(reply, "error"))) {
        const char *klass = virJSONValueObjectGetString(data, "class");
        const char *desc = virJSONValueObjectGetString(data, "desc");

        if (STREQ_NULLABLE(klass, "GenericError") &&
            STREQ_NULLABLE(desc, "guest hasn't updated any stats yet")) {
            virReportError(VIR_ERR_OPERATION_INVALID, "%s",
                           _("the guest hasn't updated any stats yet"));
            return got;
        }
    }

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return got;

    data = virJSONValueObjectGetObject(reply, "return");

    if (!(statsdata = virJSONValueObjectGet(data, "stats"))) {
        VIR_DEBUG("data does not include 'stats'");
        return got;
    }

    GET_BALLOON_STATS(statsdata, "stat-swap-in",
                      VIR_DOMAIN_MEMORY_STAT_SWAP_IN, 1024);
    GET_BALLOON_STATS(statsdata, "stat-swap-out",
                      VIR_DOMAIN_MEMORY_STAT_SWAP_OUT, 1024);
    GET_BALLOON_STATS(statsdata, "stat-major-faults",
                      VIR_DOMAIN_MEMORY_STAT_MAJOR_FAULT, 1);
    GET_BALLOON_STATS(statsdata, "stat-minor-faults",
                      VIR_DOMAIN_MEMORY_STAT_MINOR_FAULT, 1);
    GET_BALLOON_STATS(statsdata, "stat-free-memory",
                      VIR_DOMAIN_MEMORY_STAT_UNUSED, 1024);
    GET_BALLOON_STATS(statsdata, "stat-total-memory",
                      VIR_DOMAIN_MEMORY_STAT_AVAILABLE, 1024);
    GET_BALLOON_STATS(statsdata, "stat-available-memory",
                      VIR_DOMAIN_MEMORY_STAT_USABLE, 1024);
    GET_BALLOON_STATS(data, "last-update",
                      VIR_DOMAIN_MEMORY_STAT_LAST_UPDATE, 1);
    GET_BALLOON_STATS(statsdata, "stat-disk-caches",
                      VIR_DOMAIN_MEMORY_STAT_DISK_CACHES, 1024);
    GET_BALLOON_STATS(statsdata, "stat-htlb-pgalloc",
                      VIR_DOMAIN_MEMORY_STAT_HUGETLB_PGALLOC, 1);
    GET_BALLOON_STATS(statsdata, "stat-htlb-pgfail",
                      VIR_DOMAIN_MEMORY_STAT_HUGETLB_PGFAIL, 1);

    return got;
}
#undef GET_BALLOON_STATS


/*
 * Using the provided balloonpath, determine if we need to set the
 * collection interval property to enable statistics gathering.
 */
int
qemuMonitorJSONSetMemoryStatsPeriod(qemuMonitor *mon,
                                    char *balloonpath,
                                    int period)
{
    qemuMonitorJSONObjectProperty prop;

    /* Set to the value in memballoon (could enable or disable) */
    memset(&prop, 0, sizeof(qemuMonitorJSONObjectProperty));
    prop.type = QEMU_MONITOR_OBJECT_PROPERTY_INT;
    prop.val.iv = period;
    if (qemuMonitorJSONSetObjectProperty(mon, balloonpath,
                                         "guest-stats-polling-interval",
                                         &prop) < 0) {
        return -1;
    }
    return 0;
}


int
qemuMonitorJSONSetDBusVMStateIdList(qemuMonitor *mon,
                                    const char *vmstatepath,
                                    const char *idstr)
{
    qemuMonitorJSONObjectProperty prop = {
        .type = QEMU_MONITOR_OBJECT_PROPERTY_STRING,
        .val.str = (char *) idstr,
    };

    return qemuMonitorJSONSetObjectProperty(mon, vmstatepath, "id-list", &prop);
}


/* qemuMonitorJSONQueryBlock:
 * @mon: Monitor pointer
 *
 * This helper will attempt to make a "query-block" call and check for
 * errors before returning with the reply.
 *
 * Returns: NULL on error, reply on success
 */
static virJSONValue *
qemuMonitorJSONQueryBlock(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-block", NULL)))
        return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0 ||
        qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return NULL;

    return virJSONValueObjectStealArray(reply, "return");
}


static virJSONValue *
qemuMonitorJSONGetBlockDev(virJSONValue *devices,
                           size_t idx)
{
    virJSONValue *dev = virJSONValueArrayGet(devices, idx);

    if (!dev || virJSONValueGetType(dev) != VIR_JSON_TYPE_OBJECT) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-block device entry was not in expected format"));
        return NULL;
    }
    return dev;
}


static const char *
qemuMonitorJSONGetBlockDevDevice(virJSONValue *dev)
{
    const char *thisdev;

    if (!(thisdev = virJSONValueObjectGetString(dev, "device"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-block device entry was not in expected format"));
        return NULL;
    }

    return thisdev;
}


static int
qemuMonitorJSONBlockInfoAdd(GHashTable *table,
                            struct qemuDomainDiskInfo *info,
                            const char *entryname)
{
    struct qemuDomainDiskInfo *tmp = NULL;

    if (g_hash_table_contains(table, entryname)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Duplicate block info for '%s'"), entryname);
        return -1;
    }

    tmp = g_new0(struct qemuDomainDiskInfo, 1);

    *tmp = *info;
    tmp->nodename = g_strdup(info->nodename);

    g_hash_table_insert(table, g_strdup(entryname), tmp);

    return 0;
}


int qemuMonitorJSONGetBlockInfo(qemuMonitor *mon,
                                GHashTable *table)
{
    size_t i;
    g_autoptr(virJSONValue) devices = NULL;

    if (!(devices = qemuMonitorJSONQueryBlock(mon)))
        return -1;

    for (i = 0; i < virJSONValueArraySize(devices); i++) {
        virJSONValue *dev;
        virJSONValue *image;
        struct qemuDomainDiskInfo info = { false };
        const char *thisdev;
        const char *status;
        const char *qdev;

        if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
            return -1;

        if (!(thisdev = qemuMonitorJSONGetBlockDevDevice(dev)))
            return -1;

        thisdev = qemuAliasDiskDriveSkipPrefix(thisdev);
        qdev = virJSONValueObjectGetString(dev, "qdev");

        if (*thisdev == '\0')
            thisdev = NULL;

        if (!qdev && !thisdev) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-block device entry was not in expected format"));
            return -1;
        }

        if (virJSONValueObjectGetBoolean(dev, "removable", &info.removable) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("cannot read %s value"),
                           "removable");
            return -1;
        }

        /* 'tray_open' is present only if the device has a tray */
        if (virJSONValueObjectGetBoolean(dev, "tray_open", &info.tray_open) == 0)
            info.tray = true;

        /* presence of 'inserted' notifies that a medium is in the device */
        if ((image = virJSONValueObjectGetObject(dev, "inserted"))) {
            info.nodename = (char *) virJSONValueObjectGetString(image, "node-name");
        } else {
            info.empty = true;
        }

        /* Missing io-status indicates no error */
        if ((status = virJSONValueObjectGetString(dev, "io-status"))) {
            info.io_status = qemuMonitorBlockIOStatusToError(status);
            if (info.io_status < 0)
                return -1;
        }

        if (thisdev &&
            qemuMonitorJSONBlockInfoAdd(table, &info, thisdev) < 0)
            return -1;

        if (qdev && STRNEQ_NULLABLE(thisdev, qdev) &&
            qemuMonitorJSONBlockInfoAdd(table, &info, qdev) < 0)
            return -1;
    }

    return 0;
}


static qemuBlockStats *
qemuMonitorJSONBlockStatsCollectData(virJSONValue *dev,
                                     int *nstats)
{
    g_autofree qemuBlockStats *bstats = NULL;
    virJSONValue *parent;
    virJSONValue *parentstats;
    virJSONValue *stats;

    if ((stats = virJSONValueObjectGetObject(dev, "stats")) == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("blockstats stats entry was not "
                         "in expected format"));
        return NULL;
    }

    bstats = g_new0(qemuBlockStats, 1);

#define QEMU_MONITOR_BLOCK_STAT_GET(NAME, VAR, MANDATORY) \
    if (MANDATORY || virJSONValueObjectHasKey(stats, NAME)) { \
        (*nstats)++; \
        if (virJSONValueObjectGetNumberUlong(stats, NAME, &VAR) < 0) { \
            virReportError(VIR_ERR_INTERNAL_ERROR, \
                           _("cannot read %s statistic"), NAME); \
            return NULL; \
        } \
    }
    QEMU_MONITOR_BLOCK_STAT_GET("rd_bytes", bstats->rd_bytes, true);
    QEMU_MONITOR_BLOCK_STAT_GET("wr_bytes", bstats->wr_bytes, true);
    QEMU_MONITOR_BLOCK_STAT_GET("rd_operations", bstats->rd_req, true);
    QEMU_MONITOR_BLOCK_STAT_GET("wr_operations", bstats->wr_req, true);
    QEMU_MONITOR_BLOCK_STAT_GET("rd_total_time_ns", bstats->rd_total_times, false);
    QEMU_MONITOR_BLOCK_STAT_GET("wr_total_time_ns", bstats->wr_total_times, false);
    QEMU_MONITOR_BLOCK_STAT_GET("flush_operations", bstats->flush_req, false);
    QEMU_MONITOR_BLOCK_STAT_GET("flush_total_time_ns", bstats->flush_total_times, false);
#undef QEMU_MONITOR_BLOCK_STAT_GET

    if ((parent = virJSONValueObjectGetObject(dev, "parent")) &&
        (parentstats = virJSONValueObjectGetObject(parent, "stats"))) {
        if (virJSONValueObjectGetNumberUlong(parentstats, "wr_highest_offset",
                                             &bstats->wr_highest_offset) == 0)
            bstats->wr_highest_offset_valid = true;
    }

    return g_steal_pointer(&bstats);
}


static int
qemuMonitorJSONAddOneBlockStatsInfo(qemuBlockStats *bstats,
                                    const char *name,
                                    GHashTable *stats)
{
    qemuBlockStats *copy = NULL;

    copy = g_new0(qemuBlockStats, 1);

    if (bstats)
        *copy = *bstats;

    if (virHashAddEntry(stats, name, copy) < 0) {
        VIR_FREE(copy);
        return -1;
    }

    return 0;
}


static int
qemuMonitorJSONGetOneBlockStatsInfo(virJSONValue *dev,
                                    const char *dev_name,
                                    int depth,
                                    GHashTable *hash)
{
    g_autofree qemuBlockStats *bstats = NULL;
    int nstats = 0;
    const char *qdevname = NULL;
    const char *nodename = NULL;
    g_autofree char *devicename = NULL;
    virJSONValue *backing;

    if (dev_name &&
        !(devicename = qemuDomainStorageAlias(dev_name, depth)))
        return -1;

    qdevname = virJSONValueObjectGetString(dev, "qdev");
    nodename = virJSONValueObjectGetString(dev, "node-name");

    if (!devicename && !qdevname && !nodename) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("blockstats device entry was not in expected format"));
        return -1;
    }

    if (!(bstats = qemuMonitorJSONBlockStatsCollectData(dev, &nstats)))
        return -1;

    if (devicename &&
        qemuMonitorJSONAddOneBlockStatsInfo(bstats, devicename, hash) < 0)
        return -1;

    if (qdevname && STRNEQ_NULLABLE(qdevname, devicename) &&
        qemuMonitorJSONAddOneBlockStatsInfo(bstats, qdevname, hash) < 0)
        return -1;

    if (nodename &&
        qemuMonitorJSONAddOneBlockStatsInfo(bstats, nodename, hash) < 0)
        return -1;

    if ((backing = virJSONValueObjectGetObject(dev, "backing")) &&
        qemuMonitorJSONGetOneBlockStatsInfo(backing, dev_name, depth + 1, hash) < 0)
        return -1;

    return nstats;
}


static int
qemuMonitorJSONGetOneBlockStatsNodeInfo(virJSONValue *dev,
                                        GHashTable *hash)
{
    qemuBlockStats *bstats = NULL;
    int nstats = 0;
    const char *nodename = NULL;

    if (!(nodename = virJSONValueObjectGetString(dev, "node-name")))
        return 0;

    /* we already have the stats */
    if (g_hash_table_contains(hash, nodename))
        return 0;

    if (!(bstats = qemuMonitorJSONBlockStatsCollectData(dev, &nstats)))
        return -1;

    g_hash_table_insert(hash, g_strdup(nodename), bstats);

    return nstats;
}


virJSONValue *
qemuMonitorJSONQueryBlockstats(qemuMonitor *mon,
                               bool queryNodes)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-blockstats",
                                           "B:query-nodes", queryNodes,
                                           NULL)))
        return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return NULL;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return NULL;

    return virJSONValueObjectStealArray(reply, "return");
}


int
qemuMonitorJSONGetAllBlockStatsInfo(qemuMonitor *mon,
                                    GHashTable *hash)
{
    int nstats = 0;
    int rc;
    size_t i;
    g_autoptr(virJSONValue) blockstatsDevices = NULL;
    g_autoptr(virJSONValue) blockstatsNodes = NULL;

    if (!(blockstatsDevices = qemuMonitorJSONQueryBlockstats(mon, false)))
        return -1;

    for (i = 0; i < virJSONValueArraySize(blockstatsDevices); i++) {
        virJSONValue *dev = virJSONValueArrayGet(blockstatsDevices, i);
        const char *dev_name;

        if (!dev || virJSONValueGetType(dev) != VIR_JSON_TYPE_OBJECT) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("blockstats device entry was not "
                             "in expected format"));
            return -1;
        }

        if ((dev_name = virJSONValueObjectGetString(dev, "device"))) {
            if (*dev_name == '\0')
                dev_name = NULL;
        }

        rc = qemuMonitorJSONGetOneBlockStatsInfo(dev, dev_name, 0, hash);

        if (rc < 0)
            return -1;

        if (rc > nstats)
            nstats = rc;
    }

    if (!(blockstatsNodes = qemuMonitorJSONQueryBlockstats(mon, true)))
        return -1;

    for (i = 0; i < virJSONValueArraySize(blockstatsNodes); i++) {
        virJSONValue *dev = virJSONValueArrayGet(blockstatsNodes, i);

        if (!dev || virJSONValueGetType(dev) != VIR_JSON_TYPE_OBJECT) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("blockstats device entry was not in expected format"));
            return -1;
        }

        if ((rc = qemuMonitorJSONGetOneBlockStatsNodeInfo(dev, hash)) < 0)
            return -1;

        if (rc > nstats)
            nstats = rc;
    }

    return nstats;
}


static int
qemuMonitorJSONBlockStatsUpdateCapacityData(virJSONValue *image,
                                            const char *name,
                                            GHashTable *stats,
                                            qemuBlockStats **entry)
{
    qemuBlockStats *bstats;

    if (!(bstats = virHashLookup(stats, name))) {
        bstats = g_new0(qemuBlockStats, 1);

        if (virHashAddEntry(stats, name, bstats) < 0) {
            VIR_FREE(bstats);
            return -1;
        }
    }

    if (entry)
        *entry = bstats;

    /* failures can be ignored after this point */
    if (virJSONValueObjectGetNumberUlong(image, "virtual-size",
                                         &bstats->capacity) < 0)
        return 0;

    /* if actual-size is missing, image is not thin provisioned */
    if (virJSONValueObjectGetNumberUlong(image, "actual-size",
                                         &bstats->physical) < 0)
        bstats->physical = bstats->capacity;

    return 0;
}


static int
qemuMonitorJSONBlockStatsUpdateCapacityOne(virJSONValue *image,
                                           const char *dev_name,
                                           int depth,
                                           GHashTable *stats)
{
    g_autofree char *entry_name = qemuDomainStorageAlias(dev_name, depth);
    virJSONValue *backing;

    if (qemuMonitorJSONBlockStatsUpdateCapacityData(image, entry_name,
                                                    stats, NULL) < 0)
        return -1;

    if ((backing = virJSONValueObjectGetObject(image, "backing-image")) &&
        qemuMonitorJSONBlockStatsUpdateCapacityOne(backing,
                                                   dev_name,
                                                   depth + 1,
                                                   stats) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockStatsUpdateCapacity(qemuMonitor *mon,
                                        GHashTable *stats)
{
    size_t i;
    g_autoptr(virJSONValue) devices = NULL;

    if (!(devices = qemuMonitorJSONQueryBlock(mon)))
        return -1;

    for (i = 0; i < virJSONValueArraySize(devices); i++) {
        virJSONValue *dev;
        virJSONValue *inserted;
        virJSONValue *image;
        const char *dev_name;

        if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
            return -1;

        if (!(dev_name = qemuMonitorJSONGetBlockDevDevice(dev)))
            return -1;

        /* drive may be empty */
        if (!(inserted = virJSONValueObjectGetObject(dev, "inserted")) ||
            !(image = virJSONValueObjectGetObject(inserted, "image")))
            continue;

        if (qemuMonitorJSONBlockStatsUpdateCapacityOne(image, dev_name, 0, stats) < 0)
            return -1;
    }

    return 0;
}


static int
qemuMonitorJSONBlockStatsUpdateCapacityBlockdevWorker(size_t pos G_GNUC_UNUSED,
                                                      virJSONValue *val,
                                                      void *opaque)
{
    GHashTable *stats = opaque;
    virJSONValue *image;
    const char *nodename;
    qemuBlockStats *entry;

    if (!(nodename = virJSONValueObjectGetString(val, "node-name")) ||
        !(image = virJSONValueObjectGetObject(val, "image"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-named-block-nodes entry was not in expected format"));
        return -1;
    }

    if (qemuMonitorJSONBlockStatsUpdateCapacityData(image, nodename, stats, &entry) < 0)
        return -1;

    if (entry)
        ignore_value(virJSONValueObjectGetNumberUlong(val, "write_threshold",
                                                      &entry->write_threshold));

    return 1; /* we don't want to steal the value from the JSON array */
}


int
qemuMonitorJSONBlockStatsUpdateCapacityBlockdev(qemuMonitor *mon,
                                                GHashTable *stats)
{
    g_autoptr(virJSONValue) nodes = NULL;

    if (!(nodes = qemuMonitorJSONQueryNamedBlockNodes(mon, false)))
        return -1;

    if (virJSONValueArrayForeachSteal(nodes,
                                      qemuMonitorJSONBlockStatsUpdateCapacityBlockdevWorker,
                                      stats) < 0)
        return -1;

    return 0;
}


static void
qemuMonitorJSONBlockNamedNodeDataBitmapFree(qemuBlockNamedNodeDataBitmap *bitmap)
{
    if (!bitmap)
        return;

    g_free(bitmap->name);
    g_free(bitmap);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockNamedNodeDataBitmap,
                              qemuMonitorJSONBlockNamedNodeDataBitmapFree);


static void
qemuMonitorJSONBlockNamedNodeDataFree(qemuBlockNamedNodeData *data)
{
    size_t i;

    if (!data)
        return;

    for (i = 0; i < data->nbitmaps; i++)
        qemuMonitorJSONBlockNamedNodeDataBitmapFree(data->bitmaps[i]);
    g_free(data->bitmaps);
    g_free(data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockNamedNodeData, qemuMonitorJSONBlockNamedNodeDataFree);


static qemuBlockNamedNodeDataBitmap *
qemuMonitorJSONBlockGetNamedNodeDataBitmapOne(virJSONValue *val)
{
    g_autoptr(qemuBlockNamedNodeDataBitmap) bitmap = NULL;
    const char *name;

    bitmap = g_new0(qemuBlockNamedNodeDataBitmap, 1);

    if (!(name = virJSONValueObjectGetString(val, "name")))
        return NULL;

    bitmap->name = g_strdup(name);

    ignore_value(virJSONValueObjectGetBoolean(val, "recording", &bitmap->recording));
    ignore_value(virJSONValueObjectGetBoolean(val, "persistent", &bitmap->persistent));
    ignore_value(virJSONValueObjectGetBoolean(val, "busy", &bitmap->busy));
    ignore_value(virJSONValueObjectGetBoolean(val, "inconsistent", &bitmap->inconsistent));
    ignore_value(virJSONValueObjectGetNumberUlong(val, "granularity", &bitmap->granularity));
    ignore_value(virJSONValueObjectGetNumberUlong(val, "count", &bitmap->dirtybytes));

    return g_steal_pointer(&bitmap);
}


static void
qemuMonitorJSONBlockGetNamedNodeDataBitmaps(virJSONValue *bitmaps,
                                            qemuBlockNamedNodeData *data)
{
    size_t nbitmaps = virJSONValueArraySize(bitmaps);
    size_t i;

    data->bitmaps = g_new0(qemuBlockNamedNodeDataBitmap *, nbitmaps);

    for (i = 0; i < nbitmaps; i++) {
        virJSONValue *bitmap = virJSONValueArrayGet(bitmaps, i);
        qemuBlockNamedNodeDataBitmap *tmp;

        if (!bitmap)
            continue;

        if (!(tmp = qemuMonitorJSONBlockGetNamedNodeDataBitmapOne(bitmap)))
            continue;

        data->bitmaps[data->nbitmaps++] = tmp;
    }
}


static int
qemuMonitorJSONBlockGetNamedNodeDataWorker(size_t pos G_GNUC_UNUSED,
                                           virJSONValue *val,
                                           void *opaque)
{
    GHashTable *nodes = opaque;
    virJSONValue *img;
    virJSONValue *bitmaps;
    virJSONValue *format_specific;
    const char *nodename;
    g_autoptr(qemuBlockNamedNodeData) ent = NULL;

    ent = g_new0(qemuBlockNamedNodeData, 1);

    if (!(nodename = virJSONValueObjectGetString(val, "node-name")) ||
        !(img = virJSONValueObjectGetObject(val, "image")))
        goto broken;

    if (virJSONValueObjectGetNumberUlong(img, "virtual-size", &ent->capacity) < 0)
        goto broken;

    /* if actual-size is missing, image is not thin provisioned */
    if (virJSONValueObjectGetNumberUlong(img, "actual-size", &ent->physical) < 0)
        ent->physical = ent->capacity;

    /* try looking up the cluster size */
    ignore_value(virJSONValueObjectGetNumberUlong(img, "cluster-size", &ent->clusterSize));

    if ((bitmaps = virJSONValueObjectGetArray(val, "dirty-bitmaps")))
        qemuMonitorJSONBlockGetNamedNodeDataBitmaps(bitmaps, ent);

    /* query qcow2 format specific props */
    if ((format_specific = virJSONValueObjectGetObject(img, "format-specific")) &&
        STREQ_NULLABLE(virJSONValueObjectGetString(format_specific, "type"), "qcow2")) {
        virJSONValue *qcow2props = virJSONValueObjectGetObject(format_specific, "data");

        if (qcow2props) {
            if (STREQ_NULLABLE(virJSONValueObjectGetString(qcow2props, "compat"), "0.10"))
                ent->qcow2v2 = true;

            ignore_value(virJSONValueObjectGetBoolean(qcow2props, "extended-l2",
                                                      &ent->qcow2extendedL2));
        }
    }

    if (virHashAddEntry(nodes, nodename, ent) < 0)
        return -1;

    ent = NULL;

    return 1; /* we don't want to steal the value from the JSON array */

 broken:
    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                   _("query-named-block-nodes entry was not in expected format"));
    return -1;
}


GHashTable *
qemuMonitorJSONBlockGetNamedNodeDataJSON(virJSONValue *nodes)
{
    g_autoptr(GHashTable) ret = NULL;

    ret = virHashNew((GDestroyNotify) qemuMonitorJSONBlockNamedNodeDataFree);

    if (virJSONValueArrayForeachSteal(nodes,
                                      qemuMonitorJSONBlockGetNamedNodeDataWorker,
                                      ret) < 0)
        return NULL;

    return g_steal_pointer(&ret);
}


GHashTable *
qemuMonitorJSONBlockGetNamedNodeData(qemuMonitor *mon,
                                     bool supports_flat)
{
    g_autoptr(virJSONValue) nodes = NULL;

    if (!(nodes = qemuMonitorJSONQueryNamedBlockNodes(mon, supports_flat)))
        return NULL;

    return qemuMonitorJSONBlockGetNamedNodeDataJSON(nodes);
}


int qemuMonitorJSONBlockResize(qemuMonitor *mon,
                               const char *device,
                               const char *nodename,
                               unsigned long long size)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("block_resize",
                                     "S:device", device,
                                     "S:node-name", nodename,
                                     "U:size", size,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int qemuMonitorJSONSetPassword(qemuMonitor *mon,
                               const char *protocol,
                               const char *password,
                               const char *action_if_connected)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("set_password",
                                                             "s:protocol", protocol,
                                                             "s:password", password,
                                                             "s:connected", action_if_connected,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONExpirePassword(qemuMonitor *mon,
                                  const char *protocol,
                                  const char *expire_time)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("expire_password",
                                                             "s:protocol", protocol,
                                                             "s:time", expire_time,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONSetBalloon(qemuMonitor *mon,
                          unsigned long long newmem)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("balloon",
                                                             "U:value", newmem * 1024,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    /* See if balloon soft-failed */
    if (qemuMonitorJSONHasError(reply, "DeviceNotActive") ||
        qemuMonitorJSONHasError(reply, "KVMMissingCap")) {
        return 0;
    }

    /* See if any other fatal error occurred */
    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    /* Real success */
    return 1;
}


/**
 * Run QMP command to eject a media from ejectable device.
 *
 * Returns:
 *      -1 on error
 *      0 on success
 */
int qemuMonitorJSONEjectMedia(qemuMonitor *mon,
                              const char *dev_name,
                              bool force)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("eject",
                                                             "s:device", dev_name,
                                                             "b:force", force,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int qemuMonitorJSONChangeMedia(qemuMonitor *mon,
                               const char *dev_name,
                               const char *newmedia,
                               const char *format)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("change",
                                     "s:device", dev_name,
                                     "s:target", newmedia,
                                     "S:arg", format,
                                     NULL);

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


static int qemuMonitorJSONSaveMemory(qemuMonitor *mon,
                                     const char *cmdtype,
                                     unsigned long long offset,
                                     unsigned long long length,
                                     const char *path)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand(cmdtype,
                                                             "U:val", offset,
                                                             "U:size", length,
                                                             "s:filename", path,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONSaveVirtualMemory(qemuMonitor *mon,
                                 unsigned long long offset,
                                 unsigned long long length,
                                 const char *path)
{
    return qemuMonitorJSONSaveMemory(mon, "memsave", offset, length, path);
}


int
qemuMonitorJSONSavePhysicalMemory(qemuMonitor *mon,
                                  unsigned long long offset,
                                  unsigned long long length,
                                  const char *path)
{
    return qemuMonitorJSONSaveMemory(mon, "pmemsave", offset, length, path);
}


int qemuMonitorJSONSetMigrationSpeed(qemuMonitor *mon,
                                     unsigned long bandwidth)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("migrate_set_speed",
                                     "U:value", bandwidth * 1024ULL * 1024ULL,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int qemuMonitorJSONSetMigrationDowntime(qemuMonitor *mon,
                                        unsigned long long downtime)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("migrate_set_downtime",
                                     "d:value", downtime / 1000.0,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONGetMigrationCacheSize(qemuMonitor *mon,
                                     unsigned long long *cacheSize)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    *cacheSize = 0;

    cmd = qemuMonitorJSONMakeCommand("query-migrate-cache-size", NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_NUMBER) < 0)
        return -1;

    if (virJSONValueObjectGetNumberUlong(reply, "return", cacheSize) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("invalid cache size in query-migrate-cache-size reply"));
        return -1;
    }

    return 0;
}


int
qemuMonitorJSONSetMigrationCacheSize(qemuMonitor *mon,
                                     unsigned long long cacheSize)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("migrate-set-cache-size",
                                     "U:value", cacheSize,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONGetMigrationParams(qemuMonitor *mon,
                                  virJSONValue **params)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    *params = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-migrate-parameters", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    *params = virJSONValueObjectStealObject(reply, "return");
    return 0;
}

int
qemuMonitorJSONSetMigrationParams(qemuMonitor *mon,
                                  virJSONValue **params)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommandInternal("migrate-set-parameters", params)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


static int
qemuMonitorJSONGetMigrationStatsReply(virJSONValue *reply,
                                      qemuMonitorMigrationStats *stats,
                                      char **error)
{
    virJSONValue *ret;
    virJSONValue *ram;
    virJSONValue *disk;
    virJSONValue *comp;
    const char *statusstr;
    int rc;
    double mbps;
    const char *tmp;

    ret = virJSONValueObjectGetObject(reply, "return");

    if (!(statusstr = virJSONValueObjectGetString(ret, "status"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("info migration reply was missing return status"));
        return -1;
    }

    stats->status = qemuMonitorMigrationStatusTypeFromString(statusstr);
    if (stats->status < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unexpected migration status in %s"), statusstr);
        return -1;
    }

    ignore_value(virJSONValueObjectGetNumberUlong(ret, "total-time",
                                                  &stats->total_time));
    if (stats->status == QEMU_MONITOR_MIGRATION_STATUS_COMPLETED) {
        rc = virJSONValueObjectGetNumberUlong(ret, "downtime",
                                              &stats->downtime);
    } else {
        rc = virJSONValueObjectGetNumberUlong(ret, "expected-downtime",
                                              &stats->downtime);
    }
    if (rc == 0)
        stats->downtime_set = true;

    if (virJSONValueObjectGetNumberUlong(ret, "setup-time",
                                         &stats->setup_time) == 0)
        stats->setup_time_set = true;

    ignore_value(virJSONValueObjectGetNumberInt(ret, "cpu-throttle-percentage",
                                                &stats->cpu_throttle_percentage));

    switch ((qemuMonitorMigrationStatus) stats->status) {
    case QEMU_MONITOR_MIGRATION_STATUS_INACTIVE:
    case QEMU_MONITOR_MIGRATION_STATUS_SETUP:
    case QEMU_MONITOR_MIGRATION_STATUS_CANCELLED:
    case QEMU_MONITOR_MIGRATION_STATUS_WAIT_UNPLUG:
    case QEMU_MONITOR_MIGRATION_STATUS_LAST:
        break;

    case QEMU_MONITOR_MIGRATION_STATUS_ERROR:
        if (error) {
            tmp = virJSONValueObjectGetString(ret, "error-desc");
            if (tmp)
                *error = g_strdup(tmp);
        }
        break;

    case QEMU_MONITOR_MIGRATION_STATUS_ACTIVE:
    case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY:
    case QEMU_MONITOR_MIGRATION_STATUS_COMPLETED:
    case QEMU_MONITOR_MIGRATION_STATUS_CANCELLING:
    case QEMU_MONITOR_MIGRATION_STATUS_PRE_SWITCHOVER:
    case QEMU_MONITOR_MIGRATION_STATUS_DEVICE:
        ram = virJSONValueObjectGetObject(ret, "ram");
        if (!ram) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("migration was active, but no RAM info was set"));
            return -1;
        }

        if (virJSONValueObjectGetNumberUlong(ram, "transferred",
                                             &stats->ram_transferred) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("migration was active, but RAM 'transferred' "
                             "data was missing"));
            return -1;
        }
        if (virJSONValueObjectGetNumberUlong(ram, "remaining",
                                             &stats->ram_remaining) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("migration was active, but RAM 'remaining' "
                             "data was missing"));
            return -1;
        }
        if (virJSONValueObjectGetNumberUlong(ram, "total",
                                             &stats->ram_total) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("migration was active, but RAM 'total' "
                             "data was missing"));
            return -1;
        }

        if (virJSONValueObjectGetNumberDouble(ram, "mbps", &mbps) == 0 &&
            mbps > 0) {
            /* mpbs from QEMU reports Mbits/s (M as in 10^6 not Mi as 2^20) */
            stats->ram_bps = mbps * (1000 * 1000 / 8);
        }

        if (virJSONValueObjectGetNumberUlong(ram, "duplicate",
                                             &stats->ram_duplicate) == 0)
            stats->ram_duplicate_set = true;
        ignore_value(virJSONValueObjectGetNumberUlong(ram, "normal",
                                                      &stats->ram_normal));
        ignore_value(virJSONValueObjectGetNumberUlong(ram, "normal-bytes",
                                                      &stats->ram_normal_bytes));
        ignore_value(virJSONValueObjectGetNumberUlong(ram, "dirty-pages-rate",
                                                      &stats->ram_dirty_rate));
        ignore_value(virJSONValueObjectGetNumberUlong(ram, "page-size",
                                                      &stats->ram_page_size));
        ignore_value(virJSONValueObjectGetNumberUlong(ram, "dirty-sync-count",
                                                      &stats->ram_iteration));
        ignore_value(virJSONValueObjectGetNumberUlong(ram, "postcopy-requests",
                                                      &stats->ram_postcopy_reqs));

        disk = virJSONValueObjectGetObject(ret, "disk");
        if (disk) {
            rc = virJSONValueObjectGetNumberUlong(disk, "transferred",
                                                  &stats->disk_transferred);
            if (rc < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("disk migration was active, but "
                                 "'transferred' data was missing"));
                return -1;
            }

            rc = virJSONValueObjectGetNumberUlong(disk, "remaining",
                                                  &stats->disk_remaining);
            if (rc < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("disk migration was active, but 'remaining' "
                                 "data was missing"));
                return -1;
            }

            rc = virJSONValueObjectGetNumberUlong(disk, "total",
                                                  &stats->disk_total);
            if (rc < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("disk migration was active, but 'total' "
                                 "data was missing"));
                return -1;
            }

            if (virJSONValueObjectGetNumberDouble(disk, "mbps", &mbps) == 0 &&
                mbps > 0) {
                /* mpbs from QEMU reports Mbits/s (M as in 10^6 not Mi as 2^20) */
                stats->disk_bps = mbps * (1000 * 1000 / 8);
            }
        }

        comp = virJSONValueObjectGetObject(ret, "xbzrle-cache");
        if (comp) {
            stats->xbzrle_set = true;
            rc = virJSONValueObjectGetNumberUlong(comp, "cache-size",
                                                  &stats->xbzrle_cache_size);
            if (rc < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("XBZRLE is active, but 'cache-size' data "
                                 "was missing"));
                return -1;
            }

            rc = virJSONValueObjectGetNumberUlong(comp, "bytes",
                                                  &stats->xbzrle_bytes);
            if (rc < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("XBZRLE is active, but 'bytes' data "
                                 "was missing"));
                return -1;
            }

            rc = virJSONValueObjectGetNumberUlong(comp, "pages",
                                                  &stats->xbzrle_pages);
            if (rc < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("XBZRLE is active, but 'pages' data "
                                 "was missing"));
                return -1;
            }

            rc = virJSONValueObjectGetNumberUlong(comp, "cache-miss",
                                                  &stats->xbzrle_cache_miss);
            if (rc < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("XBZRLE is active, but 'cache-miss' data "
                                 "was missing"));
                return -1;
            }

            rc = virJSONValueObjectGetNumberUlong(comp, "overflow",
                                                  &stats->xbzrle_overflow);
            if (rc < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("XBZRLE is active, but 'overflow' data "
                                 "was missing"));
                return -1;
            }
        }
        break;
    }

    return 0;
}


int qemuMonitorJSONGetMigrationStats(qemuMonitor *mon,
                                     qemuMonitorMigrationStats *stats,
                                     char **error)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-migrate",
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    memset(stats, 0, sizeof(*stats));

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    if (qemuMonitorJSONGetMigrationStatsReply(reply, stats, error) < 0)
        return -1;

    return 0;
}


int qemuMonitorJSONMigrate(qemuMonitor *mon,
                           unsigned int flags,
                           const char *uri)
{
    bool detach = !!(flags & QEMU_MONITOR_MIGRATE_BACKGROUND);
    bool blk = !!(flags & QEMU_MONITOR_MIGRATE_NON_SHARED_DISK);
    bool inc = !!(flags & QEMU_MONITOR_MIGRATE_NON_SHARED_INC);
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("migrate",
                                                             "b:detach", detach,
                                                             "b:blk", blk,
                                                             "b:inc", inc,
                                                             "s:uri", uri,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONMigrateCancel(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("migrate_cancel", NULL);
    g_autoptr(virJSONValue) reply = NULL;
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


/* qemuMonitorJSONQueryDump:
 * @mon: Monitor pointer
 * @stats: Monitor dump stats
 *
 * Attempt to make a "query-dump" call, check for errors, and get/return
 * the current from the reply
 *
 * Returns: 0 on success, -1 on failure
 */
int
qemuMonitorJSONQueryDump(qemuMonitor *mon,
                         qemuMonitorDumpStats *stats)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-dump", NULL);
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *result = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    result = virJSONValueObjectGetObject(reply, "return");

    return qemuMonitorJSONExtractDumpStats(result, stats);
}


int
qemuMonitorJSONGetDumpGuestMemoryCapability(qemuMonitor *mon,
                                            const char *capability)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *caps;
    virJSONValue *formats;
    size_t i;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-dump-guest-memory-capability",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    caps = virJSONValueObjectGetObject(reply, "return");

    if (!(formats = virJSONValueObjectGetArray(caps, "formats"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("missing supported dump formats"));
        return -1;
    }

    for (i = 0; i < virJSONValueArraySize(formats); i++) {
        virJSONValue *dumpformat = virJSONValueArrayGet(formats, i);

        if (!dumpformat || virJSONValueGetType(dumpformat) != VIR_JSON_TYPE_STRING) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing entry in supported dump formats"));
            return -1;
        }

        if (STREQ(virJSONValueGetString(dumpformat), capability))
            return 1;
    }

    return 0;
}

int
qemuMonitorJSONDump(qemuMonitor *mon,
                    const char *protocol,
                    const char *dumpformat,
                    bool detach)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("dump-guest-memory",
                                     "b:paging", false,
                                     "s:protocol", protocol,
                                     "S:format", dumpformat,
                                     "B:detach", detach,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONGraphicsRelocate(qemuMonitor *mon,
                                    int type,
                                    const char *hostname,
                                    int port,
                                    int tlsPort,
                                    const char *tlsSubject)
{
    const char *protocol = "vnc";
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE)
        protocol = "spice";

    if (!(cmd = qemuMonitorJSONMakeCommand("client_migrate_info",
                                           "s:protocol", protocol,
                                           "s:hostname", hostname,
                                           "i:port", port,
                                           "i:tls-port", tlsPort,
                                           "S:cert-subject", tlsSubject,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


static int
qemuAddfdInfoParse(virJSONValue *msg,
                   qemuMonitorAddFdInfo *fdinfo)
{
    virJSONValue *returnObj;

    if (!(returnObj = virJSONValueObjectGetObject(msg, "return"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid return data in add-fd response"));
        return -1;
    }

    if (virJSONValueObjectGetNumberInt(returnObj, "fd", &fdinfo->fd) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid fd in add-fd response"));
        return -1;
    }

    if (virJSONValueObjectGetNumberInt(returnObj, "fdset-id", &fdinfo->fdset) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid fdset-id in add-fd response"));
        return -1;
    }

    return 0;
}


/* if fdset is negative, qemu will create a new fdset and add the fd to that */
int qemuMonitorJSONAddFileHandleToSet(qemuMonitor *mon,
                                      int fd,
                                      int fdset,
                                      const char *opaque,
                                      qemuMonitorAddFdInfo *fdinfo)
{
    g_autoptr(virJSONValue) args = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) cmd = NULL;

    if (virJSONValueObjectAdd(&args, "S:opaque", opaque, NULL) < 0)
        return -1;

    if (fdset >= 0 &&
        virJSONValueObjectAdd(&args, "j:fdset-id", fdset, NULL) < 0) {
        return -1;
    }

    if (!(cmd = qemuMonitorJSONMakeCommandInternal("add-fd", &args)))
        return -1;

    if (qemuMonitorJSONCommandWithFd(mon, cmd, fd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    if (qemuAddfdInfoParse(reply, fdinfo) < 0)
        return -1;

    return 0;
}

static int
qemuMonitorJSONQueryFdsetsParse(virJSONValue *msg,
                                qemuMonitorFdsets **fdsets)
{
    virJSONValue *returnArray;
    virJSONValue *entry;
    size_t i;
    g_autoptr(qemuMonitorFdsets) sets = g_new0(qemuMonitorFdsets, 1);
    int ninfo;

    returnArray = virJSONValueObjectGetArray(msg, "return");

    ninfo = virJSONValueArraySize(returnArray);
    if (ninfo > 0)
        sets->fdsets = g_new0(qemuMonitorFdsetInfo, ninfo);
    sets->nfdsets = ninfo;

    for (i = 0; i < ninfo; i++) {
        size_t j;
        virJSONValue *fdarray;
        qemuMonitorFdsetInfo *fdsetinfo = &sets->fdsets[i];

        if (!(entry = virJSONValueArrayGet(returnArray, i))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-fdsets return data missing fdset array element"));
            return -1;
        }

        if (virJSONValueObjectGetNumberUint(entry, "fdset-id", &fdsetinfo->id) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-fdsets reply was missing 'fdset-id'"));
            return -1;

        }

        fdarray = virJSONValueObjectGetArray(entry, "fds");
        fdsetinfo->nfds = virJSONValueArraySize(fdarray);
        if (fdsetinfo->nfds > 0)
            fdsetinfo->fds = g_new0(qemuMonitorFdsetFdInfo, fdsetinfo->nfds);

        for (j = 0; j < fdsetinfo->nfds; j++) {
            qemuMonitorFdsetFdInfo *fdinfo = &fdsetinfo->fds[j];
            virJSONValue *fdentry;

            if (!(fdentry = virJSONValueArrayGet(fdarray, j))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("query-fdsets return data missing fd array element"));
                return -1;
            }

            if (virJSONValueObjectGetNumberInt(fdentry, "fd", &fdinfo->fd) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("query-fdsets return data missing 'fd'"));
                return -1;
            }

            /* opaque is optional and may be missing */
            fdinfo->opaque = g_strdup(virJSONValueObjectGetString(fdentry, "opaque"));
        }
    }

    *fdsets = g_steal_pointer(&sets);
    return 0;
}


int qemuMonitorJSONQueryFdsets(qemuMonitor *mon,
                               qemuMonitorFdsets **fdsets)
{
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-fdsets",
                                                             NULL);

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    if (qemuMonitorJSONQueryFdsetsParse(reply, fdsets) < 0)
        return -1;

    return 0;
}


int qemuMonitorJSONRemoveFdset(qemuMonitor *mon,
                               unsigned int fdset)
{
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("remove-fd",
                                                             "u:fdset-id", fdset,
                                                             NULL);

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int qemuMonitorJSONSendFileHandle(qemuMonitor *mon,
                                  const char *fdname,
                                  int fd)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("getfd",
                                                             "s:fdname", fdname,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommandWithFd(mon, cmd, fd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int qemuMonitorJSONCloseFileHandle(qemuMonitor *mon,
                                   const char *fdname)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("closefd",
                                                             "s:fdname", fdname,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONAddNetdev(qemuMonitor *mon,
                         virJSONValue **props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommandInternal("netdev_add", props)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONRemoveNetdev(qemuMonitor *mon,
                            const char *alias)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("netdev_del",
                                                             "s:id", alias,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


static int
qemuMonitorJSONQueryRxFilterParse(virJSONValue *msg,
                                  virNetDevRxFilter **filter)
{
    const char *tmp;
    virJSONValue *returnArray;
    virJSONValue *entry;
    virJSONValue *table;
    virJSONValue *element;
    size_t nTable;
    size_t i;
    g_autoptr(virNetDevRxFilter) fil = virNetDevRxFilterNew();

    if (!fil)
        return -1;

    returnArray = virJSONValueObjectGetArray(msg, "return");

    if (!(entry = virJSONValueArrayGet(returnArray, 0))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-rx-filter return data missing array element"));
        return -1;
    }

    if (!(tmp = virJSONValueObjectGetString(entry, "name"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid name "
                         "in query-rx-filter response"));
        return -1;
    }
    fil->name = g_strdup(tmp);
    if ((!(tmp = virJSONValueObjectGetString(entry, "main-mac"))) ||
        virMacAddrParse(tmp, &fil->mac) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'main-mac' "
                         "in query-rx-filter response"));
        return -1;
    }
    if (virJSONValueObjectGetBoolean(entry, "promiscuous",
                                     &fil->promiscuous) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'promiscuous' "
                         "in query-rx-filter response"));
        return -1;
    }
    if (virJSONValueObjectGetBoolean(entry, "broadcast-allowed",
                                     &fil->broadcastAllowed) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'broadcast-allowed' "
                         "in query-rx-filter response"));
        return -1;
    }

    if ((!(tmp = virJSONValueObjectGetString(entry, "unicast"))) ||
        ((fil->unicast.mode
          = virNetDevRxFilterModeTypeFromString(tmp)) < 0)) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'unicast' "
                         "in query-rx-filter response"));
        return -1;
    }
    if (virJSONValueObjectGetBoolean(entry, "unicast-overflow",
                                     &fil->unicast.overflow) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'unicast-overflow' "
                         "in query-rx-filter response"));
        return -1;
    }
    if ((!(table = virJSONValueObjectGet(entry, "unicast-table"))) ||
        (!virJSONValueIsArray(table))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'unicast-table' array "
                         "in query-rx-filter response"));
        return -1;
    }
    nTable = virJSONValueArraySize(table);
    fil->unicast.table = g_new0(virMacAddr, nTable);
    for (i = 0; i < nTable; i++) {
        if (!(element = virJSONValueArrayGet(table, i)) ||
            !(tmp = virJSONValueGetString(element))) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Missing or invalid element %zu of 'unicast' "
                             "list in query-rx-filter response"), i);
            return -1;
        }
        if (virMacAddrParse(tmp, &fil->unicast.table[i]) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("invalid mac address '%s' in 'unicast-table' "
                             "array in query-rx-filter response"), tmp);
            return -1;
        }
    }
    fil->unicast.nTable = nTable;

    if ((!(tmp = virJSONValueObjectGetString(entry, "multicast"))) ||
        ((fil->multicast.mode
          = virNetDevRxFilterModeTypeFromString(tmp)) < 0)) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'multicast' "
                         "in query-rx-filter response"));
        return -1;
    }
    if (virJSONValueObjectGetBoolean(entry, "multicast-overflow",
                                     &fil->multicast.overflow) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'multicast-overflow' "
                         "in query-rx-filter response"));
        return -1;
    }
    if ((!(table = virJSONValueObjectGet(entry, "multicast-table"))) ||
        (!virJSONValueIsArray(table))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'multicast-table' array "
                         "in query-rx-filter response"));
        return -1;
    }
    nTable = virJSONValueArraySize(table);
    fil->multicast.table = g_new0(virMacAddr, nTable);
    for (i = 0; i < nTable; i++) {
        if (!(element = virJSONValueArrayGet(table, i)) ||
            !(tmp = virJSONValueGetString(element))) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Missing or invalid element %zu of 'multicast' "
                             "list in query-rx-filter response"), i);
            return -1;
        }
        if (virMacAddrParse(tmp, &fil->multicast.table[i]) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("invalid mac address '%s' in 'multicast-table' "
                             "array in query-rx-filter response"), tmp);
            return -1;
        }
    }
    fil->multicast.nTable = nTable;

    if ((!(tmp = virJSONValueObjectGetString(entry, "vlan"))) ||
        ((fil->vlan.mode
          = virNetDevRxFilterModeTypeFromString(tmp)) < 0)) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'vlan' "
                         "in query-rx-filter response"));
        return -1;
    }
    if ((!(table = virJSONValueObjectGet(entry, "vlan-table"))) ||
        (!virJSONValueIsArray(table))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'vlan-table' array "
                         "in query-rx-filter response"));
        return -1;
    }
    nTable = virJSONValueArraySize(table);
    fil->vlan.table = g_new0(unsigned int, nTable);
    for (i = 0; i < nTable; i++) {
        if (!(element = virJSONValueArrayGet(table, i)) ||
            virJSONValueGetNumberUint(element, &fil->vlan.table[i]) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Missing or invalid element %zu of 'vlan-table' "
                             "array in query-rx-filter response"), i);
            return -1;
        }
    }
    fil->vlan.nTable = nTable;

    *filter = g_steal_pointer(&fil);
    return 0;
}


int
qemuMonitorJSONQueryRxFilter(qemuMonitor *mon, const char *alias,
                             virNetDevRxFilter **filter)
{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-rx-filter",
                                                             "s:name", alias,
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    if (qemuMonitorJSONQueryRxFilterParse(reply, filter) < 0)
        return -1;

    return 0;
}


/*
 * Example return data
 *
 * {"return": [
 *      {"filename": "stdio", "label": "monitor"},
 *      {"filename": "pty:/dev/pts/6", "label": "serial0", "frontend-open": true},
 *      {"filename": "pty:/dev/pts/7", "label": "parallel0"}
 * ]}
 *
 */
static int
qemuMonitorJSONExtractChardevInfo(virJSONValue *reply,
                                  GHashTable *info)
{
    virJSONValue *data;
    int ret = -1;
    size_t i;
    qemuMonitorChardevInfo *entry = NULL;

    data = virJSONValueObjectGetArray(reply, "return");

    for (i = 0; i < virJSONValueArraySize(data); i++) {
        virJSONValue *chardev = virJSONValueArrayGet(data, i);
        const char *type;
        const char *alias;
        bool connected;

        if (!chardev) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("character device information was missing array element"));
            goto cleanup;
        }

        if (!(alias = virJSONValueObjectGetString(chardev, "label"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("character device information was missing label"));
            goto cleanup;
        }

        if (!(type = virJSONValueObjectGetString(chardev, "filename"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("character device information was missing filename"));
            goto cleanup;
        }

        entry = g_new0(qemuMonitorChardevInfo, 1);

        if (STRPREFIX(type, "pty:"))
            entry->ptyPath = g_strdup(type + strlen("pty:"));

        if (virJSONValueObjectGetBoolean(chardev, "frontend-open", &connected) == 0) {
            if (connected)
                entry->state = VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED;
            else
                entry->state = VIR_DOMAIN_CHR_DEVICE_STATE_DISCONNECTED;
        }

        if (virHashAddEntry(info, alias, entry) < 0) {
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("failed to add chardev '%s' info"), alias);
            goto cleanup;
        }

        entry = NULL;
    }

    ret = 0;

 cleanup:
    if (entry) {
        VIR_FREE(entry->ptyPath);
        VIR_FREE(entry);
    }

    return ret;
}


int
qemuMonitorJSONGetChardevInfo(qemuMonitor *mon,
                              GHashTable *info)

{
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-chardev",
                                                             NULL);
    g_autoptr(virJSONValue) reply = NULL;

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    return qemuMonitorJSONExtractChardevInfo(reply, info);
}


int qemuMonitorJSONDelDevice(qemuMonitor *mon,
                             const char *devalias)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("device_del",
                                     "s:id", devalias,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONHasError(reply, "DeviceNotFound"))
        return -2;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONAddDeviceProps(qemuMonitor *mon,
                              virJSONValue **props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

     if (!(cmd = qemuMonitorJSONMakeCommandInternal("device_add", props)))
         return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONAddObject(qemuMonitor *mon,
                         virJSONValue **props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommandInternal("object-add", props)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONDelObject(qemuMonitor *mon,
                         const char *objalias,
                         bool report_error)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("object-del", "s:id", objalias, NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckErrorFull(cmd, reply, report_error) < 0)
        return -1;

    return 0;
}


/* speed is in bytes/sec */
int
qemuMonitorJSONDriveMirror(qemuMonitor *mon,
                           const char *device, const char *file,
                           const char *format, unsigned long long speed,
                           unsigned int granularity,
                           unsigned long long buf_size,
                           bool shallow,
                           bool reuse)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    const char *syncmode = "full";
    const char *mode = "absolute-paths";

    if (shallow)
        syncmode = "top";

    if (reuse)
        mode = "existing";

    cmd = qemuMonitorJSONMakeCommand("drive-mirror",
                                     "s:device", device,
                                     "s:target", file,
                                     "Y:speed", speed,
                                     "z:granularity", granularity,
                                     "P:buf-size", buf_size,
                                     "s:sync", syncmode,
                                     "s:mode", mode,
                                     "S:format", format,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    return qemuMonitorJSONCheckError(cmd, reply);
}


int
qemuMonitorJSONBlockdevMirror(qemuMonitor *mon,
                              const char *jobname,
                              bool persistjob,
                              const char *device,
                              const char *target,
                              unsigned long long speed,
                              unsigned int granularity,
                              unsigned long long buf_size,
                              bool shallow,
                              bool syncWrite)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virTristateBool autofinalize = VIR_TRISTATE_BOOL_ABSENT;
    virTristateBool autodismiss = VIR_TRISTATE_BOOL_ABSENT;
    const char *syncmode = "full";
    const char *copymode = NULL;

    if (shallow)
        syncmode = "top";

    if (syncWrite)
        copymode = "write-blocking";

    if (persistjob) {
        autofinalize = VIR_TRISTATE_BOOL_YES;
        autodismiss = VIR_TRISTATE_BOOL_NO;
    }

    cmd = qemuMonitorJSONMakeCommand("blockdev-mirror",
                                     "S:job-id", jobname,
                                     "s:device", device,
                                     "s:target", target,
                                     "Y:speed", speed,
                                     "z:granularity", granularity,
                                     "P:buf-size", buf_size,
                                     "s:sync", syncmode,
                                     "S:copy-mode", copymode,
                                     "T:auto-finalize", autofinalize,
                                     "T:auto-dismiss", autodismiss,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    return qemuMonitorJSONCheckError(cmd, reply);
}


int
qemuMonitorJSONTransaction(qemuMonitor *mon, virJSONValue **actions)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("transaction",
                                     "a:actions", actions,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


/* speed is in bytes/sec. Returns 0 on success, -1 with error message
 * emitted on failure. */
int
qemuMonitorJSONBlockCommit(qemuMonitor *mon,
                           const char *device,
                           const char *jobname,
                           bool persistjob,
                           const char *top,
                           const char *topNode,
                           const char *base,
                           const char *baseNode,
                           const char *backingName,
                           unsigned long long speed)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virTristateBool autofinalize = VIR_TRISTATE_BOOL_ABSENT;
    virTristateBool autodismiss = VIR_TRISTATE_BOOL_ABSENT;

    if (persistjob) {
        autofinalize = VIR_TRISTATE_BOOL_YES;
        autodismiss = VIR_TRISTATE_BOOL_NO;
    }

    cmd = qemuMonitorJSONMakeCommand("block-commit",
                                     "s:device", device,
                                     "S:job-id", jobname,
                                     "Y:speed", speed,
                                     "S:top", top,
                                     "S:top-node", topNode,
                                     "S:base", base,
                                     "S:base-node", baseNode,
                                     "S:backing-file", backingName,
                                     "T:auto-finalize", autofinalize,
                                     "T:auto-dismiss", autodismiss,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


static char *
qemuMonitorJSONDiskNameLookupOne(virJSONValue *image,
                                 virStorageSource *top,
                                 virStorageSource *target)
{
    virJSONValue *backing;
    char *ret;

    /* The caller will report a generic message if we return NULL
     * without an error; but in some cases we can improve by reporting
     * a more specific message.  */
    if (!top || !image)
        return NULL;
    if (top != target) {
        backing = virJSONValueObjectGetObject(image, "backing-image");
        return qemuMonitorJSONDiskNameLookupOne(backing, top->backingStore,
                                                target);
    }
    ret = g_strdup(virJSONValueObjectGetString(image, "filename"));
    /* Sanity check - the name qemu gave us should resolve to the same
       file tracked by our target description. */
    if (virStorageSourceIsLocalStorage(target) &&
        STRNEQ(ret, target->path) &&
        !virFileLinkPointsTo(ret, target->path)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("qemu block name '%s' doesn't match expected '%s'"),
                       ret, target->path);
        VIR_FREE(ret);
    }
    return ret;
}


char *
qemuMonitorJSONDiskNameLookup(qemuMonitor *mon,
                              const char *device,
                              virStorageSource *top,
                              virStorageSource *target)
{
    g_autoptr(virJSONValue) devices = NULL;
    size_t i;

    if (!(devices = qemuMonitorJSONQueryBlock(mon)))
        return NULL;

    for (i = 0; i < virJSONValueArraySize(devices); i++) {
        virJSONValue *dev;
        virJSONValue *inserted;
        virJSONValue *image;
        const char *thisdev;

        if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
            return NULL;

        if (!(thisdev = qemuMonitorJSONGetBlockDevDevice(dev)))
            return NULL;

        if (STREQ(thisdev, device)) {
            if ((inserted = virJSONValueObjectGetObject(dev, "inserted")) &&
                (image = virJSONValueObjectGetObject(inserted, "image"))) {
                return qemuMonitorJSONDiskNameLookupOne(image, top, target);
            }
        }
    }
    /* Guarantee an error when returning NULL, but don't override a
     * more specific error if one was already generated.  */
    if (virGetLastErrorCode() == VIR_ERR_OK) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unable to find backing name for device %s"),
                       device);
    }

    return NULL;
}


int qemuMonitorJSONArbitraryCommand(qemuMonitor *mon,
                                    const char *cmd_str,
                                    char **reply_str)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = virJSONValueFromString(cmd_str)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (!(*reply_str = virJSONValueToString(reply, false)))
        return -1;

    return 0;
}

int qemuMonitorJSONInjectNMI(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("inject-nmi", NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONSendKey(qemuMonitor *mon,
                           unsigned int holdtime,
                           unsigned int *keycodes,
                           unsigned int nkeycodes)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) keys = NULL;
    size_t i;

    /* create the key data array */
    keys = virJSONValueNewArray();

    for (i = 0; i < nkeycodes; i++) {
        g_autoptr(virJSONValue) key = NULL;

        if (keycodes[i] > 0xffff) {
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("keycode %zu is invalid: 0x%X"), i, keycodes[i]);
            return -1;
        }

        /* create single key object */
        key = virJSONValueNewObject();

        /* Union KeyValue has two types, use the generic one */
        if (virJSONValueObjectAppendString(key, "type", "number") < 0)
            return -1;

        /* with the keycode */
        if (virJSONValueObjectAppendNumberInt(key, "data", keycodes[i]) < 0)
            return -1;

        if (virJSONValueArrayAppend(keys, &key) < 0)
            return -1;
    }

    cmd = qemuMonitorJSONMakeCommand("send-key",
                                     "a:keys", &keys,
                                     "p:hold-time", holdtime,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONScreendump(qemuMonitor *mon,
                              const char *device,
                              unsigned int head,
                              const char *file)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("screendump",
                                     "s:filename", file,
                                     "S:device", device,
                                     "p:head", head,
                                     NULL);

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


static int
qemuMonitorJSONParseBlockJobInfo(GHashTable *blockJobs,
                                 virJSONValue *entry,
                                 bool rawjobname)
{
    qemuMonitorBlockJobInfo *info = NULL;
    const char *device;
    const char *type;

    if (!(device = virJSONValueObjectGetString(entry, "device"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("entry was missing 'device'"));
        return -1;
    }

    if (!rawjobname)
        device = qemuAliasDiskDriveSkipPrefix(device);

    info = g_new0(qemuMonitorBlockJobInfo, 1);

    if (virHashAddEntry(blockJobs, device, info) < 0) {
        VIR_FREE(info);
        return -1;
    }

    if (!(type = virJSONValueObjectGetString(entry, "type"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("entry was missing 'type'"));
        return -1;
    }
    if (STREQ(type, "stream"))
        info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_PULL;
    else if (STREQ(type, "commit"))
        info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT;
    else if (STREQ(type, "mirror"))
        info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY;
    else if (STREQ(type, "backup"))
        info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP;
    else
        info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;

    if (virJSONValueObjectGetNumberUlong(entry, "speed", &info->bandwidth) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("entry was missing 'speed'"));
        return -1;
    }

    if (virJSONValueObjectGetNumberUlong(entry, "offset", &info->cur) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("entry was missing 'offset'"));
        return -1;
    }

    if (virJSONValueObjectGetNumberUlong(entry, "len", &info->end) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("entry was missing 'len'"));
        return -1;
    }

    if (virJSONValueObjectGetBoolean(entry, "ready", &info->ready) == 0)
        info->ready_present = true;

    return 0;
}

GHashTable *
qemuMonitorJSONGetAllBlockJobInfo(qemuMonitor *mon,
                                  bool rawjobname)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    size_t nr_results;
    size_t i;
    g_autoptr(GHashTable) blockJobs = virHashNew(g_free);

    cmd = qemuMonitorJSONMakeCommand("query-block-jobs", NULL);
    if (!cmd)
        return NULL;
    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return NULL;

    if ((data = virJSONValueObjectGetArray(reply, "return")) == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("reply was missing return data"));
        return NULL;
    }

    nr_results = virJSONValueArraySize(data);

    for (i = 0; i < nr_results; i++) {
        virJSONValue *entry = virJSONValueArrayGet(data, i);
        if (!entry) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing array element"));
            return NULL;
        }
        if (qemuMonitorJSONParseBlockJobInfo(blockJobs, entry, rawjobname) < 0)
            return NULL;
    }

    return g_steal_pointer(&blockJobs);
}


static int
qemuMonitorJSONBlockJobError(virJSONValue *cmd,
                             virJSONValue *reply,
                             const char *jobname)
{
    virJSONValue *error;

    if ((error = virJSONValueObjectGet(reply, "error")) &&
        (qemuMonitorJSONErrorIsClass(error, "DeviceNotActive"))) {
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("No active block job '%s'"), jobname);
        return -1;
    }

    return qemuMonitorJSONCheckError(cmd, reply);
}


/* speed is in bytes/sec */
int
qemuMonitorJSONBlockStream(qemuMonitor *mon,
                           const char *device,
                           const char *jobname,
                           bool persistjob,
                           const char *base,
                           const char *baseNode,
                           const char *backingName,
                           unsigned long long speed)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virTristateBool autofinalize = VIR_TRISTATE_BOOL_ABSENT;
    virTristateBool autodismiss = VIR_TRISTATE_BOOL_ABSENT;

    if (persistjob) {
        autofinalize = VIR_TRISTATE_BOOL_YES;
        autodismiss = VIR_TRISTATE_BOOL_NO;
    }

    if (!(cmd = qemuMonitorJSONMakeCommand("block-stream",
                                           "s:device", device,
                                           "S:job-id", jobname,
                                           "Y:speed", speed,
                                           "S:base", base,
                                           "S:base-node", baseNode,
                                           "S:backing-file", backingName,
                                           "T:auto-finalize", autofinalize,
                                           "T:auto-dismiss", autodismiss,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockJobCancel(qemuMonitor *mon,
                              const char *jobname,
                              bool force)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("block-job-cancel",
                                           "s:device", jobname,
                                           "B:force", force,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockJobSetSpeed(qemuMonitor *mon,
                                const char *jobname,
                                unsigned long long speed)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("block-job-set-speed",
                                           "s:device", jobname,
                                           "J:speed", speed,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONDrivePivot(qemuMonitor *mon,
                          const char *jobname)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("block-job-complete",
                                     "s:device", jobname,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONJobDismiss(qemuMonitor *mon,
                          const char *jobname)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("job-dismiss",
                                           "s:id", jobname,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONJobComplete(qemuMonitor *mon,
                           const char *jobname)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("job-complete",
                                           "s:id", jobname,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
        return -1;

    return 0;
}


int qemuMonitorJSONOpenGraphics(qemuMonitor *mon,
                                const char *protocol,
                                const char *fdname,
                                bool skipauth)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("add_client",
                                     "s:protocol", protocol,
                                     "s:fdname", fdname,
                                     "b:skipauth", skipauth,
                                     NULL);

    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


#define GET_THROTTLE_STATS_OPTIONAL(FIELD, STORE) \
    if (virJSONValueObjectGetNumberUlong(inserted, \
                                         FIELD, \
                                         &reply->STORE) < 0) { \
        reply->STORE = 0; \
    }
#define GET_THROTTLE_STATS(FIELD, STORE) \
    if (virJSONValueObjectGetNumberUlong(inserted, \
                                         FIELD, \
                                         &reply->STORE) < 0) { \
        virReportError(VIR_ERR_OPERATION_UNSUPPORTED, \
                       _("block_io_throttle field '%s' missing " \
                         "in qemu's output"), \
                       #STORE); \
        return -1; \
    }
static int
qemuMonitorJSONBlockIoThrottleInfo(virJSONValue *io_throttle,
                                   const char *drivealias,
                                   const char *qdevid,
                                   virDomainBlockIoTuneInfo *reply)
{
    size_t i;
    bool found = false;

    for (i = 0; i < virJSONValueArraySize(io_throttle); i++) {
        virJSONValue *temp_dev = virJSONValueArrayGet(io_throttle, i);
        virJSONValue *inserted;
        const char *current_drive;
        const char *current_qdev;

        if (!temp_dev || virJSONValueGetType(temp_dev) != VIR_JSON_TYPE_OBJECT) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("block_io_throttle device entry "
                             "was not in expected format"));
            return -1;
        }

        current_qdev = virJSONValueObjectGetString(temp_dev, "qdev");
        current_drive = virJSONValueObjectGetString(temp_dev, "device");

        if (!current_drive && !current_qdev) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("block_io_throttle device entry "
                             "was not in expected format"));
            return -1;
        }

        if ((drivealias && current_drive && STRNEQ(current_drive, drivealias)) ||
            (qdevid && current_qdev && STRNEQ(current_qdev, qdevid)))
            continue;

        found = true;
        if (!(inserted = virJSONValueObjectGetObject(temp_dev, "inserted"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("block_io_throttle inserted entry "
                             "was not in expected format"));
            return -1;
        }
        GET_THROTTLE_STATS("bps", total_bytes_sec);
        GET_THROTTLE_STATS("bps_rd", read_bytes_sec);
        GET_THROTTLE_STATS("bps_wr", write_bytes_sec);
        GET_THROTTLE_STATS("iops", total_iops_sec);
        GET_THROTTLE_STATS("iops_rd", read_iops_sec);
        GET_THROTTLE_STATS("iops_wr", write_iops_sec);
        GET_THROTTLE_STATS_OPTIONAL("bps_max", total_bytes_sec_max);
        GET_THROTTLE_STATS_OPTIONAL("bps_rd_max", read_bytes_sec_max);
        GET_THROTTLE_STATS_OPTIONAL("bps_wr_max", write_bytes_sec_max);
        GET_THROTTLE_STATS_OPTIONAL("iops_max", total_iops_sec_max);
        GET_THROTTLE_STATS_OPTIONAL("iops_rd_max", read_iops_sec_max);
        GET_THROTTLE_STATS_OPTIONAL("iops_wr_max", write_iops_sec_max);
        GET_THROTTLE_STATS_OPTIONAL("iops_size", size_iops_sec);

        reply->group_name = g_strdup(virJSONValueObjectGetString(inserted, "group"));

        GET_THROTTLE_STATS_OPTIONAL("bps_max_length", total_bytes_sec_max_length);
        GET_THROTTLE_STATS_OPTIONAL("bps_rd_max_length", read_bytes_sec_max_length);
        GET_THROTTLE_STATS_OPTIONAL("bps_wr_max_length", write_bytes_sec_max_length);
        GET_THROTTLE_STATS_OPTIONAL("iops_max_length", total_iops_sec_max_length);
        GET_THROTTLE_STATS_OPTIONAL("iops_rd_max_length", read_iops_sec_max_length);
        GET_THROTTLE_STATS_OPTIONAL("iops_wr_max_length", write_iops_sec_max_length);

        break;
    }

    if (!found) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("cannot find throttling info for device '%s'"),
                       drivealias ? drivealias : qdevid);
        return -1;
    }

    return 0;
}
#undef GET_THROTTLE_STATS
#undef GET_THROTTLE_STATS_OPTIONAL

int qemuMonitorJSONSetBlockIoThrottle(qemuMonitor *mon,
                                      const char *drivealias,
                                      const char *qomid,
                                      virDomainBlockIoTuneInfo *info)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) result = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("block_set_io_throttle",
                                           "S:device", drivealias,
                                           "S:id", qomid,
                                           "U:bps", info->total_bytes_sec,
                                           "U:bps_rd", info->read_bytes_sec,
                                           "U:bps_wr", info->write_bytes_sec,
                                           "U:iops", info->total_iops_sec,
                                           "U:iops_rd", info->read_iops_sec,
                                           "U:iops_wr", info->write_iops_sec,
                                           "U:bps_max", info->total_bytes_sec_max,
                                           "U:bps_rd_max", info->read_bytes_sec_max,
                                           "U:bps_wr_max", info->write_bytes_sec_max,
                                           "U:iops_max", info->total_iops_sec_max,
                                           "U:iops_rd_max", info->read_iops_sec_max,
                                           "U:iops_wr_max", info->write_iops_sec_max,
                                           "U:iops_size", info->size_iops_sec,
                                           "S:group", info->group_name,
                                           "P:bps_max_length", info->total_bytes_sec_max_length,
                                           "P:bps_rd_max_length", info->read_bytes_sec_max_length,
                                           "P:bps_wr_max_length", info->write_bytes_sec_max_length,
                                           "P:iops_max_length", info->total_iops_sec_max_length,
                                           "P:iops_rd_max_length", info->read_iops_sec_max_length,
                                           "P:iops_wr_max_length", info->write_iops_sec_max_length,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &result) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, result) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONGetBlockIoThrottle(qemuMonitor *mon,
                                      const char *drivealias,
                                      const char *qdevid,
                                      virDomainBlockIoTuneInfo *reply)
{
    g_autoptr(virJSONValue) devices = NULL;

    if (!(devices = qemuMonitorJSONQueryBlock(mon)))
        return -1;

    return qemuMonitorJSONBlockIoThrottleInfo(devices, drivealias, qdevid, reply);
}

int qemuMonitorJSONSystemWakeup(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("system_wakeup", NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int qemuMonitorJSONGetVersion(qemuMonitor *mon,
                              int *major,
                              int *minor,
                              int *micro,
                              char **package)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    virJSONValue *qemu;

    *major = *minor = *micro = 0;
    if (package)
        *package = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-version", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    data = virJSONValueObjectGetObject(reply, "return");

    if (!(qemu = virJSONValueObjectGetObject(data, "qemu"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-version reply was missing 'qemu' data"));
        return -1;
    }

    if (virJSONValueObjectGetNumberInt(qemu, "major", major) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-version reply was missing 'major' version"));
        return -1;
    }
    if (virJSONValueObjectGetNumberInt(qemu, "minor", minor) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-version reply was missing 'minor' version"));
        return -1;
    }
    if (virJSONValueObjectGetNumberInt(qemu, "micro", micro) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-version reply was missing 'micro' version"));
        return -1;
    }

    if (package) {
        const char *tmp;
        if (!(tmp = virJSONValueObjectGetString(data, "package"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-version reply was missing 'package' version"));
            return -1;
        }
        *package = g_strdup(tmp);
    }

    return 0;
}


int qemuMonitorJSONGetMachines(qemuMonitor *mon,
                               qemuMonitorMachineInfo ***machines)
{
    int ret = -1;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    qemuMonitorMachineInfo **infolist = NULL;
    size_t n = 0;
    size_t i;

    *machines = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-machines", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        goto cleanup;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        goto cleanup;

    data = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(data);

    /* null-terminated list */
    infolist = g_new0(qemuMonitorMachineInfo *, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValue *child = virJSONValueArrayGet(data, i);
        const char *tmp;
        qemuMonitorMachineInfo *info;

        info = g_new0(qemuMonitorMachineInfo, 1);

        infolist[i] = info;

        if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-machines reply data was missing 'name'"));
            goto cleanup;
        }

        info->name = g_strdup(tmp);

        if (virJSONValueObjectHasKey(child, "is-default") &&
            virJSONValueObjectGetBoolean(child, "is-default", &info->isDefault) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-machines reply has malformed 'is-default' data"));
            goto cleanup;
        }

        if (virJSONValueObjectHasKey(child, "alias")) {
            if (!(tmp = virJSONValueObjectGetString(child, "alias"))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("query-machines reply has malformed 'alias' data"));
                goto cleanup;
            }
            info->alias = g_strdup(tmp);
        }
        if (virJSONValueObjectHasKey(child, "cpu-max") &&
            virJSONValueObjectGetNumberUint(child, "cpu-max", &info->maxCpus) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-machines reply has malformed 'cpu-max' data"));
            goto cleanup;
        }

        ignore_value(virJSONValueObjectGetBoolean(child, "hotpluggable-cpus",
                                                  &info->hotplugCpus));

        if (virJSONValueObjectHasKey(child, "default-cpu-type")) {
            if (!(tmp = virJSONValueObjectGetString(child, "default-cpu-type"))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("query-machines reply has malformed "
                                 "'default-cpu-type' data"));
                goto cleanup;
            }

            info->defaultCPU = g_strdup(tmp);
        }

        if (virJSONValueObjectHasKey(child, "numa-mem-supported")) {
            if (virJSONValueObjectGetBoolean(child, "numa-mem-supported", &info->numaMemSupported) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("qemu-machines reply has malformed "
                                 "'numa-mem-supported' data"));
                goto cleanup;
            }
        } else {
            info->numaMemSupported = true;
        }

        if (virJSONValueObjectHasKey(child, "default-ram-id")) {
            if (!(tmp = virJSONValueObjectGetString(child, "default-ram-id"))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("query-machines reply has malformed "
                                 "'default-ram-id' data"));
                goto cleanup;
            }

            info->defaultRAMid = g_strdup(tmp);
        }

        if (virJSONValueObjectHasKey(child, "deprecated") &&
            virJSONValueObjectGetBoolean(child, "deprecated", &info->deprecated) < 0)
            goto cleanup;
    }

    ret = n;
    *machines = g_steal_pointer(&infolist);

 cleanup:
    if (infolist) {
        for (i = 0; i < n; i++)
            qemuMonitorMachineInfoFree(infolist[i]);
        VIR_FREE(infolist);
    }
    return ret;
}


int
qemuMonitorJSONGetCPUDefinitions(qemuMonitor *mon,
                                 qemuMonitorCPUDefs **cpuDefs)
{
    g_autoptr(qemuMonitorCPUDefs) defs = NULL;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    size_t ncpus;
    size_t i;

    *cpuDefs = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-definitions", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    /* Urgh, some QEMU architectures have the query-cpu-definitions
     * command, but return 'GenericError' with string "Not supported",
     * instead of simply omitting the command entirely :-(
     */
    if (qemuMonitorJSONHasError(reply, "GenericError"))
        return 0;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    data = virJSONValueObjectGetArray(reply, "return");
    ncpus = virJSONValueArraySize(data);

    if (!(defs = qemuMonitorCPUDefsNew(ncpus)))
        return -1;

    for (i = 0; i < defs->ncpus; i++) {
        virJSONValue *child = virJSONValueArrayGet(data, i);
        const char *tmp;
        qemuMonitorCPUDefInfo *cpu = defs->cpus + i;

        if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-cpu-definitions reply data was missing 'name'"));
            return -1;
        }

        cpu->name = g_strdup(tmp);

        if ((tmp = virJSONValueObjectGetString(child, "typename")) && *tmp)
            cpu->type = g_strdup(tmp);

        if (virJSONValueObjectHasKey(child, "unavailable-features")) {
            if (!(cpu->blockers = virJSONValueObjectGetStringArray(child,
                                                                   "unavailable-features")))
                return -1;

            if (g_strv_length(cpu->blockers) == 0) {
                cpu->usable = VIR_DOMCAPS_CPU_USABLE_YES;
                g_clear_pointer(&cpu->blockers, g_strfreev);
                continue;
            }

            cpu->usable = VIR_DOMCAPS_CPU_USABLE_NO;
        }

        if (virJSONValueObjectHasKey(child, "deprecated") &&
            virJSONValueObjectGetBoolean(child, "deprecated", &cpu->deprecated) < 0)
            return -1;
    }

    *cpuDefs = g_steal_pointer(&defs);
    return 0;
}


static int
qemuMonitorJSONParseCPUModelProperty(const char *key,
                                     virJSONValue *value,
                                     void *opaque)
{
    qemuMonitorCPUModelInfo *machine_model = opaque;
    qemuMonitorCPUProperty *prop;

    prop = machine_model->props + machine_model->nprops;

    switch ((virJSONType)virJSONValueGetType(value)) {
    case VIR_JSON_TYPE_STRING:
        prop->value.string = g_strdup(virJSONValueGetString(value));
        prop->type = QEMU_MONITOR_CPU_PROPERTY_STRING;
        break;

    case VIR_JSON_TYPE_NUMBER:
        /* Ignore numbers which cannot be parsed as unsigned long long */
        if (virJSONValueGetNumberLong(value, &prop->value.number) < 0)
            return 0;
        prop->type = QEMU_MONITOR_CPU_PROPERTY_NUMBER;
        break;

    case VIR_JSON_TYPE_BOOLEAN:
        virJSONValueGetBoolean(value, &prop->value.boolean);
        prop->type = QEMU_MONITOR_CPU_PROPERTY_BOOLEAN;
        break;

    case VIR_JSON_TYPE_OBJECT:
    case VIR_JSON_TYPE_ARRAY:
    case VIR_JSON_TYPE_NULL:
        return 0;
    }

    machine_model->nprops++;
    prop->name = g_strdup(key);

    return 0;
}


static virJSONValue *
qemuMonitorJSONMakeCPUModel(virCPUDef *cpu,
                            bool migratable)
{
    g_autoptr(virJSONValue) model = virJSONValueNewObject();
    size_t i;

    if (virJSONValueObjectAppendString(model, "name", cpu->model) < 0)
        return NULL;

    if (cpu->nfeatures || !migratable) {
        g_autoptr(virJSONValue) props = virJSONValueNewObject();

        for (i = 0; i < cpu->nfeatures; i++) {
            char *name = cpu->features[i].name;
            bool enabled = false;

            /* policy may be reported as -1 if the CPU def is a host model */
            if (cpu->features[i].policy == VIR_CPU_FEATURE_REQUIRE ||
                cpu->features[i].policy == VIR_CPU_FEATURE_FORCE ||
                cpu->features[i].policy == -1)
                enabled = true;

            if (virJSONValueObjectAppendBoolean(props, name, enabled) < 0)
                return NULL;
        }

        if (!migratable &&
            virJSONValueObjectAppendBoolean(props, "migratable", false) < 0) {
            return NULL;
        }

        if (virJSONValueObjectAppend(model, "props", &props) < 0)
            return NULL;
    }

    return g_steal_pointer(&model);
}


static int
qemuMonitorJSONParseCPUModelData(virJSONValue *data,
                                 const char *cmd_name,
                                 bool fail_no_props,
                                 virJSONValue **cpu_model,
                                 virJSONValue **cpu_props,
                                 const char **cpu_name)
{
    if (!(*cpu_model = virJSONValueObjectGetObject(data, "model"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("%s reply data was missing 'model'"), cmd_name);
        return -1;
    }

    if (!(*cpu_name = virJSONValueObjectGetString(*cpu_model, "name"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("%s reply data was missing 'name'"), cmd_name);
        return -1;
    }

    if (!(*cpu_props = virJSONValueObjectGetObject(*cpu_model, "props")) &&
        fail_no_props) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("%s reply data was missing 'props'"), cmd_name);
        return -1;
    }

    return 0;
}


static int
qemuMonitorJSONParseCPUModel(const char *cpu_name,
                             virJSONValue *cpu_props,
                             qemuMonitorCPUModelInfo **model_info)
{
    g_autoptr(qemuMonitorCPUModelInfo) machine_model = NULL;

    machine_model = g_new0(qemuMonitorCPUModelInfo, 1);
    machine_model->name = g_strdup(cpu_name);

    if (cpu_props) {
        size_t nprops = virJSONValueObjectKeysNumber(cpu_props);

        machine_model->props = g_new0(qemuMonitorCPUProperty, nprops);

        if (virJSONValueObjectForeachKeyValue(cpu_props,
                                              qemuMonitorJSONParseCPUModelProperty,
                                              machine_model) < 0)
            return -1;
    }

    *model_info = g_steal_pointer(&machine_model);
    return 0;
}


static int
qemuMonitorJSONQueryCPUModelExpansionOne(qemuMonitor *mon,
                                         qemuMonitorCPUModelExpansionType type,
                                         virJSONValue **model,
                                         virJSONValue **data)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    const char *typeStr = "";

    switch (type) {
    case QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC:
    case QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC_FULL:
        typeStr = "static";
        break;

    case QEMU_MONITOR_CPU_MODEL_EXPANSION_FULL:
        typeStr = "full";
        break;
    }
    if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-model-expansion",
                                           "s:type", typeStr,
                                           "a:model", model,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    /* Even though query-cpu-model-expansion is advertised by query-commands it
     * may just return GenericError if it is not implemented for the requested
     * guest architecture or it is not supported in the host environment.
     */
    if (qemuMonitorJSONHasError(reply, "GenericError"))
        return 0;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    *data = virJSONValueObjectStealObject(reply, "return");

    return 1;
}


int
qemuMonitorJSONGetCPUModelExpansion(qemuMonitor *mon,
                                    qemuMonitorCPUModelExpansionType type,
                                    virCPUDef *cpu,
                                    bool migratable,
                                    bool fail_no_props,
                                    qemuMonitorCPUModelInfo **model_info)
{
    g_autoptr(virJSONValue) model = NULL;
    g_autoptr(virJSONValue) data = NULL;
    g_autoptr(virJSONValue) fullData = NULL;
    virJSONValue *cpu_model;
    virJSONValue *cpu_props = NULL;
    const char *cpu_name = "";
    int rc;

    *model_info = NULL;

    if (!(model = qemuMonitorJSONMakeCPUModel(cpu, migratable)))
        return -1;

    if ((rc = qemuMonitorJSONQueryCPUModelExpansionOne(mon, type, &model, &data)) <= 0)
        return rc;

    if (qemuMonitorJSONParseCPUModelData(data, "query-cpu-model-expansion",
                                         fail_no_props, &cpu_model, &cpu_props,
                                         &cpu_name) < 0)
        return -1;

    /* QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC_FULL requests "full" expansion
     * on the result of the initial "static" expansion. */
    if (type == QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC_FULL) {
        g_autoptr(virJSONValue) fullModel = virJSONValueCopy(cpu_model);

        if (!fullModel)
            return -1;

        type = QEMU_MONITOR_CPU_MODEL_EXPANSION_FULL;

        if ((rc = qemuMonitorJSONQueryCPUModelExpansionOne(mon, type, &fullModel, &fullData)) <= 0)
            return rc;

        if (qemuMonitorJSONParseCPUModelData(fullData, "query-cpu-model-expansion",
                                             fail_no_props, &cpu_model, &cpu_props,
                                             &cpu_name) < 0)
            return -1;
    }

    return qemuMonitorJSONParseCPUModel(cpu_name, cpu_props, model_info);
}


int
qemuMonitorJSONGetCPUModelBaseline(qemuMonitor *mon,
                                   virCPUDef *cpu_a,
                                   virCPUDef *cpu_b,
                                   qemuMonitorCPUModelInfo **baseline)
{
    g_autoptr(virJSONValue) model_a = NULL;
    g_autoptr(virJSONValue) model_b = NULL;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    virJSONValue *cpu_model;
    virJSONValue *cpu_props = NULL;
    const char *cpu_name = "";

    if (!(model_a = qemuMonitorJSONMakeCPUModel(cpu_a, true)) ||
        !(model_b = qemuMonitorJSONMakeCPUModel(cpu_b, true)))
        return -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-model-baseline",
                                           "a:modela", &model_a,
                                           "a:modelb", &model_b,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    data = virJSONValueObjectGetObject(reply, "return");

    if (qemuMonitorJSONParseCPUModelData(data, "query-cpu-model-baseline",
                                         false, &cpu_model, &cpu_props,
                                         &cpu_name) < 0)
        return -1;

    return qemuMonitorJSONParseCPUModel(cpu_name, cpu_props, baseline);
}


int
qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon,
                                     virCPUDef *cpu_a,
                                     virCPUDef *cpu_b,
                                     char **result)
{
    g_autoptr(virJSONValue) model_a = NULL;
    g_autoptr(virJSONValue) model_b = NULL;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    const char *data_result;
    virJSONValue *data;

    if (!(model_a = qemuMonitorJSONMakeCPUModel(cpu_a, true)) ||
        !(model_b = qemuMonitorJSONMakeCPUModel(cpu_b, true)))
        return -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-model-comparison",
                                           "a:modela", &model_a,
                                           "a:modelb", &model_b,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    data = virJSONValueObjectGetObject(reply, "return");

    if (!(data_result = virJSONValueObjectGetString(data, "result"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-cpu-model-comparison reply data was missing "
                         "'result'"));
        return -1;
    }

    *result = g_strdup(data_result);
    return 0;
}


int qemuMonitorJSONGetCommands(qemuMonitor *mon,
                               char ***commands)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    g_auto(GStrv) commandlist = NULL;
    size_t n = 0;
    size_t i;

    *commands = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-commands", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    data = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(data);

    /* null-terminated list */
    commandlist = g_new0(char *, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValue *child = virJSONValueArrayGet(data, i);
        const char *tmp;

        if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-commands reply data was missing 'name'"));
            return -1;
        }

        commandlist[i] = g_strdup(tmp);
    }

    *commands = g_steal_pointer(&commandlist);
    return n;
}


static int
qemuMonitorJSONGetCommandLineOptionsWorker(size_t pos G_GNUC_UNUSED,
                                           virJSONValue *item,
                                           void *opaque)
{
    const char *name = virJSONValueObjectGetString(item, "option");
    g_autoptr(virJSONValue) parameters = NULL;
    GHashTable *options = opaque;

    if (!name ||
        virJSONValueObjectRemoveKey(item, "parameters", &parameters) <= 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("reply data was missing 'option' name or parameters"));
        return -1;
    }

    g_hash_table_insert(options, g_strdup(name), parameters);
    parameters = NULL;

    return 1;
}


GHashTable *
qemuMonitorJSONGetCommandLineOptions(qemuMonitor *mon)
{
    g_autoptr(GHashTable) ret = virHashNew(virJSONValueHashFree);
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-command-line-options", NULL)))
        return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return NULL;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return NULL;

    if (virJSONValueArrayForeachSteal(virJSONValueObjectGetArray(reply, "return"),
                                      qemuMonitorJSONGetCommandLineOptionsWorker,
                                      ret) < 0)
        return NULL;

    return g_steal_pointer(&ret);
}


int qemuMonitorJSONGetKVMState(qemuMonitor *mon,
                               bool *enabled,
                               bool *present)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data = NULL;

    /* Safe defaults */
    *enabled = *present = false;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-kvm", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    data = virJSONValueObjectGetObject(reply, "return");

    if (virJSONValueObjectGetBoolean(data, "enabled", enabled) < 0 ||
        virJSONValueObjectGetBoolean(data, "present", present) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-kvm replied unexpected data"));
        return -1;
    }

    return 0;
}


int
qemuMonitorJSONGetObjectTypes(qemuMonitor *mon,
                              char ***types)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    g_auto(GStrv) typelist = NULL;
    size_t n = 0;
    size_t i;

    *types = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-list-types", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    data = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(data);

    /* null-terminated list */
    typelist = g_new0(char *, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValue *child = virJSONValueArrayGet(data, i);
        const char *tmp;

        if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("qom-list-types reply data was missing 'name'"));
            return -1;
        }

        typelist[i] = g_strdup(tmp);
    }

    *types = g_steal_pointer(&typelist);
    return n;
}


int qemuMonitorJSONGetObjectListPaths(qemuMonitor *mon,
                                      const char *path,
                                      qemuMonitorJSONListPath ***paths)
{
    int ret = -1;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    qemuMonitorJSONListPath **pathlist = NULL;
    size_t n = 0;
    size_t i;

    *paths = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-list",
                                           "s:path", path,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        goto cleanup;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        goto cleanup;

    data = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(data);

    /* null-terminated list */
    pathlist = g_new0(qemuMonitorJSONListPath *, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValue *child = virJSONValueArrayGet(data, i);
        const char *tmp;
        qemuMonitorJSONListPath *info;

        info = g_new0(qemuMonitorJSONListPath, 1);

        pathlist[i] = info;

        if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("qom-list reply data was missing 'name'"));
            goto cleanup;
        }

        info->name = g_strdup(tmp);

        if (virJSONValueObjectHasKey(child, "type")) {
            if (!(tmp = virJSONValueObjectGetString(child, "type"))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("qom-list reply has malformed 'type' data"));
                goto cleanup;
            }
            info->type = g_strdup(tmp);
        }
    }

    ret = n;
    *paths = g_steal_pointer(&pathlist);

 cleanup:
    if (pathlist) {
        for (i = 0; i < n; i++)
            qemuMonitorJSONListPathFree(pathlist[i]);
        VIR_FREE(pathlist);
    }
    return ret;
}

void qemuMonitorJSONListPathFree(qemuMonitorJSONListPath *paths)
{
    if (!paths)
        return;
    g_free(paths->name);
    g_free(paths->type);
    g_free(paths);
}


int qemuMonitorJSONGetObjectProperty(qemuMonitor *mon,
                                     const char *path,
                                     const char *property,
                                     qemuMonitorJSONObjectProperty *prop)
{
    int ret = -1;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    const char *tmp;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", path,
                                           "s:property", property,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    data = virJSONValueObjectGet(reply, "return");

    switch ((qemuMonitorJSONObjectPropertyType) prop->type) {
    /* Simple cases of boolean, int, long, uint, ulong, double, and string
     * will receive return value as part of {"return": xxx} statement
     */
    case QEMU_MONITOR_OBJECT_PROPERTY_BOOLEAN:
        ret = virJSONValueGetBoolean(data, &prop->val.b);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_INT:
        ret = virJSONValueGetNumberInt(data, &prop->val.iv);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_LONG:
        ret = virJSONValueGetNumberLong(data, &prop->val.l);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_UINT:
        ret = virJSONValueGetNumberUint(data, &prop->val.ui);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_ULONG:
        ret = virJSONValueGetNumberUlong(data, &prop->val.ul);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_DOUBLE:
        ret = virJSONValueGetNumberDouble(data, &prop->val.d);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_STRING:
        tmp = virJSONValueGetString(data);
        if (tmp)
            prop->val.str = g_strdup(tmp);
        if (tmp)
            ret = 0;
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_LAST:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("qom-get invalid object property type %d"),
                       prop->type);
        return -1;
        break;
    }

    if (ret == -1) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("qom-get reply was missing return data"));
        return -1;
    }

    return 0;
}


static int
qemuMonitorJSONGetStringListProperty(qemuMonitor *mon,
                                     const char *path,
                                     const char *property,
                                     char ***strList)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    *strList = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", path,
                                           "s:property", property,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    if (!(*strList = virJSONValueObjectGetStringArray(reply, "return")))
        return -1;

    return 0;
}


#define MAKE_SET_CMD(STRING, VALUE) \
    cmd = qemuMonitorJSONMakeCommand("qom-set", \
                                      "s:path", path, \
                                      "s:property", property, \
                                      STRING, VALUE, \
                                      NULL)
int qemuMonitorJSONSetObjectProperty(qemuMonitor *mon,
                                     const char *path,
                                     const char *property,
                                     qemuMonitorJSONObjectProperty *prop)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    switch ((qemuMonitorJSONObjectPropertyType) prop->type) {
    /* Simple cases of boolean, int, long, uint, ulong, double, and string
     * will receive return value as part of {"return": xxx} statement
     */
    case QEMU_MONITOR_OBJECT_PROPERTY_BOOLEAN:
        MAKE_SET_CMD("b:value", prop->val.b);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_INT:
        MAKE_SET_CMD("i:value", prop->val.iv);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_LONG:
        MAKE_SET_CMD("I:value", prop->val.l);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_UINT:
        MAKE_SET_CMD("u:value", prop->val.ui);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_ULONG:
        MAKE_SET_CMD("U:value", prop->val.ul);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_DOUBLE:
        MAKE_SET_CMD("d:value", prop->val.d);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_STRING:
        MAKE_SET_CMD("s:value", prop->val.str);
        break;
    case QEMU_MONITOR_OBJECT_PROPERTY_LAST:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("qom-set invalid object property type %d"),
                       prop->type);
        return -1;

    }
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}
#undef MAKE_SET_CMD


static int
qemuMonitorJSONParsePropsList(virJSONValue *cmd,
                              virJSONValue *reply,
                              const char *type,
                              char ***props)
{
    virJSONValue *data;
    g_auto(GStrv) proplist = NULL;
    size_t n = 0;
    size_t count = 0;
    size_t i;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    data = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(data);

    /* null-terminated list */
    proplist = g_new0(char *, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValue *child = virJSONValueArrayGet(data, i);
        const char *tmp;

        if (type &&
            STRNEQ_NULLABLE(virJSONValueObjectGetString(child, "type"), type))
            continue;

        if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("reply data was missing 'name'"));
            return -1;
        }

        proplist[count++] = g_strdup(tmp);
    }

    *props = g_steal_pointer(&proplist);
    return count;
}


static int
qemuMonitorJSONGetDevicePropsWorker(size_t pos G_GNUC_UNUSED,
                                    virJSONValue *item,
                                    void *opaque)
{
    const char *name = virJSONValueObjectGetString(item, "name");
    GHashTable *devices = opaque;

    if (!name) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("reply data was missing 'name'"));
        return -1;
    }

    if (virHashAddEntry(devices, name, item) < 0)
        return -1;

    return 0;
}


GHashTable *
qemuMonitorJSONGetDeviceProps(qemuMonitor *mon,
                              const char *device)
{
    g_autoptr(GHashTable) props = virHashNew(virJSONValueHashFree);
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("device-list-properties",
                                           "s:typename", device,
                                           NULL)))
        return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return NULL;

    /* return empty hash */
    if (qemuMonitorJSONHasError(reply, "DeviceNotFound"))
        return g_steal_pointer(&props);

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return NULL;

    if (virJSONValueArrayForeachSteal(virJSONValueObjectGetArray(reply, "return"),
                                      qemuMonitorJSONGetDevicePropsWorker,
                                      props) < 0)
        return NULL;

    return g_steal_pointer(&props);
}


int
qemuMonitorJSONGetObjectProps(qemuMonitor *mon,
                              const char *object,
                              char ***props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    *props = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-list-properties",
                                           "s:typename", object,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONHasError(reply, "DeviceNotFound"))
        return 0;

    return qemuMonitorJSONParsePropsList(cmd, reply, NULL, props);
}


char *
qemuMonitorJSONGetTargetArch(qemuMonitor *mon)
{
    const char *arch;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-target", NULL)))
        return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return NULL;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return NULL;

    data = virJSONValueObjectGetObject(reply, "return");

    if (!(arch = virJSONValueObjectGetString(data, "arch"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-target reply was missing arch data"));
        return NULL;
    }

    return g_strdup(arch);
}


int
qemuMonitorJSONGetMigrationCapabilities(qemuMonitor *mon,
                                        char ***capabilities)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *caps;
    g_auto(GStrv) list = NULL;
    size_t i;
    size_t n;

    *capabilities = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-migrate-capabilities",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    caps = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(caps);

    list = g_new0(char *, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValue *cap = virJSONValueArrayGet(caps, i);
        const char *name;

        if (!cap || virJSONValueGetType(cap) != VIR_JSON_TYPE_OBJECT) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing entry in migration capabilities list"));
            return -1;
        }

        if (!(name = virJSONValueObjectGetString(cap, "capability"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing migration capability name"));
            return -1;
        }

        list[i] = g_strdup(name);
    }

    *capabilities = g_steal_pointer(&list);
    return n;
}


int
qemuMonitorJSONSetMigrationCapabilities(qemuMonitor *mon,
                                        virJSONValue **caps)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("migrate-set-capabilities",
                                           "a:capabilities", caps,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


/**
 * qemuMonitorJSONGetGICCapabilities:
 * @mon: QEMU JSON monitor
 * @capabilities: where to store the GIC capabilities
 *
 * Use @mon to obtain information about the GIC capabilities for the
 * corresponding QEMU binary, and store them in @capabilities.
 *
 * If the QEMU binary has no GIC capabilities, or if GIC capabilities could
 * not be determined due to the lack of 'query-gic-capabilities' QMP command,
 * a NULL pointer will be returned instead of an empty array.
 *
 * Returns: the number of GIC capabilities obtained from the monitor,
 *          <0 on failure
 */
int
qemuMonitorJSONGetGICCapabilities(qemuMonitor *mon,
                                  virGICCapability **capabilities)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *caps;
    g_autofree virGICCapability *list = NULL;
    size_t i;
    size_t n;

    *capabilities = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-gic-capabilities",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    /* If the 'query-gic-capabilities' QMP command was not available
     * we simply successfully return zero capabilities.
     * This is the case for QEMU <2.6 and all non-ARM architectures */
    if (qemuMonitorJSONHasError(reply, "CommandNotFound"))
        return 0;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    caps = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(caps);

    /* If the returned array was empty we have to return successfully */
    if (n == 0)
        return 0;

    list = g_new0(virGICCapability, n);

    for (i = 0; i < n; i++) {
        virJSONValue *cap = virJSONValueArrayGet(caps, i);
        int version;
        bool kernel;
        bool emulated;

        if (!cap || virJSONValueGetType(cap) != VIR_JSON_TYPE_OBJECT) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing entry in GIC capabilities list"));
            return -1;
        }

        if (virJSONValueObjectGetNumberInt(cap, "version", &version) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing GIC version"));
            return -1;
        }

        if (virJSONValueObjectGetBoolean(cap, "kernel", &kernel) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing in-kernel GIC information"));
            return -1;
        }

        if (virJSONValueObjectGetBoolean(cap, "emulated", &emulated) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing emulated GIC information"));
            return -1;
        }

        list[i].version = version;
        if (kernel)
            list[i].implementation |= VIR_GIC_IMPLEMENTATION_KERNEL;
        if (emulated)
            list[i].implementation |= VIR_GIC_IMPLEMENTATION_EMULATED;
    }

    *capabilities = g_steal_pointer(&list);
    return n;
}


/**
 * qemuMonitorJSONGetSEVCapabilities:
 * @mon: qemu monitor object
 * @capabilities: pointer to pointer to a SEV capability structure to be filled
 *
 * This function queries and fills in AMD's SEV platform-specific data.
 * Note that from QEMU's POV both -object sev-guest and query-sev-capabilities
 * can be present even if SEV is not available, which basically leaves us with
 * checking for JSON "GenericError" in order to differentiate between
 * compiled-in support and actual SEV support on the platform.
 *
 * Returns -1 on error, 0 if SEV is not supported, and 1 if SEV is supported on
 * the platform.
 */
int
qemuMonitorJSONGetSEVCapabilities(qemuMonitor *mon,
                                  virSEVCapability **capabilities)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *caps;
    const char *pdh = NULL;
    const char *cert_chain = NULL;
    unsigned int cbitpos;
    unsigned int reduced_phys_bits;
    g_autoptr(virSEVCapability) capability = NULL;

    *capabilities = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-sev-capabilities",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    /* QEMU has only compiled-in support of SEV */
    if (qemuMonitorJSONHasError(reply, "GenericError"))
        return 0;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    caps = virJSONValueObjectGetObject(reply, "return");

    if (virJSONValueObjectGetNumberUint(caps, "cbitpos", &cbitpos) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-sev-capabilities reply was missing"
                         " 'cbitpos' field"));
        return -1;
    }

    if (virJSONValueObjectGetNumberUint(caps, "reduced-phys-bits",
                                        &reduced_phys_bits) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-sev-capabilities reply was missing"
                         " 'reduced-phys-bits' field"));
        return -1;
    }

    if (!(pdh = virJSONValueObjectGetString(caps, "pdh"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-sev-capabilities reply was missing"
                         " 'pdh' field"));
        return -1;
    }

    if (!(cert_chain = virJSONValueObjectGetString(caps, "cert-chain"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-sev-capabilities reply was missing"
                         " 'cert-chain' field"));
        return -1;
    }

    capability = g_new0(virSEVCapability, 1);

    capability->pdh = g_strdup(pdh);

    capability->cert_chain = g_strdup(cert_chain);

    capability->cbitpos = cbitpos;
    capability->reduced_phys_bits = reduced_phys_bits;
    *capabilities = g_steal_pointer(&capability);
    return 1;
}

static virJSONValue *
qemuMonitorJSONBuildInetSocketAddress(const char *host,
                                      const char *port)
{
    g_autoptr(virJSONValue) addr = NULL;
    g_autoptr(virJSONValue) data = NULL;

    if (virJSONValueObjectAdd(&data,
                              "s:host", host,
                              "s:port", port,
                              NULL) < 0)
        return NULL;

    if (virJSONValueObjectAdd(&addr,
                              "s:type", "inet",
                              "a:data", &data,
                              NULL) < 0)
        return NULL;

    return g_steal_pointer(&addr);
}

static virJSONValue *
qemuMonitorJSONBuildUnixSocketAddress(const char *path)
{
    g_autoptr(virJSONValue) addr = NULL;
    g_autoptr(virJSONValue) data = NULL;

    if (virJSONValueObjectAdd(&data, "s:path", path, NULL) < 0)
        return NULL;

    if (virJSONValueObjectAdd(&addr,
                              "s:type", "unix",
                              "a:data", &data, NULL) < 0)
        return NULL;

    return g_steal_pointer(&addr);
}


static virJSONValue *
qemuMonitorJSONBuildFDSocketAddress(const char *fdname)
{
    g_autoptr(virJSONValue) addr = NULL;
    g_autoptr(virJSONValue) data = NULL;

    if (virJSONValueObjectAdd(&data, "s:str", fdname, NULL) < 0)
        return NULL;

    if (virJSONValueObjectAdd(&addr,
                              "s:type", "fd",
                              "a:data", &data, NULL) < 0)
        return NULL;

    return g_steal_pointer(&addr);
}
int
qemuMonitorJSONNBDServerStart(qemuMonitor *mon,
                              const virStorageNetHostDef *server,
                              const char *tls_alias)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) addr = NULL;
    g_autofree char *port_str = NULL;

    switch ((virStorageNetHostTransport)server->transport) {
    case VIR_STORAGE_NET_HOST_TRANS_TCP:
        port_str = g_strdup_printf("%u", server->port);
        addr = qemuMonitorJSONBuildInetSocketAddress(server->name, port_str);
        break;
    case VIR_STORAGE_NET_HOST_TRANS_UNIX:
        addr = qemuMonitorJSONBuildUnixSocketAddress(server->socket);
        break;
    case VIR_STORAGE_NET_HOST_TRANS_RDMA:
    case VIR_STORAGE_NET_HOST_TRANS_LAST:
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("invalid server address"));
        return -1;
    }
    if (!addr)
        return -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("nbd-server-start",
                                           "a:addr", &addr,
                                           "S:tls-creds", tls_alias,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int
qemuMonitorJSONNBDServerAdd(qemuMonitor *mon,
                            const char *deviceID,
                            const char *export,
                            bool writable,
                            const char *bitmap)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    /* Note: bitmap must be NULL if QEMU_CAPS_NBD_BITMAP is lacking */
    if (!(cmd = qemuMonitorJSONMakeCommand("nbd-server-add",
                                           "s:device", deviceID,
                                           "S:name", export,
                                           "b:writable", writable,
                                           "S:bitmap", bitmap,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

int
qemuMonitorJSONNBDServerStop(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("nbd-server-stop",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockExportAdd(qemuMonitor *mon,
                              virJSONValue **props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommandInternal("block-export-add", props)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


static int
qemuMonitorJSONGetStringArray(qemuMonitor *mon,
                              const char *qmpCmd,
                              char ***array)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    *array = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand(qmpCmd, NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONHasError(reply, "CommandNotFound"))
        return 0;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    if (!(*array = virJSONValueObjectGetStringArray(reply, "return")))
        return -1;

    return 0;
}

int qemuMonitorJSONGetTPMModels(qemuMonitor *mon,
                                char ***tpmmodels)
{
    return qemuMonitorJSONGetStringArray(mon, "query-tpm-models", tpmmodels);
}


int qemuMonitorJSONGetTPMTypes(qemuMonitor *mon,
                               char ***tpmtypes)
{
    return qemuMonitorJSONGetStringArray(mon, "query-tpm-types", tpmtypes);
}


static virJSONValue *
qemuMonitorJSONAttachCharDevGetProps(const char *chrID,
                                     const virDomainChrSourceDef *chr)
{
    qemuDomainChrSourcePrivate *chrSourcePriv = QEMU_DOMAIN_CHR_SOURCE_PRIVATE(chr);
    g_autoptr(virJSONValue) props = NULL;
    g_autoptr(virJSONValue) backend = NULL;
    g_autoptr(virJSONValue) backendData = virJSONValueNewObject();
    const char *backendType = NULL;

    switch ((virDomainChrType)chr->type) {
    case VIR_DOMAIN_CHR_TYPE_NULL:
    case VIR_DOMAIN_CHR_TYPE_VC:
    case VIR_DOMAIN_CHR_TYPE_PTY:
        backendType = virDomainChrTypeToString(chr->type);
        break;

    case VIR_DOMAIN_CHR_TYPE_FILE: {
        const char *path = chr->data.file.path;
        virTristateSwitch append = chr->data.file.append;
        backendType = "file";

        if (chrSourcePriv->sourcefd) {
            path = qemuFDPassGetPath(chrSourcePriv->sourcefd);
            append = VIR_TRISTATE_SWITCH_ON;
        }

        if (virJSONValueObjectAdd(&backendData,
                                  "s:out", path,
                                  "T:append", append,
                                  NULL) < 0)
            return NULL;
    }
        break;

    case VIR_DOMAIN_CHR_TYPE_DEV:
        if (STRPREFIX(chrID, "parallel"))
            backendType = "parallel";
        else
            backendType = "serial";

        if (virJSONValueObjectAdd(&backendData,
                                  "s:device", chr->data.file.path,
                                  NULL) < 0)
            return NULL;

        break;

    case VIR_DOMAIN_CHR_TYPE_UNIX:
    case VIR_DOMAIN_CHR_TYPE_TCP: {
        const char *tlsalias = NULL;
        g_autoptr(virJSONValue) addr = NULL;
        virTristateBool waitval = VIR_TRISTATE_BOOL_ABSENT;
        virTristateBool telnet = VIR_TRISTATE_BOOL_ABSENT;
        bool server = false;
        int reconnect = -1;

        backendType = "socket";

        if (chr->type == VIR_DOMAIN_CHR_TYPE_TCP) {
            telnet = virTristateBoolFromBool(chr->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET);

            if (chr->data.tcp.listen) {
                server = true;
                waitval = VIR_TRISTATE_BOOL_NO;
            }

            tlsalias = chrSourcePriv->tlsCredsAlias;

            if (!(addr = qemuMonitorJSONBuildInetSocketAddress(chr->data.tcp.host,
                                                               chr->data.tcp.service)))
                return NULL;

            if (chr->data.tcp.reconnect.enabled == VIR_TRISTATE_BOOL_YES)
                reconnect = chr->data.tcp.reconnect.timeout;
            else if (chr->data.tcp.reconnect.enabled == VIR_TRISTATE_BOOL_NO)
                reconnect = 0;
        } else {
            if (chr->data.nix.listen) {
                server = true;
                waitval = VIR_TRISTATE_BOOL_NO;
            }

            if (chrSourcePriv->sourcefd) {
                if (!(addr = qemuMonitorJSONBuildFDSocketAddress(qemuFDPassGetPath(chrSourcePriv->sourcefd))))
                    return NULL;
            } else {
                if (!(addr = qemuMonitorJSONBuildUnixSocketAddress(chr->data.nix.path)))
                    return NULL;

                if (chr->data.nix.reconnect.enabled == VIR_TRISTATE_BOOL_YES)
                    reconnect = chr->data.tcp.reconnect.timeout;
                else if (chr->data.nix.reconnect.enabled == VIR_TRISTATE_BOOL_NO)
                    reconnect = 0;
            }
        }

        if (virJSONValueObjectAdd(&backendData,
                                  "a:addr", &addr,
                                  "T:wait", waitval,
                                  "T:telnet", telnet,
                                  "b:server", server,
                                  "S:tls-creds", tlsalias,
                                  "k:reconnect", reconnect,
                                  NULL) < 0)
            return NULL;
    }
        break;

    case VIR_DOMAIN_CHR_TYPE_UDP: {
        g_autoptr(virJSONValue) local = NULL;
        g_autoptr(virJSONValue) remote = NULL;

        backendType = "udp";

        if (!(remote = qemuMonitorJSONBuildInetSocketAddress(NULLSTR_EMPTY(chr->data.udp.connectHost),
                                                             chr->data.udp.connectService)))
            return NULL;

        if (chr->data.udp.bindHost || chr->data.udp.bindService) {
            if (!(local = qemuMonitorJSONBuildInetSocketAddress(NULLSTR_EMPTY(chr->data.udp.bindHost),
                                                                NULLSTR_EMPTY(chr->data.udp.bindService))))
                return NULL;
        }

        if (virJSONValueObjectAdd(&backendData,
                                  "a:remote", &remote,
                                  "A:local", &local,
                                  NULL) < 0)
            return NULL;
    }
        break;

    case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
        backendType = "spicevmc";

        if (virJSONValueObjectAdd(&backendData,
                                  "s:type", virDomainChrSpicevmcTypeToString(chr->data.spicevmc),
                                  NULL) < 0)
            return NULL;

        break;

    case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
    case VIR_DOMAIN_CHR_TYPE_PIPE:
    case VIR_DOMAIN_CHR_TYPE_STDIO:
    case VIR_DOMAIN_CHR_TYPE_NMDM:
        virReportError(VIR_ERR_OPERATION_FAILED,
                       _("Hotplug unsupported for char device type '%s'"),
                       virDomainChrTypeToString(chr->type));
        return NULL;

    case VIR_DOMAIN_CHR_TYPE_LAST:
    default:
        virReportEnumRangeError(virDomainChrType, chr->type);
        return NULL;
    }

    if (chr->logfile) {
        const char *path = chr->logfile;
        virTristateSwitch append = chr->logappend;

        if (chrSourcePriv->logfd) {
            path = qemuFDPassGetPath(chrSourcePriv->logfd);
            append = VIR_TRISTATE_SWITCH_ON;
        }

        if (virJSONValueObjectAdd(&backendData,
                                  "s:logfile", path,
                                  "T:logappend", append,
                                  NULL) < 0)
            return NULL;
    }

    if (virJSONValueObjectAdd(&backend,
                              "s:type", backendType,
                              "A:data", &backendData,
                              NULL) < 0)
        return NULL;

    if (virJSONValueObjectAdd(&props,
                              "s:id", chrID,
                              "a:backend", &backend,
                              NULL) < 0)
        return NULL;

    return g_steal_pointer(&props);
}


int
qemuMonitorJSONAttachCharDev(qemuMonitor *mon,
                             const char *chrID,
                             virDomainChrSourceDef *chr)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) props = NULL;

    if (!(props = qemuMonitorJSONAttachCharDevGetProps(chrID, chr)))
        return -1;

    if (!(cmd = qemuMonitorJSONMakeCommandInternal("chardev-add", &props)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (chr->type == VIR_DOMAIN_CHR_TYPE_PTY) {
        if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
            return -1;
    } else {
        if (qemuMonitorJSONCheckError(cmd, reply) < 0)
            return -1;
    }

    if (chr->type == VIR_DOMAIN_CHR_TYPE_PTY) {
        virJSONValue *data = virJSONValueObjectGetObject(reply, "return");
        const char *path;

        if (!(path = virJSONValueObjectGetString(data, "pty"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("chardev-add reply was missing pty path"));
            return -1;
        }

        chr->data.file.path = g_strdup(path);
    }

    return 0;
}

int
qemuMonitorJSONDetachCharDev(qemuMonitor *mon,
                             const char *chrID)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("chardev-remove",
                                           "s:id", chrID,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONGetDeviceAliases(qemuMonitor *mon,
                                char ***aliases)
{
    qemuMonitorJSONListPath **paths = NULL;
    char **alias;
    int ret = -1;
    size_t i;
    int n;

    *aliases = NULL;

    n = qemuMonitorJSONGetObjectListPaths(mon, "/machine/peripheral", &paths);
    if (n < 0)
        return -1;

    *aliases = g_new0(char *, n + 1);

    alias = *aliases;
    for (i = 0; i < n; i++) {
        if (STRPREFIX(paths[i]->type, "child<")) {
            *alias = g_steal_pointer(&paths[i]->name);
            alias++;
        }
    }

    ret = 0;

    for (i = 0; i < n; i++)
        qemuMonitorJSONListPathFree(paths[i]);
    VIR_FREE(paths);
    return ret;
}


static int
qemuMonitorJSONParseCPUx86FeatureWord(virJSONValue *data,
                                      virCPUx86CPUID *cpuid)
{
    const char *reg;
    unsigned long long eax_in;
    unsigned long long ecx_in = 0;
    unsigned long long features;

    memset(cpuid, 0, sizeof(*cpuid));

    if (!(reg = virJSONValueObjectGetString(data, "cpuid-register"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("missing cpuid-register in CPU data"));
        return -1;
    }
    if (virJSONValueObjectGetNumberUlong(data, "cpuid-input-eax", &eax_in) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("missing or invalid cpuid-input-eax in CPU data"));
        return -1;
    }
    ignore_value(virJSONValueObjectGetNumberUlong(data, "cpuid-input-ecx",
                                                  &ecx_in));
    if (virJSONValueObjectGetNumberUlong(data, "features", &features) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("missing or invalid features in CPU data"));
        return -1;
    }

    cpuid->eax_in = eax_in;
    cpuid->ecx_in = ecx_in;
    if (STREQ(reg, "EAX")) {
        cpuid->eax = features;
    } else if (STREQ(reg, "EBX")) {
        cpuid->ebx = features;
    } else if (STREQ(reg, "ECX")) {
        cpuid->ecx = features;
    } else if (STREQ(reg, "EDX")) {
        cpuid->edx = features;
    } else {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unknown CPU register '%s'"), reg);
        return -1;
    }

    return 0;
}


static virCPUData *
qemuMonitorJSONParseCPUx86Features(virJSONValue *data)
{
    g_autoptr(virCPUData) cpudata = NULL;
    virCPUx86DataItem item = { 0 };
    size_t i;

    if (!(cpudata = virCPUDataNew(VIR_ARCH_X86_64)))
        return NULL;

    item.type = VIR_CPU_X86_DATA_CPUID;
    for (i = 0; i < virJSONValueArraySize(data); i++) {
        if (qemuMonitorJSONParseCPUx86FeatureWord(virJSONValueArrayGet(data, i),
                                                  &item.data.cpuid) < 0 ||
            virCPUx86DataAdd(cpudata, &item) < 0)
            return NULL;
    }

    return g_steal_pointer(&cpudata);
}


static int
qemuMonitorJSONGetCPUx86Data(qemuMonitor *mon,
                             const char *cpuQOMPath,
                             const char *property,
                             virCPUData **cpudata)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", cpuQOMPath,
                                           "s:property", property,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    data = virJSONValueObjectGetArray(reply, "return");
    if (!(*cpudata = qemuMonitorJSONParseCPUx86Features(data)))
        return -1;

    return 0;
}


/*
 * Returns -1 on error, 0 if QEMU does not support reporting CPUID features
 * of a guest CPU, and 1 if the feature is supported.
 */
static int
qemuMonitorJSONCheckCPUx86(qemuMonitor *mon,
                           const char *cpuQOMPath)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    size_t i;
    size_t n;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-list",
                                           "s:path", cpuQOMPath,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if ((data = virJSONValueObjectGet(reply, "error"))) {
        const char *klass = virJSONValueObjectGetString(data, "class");
        if (STREQ_NULLABLE(klass, "DeviceNotFound") ||
            STREQ_NULLABLE(klass, "CommandNotFound")) {
            return 0;
        }
    }

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    data = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(data);

    for (i = 0; i < n; i++) {
        virJSONValue *element = virJSONValueArrayGet(data, i);
        if (STREQ_NULLABLE(virJSONValueObjectGetString(element, "name"),
                           "feature-words"))
            return 1;
    }

    return 0;
}


/**
 * qemuMonitorJSONGetGuestCPUx86:
 * @mon: Pointer to the monitor
 * @cpuQOMPath: QOM path of a CPU to probe
 * @data: returns the cpu data of the guest
 * @disabled: returns the CPU data for features which were disabled by QEMU
 *
 * Retrieve the definition of the guest CPU from a running qemu instance.
 *
 * Returns 0 on success, -2 if guest doesn't support this feature,
 * -1 on other errors.
 */
int
qemuMonitorJSONGetGuestCPUx86(qemuMonitor *mon,
                              const char *cpuQOMPath,
                              virCPUData **data,
                              virCPUData **disabled)
{
    g_autoptr(virCPUData) cpuEnabled = NULL;
    g_autoptr(virCPUData) cpuDisabled = NULL;
    int rc;

    if ((rc = qemuMonitorJSONCheckCPUx86(mon, cpuQOMPath)) < 0)
        return -1;
    else if (!rc)
        return -2;

    if (qemuMonitorJSONGetCPUx86Data(mon, cpuQOMPath, "feature-words",
                                     &cpuEnabled) < 0)
        return -1;

    if (disabled &&
        qemuMonitorJSONGetCPUx86Data(mon, cpuQOMPath, "filtered-features",
                                     &cpuDisabled) < 0)
        return -1;

    *data = g_steal_pointer(&cpuEnabled);
    if (disabled)
        *disabled = g_steal_pointer(&cpuDisabled);
    return 0;
}


static int
qemuMonitorJSONGetCPUProperties(qemuMonitor *mon,
                                const char *cpuQOMPath,
                                char ***props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    *props = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-list",
                                           "s:path", cpuQOMPath,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONHasError(reply, "DeviceNotFound"))
        return 0;

    return qemuMonitorJSONParsePropsList(cmd, reply, "bool", props);
}


static int
qemuMonitorJSONGetCPUData(qemuMonitor *mon,
                          const char *cpuQOMPath,
                          qemuMonitorCPUFeatureTranslationCallback translate,
                          void *opaque,
                          virCPUData *data)
{
    qemuMonitorJSONObjectProperty prop = { .type = QEMU_MONITOR_OBJECT_PROPERTY_BOOLEAN };
    g_auto(GStrv) props = NULL;
    char **p;

    if (qemuMonitorJSONGetCPUProperties(mon, cpuQOMPath, &props) < 0)
        return -1;

    for (p = props; p && *p; p++) {
        const char *name = *p;

        if (qemuMonitorJSONGetObjectProperty(mon, cpuQOMPath, name, &prop) < 0)
            return -1;

        if (!prop.val.b)
            continue;

        if (translate)
            name = translate(name, opaque);

        if (virCPUDataAddFeature(data, name) < 0)
            return -1;
    }

    return 0;
}


static int
qemuMonitorJSONGetCPUDataDisabled(qemuMonitor *mon,
                                  const char *cpuQOMPath,
                                  qemuMonitorCPUFeatureTranslationCallback translate,
                                  void *opaque,
                                  virCPUData *data)
{
    g_auto(GStrv) props = NULL;
    char **p;

    if (qemuMonitorJSONGetStringListProperty(mon, cpuQOMPath,
                                             "unavailable-features", &props) < 0)
        return -1;

    for (p = props; p && *p; p++) {
        const char *name = *p;

        if (translate)
            name = translate(name, opaque);

        if (virCPUDataAddFeature(data, name) < 0)
            return -1;
    }

    return 0;
}


/**
 * qemuMonitorJSONGetGuestCPU:
 * @mon: Pointer to the monitor
 * @arch: CPU architecture
 * @cpuQOMPath: QOM path of a CPU to probe
 * @translate: callback for translating CPU feature names from QEMU to libvirt
 * @opaque: data for @translate callback
 * @enabled: returns the CPU data for all enabled features
 * @disabled: returns the CPU data for features which we asked for
 *      (either explicitly or via a named CPU model) but QEMU disabled them
 *
 * Retrieve the definition of the guest CPU from a running QEMU instance.
 *
 * Returns 0 on success, -1 on error.
 */
int
qemuMonitorJSONGetGuestCPU(qemuMonitor *mon,
                           virArch arch,
                           const char *cpuQOMPath,
                           qemuMonitorCPUFeatureTranslationCallback translate,
                           void *opaque,
                           virCPUData **enabled,
                           virCPUData **disabled)
{
    g_autoptr(virCPUData) cpuEnabled = NULL;
    g_autoptr(virCPUData) cpuDisabled = NULL;

    if (!(cpuEnabled = virCPUDataNew(arch)) ||
        !(cpuDisabled = virCPUDataNew(arch)))
        return -1;

    if (qemuMonitorJSONGetCPUData(mon, cpuQOMPath, translate, opaque, cpuEnabled) < 0)
        return -1;

    if (disabled &&
        qemuMonitorJSONGetCPUDataDisabled(mon, cpuQOMPath, translate, opaque, cpuDisabled) < 0)
        return -1;

    *enabled = g_steal_pointer(&cpuEnabled);
    if (disabled)
        *disabled = g_steal_pointer(&cpuDisabled);

    return 0;
}


int
qemuMonitorJSONRTCResetReinjection(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("rtc-reset-reinjection",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

/**
 * Query and parse returned array of data such as:
 *
 *  {u'return': [{u'id': u'iothread1', u'thread-id': 30992}, \
 *               {u'id': u'iothread2', u'thread-id': 30993}]}
 */
int
qemuMonitorJSONGetIOThreads(qemuMonitor *mon,
                            qemuMonitorIOThreadInfo ***iothreads,
                            int *niothreads)
{
    int ret = -1;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;
    qemuMonitorIOThreadInfo **infolist = NULL;
    size_t n = 0;
    size_t i;

    *iothreads = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-iothreads", NULL)))
        return ret;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        goto cleanup;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        goto cleanup;

    data = virJSONValueObjectGetArray(reply, "return");
    n = virJSONValueArraySize(data);

    /* null-terminated list */
    infolist = g_new0(qemuMonitorIOThreadInfo *, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValue *child = virJSONValueArrayGet(data, i);
        const char *tmp;
        qemuMonitorIOThreadInfo *info;

        if (!(tmp = virJSONValueObjectGetString(child, "id"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-iothreads reply data was missing 'id'"));
            goto cleanup;
        }

        if (!STRPREFIX(tmp, "iothread"))
            continue;

        info = g_new0(qemuMonitorIOThreadInfo, 1);

        infolist[i] = info;

        if (virStrToLong_ui(tmp + strlen("iothread"),
                            NULL, 10, &info->iothread_id) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("failed to find iothread id for '%s'"),
                           tmp);
            goto cleanup;
        }

        if (virJSONValueObjectGetNumberInt(child, "thread-id",
                                           &info->thread_id) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-iothreads reply has malformed "
                             "'thread-id' data"));
            goto cleanup;
        }

        /* Fetch poll values (since QEMU 2.9 ) if available. QEMU
         * stores these values as int64_t's; however, the qapi type
         * is an int. The qapi/misc.json also mis-describes the grow
         * and shrink values as pure add/remove values. The source
         * util/aio-posix.c function aio_poll uses them as a factor
         * or divisor in it's calculation. We will fetch and store
         * them as defined in our structures. */
        if (virJSONValueObjectGetNumberUlong(child, "poll-max-ns",
                                             &info->poll_max_ns) == 0 &&
            virJSONValueObjectGetNumberUint(child, "poll-grow",
                                            &info->poll_grow) == 0 &&
            virJSONValueObjectGetNumberUint(child, "poll-shrink",
                                            &info->poll_shrink) == 0)
            info->poll_valid = true;
    }

    *niothreads = n;
    *iothreads = g_steal_pointer(&infolist);
    ret = 0;

 cleanup:
    if (infolist) {
        for (i = 0; i < n; i++)
            VIR_FREE(infolist[i]);
        VIR_FREE(infolist);
    }
    return ret;
}


int
qemuMonitorJSONSetIOThread(qemuMonitor *mon,
                           qemuMonitorIOThreadInfo *iothreadInfo)
{
    g_autofree char *path = NULL;
    qemuMonitorJSONObjectProperty prop;

    path = g_strdup_printf("/objects/iothread%u", iothreadInfo->iothread_id);

#define VIR_IOTHREAD_SET_PROP(propName, propVal) \
    if (iothreadInfo->set_##propVal) { \
        memset(&prop, 0, sizeof(qemuMonitorJSONObjectProperty)); \
        prop.type = QEMU_MONITOR_OBJECT_PROPERTY_INT; \
        prop.val.iv = iothreadInfo->propVal; \
        if (qemuMonitorJSONSetObjectProperty(mon, path, propName, &prop) < 0) \
            return -1; \
    }

    VIR_IOTHREAD_SET_PROP("poll-max-ns", poll_max_ns);
    VIR_IOTHREAD_SET_PROP("poll-grow", poll_grow);
    VIR_IOTHREAD_SET_PROP("poll-shrink", poll_shrink);

#undef VIR_IOTHREAD_SET_PROP

    return 0;
}


int
qemuMonitorJSONGetMemoryDeviceInfo(qemuMonitor *mon,
                                   GHashTable *info)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data = NULL;
    size_t i;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-memory-devices", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    data = virJSONValueObjectGetArray(reply, "return");

    for (i = 0; i < virJSONValueArraySize(data); i++) {
        virJSONValue *elem = virJSONValueArrayGet(data, i);
        g_autofree qemuMonitorMemoryDeviceInfo *meminfo = NULL;
        virJSONValue *dimminfo;
        const char *devalias;
        const char *type;

        if (!(type = virJSONValueObjectGetString(elem, "type"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-memory-devices reply data doesn't contain "
                             "enum type discriminator"));
            return -1;
        }

        if (!(dimminfo = virJSONValueObjectGetObject(elem, "data"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-memory-devices reply data doesn't "
                             "contain enum data"));
            return -1;
        }

        /* While 'id' attribute is marked as optional in QEMU's QAPI
         * specification, Libvirt always sets it. Thus we can fail if not
         * present. */
        if (!(devalias = virJSONValueObjectGetString(dimminfo, "id"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("dimm memory info data is missing 'id'"));
            return -1;
        }

        meminfo = g_new0(qemuMonitorMemoryDeviceInfo, 1);

        /* dimm memory devices */
        if (STREQ(type, "dimm")) {
            if (virJSONValueObjectGetNumberUlong(dimminfo, "addr",
                                                 &meminfo->address) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing addr in dimm memory info"));
                return -1;
            }

            if (virJSONValueObjectGetNumberUint(dimminfo, "slot",
                                                &meminfo->slot) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing slot in dimm memory info"));
                return -1;
            }

            if (virJSONValueObjectGetBoolean(dimminfo, "hotplugged",
                                             &meminfo->hotplugged) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing hotplugged in dimm memory info"));
                return -1;

            }

            if (virJSONValueObjectGetBoolean(dimminfo, "hotpluggable",
                                             &meminfo->hotpluggable) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing hotpluggable in dimm memory info"));
                return -1;

            }

        } else if (STREQ(type, "virtio-mem")) {
            if (virJSONValueObjectGetNumberUlong(dimminfo, "size",
                                                 &meminfo->size) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing size in virtio memory info"));
                return -1;
            }
        } else {
            /* type not handled yet */
            continue;
        }

        if (virHashAddEntry(info, devalias, meminfo) < 0)
            return -1;

        meminfo = NULL;
    }

    return 0;
}


/**
 * Search for a QOM object link by alias and name.
 *
 * For @alias and @name, this function tries to find QOM object named @name
 * with id @alias in /machine/peripheral.
 *
 * Returns:
 *   0  - Found
 *  -1  - Error - bail out
 *  -2  - Not found
 */
static int
qemuMonitorJSONFindObjectPathByAlias(qemuMonitor *mon,
                                     const char *name,
                                     const char *alias,
                                     char **path)
{
    qemuMonitorJSONListPath **paths = NULL;
    g_autofree char *child = NULL;
    int npaths;
    int ret = -1;
    size_t i;

    npaths = qemuMonitorJSONGetObjectListPaths(mon, "/machine/peripheral", &paths);
    if (npaths < 0)
        return -1;

    child = g_strdup_printf("child<%s>", name);

    for (i = 0; i < npaths; i++) {
        if (STREQ(paths[i]->name, alias) && STREQ(paths[i]->type, child)) {
            *path = g_strdup_printf("/machine/peripheral/%s", alias);

            ret = 0;
            goto cleanup;
        }
    }

    ret = -2;

 cleanup:
    for (i = 0; i < npaths; i++)
        qemuMonitorJSONListPathFree(paths[i]);
    VIR_FREE(paths);
    return ret;
}


/**
 * Recursively search for a QOM object link only by name.
 *
 * For @name, this function finds the first QOM object
 * named @name, recursively going through all the "child<>"
 * entries, starting from @curpath.
 *
 * Returns:
 *   0  - Found
 *  -1  - Error - bail out
 *  -2  - Not found
 */
static int
qemuMonitorJSONFindObjectPathByName(qemuMonitor *mon,
                                    const char *curpath,
                                    const char *name,
                                    char **path)
{
    ssize_t i, npaths = 0;
    int ret = -2;
    qemuMonitorJSONListPath **paths = NULL;

    VIR_DEBUG("Searching for '%s' Object Path starting at '%s'", name, curpath);

    npaths = qemuMonitorJSONGetObjectListPaths(mon, curpath, &paths);
    if (npaths < 0)
        goto cleanup;

    for (i = 0; i < npaths && ret == -2; i++) {

        if (STREQ_NULLABLE(paths[i]->type, name)) {
            VIR_DEBUG("Path to '%s' is '%s/%s'", name, curpath, paths[i]->name);
            ret = 0;
            *path = g_strdup_printf("%s/%s", curpath, paths[i]->name);
            goto cleanup;
        }

        /* Type entries that begin with "child<" are a branch that can be
         * traversed looking for more entries
         */
        if (paths[i]->type && STRPREFIX(paths[i]->type, "child<")) {
            g_autofree char *nextpath = g_strdup_printf("%s/%s", curpath, paths[i]->name);

            ret = qemuMonitorJSONFindObjectPathByName(mon, nextpath, name, path);
        }
    }

 cleanup:
    for (i = 0; i < npaths; i++)
        qemuMonitorJSONListPathFree(paths[i]);
    VIR_FREE(paths);
    return ret;
}


/**
 * Recursively search for a QOM object link.
 *
 * For @name and @alias, this function finds the first QOM object.
 * The search is done at first by @alias and @name and if nothing was found
 * it continues recursively only with @name.
 *
 * Returns:
 *   0  - Found
 *  -1  - Error
 *  -2  - Not found
 */
int
qemuMonitorJSONFindLinkPath(qemuMonitor *mon,
                            const char *name,
                            const char *alias,
                            char **path)
{
    g_autofree char *linkname = NULL;
    int ret = -1;

    if (alias) {
        ret = qemuMonitorJSONFindObjectPathByAlias(mon, name, alias, path);
        if (ret == -1 || ret == 0)
            return ret;
    }

    linkname = g_strdup_printf("link<%s>", name);

    ret = qemuMonitorJSONFindObjectPathByName(mon, "/", linkname, path);
    return ret;
}


int
qemuMonitorJSONMigrateIncoming(qemuMonitor *mon,
                               const char *uri)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("migrate-incoming",
                                           "s:uri", uri,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    return qemuMonitorJSONCheckError(cmd, reply);
}


int
qemuMonitorJSONMigrateStartPostCopy(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("migrate-start-postcopy", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    return qemuMonitorJSONCheckError(cmd, reply);
}


int
qemuMonitorJSONMigrateContinue(qemuMonitor *mon,
                               qemuMonitorMigrationStatus status)
{
    const char *statusStr = qemuMonitorMigrationStatusTypeToString(status);
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("migrate-continue",
                                           "s:state", statusStr,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    return qemuMonitorJSONCheckError(cmd, reply);
}


int
qemuMonitorJSONGetRTCTime(qemuMonitor *mon,
                          struct tm *tm)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", "/machine",
                                           "s:property", "rtc-time",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    data = virJSONValueObjectGet(reply, "return");

    if (virJSONValueObjectGetNumberInt(data, "tm_year", &tm->tm_year) < 0 ||
        virJSONValueObjectGetNumberInt(data, "tm_mon", &tm->tm_mon) < 0 ||
        virJSONValueObjectGetNumberInt(data, "tm_mday", &tm->tm_mday) < 0 ||
        virJSONValueObjectGetNumberInt(data, "tm_hour", &tm->tm_hour) < 0 ||
        virJSONValueObjectGetNumberInt(data, "tm_min", &tm->tm_min) < 0 ||
        virJSONValueObjectGetNumberInt(data, "tm_sec", &tm->tm_sec) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("qemu returned malformed time"));
        return -1;
    }

    return 0;
}


void
qemuMonitorQueryHotpluggableCpusFree(struct qemuMonitorQueryHotpluggableCpusEntry *entries,
                                     size_t nentries)
{
    struct qemuMonitorQueryHotpluggableCpusEntry *entry;
    size_t i;

    if (!entries)
        return;

    for (i = 0; i < nentries; i++) {
        entry = entries + i;

        g_free(entry->type);
        g_free(entry->qom_path);
        g_free(entry->alias);
        virJSONValueFree(entry->props);
    }

    g_free(entries);
}


/**
 * [{
 *    "props": {
 *      "core-id": 0,
 *      "thread-id": 0,
 *      "socket-id": 0
 *    },
 *    "vcpus-count": 1,
 *    "qom-path": "/machine/unattached/device[0]",
 *    "type": "qemu64-x86_64-cpu"
 *  },
 *  {...}
 * ]
 */
static int
qemuMonitorJSONProcessHotpluggableCpusReply(virJSONValue *vcpu,
                                            struct qemuMonitorQueryHotpluggableCpusEntry *entry)
{
    virJSONValue *props;
    const char *tmp;

    if (!(tmp = virJSONValueObjectGetString(vcpu, "type"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-hotpluggable-cpus didn't return device type"));
        return -1;
    }

    entry->type = g_strdup(tmp);

    if (virJSONValueObjectGetNumberUint(vcpu, "vcpus-count", &entry->vcpus) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-hotpluggable-cpus didn't return vcpus-count"));
        return -1;
    }

    if (!(props = virJSONValueObjectGetObject(vcpu, "props"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-hotpluggable-cpus didn't return device props"));
        return -1;
    }

    if (!(entry->props = virJSONValueCopy(props)))
        return -1;

    entry->node_id = -1;
    entry->socket_id = -1;
    entry->die_id = -1;
    entry->core_id = -1;
    entry->thread_id = -1;

    ignore_value(virJSONValueObjectGetNumberInt(props, "node-id", &entry->node_id));
    ignore_value(virJSONValueObjectGetNumberInt(props, "socket-id", &entry->socket_id));
    ignore_value(virJSONValueObjectGetNumberInt(props, "die-id", &entry->die_id));
    ignore_value(virJSONValueObjectGetNumberInt(props, "core-id", &entry->core_id));
    ignore_value(virJSONValueObjectGetNumberInt(props, "thread-id", &entry->thread_id));

    if (entry->node_id == -1 && entry->socket_id == -1 &&
        entry->core_id == -1 && entry->thread_id == -1) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-hotpluggable-cpus entry doesn't report "
                         "topology information"));
        return -1;
    }

    /* qom path is not present unless the vCPU is online */
    if ((tmp = virJSONValueObjectGetString(vcpu, "qom-path"))) {
        entry->qom_path = g_strdup(tmp);

        /* alias is the part after last slash having a "vcpu" prefix */
        if ((tmp = strrchr(tmp, '/')) && STRPREFIX(tmp + 1, "vcpu"))
            entry->alias = g_strdup(tmp + 1);
    }

    return 0;
}


static int
qemuMonitorQueryHotpluggableCpusEntrySort(const void *p1,
                                          const void *p2)
{
    const struct qemuMonitorQueryHotpluggableCpusEntry *a = p1;
    const struct qemuMonitorQueryHotpluggableCpusEntry *b = p2;

    if (a->socket_id != b->socket_id)
        return a->socket_id - b->socket_id;

    if (a->die_id != b->die_id)
        return a->die_id - b->die_id;

    if (a->core_id != b->core_id)
        return a->core_id - b->core_id;

    return a->thread_id - b->thread_id;
}


int
qemuMonitorJSONGetHotpluggableCPUs(qemuMonitor *mon,
                                   struct qemuMonitorQueryHotpluggableCpusEntry **entries,
                                   size_t *nentries)
{
    struct qemuMonitorQueryHotpluggableCpusEntry *info = NULL;
    size_t ninfo = 0;
    int ret = -1;
    size_t i;
    virJSONValue *data;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *vcpu;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-hotpluggable-cpus", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        goto cleanup;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        goto cleanup;

    data = virJSONValueObjectGet(reply, "return");
    ninfo = virJSONValueArraySize(data);

    info = g_new0(struct qemuMonitorQueryHotpluggableCpusEntry, ninfo);

    for (i = 0; i < ninfo; i++) {
        vcpu = virJSONValueArrayGet(data, i);

        if (qemuMonitorJSONProcessHotpluggableCpusReply(vcpu, info + i) < 0)
            goto cleanup;
    }

    qsort(info, ninfo, sizeof(*info), qemuMonitorQueryHotpluggableCpusEntrySort);

    *entries = g_steal_pointer(&info);
    *nentries = ninfo;
    ret = 0;

 cleanup:
    qemuMonitorQueryHotpluggableCpusFree(info, ninfo);
    return ret;
}


virJSONValue *
qemuMonitorJSONQueryQMPSchema(qemuMonitor *mon)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-qmp-schema", NULL)))
        return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return NULL;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return NULL;

    return virJSONValueObjectStealArray(reply, "return");
}


int
qemuMonitorJSONSetBlockThreshold(qemuMonitor *mon,
                                 const char *nodename,
                                 unsigned long long threshold)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("block-set-write-threshold",
                                           "s:node-name", nodename,
                                           "U:write-threshold", threshold,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


virJSONValue *
qemuMonitorJSONQueryNamedBlockNodes(qemuMonitor *mon,
                                    bool flat)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-named-block-nodes",
                                           "B:flat", flat,
                                           NULL)))
        return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return NULL;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return NULL;

    return virJSONValueObjectStealArray(reply, "return");
}


int
qemuMonitorJSONSetWatchdogAction(qemuMonitor *mon,
                                 const char *action)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("watchdog-set-action",
                                           "s:action", action,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockdevCreate(qemuMonitor *mon,
                              const char *jobname,
                              virJSONValue **props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("blockdev-create",
                                     "s:job-id", jobname,
                                     "a:options", props,
                                     NULL);
    if (!cmd)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockdevAdd(qemuMonitor *mon,
                           virJSONValue **props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommandInternal("blockdev-add", props)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockdevReopen(qemuMonitor *mon,
                              virJSONValue **props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommandInternal("blockdev-reopen", props)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockdevDel(qemuMonitor *mon,
                           const char *nodename)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-del",
                                           "s:node-name", nodename,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockdevTrayOpen(qemuMonitor *mon,
                                const char *id,
                                bool force)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-open-tray",
                                           "s:id", id,
                                           "b:force", force, NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockdevTrayClose(qemuMonitor *mon,
                                 const char *id)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-close-tray",
                                           "s:id", id, NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockdevMediumRemove(qemuMonitor *mon,
                                    const char *id)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-remove-medium",
                                           "s:id", id, NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONBlockdevMediumInsert(qemuMonitor *mon,
                                    const char *id,
                                    const char *nodename)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-insert-medium",
                                           "s:id", id,
                                           "s:node-name", nodename,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


/**
 * The function is used to retrieve the measurement of a SEV guest.
 * The measurement is signature of the memory contents that was encrypted
 * through the SEV launch flow.
 *
 * A example JSON output:
 *
 * { "execute" : "query-sev-launch-measure" }
 * { "return" : { "data" : "4l8LXeNlSPUDlXPJG5966/8%YZ" } }
 */
char *
qemuMonitorJSONGetSEVMeasurement(qemuMonitor *mon)
{
    const char *tmp;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-sev-launch-measure", NULL)))
         return NULL;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return NULL;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return NULL;

    data = virJSONValueObjectGetObject(reply, "return");

    if (!(tmp = virJSONValueObjectGetString(data, "data")))
        return NULL;

    return g_strdup(tmp);
}


/**
 * Retrieve info about the SEV setup, returning those fields that
 * are required to do a launch attestation, as per
 *
 * HMAC(0x04 || API_MAJOR || API_MINOR || BUILD || GCTX.POLICY || GCTX.LD || MNONCE; GCTX.TIK)
 *
 * specified in section 6.5.1 of AMD Secure Encrypted
 * Virtualization API.
 *
 *  { "execute": "query-sev" }
 *  { "return": { "enabled": true, "api-major" : 0, "api-minor" : 0,
 *                "build-id" : 0, "policy" : 0, "state" : "running",
 *                "handle" : 1 } }
 */
int
qemuMonitorJSONGetSEVInfo(qemuMonitor *mon,
                          unsigned int *apiMajor,
                          unsigned int *apiMinor,
                          unsigned int *buildID,
                          unsigned int *policy)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-sev", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    data = virJSONValueObjectGetObject(reply, "return");

    if (virJSONValueObjectGetNumberUint(data, "api-major", apiMajor) < 0 ||
        virJSONValueObjectGetNumberUint(data, "api-minor", apiMinor) < 0 ||
        virJSONValueObjectGetNumberUint(data, "build-id", buildID) < 0 ||
        virJSONValueObjectGetNumberUint(data, "policy", policy) < 0)
        return -1;

    return 0;
}


/**
 * Set a launch secret in guest memory
 *
 * Example JSON:
 *
 * { "execute" : "sev-inject-launch-secret",
 *   "data": { "packet-header": "str", "secret": "str", "gpa": "uint64" } }
 *
 * The guest physical address (gpa) parameter is optional
 */
int
qemuMonitorJSONSetLaunchSecurityState(qemuMonitor *mon,
                                      const char *secrethdr,
                                      const char *secret,
                                      unsigned long long setaddr,
                                      bool hasSetaddr)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (hasSetaddr) {
        cmd = qemuMonitorJSONMakeCommand("sev-inject-launch-secret",
                                         "s:packet-header", secrethdr,
                                         "s:secret", secret,
                                         "U:gpa", setaddr,
                                         NULL);
    } else {
        cmd = qemuMonitorJSONMakeCommand("sev-inject-launch-secret",
                                         "s:packet-header", secrethdr,
                                         "s:secret", secret,
                                         NULL);
    }
    if (cmd == NULL)
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


/*
 * Example return data
 *
 * "return": [
 *   { "connected": true, "id": "pr-helper0" }
 *  ]
 */
static int
qemuMonitorJSONExtractPRManagerInfo(virJSONValue *reply,
                                    GHashTable *info)
{
    virJSONValue *data;
    size_t i;

    data = virJSONValueObjectGetArray(reply, "return");

    for (i = 0; i < virJSONValueArraySize(data); i++) {
        g_autofree qemuMonitorPRManagerInfo *entry = NULL;
        virJSONValue *prManager = virJSONValueArrayGet(data, i);
        const char *alias;

        if (!(alias = virJSONValueObjectGetString(prManager, "id")))
            goto malformed;

        entry = g_new0(qemuMonitorPRManagerInfo, 1);

        if (virJSONValueObjectGetBoolean(prManager,
                                         "connected",
                                         &entry->connected) < 0) {
            goto malformed;
        }

        if (virHashAddEntry(info, alias, entry) < 0)
            return -1;

        entry = NULL;
    }

    return 0;

 malformed:
    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                   _("malformed prManager reply"));
    return -1;
}


int
qemuMonitorJSONGetPRManagerInfo(qemuMonitor *mon,
                                GHashTable *info)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-pr-managers",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    return qemuMonitorJSONExtractPRManagerInfo(reply, info);
}


static int
qemuMonitorJSONExtractCurrentMachineInfo(virJSONValue *reply,
                                         qemuMonitorCurrentMachineInfo *info)
{
    virJSONValue *data;

    data = virJSONValueObjectGetObject(reply, "return");
    if (!data)
        goto malformed;

    if (virJSONValueObjectGetBoolean(data, "wakeup-suspend-support",
                                     &info->wakeupSuspendSupport) < 0)
        goto malformed;

    return 0;

 malformed:
    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                   _("malformed qemu-current-machine reply"));
    return -1;
}


int
qemuMonitorJSONGetCurrentMachineInfo(qemuMonitor *mon,
                                     qemuMonitorCurrentMachineInfo *info)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-current-machine",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        return -1;

    return qemuMonitorJSONExtractCurrentMachineInfo(reply, info);
}


int
qemuMonitorJSONTransactionBitmapAdd(virJSONValue *actions,
                                    const char *node,
                                    const char *name,
                                    bool persistent,
                                    bool disabled,
                                    unsigned long long granularity)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-add",
                                         "s:node", node,
                                         "s:name", name,
                                         "b:persistent", persistent,
                                         "b:disabled", disabled,
                                         "P:granularity", granularity,
                                         NULL);
}


int
qemuMonitorJSONTransactionBitmapRemove(virJSONValue *actions,
                                       const char *node,
                                       const char *name)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-remove",
                                         "s:node", node,
                                         "s:name", name,
                                         NULL);
}


int
qemuMonitorJSONBitmapRemove(qemuMonitor *mon,
                            const char *node,
                            const char *name)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-remove",
                                           "s:node", node,
                                           "s:name", name,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONTransactionBitmapEnable(virJSONValue *actions,
                                       const char *node,
                                       const char *name)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-enable",
                                         "s:node", node,
                                         "s:name", name,
                                         NULL);
}


int
qemuMonitorJSONTransactionBitmapDisable(virJSONValue *actions,
                                        const char *node,
                                        const char *name)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-disable",
                                         "s:node", node,
                                         "s:name", name,
                                         NULL);
}


int
qemuMonitorJSONTransactionBitmapMerge(virJSONValue *actions,
                                      const char *node,
                                      const char *target,
                                      virJSONValue **sources)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-merge",
                                         "s:node", node,
                                         "s:target", target,
                                         "a:bitmaps", sources,
                                         NULL);
}


int
qemuMonitorJSONTransactionBitmapMergeSourceAddBitmap(virJSONValue *sources,
                                                     const char *sourcenode,
                                                     const char *sourcebitmap)
{
    g_autoptr(virJSONValue) sourceobj = NULL;

    if (virJSONValueObjectAdd(&sourceobj,
                              "s:node", sourcenode,
                              "s:name", sourcebitmap,
                              NULL) < 0)
        return -1;

    if (virJSONValueArrayAppend(sources, &sourceobj) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONTransactionSnapshotLegacy(virJSONValue *actions,
                                         const char *device,
                                         const char *path,
                                         const char *format,
                                         bool existing)
{
    const char *mode = NULL;

    if (existing)
        mode = "existing";

    return qemuMonitorJSONTransactionAdd(actions,
                                         "blockdev-snapshot-sync",
                                         "s:device", device,
                                         "s:snapshot-file", path,
                                         "s:format", format,
                                         "S:mode", mode,
                                         NULL);
}


int
qemuMonitorJSONTransactionSnapshotBlockdev(virJSONValue *actions,
                                           const char *node,
                                           const char *overlay)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "blockdev-snapshot",
                                         "s:node", node,
                                         "s:overlay", overlay,
                                         NULL);
}

VIR_ENUM_DECL(qemuMonitorTransactionBackupSyncMode);
VIR_ENUM_IMPL(qemuMonitorTransactionBackupSyncMode,
              QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_LAST,
              "none",
              "incremental",
              "full");

int
qemuMonitorJSONTransactionBackup(virJSONValue *actions,
                                 const char *device,
                                 const char *jobname,
                                 const char *target,
                                 const char *bitmap,
                                 qemuMonitorTransactionBackupSyncMode syncmode)
{
    const char *syncmodestr = qemuMonitorTransactionBackupSyncModeTypeToString(syncmode);

    return qemuMonitorJSONTransactionAdd(actions,
                                         "blockdev-backup",
                                         "s:device", device,
                                         "s:job-id", jobname,
                                         "s:target", target,
                                         "s:sync", syncmodestr,
                                         "S:bitmap", bitmap,
                                         "T:auto-finalize", VIR_TRISTATE_BOOL_YES,
                                         "T:auto-dismiss", VIR_TRISTATE_BOOL_NO,
                                         NULL);
}


static qemuMonitorJobInfo *
qemuMonitorJSONGetJobInfoOne(virJSONValue *data)
{
    const char *id = virJSONValueObjectGetString(data, "id");
    const char *type = virJSONValueObjectGetString(data, "type");
    const char *status = virJSONValueObjectGetString(data, "status");
    const char *errmsg = virJSONValueObjectGetString(data, "error");
    int tmp;
    g_autoptr(qemuMonitorJobInfo) job = NULL;

    job = g_new0(qemuMonitorJobInfo, 1);

    if ((tmp = qemuMonitorJobTypeFromString(type)) < 0)
        tmp = QEMU_MONITOR_JOB_TYPE_UNKNOWN;

    job->type = tmp;

    if ((tmp = qemuMonitorJobStatusTypeFromString(status)) < 0)
        tmp = QEMU_MONITOR_JOB_STATUS_UNKNOWN;

    job->status = tmp;

    job->id = g_strdup(id);
    job->error = g_strdup(errmsg);

    /* failure to fetch progress stats is not fatal */
    ignore_value(virJSONValueObjectGetNumberUlong(data, "current-progress",
                                                  &job->progressCurrent));
    ignore_value(virJSONValueObjectGetNumberUlong(data, "total-progress",
                                                  &job->progressTotal));

    return g_steal_pointer(&job);
}


int
qemuMonitorJSONGetJobInfo(qemuMonitor *mon,
                          qemuMonitorJobInfo ***jobs,
                          size_t *njobs)
{
    virJSONValue *data;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    size_t i;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-jobs", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
        return -1;

    data = virJSONValueObjectGetArray(reply, "return");

    for (i = 0; i < virJSONValueArraySize(data); i++) {
        qemuMonitorJobInfo *job = NULL;

        if (!(job = qemuMonitorJSONGetJobInfoOne(virJSONValueArrayGet(data, i))))
            return -1;

        VIR_APPEND_ELEMENT(*jobs, *njobs, job);
    }

    return 0;
}


int
qemuMonitorJSONGetCPUMigratable(qemuMonitor *mon,
                                const char *cpuQOMPath,
                                bool *migratable)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", cpuQOMPath,
                                           "s:property", "migratable",
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONHasError(reply, "GenericError"))
        return 1;

    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_BOOLEAN) < 0)
        return -1;

    return virJSONValueGetBoolean(virJSONValueObjectGet(reply, "return"),
                                  migratable);
}


int
qemuMonitorJSONStartDirtyRateCalc(qemuMonitor *mon,
                                  int seconds,
                                  qemuMonitorDirtyRateCalcMode mode)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    const char *modestr = NULL;

    if (mode != QEMU_MONITOR_DIRTYRATE_CALC_MODE_PAGE_SAMPLING)
       modestr = qemuMonitorDirtyRateCalcModeTypeToString(mode);

    if (!(cmd = qemuMonitorJSONMakeCommand("calc-dirty-rate",
                                           "i:calc-time", seconds,
                                           "S:mode", modestr,
                                           NULL))) {
        return -1;
    }

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}

VIR_ENUM_DECL(qemuMonitorDirtyRateStatus);
VIR_ENUM_IMPL(qemuMonitorDirtyRateStatus,
              VIR_DOMAIN_DIRTYRATE_LAST,
              "unstarted",
              "measuring",
              "measured");

static int
qemuMonitorJSONExtractVcpuDirtyRate(virJSONValue *data,
                                    qemuMonitorDirtyRateInfo *info)
{
    size_t nvcpus;
    size_t i;

    nvcpus = virJSONValueArraySize(data);
    info->nvcpus = nvcpus;
    info->rates = g_new0(qemuMonitorDirtyRateVcpu, nvcpus);

    for (i = 0; i < nvcpus; i++) {
        virJSONValue *entry = virJSONValueArrayGet(data, i);
        if (virJSONValueObjectGetNumberInt(entry, "id",
                                           &info->rates[i].idx) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-dirty-rate reply was missing 'id' data"));
            return -1;
        }

        if (virJSONValueObjectGetNumberUlong(entry, "dirty-rate",
                                            &info->rates[i].value) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("query-dirty-rate reply was missing 'dirty-rate' data"));
            return -1;
        }
    }

    return 0;
}

static int
qemuMonitorJSONExtractDirtyRateInfo(virJSONValue *data,
                                    qemuMonitorDirtyRateInfo *info)
{
    const char *statusstr;
    const char *modestr;
    int status;
    int mode;
    virJSONValue *rates = NULL;

    if (!(statusstr = virJSONValueObjectGetString(data, "status"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-dirty-rate reply was missing 'status' data"));
        return -1;
    }

    if ((status = qemuMonitorDirtyRateStatusTypeFromString(statusstr)) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown dirty rate status: %s"), statusstr);
        return -1;
    }
    info->status = status;

    /* `query-dirty-rate` replies `dirty-rate` data only if the status of the latest
     * calculation is `measured`.
     */
    if ((info->status == VIR_DOMAIN_DIRTYRATE_MEASURED) &&
        (virJSONValueObjectGetNumberLong(data, "dirty-rate", &info->dirtyRate) < 0)) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-dirty-rate reply was missing 'dirty-rate' data"));
        return -1;
    }

    if (virJSONValueObjectGetNumberLong(data, "start-time", &info->startTime) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-dirty-rate reply was missing 'start-time' data"));
        return -1;
    }

    if (virJSONValueObjectGetNumberInt(data, "calc-time", &info->calcTime) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-dirty-rate reply was missing 'calc-time' data"));
        return -1;
    }

    if ((modestr = virJSONValueObjectGetString(data, "mode"))) {
        if ((mode = qemuMonitorDirtyRateCalcModeTypeFromString(modestr)) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown dirty page rate calculation mode: %s"), modestr);
            return -1;
        }
        info->mode = mode;
    } else {
        info->mode = QEMU_MONITOR_DIRTYRATE_CALC_MODE_PAGE_SAMPLING;
    }

    if ((rates = virJSONValueObjectGetArray(data, "vcpu-dirty-rate"))) {
        if (qemuMonitorJSONExtractVcpuDirtyRate(rates, info) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-dirty-rate parsing 'vcpu-dirty-rate' in failure"));
            return -1;
        }
    }

    return 0;
}


int
qemuMonitorJSONQueryDirtyRate(qemuMonitor *mon,
                              qemuMonitorDirtyRateInfo *info)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValue *data = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-dirty-rate", NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    if (!(data = virJSONValueObjectGetObject(reply, "return"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-dirty-rate reply was missing 'return' data"));
        return -1;
    }

    return qemuMonitorJSONExtractDirtyRateInfo(data, info);
}


VIR_ENUM_DECL(qemuMonitorActionShutdown);
VIR_ENUM_IMPL(qemuMonitorActionShutdown,
              QEMU_MONITOR_ACTION_SHUTDOWN_LAST,
              "",
              "poweroff",
              "pause");

VIR_ENUM_DECL(qemuMonitorActionReboot);
VIR_ENUM_IMPL(qemuMonitorActionReboot,
              QEMU_MONITOR_ACTION_REBOOT_LAST,
              "",
              "reset",
              "shutdown");

VIR_ENUM_DECL(qemuMonitorActionWatchdog);
VIR_ENUM_IMPL(qemuMonitorActionWatchdog,
              QEMU_MONITOR_ACTION_WATCHDOG_LAST,
              "",
              "reset",
              "shutdown",
              "poweroff",
              "pause",
              "debug",
              "none",
              "inject-nmi");

VIR_ENUM_DECL(qemuMonitorActionPanic);
VIR_ENUM_IMPL(qemuMonitorActionPanic,
              QEMU_MONITOR_ACTION_PANIC_LAST,
              "",
              "pause",
              "shutdown",
              "none");


int
qemuMonitorJSONSetAction(qemuMonitor *mon,
                         qemuMonitorActionShutdown shutdown,
                         qemuMonitorActionReboot reboot,
                         qemuMonitorActionWatchdog watchdog,
                         qemuMonitorActionPanic panic)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    const char *actionShutdown = NULL;
    const char *actionReboot = NULL;
    const char *actionWatchdog = NULL;
    const char *actionPanic = NULL;

    if (shutdown != QEMU_MONITOR_ACTION_SHUTDOWN_KEEP)
        actionShutdown = qemuMonitorActionShutdownTypeToString(shutdown);

    if (reboot != QEMU_MONITOR_ACTION_REBOOT_KEEP)
        actionReboot = qemuMonitorActionRebootTypeToString(reboot);

    if (watchdog != QEMU_MONITOR_ACTION_WATCHDOG_KEEP)
        actionWatchdog = qemuMonitorActionWatchdogTypeToString(watchdog);

    if (panic != QEMU_MONITOR_ACTION_PANIC_KEEP)
        actionPanic = qemuMonitorActionPanicTypeToString(panic);

    if (!(cmd = qemuMonitorJSONMakeCommand("set-action",
                                           "S:shutdown", actionShutdown,
                                           "S:reboot", actionReboot,
                                           "S:watchdog", actionWatchdog,
                                           "S:panic", actionPanic,
                                           NULL)))
        return -1;

    if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
        return -1;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        return -1;

    return 0;
}


int
qemuMonitorJSONChangeMemoryRequestedSize(qemuMonitor *mon,
                                         const char *alias,
                                         unsigned long long requestedsize)
{
    g_autofree char *path = g_strdup_printf("/machine/peripheral/%s", alias);
    qemuMonitorJSONObjectProperty prop = {
        .type = QEMU_MONITOR_OBJECT_PROPERTY_ULONG,
        .val.ul = requestedsize * 1024, /* monitor needs bytes */
    };

    return qemuMonitorJSONSetObjectProperty(mon, path, "requested-size", &prop);
}
