/*
 * 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 VIR_FROM_THIS VIR_FROM_QEMU

VIR_LOG_INIT("qemu.qemu_monitor_json");

#define QOM_CPU_PATH  "/machine/unattached/device[0]"

#define LINE_ENDING "\r\n"

VIR_ENUM_IMPL(qemuMonitorJob,
              QEMU_MONITOR_JOB_TYPE_LAST,
              "",
              "commit",
              "stream",
              "mirror",
              "backup",
              "create");

VIR_ENUM_IMPL(qemuMonitorJobStatus,
              QEMU_MONITOR_JOB_STATUS_LAST,
              "",
              "created",
              "running",
              "paused",
              "ready",
              "standby",
              "waiting",
              "pending",
              "aborting",
              "concluded",
              "undefined",
              "null");

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

typedef struct {
    const char *type;
    void (*handler)(qemuMonitorPtr mon, virJSONValuePtr 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, },
    { "DUMP_COMPLETED", qemuMonitorJSONHandleDumpCompleted, },
    { "GUEST_CRASHLOADED", qemuMonitorJSONHandleGuestCrashloaded, },
    { "GUEST_PANICKED", qemuMonitorJSONHandleGuestPanic, },
    { "JOB_STATUS_CHANGE", qemuMonitorJSONHandleJobStatusChange, },
    { "MEMORY_FAILURE", qemuMonitorJSONHandleMemoryFailure, },
    { "MIGRATION", qemuMonitorJSONHandleMigrationStatus, },
    { "MIGRATION_PASS", qemuMonitorJSONHandleMigrationPass, },
    { "NIC_RX_FILTER_CHANGED", qemuMonitorJSONHandleNicRxFilterChanged, },
    { "POWERDOWN", qemuMonitorJSONHandlePowerdown, },
    { "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(qemuMonitorPtr mon,
                              virJSONValuePtr obj)
{
    const char *type;
    qemuEventHandler *handler;
    virJSONValuePtr data;
    char *details = NULL;
    virJSONValuePtr 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);
    VIR_FREE(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(qemuMonitorPtr mon,
                             const char *line,
                             qemuMonitorMessagePtr msg)
{
    virJSONValuePtr obj = NULL;
    int ret = -1;

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

    if (!(obj = virJSONValueFromString(line)))
        goto cleanup;

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

    if (virJSONValueObjectHasKey(obj, "QMP") == 1) {
        ret = 0;
    } else if (virJSONValueObjectHasKey(obj, "event") == 1) {
        PROBE(QEMU_MONITOR_RECV_EVENT,
              "mon=%p event=%s", mon, line);
        ret = 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 = obj;
            msg->finished = 1;
            obj = NULL;
            ret = 0;
        } else {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Unexpected JSON reply '%s'"), line);
        }
    } else {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unknown JSON reply '%s'"), line);
    }

 cleanup:
    virJSONValueFree(obj);
    return ret;
}

int qemuMonitorJSONIOProcess(qemuMonitorPtr mon,
                             const char *data,
                             size_t len,
                             qemuMonitorMessagePtr 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);
            char *line;
            line = g_strndup(data + used, got);
            used += got + strlen(LINE_ENDING);
            line[got] = '\0'; /* kill \n */
            if (qemuMonitorJSONIOProcessLine(mon, line, msg) < 0) {
                VIR_FREE(line);
                return -1;
            }

            VIR_FREE(line);
        } else {
            break;
        }
    }

#if DEBUG_IO
    VIR_DEBUG("Total used %d bytes out of %zd available in buffer", used, len);
#endif

    return used;
}

static int
qemuMonitorJSONCommandWithFd(qemuMonitorPtr mon,
                             virJSONValuePtr cmd,
                             int scm_fd,
                             virJSONValuePtr *reply)
{
    int ret = -1;
    qemuMonitorMessage msg;
    g_auto(virBuffer) cmdbuf = VIR_BUFFER_INITIALIZER;
    char *id = NULL;

    *reply = NULL;

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

    if (virJSONValueObjectHasKey(cmd, "execute") == 1) {
        if (!(id = qemuMonitorNextCommandID(mon)))
            goto cleanup;
        if (virJSONValueObjectAppendString(cmd, "id", id) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Unable to append command 'id' string"));
            goto cleanup;
        }
    }

    if (virJSONValueToBuffer(cmd, &cmdbuf, false) < 0)
        goto cleanup;
    virBufferAddLit(&cmdbuf, "\r\n");

    msg.txLength = virBufferUse(&cmdbuf);
    msg.txBuffer = virBufferContentAndReset(&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;
        }
    }

 cleanup:
    VIR_FREE(id);
    VIR_FREE(msg.txBuffer);

    return ret;
}


static int
qemuMonitorJSONCommand(qemuMonitorPtr mon,
                       virJSONValuePtr cmd,
                       virJSONValuePtr *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(virJSONValuePtr 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(virJSONValuePtr cmd)
{
    const char *name = virJSONValueObjectGetString(cmd, "execute");
    if (name)
        return name;
    else
        return "<unknown>";
}

static int
qemuMonitorJSONCheckErrorFull(virJSONValuePtr cmd,
                              virJSONValuePtr reply,
                              bool report)
{
    if (virJSONValueObjectHasKey(reply, "error")) {
        virJSONValuePtr 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(virJSONValuePtr cmd,
                          virJSONValuePtr reply)
{
    return qemuMonitorJSONCheckErrorFull(cmd, reply, true);
}


static int
qemuMonitorJSONCheckReply(virJSONValuePtr cmd,
                          virJSONValuePtr reply,
                          virJSONType type)
{
    virJSONValuePtr 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(virJSONValuePtr error,
                            const char *klass)
{
    return STREQ_NULLABLE(virJSONValueObjectGetString(error, "class"), klass);
}


static bool
qemuMonitorJSONHasError(virJSONValuePtr reply,
                        const char *klass)
{
    virJSONValuePtr 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(virJSONValuePtr actions,
                              const char *cmdname,
                              ...)
{
    virJSONValuePtr entry = NULL;
    virJSONValuePtr data = NULL;
    va_list args;
    int ret = -1;

    va_start(args, cmdname);

    if (virJSONValueObjectCreateVArgs(&data, args) < 0)
        goto cleanup;

    if (virJSONValueObjectCreate(&entry,
                                 "s:type", cmdname,
                                 "A:data", &data, NULL) < 0)
        goto cleanup;

    if (virJSONValueArrayAppend(actions, entry) < 0)
        goto cleanup;

    entry = NULL;
    ret = 0;

 cleanup:
    virJSONValueFree(entry);
    virJSONValueFree(data);
    va_end(args);
    return ret;
}


/**
 * 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 virJSONValuePtr
qemuMonitorJSONMakeCommandInternal(const char *cmdname,
                                   virJSONValuePtr *arguments)
{
    virJSONValuePtr ret = NULL;

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

    return ret;
}


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

    va_start(args, cmdname);

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

    obj = qemuMonitorJSONMakeCommandInternal(cmdname, &jargs);

 cleanup:
    va_end(args);

    return obj;
}


static void
qemuMonitorJSONParseKeywordsFree(int nkeywords,
                                 char **keywords,
                                 char **values)
{
    size_t i;
    for (i = 0; i < nkeywords; i++) {
        VIR_FREE(keywords[i]);
        VIR_FREE(values[i]);
    }
    VIR_FREE(keywords);
    VIR_FREE(values);
}


/*
 * Takes a string containing a set of key=value,key=value,key...
 * parameters and splits them up, returning two arrays with
 * the individual keys and values.
 * The "=value" part is optional and if a key with no value is found,
 * NULL will be placed into corresponding place in retvalues.
 */
static int
qemuMonitorJSONParseKeywords(const char *str,
                             char ***retkeywords,
                             char ***retvalues,
                             int *retnkeywords)
{
    int keywordCount = 0;
    int keywordAlloc = 0;
    char **keywords = NULL;
    char **values = NULL;
    const char *start = str;
    const char *end;

    *retkeywords = NULL;
    *retvalues = NULL;
    *retnkeywords = 0;
    end = start + strlen(str);

    while (start) {
        const char *separator;
        const char *endmark;
        char *keyword;
        char *value = NULL;

        endmark = start;
        do {
            /* QEMU accepts ',,' as an escape for a literal comma;
             * skip past those here while searching for the end of the
             * value, then strip them down below */
            endmark = strchr(endmark, ',');
        } while (endmark && endmark[1] == ',' && (endmark += 2));
        if (!endmark)
            endmark = end;
        if (!(separator = strchr(start, '=')))
            separator = end;

        if (separator >= endmark)
            separator = endmark;

        keyword = g_strndup(start, separator - start);

        if (separator < endmark) {
            separator++;
            value = g_strndup(separator, endmark - separator);
            if (strchr(value, ',')) {
                char *p = strchr(value, ',') + 1;
                char *q = p + 1;
                while (*q) {
                    if (*q == ',')
                        q++;
                    *p++ = *q++;
                }
                *p = '\0';
            }
        }

        if (keywordAlloc == keywordCount) {
            if (VIR_REALLOC_N(keywords, keywordAlloc + 10) < 0 ||
                VIR_REALLOC_N(values, keywordAlloc + 10) < 0) {
                VIR_FREE(keyword);
                VIR_FREE(value);
                goto error;
            }
            keywordAlloc += 10;
        }

        keywords[keywordCount] = keyword;
        values[keywordCount] = value;
        keywordCount++;

        start = endmark < end ? endmark + 1 : NULL;
    }

    *retkeywords = keywords;
    *retvalues = values;
    *retnkeywords = keywordCount;
    return 0;

 error:
    qemuMonitorJSONParseKeywordsFree(keywordCount, keywords, values);
    return -1;
}


static virJSONValuePtr
qemuMonitorJSONKeywordStringToJSON(const char *str, const char *firstkeyword)
{
    virJSONValuePtr ret = virJSONValueNewObject();
    char **keywords = NULL;
    char **values = NULL;
    int nkeywords = 0;
    size_t i;

    if (qemuMonitorJSONParseKeywords(str, &keywords, &values, &nkeywords) < 0)
        goto error;

    for (i = 0; i < nkeywords; i++) {
        if (values[i] == NULL) {
            if (i != 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("unexpected empty keyword in %s"), str);
                goto error;
            } else {
                /* This 3rd arg isn't a typo - the way the parser works is
                 * that the value ended up in the keyword field */
                if (virJSONValueObjectAppendString(ret, firstkeyword, keywords[i]) < 0)
                    goto error;
            }
        } else {
            if (virJSONValueObjectAppendString(ret, keywords[i], values[i]) < 0)
                goto error;
        }
    }

    qemuMonitorJSONParseKeywordsFree(nkeywords, keywords, values);
    return ret;

 error:
    qemuMonitorJSONParseKeywordsFree(nkeywords, keywords, values);
    virJSONValueFree(ret);
    return NULL;
}


static void qemuMonitorJSONHandleShutdown(qemuMonitorPtr mon, virJSONValuePtr data)
{
    bool guest = false;
    virTristateBool guest_initiated = VIR_TRISTATE_BOOL_ABSENT;

    if (data && virJSONValueObjectGetBoolean(data, "guest", &guest) == 0)
        guest_initiated = guest ? VIR_TRISTATE_BOOL_YES : VIR_TRISTATE_BOOL_NO;

    qemuMonitorEmitShutdown(mon, guest_initiated);
}

static void qemuMonitorJSONHandleReset(qemuMonitorPtr mon, virJSONValuePtr data G_GNUC_UNUSED)
{
    qemuMonitorEmitReset(mon);
}

static void qemuMonitorJSONHandlePowerdown(qemuMonitorPtr mon, virJSONValuePtr data G_GNUC_UNUSED)
{
    qemuMonitorEmitPowerdown(mon);
}

static void qemuMonitorJSONHandleStop(qemuMonitorPtr mon, virJSONValuePtr data G_GNUC_UNUSED)
{
    qemuMonitorEmitStop(mon);
}

static void qemuMonitorJSONHandleResume(qemuMonitorPtr mon, virJSONValuePtr data G_GNUC_UNUSED)
{
    qemuMonitorEmitResume(mon);
}


static qemuMonitorEventPanicInfoPtr
qemuMonitorJSONGuestPanicExtractInfoHyperv(virJSONValuePtr data)
{
    qemuMonitorEventPanicInfoPtr ret;

    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"));
        goto error;
    }

    return ret;

 error:
    qemuMonitorEventPanicInfoFree(ret);
    return NULL;
}

static qemuMonitorEventPanicInfoPtr
qemuMonitorJSONGuestPanicExtractInfoS390(virJSONValuePtr data)
{
    qemuMonitorEventPanicInfoPtr ret;
    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"));
        goto error;
    }

    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 ret;

 error:
    qemuMonitorEventPanicInfoFree(ret);
    return NULL;
}

static qemuMonitorEventPanicInfoPtr
qemuMonitorJSONGuestPanicExtractInfo(virJSONValuePtr 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(qemuMonitorPtr mon,
                                virJSONValuePtr data)
{
    virJSONValuePtr infojson = virJSONValueObjectGetObject(data, "info");
    qemuMonitorEventPanicInfoPtr info = NULL;

    if (infojson)
        info = qemuMonitorJSONGuestPanicExtractInfo(infojson);

    qemuMonitorEmitGuestPanic(mon, info);
}


static void qemuMonitorJSONHandleRTCChange(qemuMonitorPtr mon, virJSONValuePtr 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(qemuMonitorPtr mon, virJSONValuePtr 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(qemuMonitorPtr mon, virJSONValuePtr 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(qemuMonitorPtr mon,
                                 virJSONValuePtr data,
                                 int phase)
{
    const char *localNode, *localService, *localFamily;
    const char *remoteNode, *remoteService, *remoteFamily;
    const char *authScheme, *saslUsername, *x509dname;
    int localFamilyID, remoteFamilyID;
    virJSONValuePtr client;
    virJSONValuePtr 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(qemuMonitorPtr mon, virJSONValuePtr data)
{
    qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_CONNECT);
}


static void qemuMonitorJSONHandleVNCInitialize(qemuMonitorPtr mon, virJSONValuePtr data)
{
    qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE);
}


static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitorPtr mon, virJSONValuePtr data)
{
    qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT);
}


static void
qemuMonitorJSONHandleGraphicsSPICE(qemuMonitorPtr mon,
                                   virJSONValuePtr data,
                                   int phase)
{
    const char *lhost, *lport, *lfamily;
    const char *rhost, *rport, *rfamily;
    const char *auth = "";
    int lfamilyID, rfamilyID;
    virJSONValuePtr client;
    virJSONValuePtr 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(qemuMonitorPtr mon, virJSONValuePtr data)
{
    qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_CONNECT);
}


static void qemuMonitorJSONHandleSPICEInitialize(qemuMonitorPtr mon, virJSONValuePtr data)
{
    qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE);
}


static void qemuMonitorJSONHandleSPICEDisconnect(qemuMonitorPtr mon, virJSONValuePtr data)
{
    qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT);
}

static void
qemuMonitorJSONHandleBlockJobImpl(qemuMonitorPtr mon,
                                  virJSONValuePtr 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(qemuMonitorPtr mon,
                                     virJSONValuePtr 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(qemuMonitorPtr mon,
                                virJSONValuePtr 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(qemuMonitorPtr mon,
                              virJSONValuePtr data G_GNUC_UNUSED)
{
    qemuMonitorEmitPMWakeup(mon);
}

static void
qemuMonitorJSONHandlePMSuspend(qemuMonitorPtr mon,
                               virJSONValuePtr data G_GNUC_UNUSED)
{
    qemuMonitorEmitPMSuspend(mon);
}

static void
qemuMonitorJSONHandleBlockJobCompleted(qemuMonitorPtr mon,
                                       virJSONValuePtr data)
{
    qemuMonitorJSONHandleBlockJobImpl(mon, data,
                                      VIR_DOMAIN_BLOCK_JOB_COMPLETED);
}

static void
qemuMonitorJSONHandleBlockJobCanceled(qemuMonitorPtr mon,
                                       virJSONValuePtr data)
{
    qemuMonitorJSONHandleBlockJobImpl(mon, data,
                                      VIR_DOMAIN_BLOCK_JOB_CANCELED);
}

static void
qemuMonitorJSONHandleBlockJobReady(qemuMonitorPtr mon,
                                   virJSONValuePtr data)
{
    qemuMonitorJSONHandleBlockJobImpl(mon, data,
                                      VIR_DOMAIN_BLOCK_JOB_READY);
}

static void
qemuMonitorJSONHandleBalloonChange(qemuMonitorPtr mon,
                                   virJSONValuePtr 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(qemuMonitorPtr mon,
                                   virJSONValuePtr data G_GNUC_UNUSED)
{
    qemuMonitorEmitPMSuspendDisk(mon);
}

static void
qemuMonitorJSONHandleDeviceDeleted(qemuMonitorPtr mon, virJSONValuePtr data)
{
    const char *device;

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

    qemuMonitorEmitDeviceDeleted(mon, device);
}


static void
qemuMonitorJSONHandleNicRxFilterChanged(qemuMonitorPtr mon, virJSONValuePtr 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(qemuMonitorPtr mon,
                                  virJSONValuePtr 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(qemuMonitorPtr mon,
                                   virJSONValuePtr data G_GNUC_UNUSED)
{
    qemuMonitorEmitSpiceMigrated(mon);
}


static void
qemuMonitorJSONHandleMemoryFailure(qemuMonitorPtr mon,
                                   virJSONValuePtr data)
{
    virJSONValuePtr 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(qemuMonitorPtr mon,
                                     virJSONValuePtr 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(qemuMonitorPtr mon,
                                   virJSONValuePtr 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(qemuMonitorPtr mon, virJSONValuePtr data)
{
    virJSONValuePtr 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(qemuMonitorPtr mon, virJSONValuePtr 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(virJSONValuePtr result,
                                qemuMonitorDumpStatsPtr 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(qemuMonitorPtr mon,
                                   virJSONValuePtr data)
{
    virJSONValuePtr 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(qemuMonitorPtr mon,
                                                        virJSONValuePtr 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(qemuMonitorPtr mon,
                                                      virJSONValuePtr 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(qemuMonitorPtr mon,
                                      virJSONValuePtr data)
{
    VIR_DEBUG("qemuMonitorJSONHandleGuestCrashloaded event, mon %p, data %p", mon, data);

    qemuMonitorEmitGuestCrashloaded(mon);
}


int
qemuMonitorJSONHumanCommand(qemuMonitorPtr mon,
                            const char *cmd_str,
                            char **reply_str)
{
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr obj;
    int ret = -1;

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

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

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

    if (qemuMonitorJSONCheckError(cmd, reply))
        goto cleanup;

    obj = virJSONValueObjectGet(reply, "return");

    if (reply_str) {
        const char *data;

        data = virJSONValueGetString(obj);
        *reply_str = g_strdup(NULLSTR_EMPTY(data));
    }

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONSetCapabilities(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("qmp_capabilities", NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONStartCPUs(qemuMonitorPtr mon)
{
    int ret;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("cont", NULL);
    virJSONValuePtr reply = NULL;
    size_t i = 0;
    int timeout = 3;
    if (!cmd)
        return -1;

    do {
        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;

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

    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONStopCPUs(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("stop", NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONGetStatus(qemuMonitorPtr mon,
                         bool *running,
                         virDomainPausedReason *reason)
{
    int ret = -1;
    const char *status;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;

    if (reason)
        *reason = VIR_DOMAIN_PAUSED_UNKNOWN;

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

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

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

    data = virJSONValueObjectGetObject(reply, "return");

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

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

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONSystemPowerdown(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("system_powerdown", NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONSetLink(qemuMonitorPtr mon,
                           const char *name,
                           virDomainNetInterfaceLinkState state)
{
    int ret = -1;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr 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)
        goto cleanup;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONSystemReset(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("system_reset", NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/**
 * 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(virJSONValuePtr 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(virJSONValuePtr 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++) {
        virJSONValuePtr 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(qemuMonitorPtr mon,
                         struct qemuMonitorQueryCpusEntry **entries,
                         size_t *nentries,
                         bool force,
                         bool fast)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr 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)
        goto cleanup;

    if (force && qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    if (!(data = virJSONValueObjectGetArray(reply, "return"))) {
        ret = -2;
        goto cleanup;
    }

    ret = qemuMonitorJSONExtractCPUInfo(data, entries, nentries, fast);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/**
 * 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(qemuMonitorPtr mon,
                                     virDomainVideoDefPtr 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(qemuMonitorPtr mon,
                                     virDomainVideoDefPtr 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(qemuMonitorPtr mon,
                              unsigned long long *currmem)
{
    int ret = -1;
    virJSONValuePtr data;
    unsigned long long mem;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("query-balloon",
                                                     NULL);
    virJSONValuePtr reply = NULL;

    *currmem = 0;

    if (!cmd)
        return -1;

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

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

    /* See if any other fatal error occurred */
    if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
        goto cleanup;

    data = virJSONValueObjectGetObject(reply, "return");

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

    *currmem = (mem/1024);
    ret = 1;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/* 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(qemuMonitorPtr mon,
                                  char *balloonpath,
                                  virDomainMemoryStatPtr stats,
                                  unsigned int nr_stats)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    virJSONValuePtr 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)
        goto cleanup;

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

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

    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"));
            goto cleanup;
        }
    }

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

    data = virJSONValueObjectGetObject(reply, "return");

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

    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);

    ret = got;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}
#undef GET_BALLOON_STATS


/*
 * Using the provided balloonpath, determine if we need to set the
 * collection interval property to enable statistics gathering.
 */
int
qemuMonitorJSONSetMemoryStatsPeriod(qemuMonitorPtr 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(qemuMonitorPtr mon,
                                    const char *vmstatepath,
                                    const char **list)
{
    g_autofree char *str = virStringListJoin(list, ",");
    qemuMonitorJSONObjectProperty prop = {
        .type = QEMU_MONITOR_OBJECT_PROPERTY_STRING,
        .val.str = str,
    };

    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 virJSONValuePtr
qemuMonitorJSONQueryBlock(qemuMonitorPtr mon)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr devices = NULL;

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

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

    devices = virJSONValueObjectStealArray(reply, "return");

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return devices;
}


static virJSONValuePtr
qemuMonitorJSONGetBlockDev(virJSONValuePtr devices,
                           size_t idx)
{
    virJSONValuePtr 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(virJSONValuePtr 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;
    int ret = -1;

    tmp = g_new0(struct qemuDomainDiskInfo, 1);

    *tmp = *info;
    tmp->nodename = NULL;

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

    if (virHashAddEntry(table, entryname, tmp) < 0)
        goto cleanup;

    tmp = NULL;
    ret = 0;

 cleanup:
    if (tmp)
        VIR_FREE(tmp->nodename);
    VIR_FREE(tmp);
    return ret;
}


int qemuMonitorJSONGetBlockInfo(qemuMonitorPtr mon,
                                GHashTable *table)
{
    int ret = -1;
    size_t i;

    virJSONValuePtr devices;

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

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

        if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
            goto cleanup;

        if (!(thisdev = qemuMonitorJSONGetBlockDevDevice(dev)))
            goto cleanup;

        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"));
            goto cleanup;
        }

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

        /* '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)
                goto cleanup;
        }

        if (thisdev &&
            qemuMonitorJSONBlockInfoAdd(table, &info, thisdev) < 0)
            goto cleanup;

        if (qdev && STRNEQ_NULLABLE(thisdev, qdev) &&
            qemuMonitorJSONBlockInfoAdd(table, &info, qdev) < 0)
            goto cleanup;
    }

    ret = 0;
 cleanup:
    virJSONValueFree(devices);
    return ret;
}


static qemuBlockStatsPtr
qemuMonitorJSONBlockStatsCollectData(virJSONValuePtr dev,
                                     int *nstats)
{
    g_autofree qemuBlockStatsPtr bstats = NULL;
    virJSONValuePtr parent;
    virJSONValuePtr parentstats;
    virJSONValuePtr 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(qemuBlockStatsPtr bstats,
                                    const char *name,
                                    GHashTable *stats)
{
    qemuBlockStatsPtr 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(virJSONValuePtr dev,
                                    const char *dev_name,
                                    int depth,
                                    GHashTable *hash,
                                    bool backingChain)
{
    g_autofree qemuBlockStatsPtr bstats = NULL;
    int nstats = 0;
    const char *qdevname = NULL;
    const char *nodename = NULL;
    g_autofree char *devicename = NULL;
    virJSONValuePtr 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 (backingChain &&
        (backing = virJSONValueObjectGetObject(dev, "backing")) &&
        qemuMonitorJSONGetOneBlockStatsInfo(backing, dev_name, depth + 1,
                                            hash, true) < 0)
        return -1;

    return nstats;
}


virJSONValuePtr
qemuMonitorJSONQueryBlockstats(qemuMonitorPtr mon)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr ret = NULL;

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

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

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

    ret = virJSONValueObjectStealArray(reply, "return");

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONGetAllBlockStatsInfo(qemuMonitorPtr mon,
                                    GHashTable *hash,
                                    bool backingChain)
{
    int nstats = 0;
    int rc;
    size_t i;
    g_autoptr(virJSONValue) devices = NULL;

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

    for (i = 0; i < virJSONValueArraySize(devices); i++) {
        virJSONValuePtr dev = virJSONValueArrayGet(devices, 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"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("blockstats device entry was not "
                             "in expected format"));
            return -1;
        }

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

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

        if (rc < 0)
            return -1;

        if (rc > nstats)
            nstats = rc;
    }

    return nstats;
}


static int
qemuMonitorJSONBlockStatsUpdateCapacityData(virJSONValuePtr image,
                                            const char *name,
                                            GHashTable *stats,
                                            qemuBlockStatsPtr *entry)
{
    qemuBlockStatsPtr 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(virJSONValuePtr image,
                                           const char *dev_name,
                                           int depth,
                                           GHashTable *stats,
                                           bool backingChain)
{
    int ret = -1;
    char *entry_name = qemuDomainStorageAlias(dev_name, depth);
    virJSONValuePtr backing;

    if (qemuMonitorJSONBlockStatsUpdateCapacityData(image, entry_name,
                                                    stats, NULL) < 0)
        goto cleanup;

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

    ret = 0;
 cleanup:
    VIR_FREE(entry_name);
    return ret;
}


int
qemuMonitorJSONBlockStatsUpdateCapacity(qemuMonitorPtr mon,
                                        GHashTable *stats,
                                        bool backingChain)
{
    int ret = -1;
    size_t i;
    virJSONValuePtr devices;

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

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

        if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
            goto cleanup;

        if (!(dev_name = qemuMonitorJSONGetBlockDevDevice(dev)))
            goto cleanup;

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

        if (qemuMonitorJSONBlockStatsUpdateCapacityOne(image, dev_name, 0,
                                                       stats,
                                                       backingChain) < 0)
            goto cleanup;
    }

    ret = 0;

 cleanup:
    virJSONValueFree(devices);
    return ret;
}


static int
qemuMonitorJSONBlockStatsUpdateCapacityBlockdevWorker(size_t pos G_GNUC_UNUSED,
                                                      virJSONValuePtr val,
                                                      void *opaque)
{
    GHashTable *stats = opaque;
    virJSONValuePtr image;
    const char *nodename;
    qemuBlockStatsPtr 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(qemuMonitorPtr mon,
                                                GHashTable *stats)
{
    virJSONValuePtr nodes;
    int ret = -1;

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

    if (virJSONValueArrayForeachSteal(nodes,
                                      qemuMonitorJSONBlockStatsUpdateCapacityBlockdevWorker,
                                      stats) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(nodes);
    return ret;
}


static void
qemuMonitorJSONBlockNamedNodeDataBitmapFree(qemuBlockNamedNodeDataBitmapPtr bitmap)
{
    if (!bitmap)
        return;

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

G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockNamedNodeDataBitmap,
                              qemuMonitorJSONBlockNamedNodeDataBitmapFree);


static void
qemuMonitorJSONBlockNamedNodeDataFree(qemuBlockNamedNodeDataPtr 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 qemuBlockNamedNodeDataBitmapPtr
qemuMonitorJSONBlockGetNamedNodeDataBitmapOne(virJSONValuePtr 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(virJSONValuePtr bitmaps,
                                            qemuBlockNamedNodeDataPtr data)
{
    size_t nbitmaps = virJSONValueArraySize(bitmaps);
    size_t i;

    data->bitmaps = g_new0(qemuBlockNamedNodeDataBitmapPtr, nbitmaps);

    for (i = 0; i < nbitmaps; i++) {
        virJSONValuePtr bitmap = virJSONValueArrayGet(bitmaps, i);
        qemuBlockNamedNodeDataBitmapPtr tmp;

        if (!bitmap)
            continue;

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

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


static int
qemuMonitorJSONBlockGetNamedNodeDataWorker(size_t pos G_GNUC_UNUSED,
                                           virJSONValuePtr val,
                                           void *opaque)
{
    GHashTable *nodes = opaque;
    virJSONValuePtr img;
    virJSONValuePtr bitmaps;
    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);

    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(virJSONValuePtr nodes)
{
    g_autoptr(GHashTable) ret = NULL;

    ret = virHashNew((virHashDataFree) qemuMonitorJSONBlockNamedNodeDataFree);

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

    return g_steal_pointer(&ret);
}


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

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

    return qemuMonitorJSONBlockGetNamedNodeDataJSON(nodes);
}


int qemuMonitorJSONBlockResize(qemuMonitorPtr mon,
                               const char *device,
                               const char *nodename,
                               unsigned long long size)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr 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)
        goto cleanup;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONSetPassword(qemuMonitorPtr mon,
                               const char *protocol,
                               const char *password,
                               const char *action_if_connected)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("set_password",
                                                     "s:protocol", protocol,
                                                     "s:password", password,
                                                     "s:connected", action_if_connected,
                                                     NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONExpirePassword(qemuMonitorPtr mon,
                                  const char *protocol,
                                  const char *expire_time)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("expire_password",
                                                     "s:protocol", protocol,
                                                     "s:time", expire_time,
                                                     NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONSetBalloon(qemuMonitorPtr mon,
                          unsigned long long newmem)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("balloon",
                                                     "U:value", newmem * 1024,
                                                     NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

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

    /* See if any other fatal error occurred */
    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    /* Real success */
    ret = 1;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONSetCPU(qemuMonitorPtr mon,
                          int cpu, bool online)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;

    if (online) {
        cmd = qemuMonitorJSONMakeCommand("cpu-add",
                                         "i:id", cpu,
                                         NULL);
    } else {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("vCPU unplug is not supported by this QEMU"));
        goto cleanup;
    }
    if (!cmd)
        goto cleanup;

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

    ret = qemuMonitorJSONCheckError(cmd, reply);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


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

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONChangeMedia(qemuMonitorPtr mon,
                               const char *dev_name,
                               const char *newmedia,
                               const char *format)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr 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)
        goto cleanup;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


static int qemuMonitorJSONSaveMemory(qemuMonitorPtr mon,
                                     const char *cmdtype,
                                     unsigned long long offset,
                                     unsigned long long length,
                                     const char *path)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand(cmdtype,
                                                     "U:val", offset,
                                                     "U:size", length,
                                                     "s:filename", path,
                                                     NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


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

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


int qemuMonitorJSONSetMigrationSpeed(qemuMonitorPtr mon,
                                     unsigned long bandwidth)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    cmd = qemuMonitorJSONMakeCommand("migrate_set_speed",
                                     "U:value", bandwidth * 1024ULL * 1024ULL,
                                     NULL);
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONSetMigrationDowntime(qemuMonitorPtr mon,
                                        unsigned long long downtime)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

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

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONGetMigrationCacheSize(qemuMonitorPtr mon,
                                     unsigned long long *cacheSize)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    *cacheSize = 0;

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

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

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

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

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONSetMigrationCacheSize(qemuMonitorPtr mon,
                                     unsigned long long cacheSize)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

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

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONGetMigrationParams(qemuMonitorPtr mon,
                                  virJSONValuePtr *params)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;

    *params = NULL;

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

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

    if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
        ret = 0;
        goto cleanup;
    }

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

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

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int
qemuMonitorJSONSetMigrationParams(qemuMonitorPtr mon,
                                  virJSONValuePtr *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(virJSONValuePtr reply,
                                      qemuMonitorMigrationStatsPtr stats,
                                      char **error)
{
    virJSONValuePtr ret;
    virJSONValuePtr ram;
    virJSONValuePtr disk;
    virJSONValuePtr 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(qemuMonitorPtr mon,
                                     qemuMonitorMigrationStatsPtr stats,
                                     char **error)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("query-migrate",
                                                     NULL);
    virJSONValuePtr reply = NULL;

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

    if (!cmd)
        return -1;

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

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

    if (qemuMonitorJSONGetMigrationStatsReply(reply, stats, error) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    if (ret < 0)
        memset(stats, 0, sizeof(*stats));
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONMigrate(qemuMonitorPtr mon,
                           unsigned int flags,
                           const char *uri)
{
    int ret = -1;
    virJSONValuePtr cmd =
      qemuMonitorJSONMakeCommand("migrate",
                                 "b:detach", flags & QEMU_MONITOR_MIGRATE_BACKGROUND ? 1 : 0,
                                 "b:blk", flags & QEMU_MONITOR_MIGRATE_NON_SHARED_DISK ? 1 : 0,
                                 "b:inc", flags & QEMU_MONITOR_MIGRATE_NON_SHARED_INC ? 1 : 0,
                                 "s:uri", uri,
                                 NULL);
    virJSONValuePtr reply = NULL;

    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONMigrateCancel(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("migrate_cancel", NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/* 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(qemuMonitorPtr mon,
                         qemuMonitorDumpStatsPtr stats)
{
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("query-dump", NULL);
    virJSONValuePtr reply = NULL;
    virJSONValuePtr result = NULL;
    int ret = -1;

    if (!cmd)
        return -1;

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

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

    result = virJSONValueObjectGetObject(reply, "return");

    ret = qemuMonitorJSONExtractDumpStats(result, stats);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONGetDumpGuestMemoryCapability(qemuMonitorPtr mon,
                                            const char *capability)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr caps;
    virJSONValuePtr formats;
    size_t i;

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

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

    if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
        ret = 0;
        goto cleanup;
    }

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

    caps = virJSONValueObjectGetObject(reply, "return");

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

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

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

        if (STREQ(virJSONValueGetString(dumpformat), capability)) {
            ret = 1;
            goto cleanup;
        }
    }

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int
qemuMonitorJSONDump(qemuMonitorPtr mon,
                    const char *protocol,
                    const char *dumpformat,
                    bool detach)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr 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)
        goto cleanup;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONGraphicsRelocate(qemuMonitorPtr mon,
                                    int type,
                                    const char *hostname,
                                    int port,
                                    int tlsPort,
                                    const char *tlsSubject)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("client_migrate_info",
                                                     "s:protocol",
                                                     (type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE ? "spice" : "vnc"),
                                                     "s:hostname", hostname,
                                                     "i:port", port,
                                                     "i:tls-port", tlsPort,
                                                     "S:cert-subject", tlsSubject,
                                                     NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


static int
qemuAddfdInfoParse(virJSONValuePtr msg,
                   qemuMonitorAddFdInfoPtr fdinfo)
{
    virJSONValuePtr 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(qemuMonitorPtr mon,
                                      int fd,
                                      int fdset,
                                      const char *opaque,
                                      qemuMonitorAddFdInfoPtr fdinfo)
{
    g_autoptr(virJSONValue) args = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) cmd = NULL;

    if (virJSONValueObjectCreate(&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(virJSONValuePtr msg,
                                qemuMonitorFdsetsPtr *fdsets)
{
    virJSONValuePtr returnArray, 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;
        const char *tmp;
        virJSONValuePtr fdarray;
        qemuMonitorFdsetInfoPtr 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 (virJSONValueObjectGetNumberInt(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++) {
            qemuMonitorFdsetFdInfoPtr fdinfo = &fdsetinfo->fds[j];
            virJSONValuePtr 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 */
            tmp = virJSONValueObjectGetString(fdentry, "opaque");
            if (tmp)
                fdinfo->opaque = g_strdup(tmp);
        }
    }

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


int qemuMonitorJSONQueryFdsets(qemuMonitorPtr mon,
                               qemuMonitorFdsetsPtr *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(qemuMonitorPtr mon,
                               int fdset)
{
    g_autoptr(virJSONValue) reply = NULL;
    g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("remove-fd",
                                                             "i: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(qemuMonitorPtr mon,
                                  const char *fdname,
                                  int fd)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("getfd",
                                                     "s:fdname", fdname,
                                                     NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONCloseFileHandle(qemuMonitorPtr mon,
                                   const char *fdname)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("closefd",
                                                     "s:fdname", fdname,
                                                     NULL);
    virJSONValuePtr reply = NULL;
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONAddNetdev(qemuMonitorPtr mon,
                         virJSONValuePtr *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(qemuMonitorPtr 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(virJSONValuePtr msg,
                                  virNetDevRxFilterPtr *filter)
{
    int ret = -1;
    const char *tmp;
    virJSONValuePtr returnArray, entry, table, element;
    size_t nTable;
    size_t i;
    virNetDevRxFilterPtr fil = virNetDevRxFilterNew();

    if (!fil)
        goto cleanup;

    returnArray = virJSONValueObjectGetArray(msg, "return");

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

    if (!(tmp = virJSONValueObjectGetString(entry, "name"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid name "
                         "in query-rx-filter response"));
        goto cleanup;
    }
    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"));
        goto cleanup;
    }
    if (virJSONValueObjectGetBoolean(entry, "promiscuous",
                                     &fil->promiscuous) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'promiscuous' "
                         "in query-rx-filter response"));
        goto cleanup;
    }
    if (virJSONValueObjectGetBoolean(entry, "broadcast-allowed",
                                     &fil->broadcastAllowed) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing or invalid 'broadcast-allowed' "
                         "in query-rx-filter response"));
        goto cleanup;
    }

    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"));
        goto cleanup;
    }
    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"));
        goto cleanup;
    }
    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"));
        goto cleanup;
    }
    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);
            goto cleanup;
        }
        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);
            goto cleanup;
        }
    }
    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"));
        goto cleanup;
    }
    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"));
        goto cleanup;
    }
    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"));
        goto cleanup;
    }
    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);
            goto cleanup;
        }
        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);
            goto cleanup;
        }
    }
    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"));
        goto cleanup;
    }
    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"));
        goto cleanup;
    }
    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);
            goto cleanup;
        }
    }
    fil->vlan.nTable = nTable;

    ret = 0;
 cleanup:
    if (ret < 0) {
        virNetDevRxFilterFree(fil);
        fil = NULL;
    }
    *filter = fil;
    return ret;
}


int
qemuMonitorJSONQueryRxFilter(qemuMonitorPtr mon, const char *alias,
                             virNetDevRxFilterPtr *filter)
{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("query-rx-filter",
                                                     "s:name", alias,
                                                     NULL);
    virJSONValuePtr reply = NULL;

    if (!cmd)
        goto cleanup;

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

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

    if (qemuMonitorJSONQueryRxFilterParse(reply, filter) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    if (ret < 0) {
        virNetDevRxFilterFree(*filter);
        *filter = NULL;
    }
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/*
 * 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(virJSONValuePtr reply,
                                  GHashTable *info)
{
    virJSONValuePtr data;
    int ret = -1;
    size_t i;
    qemuMonitorChardevInfoPtr entry = NULL;

    data = virJSONValueObjectGetArray(reply, "return");

    for (i = 0; i < virJSONValueArraySize(data); i++) {
        virJSONValuePtr 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(qemuMonitorPtr mon,
                              GHashTable *info)

{
    int ret = -1;
    virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("query-chardev",
                                                     NULL);
    virJSONValuePtr reply = NULL;

    if (!cmd)
        return -1;

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

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

    ret = qemuMonitorJSONExtractChardevInfo(reply, info);
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONDelDevice(qemuMonitorPtr mon,
                             const char *devalias)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

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

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

    if (qemuMonitorJSONHasError(reply, "DeviceNotFound")) {
        ret = -2;
        goto cleanup;
    }

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONAddDeviceArgs(qemuMonitorPtr mon,
                             virJSONValuePtr args)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("device_add", NULL)))
        goto cleanup;

    if (virJSONValueObjectAppend(cmd, "arguments", args) < 0)
        goto cleanup;
    args = NULL; /* obj owns reference to args now */

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(args);
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONAddDevice(qemuMonitorPtr mon,
                         const char *devicestr)
{
    virJSONValuePtr args;

    if (!(args = qemuMonitorJSONKeywordStringToJSON(devicestr, "driver")))
        return -1;

    return qemuMonitorJSONAddDeviceArgs(mon, args);
}


int
qemuMonitorJSONAddObject(qemuMonitorPtr mon,
                         virJSONValuePtr *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(qemuMonitorPtr 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(qemuMonitorPtr 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;

    cmd = qemuMonitorJSONMakeCommand("drive-mirror",
                                     "s:device", device,
                                     "s:target", file,
                                     "Y:speed", speed,
                                     "z:granularity", granularity,
                                     "P:buf-size", buf_size,
                                     "s:sync", shallow ? "top" : "full",
                                     "s:mode", reuse ? "existing" : "absolute-paths",
                                     "S:format", format,
                                     NULL);
    if (!cmd)
        return -1;

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

    return qemuMonitorJSONCheckError(cmd, reply);
}


int
qemuMonitorJSONBlockdevMirror(qemuMonitorPtr 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)
{
    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("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", shallow ? "top" : "full",
                                     "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(qemuMonitorPtr mon, virJSONValuePtr *actions)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("transaction",
                                     "a:actions", actions,
                                     NULL);
    if (!cmd)
        goto cleanup;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

/* Probe if active commit is supported: pass in a bogus device and NULL top
 * and base.  The probe return is true if active commit is detected or false
 * if not supported or on any error */
bool
qemuMonitorJSONSupportsActiveCommit(qemuMonitorPtr mon)
{
    bool ret = false;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("block-commit", "s:device",
                                           "bogus", NULL)))
        return false;

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

    if (qemuMonitorJSONHasError(reply, "DeviceNotFound")) {
        VIR_DEBUG("block-commit supports active commit");
        ret = true;
        goto cleanup;
    }

    /* This is a false negative for qemu 2.0; but probably not
     * worth the additional complexity to worry about it */
    VIR_DEBUG("block-commit requires 'top' parameter, "
              "assuming it lacks active commit");
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/* speed is in bytes/sec. Returns 0 on success, -1 with error message
 * emitted on failure. */
int
qemuMonitorJSONBlockCommit(qemuMonitorPtr 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)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr 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)
        goto cleanup;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


static char *
qemuMonitorJSONDiskNameLookupOne(virJSONValuePtr image,
                                 virStorageSourcePtr top,
                                 virStorageSourcePtr target)
{
    virJSONValuePtr 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(qemuMonitorPtr mon,
                              const char *device,
                              virStorageSourcePtr top,
                              virStorageSourcePtr target)
{
    char *ret = NULL;
    virJSONValuePtr devices;
    size_t i;

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

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

        if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
            goto cleanup;

        if (!(thisdev = qemuMonitorJSONGetBlockDevDevice(dev)))
            goto cleanup;

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

 cleanup:
    virJSONValueFree(devices);

    return ret;
}


int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon,
                                    const char *cmd_str,
                                    char **reply_str)
{
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    int ret = -1;

    if (!(cmd = virJSONValueFromString(cmd_str)))
        goto cleanup;

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

    if (!(*reply_str = virJSONValueToString(reply, false)))
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONInjectNMI(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

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

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONSendKey(qemuMonitorPtr mon,
                           unsigned int holdtime,
                           unsigned int *keycodes,
                           unsigned int nkeycodes)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr keys = NULL;
    virJSONValuePtr key = NULL;
    size_t i;

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

    for (i = 0; i < nkeycodes; i++) {
        if (keycodes[i] > 0xffff) {
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("keycode %zu is invalid: 0x%X"), i, keycodes[i]);
            goto cleanup;
        }

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

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

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

        if (virJSONValueArrayAppend(keys, key) < 0)
            goto cleanup;

        key = NULL;

    }

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

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    virJSONValueFree(keys);
    virJSONValueFree(key);
    return ret;
}

int qemuMonitorJSONScreendump(qemuMonitorPtr mon,
                              const char *device,
                              unsigned int head,
                              const char *file)
{
    int ret = -1;
    virJSONValuePtr cmd, 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)
        goto cleanup;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


static int
qemuMonitorJSONParseBlockJobInfo(GHashTable *blockJobs,
                                 virJSONValuePtr entry,
                                 bool rawjobname)
{
    qemuMonitorBlockJobInfoPtr 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(qemuMonitorPtr mon,
                                  bool rawjobname)
{
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    size_t nr_results;
    size_t i;
    GHashTable *blockJobs = NULL;

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

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

    nr_results = virJSONValueArraySize(data);
    if (!(blockJobs = virHashNew(g_free)))
        goto cleanup;

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

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return blockJobs;

 error:
    virHashFree(blockJobs);
    blockJobs = NULL;
    goto cleanup;
}


static int
qemuMonitorJSONBlockJobError(virJSONValuePtr cmd,
                             virJSONValuePtr reply,
                             const char *jobname)
{
    virJSONValuePtr 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(qemuMonitorPtr mon,
                           const char *device,
                           const char *jobname,
                           bool persistjob,
                           const char *base,
                           const char *baseNode,
                           const char *backingName,
                           unsigned long long speed)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr 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)
        goto cleanup;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONBlockJobCancel(qemuMonitorPtr mon,
                              const char *jobname)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;

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

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

    if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONBlockJobSetSpeed(qemuMonitorPtr mon,
                                const char *jobname,
                                unsigned long long speed)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

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

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

    if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONDrivePivot(qemuMonitorPtr mon,
                          const char *jobname)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

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

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

    if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONJobDismiss(qemuMonitorPtr 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
qemuMonitorJSONJobCancel(qemuMonitorPtr mon,
                         const char *jobname,
                         bool quiet)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

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

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

    if (quiet) {
        if (virJSONValueObjectHasKey(reply, "error") != 0)
            return -1;
    } else {
        if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
            return -1;
    }

    return 0;
}


int
qemuMonitorJSONJobComplete(qemuMonitorPtr 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(qemuMonitorPtr mon,
                                const char *protocol,
                                const char *fdname,
                                bool skipauth)
{
    int ret = -1;
    virJSONValuePtr cmd, 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)
        goto cleanup;

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


#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(virJSONValuePtr io_throttle,
                                   const char *drivealias,
                                   const char *qdevid,
                                   virDomainBlockIoTuneInfoPtr reply)
{
    size_t i;
    bool found = false;

    for (i = 0; i < virJSONValueArraySize(io_throttle); i++) {
        virJSONValuePtr temp_dev = virJSONValueArrayGet(io_throttle, i);
        virJSONValuePtr 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(qemuMonitorPtr mon,
                                      const char *drivealias,
                                      const char *qomid,
                                      virDomainBlockIoTuneInfoPtr info,
                                      bool supportMaxOptions,
                                      bool supportGroupNameOption,
                                      bool supportMaxLengthOptions)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr result = NULL;
    virJSONValuePtr args = NULL;
    const char *errdev = drivealias;

    if (!errdev)
        errdev = qomid;

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

    if (virJSONValueObjectCreate(&args,
                                 "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,
                                 NULL) < 0)
        goto cleanup;

    if (supportMaxOptions &&
        virJSONValueObjectAdd(args,
                              "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,
                              NULL) < 0)
        goto cleanup;

    if (supportGroupNameOption &&
        virJSONValueObjectAdd(args,
                              "S:group", info->group_name,
                              NULL) < 0)
        goto cleanup;

    if (supportMaxLengthOptions &&
        virJSONValueObjectAdd(args,
                              "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) < 0)
        goto cleanup;

    if (virJSONValueObjectAppend(cmd, "arguments", args) < 0)
        goto cleanup;
    args = NULL; /* obj owns reference to args now */

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

    if (virJSONValueObjectHasKey(result, "error")) {
        if (qemuMonitorJSONHasError(result, "DeviceNotActive")) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("No active operation on device: %s"), errdev);
        } else if (qemuMonitorJSONHasError(result, "NotSupported")) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("Operation is not supported for device: %s"), errdev);
        } else {
            virJSONValuePtr error = virJSONValueObjectGet(result, "error");
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("unable to execute '%s', unexpected error: '%s'"),
                           qemuMonitorJSONCommandName(cmd),
                           qemuMonitorJSONStringifyError(error));
        }
        goto cleanup;
    }

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(result);
    virJSONValueFree(args);
    return ret;
}

int qemuMonitorJSONGetBlockIoThrottle(qemuMonitorPtr mon,
                                      const char *drivealias,
                                      const char *qdevid,
                                      virDomainBlockIoTuneInfoPtr reply)
{
    int ret = -1;
    virJSONValuePtr devices = NULL;

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

    ret = qemuMonitorJSONBlockIoThrottleInfo(devices, drivealias, qdevid, reply);
    virJSONValueFree(devices);
    return ret;
}

int qemuMonitorJSONSystemWakeup(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;

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

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONGetVersion(qemuMonitorPtr mon,
                              int *major,
                              int *minor,
                              int *micro,
                              char **package)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    virJSONValuePtr qemu;

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

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

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

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

    data = virJSONValueObjectGetObject(reply, "return");

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

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

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

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONGetMachines(qemuMonitorPtr mon,
                               qemuMonitorMachineInfoPtr **machines)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    qemuMonitorMachineInfoPtr *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(qemuMonitorMachineInfoPtr, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValuePtr child = virJSONValueArrayGet(data, i);
        const char *tmp;
        qemuMonitorMachineInfoPtr 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);
        }
    }

    ret = n;
    *machines = infolist;
    infolist = NULL;

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


int
qemuMonitorJSONGetCPUDefinitions(qemuMonitorPtr mon,
                                 qemuMonitorCPUDefsPtr *cpuDefs)
{
    g_autoptr(qemuMonitorCPUDefs) defs = NULL;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValuePtr 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++) {
        virJSONValuePtr child = virJSONValueArrayGet(data, i);
        const char *tmp;
        qemuMonitorCPUDefInfoPtr 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;
        }
    }

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


VIR_ENUM_IMPL(qemuMonitorCPUProperty,
              QEMU_MONITOR_CPU_PROPERTY_LAST,
              "boolean", "string", "number",
);

static int
qemuMonitorJSONParseCPUModelProperty(const char *key,
                                     virJSONValue *value,
                                     void *opaque)
{
    qemuMonitorCPUModelInfoPtr machine_model = opaque;
    qemuMonitorCPUPropertyPtr 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 virJSONValuePtr
qemuMonitorJSONMakeCPUModel(virCPUDefPtr cpu,
                            bool migratable)
{
    virJSONValuePtr model = virJSONValueNewObject();
    virJSONValuePtr props = NULL;
    size_t i;

    if (virJSONValueObjectAppendString(model, "name", cpu->model) < 0)
        goto error;

    if (cpu->nfeatures || !migratable) {
        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)
                goto error;
        }

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

        if (virJSONValueObjectAppend(model, "props", props) < 0)
            goto error;
    }

    return model;

 error:
    virJSONValueFree(model);
    virJSONValueFree(props);
    return NULL;
}


static int
qemuMonitorJSONParseCPUModelData(virJSONValuePtr data,
                                 const char *cmd_name,
                                 bool fail_no_props,
                                 virJSONValuePtr *cpu_model,
                                 virJSONValuePtr *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,
                             virJSONValuePtr cpu_props,
                             qemuMonitorCPUModelInfoPtr *model_info)
{
    qemuMonitorCPUModelInfoPtr machine_model = NULL;
    int ret = -1;

    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)
            goto cleanup;
    }

    *model_info = g_steal_pointer(&machine_model);
    ret = 0;

 cleanup:
    qemuMonitorCPUModelInfoFree(machine_model);
    return ret;
}


int
qemuMonitorJSONGetCPUModelExpansion(qemuMonitorPtr mon,
                                    qemuMonitorCPUModelExpansionType type,
                                    virCPUDefPtr cpu,
                                    bool migratable,
                                    bool fail_no_props,
                                    qemuMonitorCPUModelInfoPtr *model_info)
{
    g_autoptr(virJSONValue) model = NULL;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValuePtr data;
    virJSONValuePtr cpu_model;
    virJSONValuePtr cpu_props = NULL;
    const char *cpu_name = "";
    const char *typeStr = "";

    *model_info = NULL;

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

 retry:
    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 = virJSONValueObjectGetObject(reply, "return");

    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) {
        if (!(model = virJSONValueCopy(cpu_model)))
            return -1;

        virJSONValueFree(cmd);
        virJSONValueFree(reply);
        type = QEMU_MONITOR_CPU_MODEL_EXPANSION_FULL;
        goto retry;
    }

    return qemuMonitorJSONParseCPUModel(cpu_name, cpu_props, model_info);
}


int
qemuMonitorJSONGetCPUModelBaseline(qemuMonitorPtr mon,
                                   virCPUDefPtr cpu_a,
                                   virCPUDefPtr cpu_b,
                                   qemuMonitorCPUModelInfoPtr *baseline)
{
    g_autoptr(virJSONValue) model_a = NULL;
    g_autoptr(virJSONValue) model_b = NULL;
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;
    virJSONValuePtr data;
    virJSONValuePtr cpu_model;
    virJSONValuePtr 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(qemuMonitorPtr mon,
                                     virCPUDefPtr cpu_a,
                                     virCPUDefPtr 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;
    virJSONValuePtr 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(qemuMonitorPtr mon,
                               char ***commands)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    char **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)
        goto cleanup;

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

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

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

    for (i = 0; i < n; i++) {
        virJSONValuePtr 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'"));
            goto cleanup;
        }

        commandlist[i] = g_strdup(tmp);
    }

    ret = n;
    *commands = commandlist;
    commandlist = NULL;


 cleanup:
    g_strfreev(commandlist);
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONGetEvents(qemuMonitorPtr mon,
                             char ***events)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    char **eventlist = NULL;
    size_t n = 0;
    size_t i;

    *events = NULL;

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

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

    if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
        ret = 0;
        goto cleanup;
    }

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

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

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

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

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

        eventlist[i] = g_strdup(tmp);
    }

    ret = n;
    *events = eventlist;
    eventlist = NULL;

 cleanup:
    g_strfreev(eventlist);
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


static int
qemuMonitorJSONGetCommandLineOptionsWorker(size_t pos G_GNUC_UNUSED,
                                           virJSONValuePtr 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(qemuMonitorPtr 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(qemuMonitorPtr mon,
                               bool *enabled,
                               bool *present)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data = NULL;

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

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

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

    if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
        ret = 0;
        goto cleanup;
    }

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

    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"));
        goto cleanup;
    }

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONGetObjectTypes(qemuMonitorPtr mon,
                                  char ***types)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    char **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)
        goto cleanup;

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

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

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

    for (i = 0; i < n; i++) {
        virJSONValuePtr 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'"));
            goto cleanup;
        }

        typelist[i] = g_strdup(tmp);
    }

    ret = n;
    *types = typelist;
    typelist = NULL;

 cleanup:
    g_strfreev(typelist);
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int qemuMonitorJSONGetObjectListPaths(qemuMonitorPtr mon,
                                      const char *path,
                                      qemuMonitorJSONListPathPtr **paths)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    qemuMonitorJSONListPathPtr *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(qemuMonitorJSONListPathPtr, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValuePtr child = virJSONValueArrayGet(data, i);
        const char *tmp;
        qemuMonitorJSONListPathPtr 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 = pathlist;
    pathlist = NULL;

 cleanup:
    if (pathlist) {
        for (i = 0; i < n; i++)
            qemuMonitorJSONListPathFree(pathlist[i]);
        VIR_FREE(pathlist);
    }
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

void qemuMonitorJSONListPathFree(qemuMonitorJSONListPathPtr paths)
{
    if (!paths)
        return;
    VIR_FREE(paths->name);
    VIR_FREE(paths->type);
    VIR_FREE(paths);
}


int qemuMonitorJSONGetObjectProperty(qemuMonitorPtr mon,
                                     const char *path,
                                     const char *property,
                                     qemuMonitorJSONObjectPropertyPtr prop)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    const char *tmp;

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

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    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);
        goto cleanup;
        break;
    }

    if (ret == -1) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("qom-get reply was missing return data"));
        goto cleanup;
    }

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);

    return ret;
}


static int
qemuMonitorJSONGetStringListProperty(qemuMonitorPtr 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 g_strv_length(*strList);
}


#define MAKE_SET_CMD(STRING, VALUE) \
    cmd = qemuMonitorJSONMakeCommand("qom-set", \
                                      "s:path", path, \
                                      "s:property", property, \
                                      STRING, VALUE, \
                                      NULL)
int qemuMonitorJSONSetObjectProperty(qemuMonitorPtr mon,
                                     const char *path,
                                     const char *property,
                                     qemuMonitorJSONObjectPropertyPtr prop)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr 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);
        goto cleanup;

    }
    if (!cmd)
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);

    return ret;
}
#undef MAKE_SET_CMD


static int
qemuMonitorJSONParsePropsList(virJSONValuePtr cmd,
                              virJSONValuePtr reply,
                              const char *type,
                              char ***props)
{
    virJSONValuePtr data;
    char **proplist = NULL;
    size_t n = 0;
    size_t count = 0;
    size_t i;
    int ret = -1;

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

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

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

    for (i = 0; i < n; i++) {
        virJSONValuePtr 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'"));
            goto cleanup;
        }

        proplist[count++] = g_strdup(tmp);
    }

    ret = count;
    *props = proplist;
    proplist = NULL;

 cleanup:
    g_strfreev(proplist);
    return ret;
}


static int
qemuMonitorJSONGetDevicePropsWorker(size_t pos G_GNUC_UNUSED,
                                    virJSONValuePtr 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(qemuMonitorPtr 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(qemuMonitorPtr mon,
                              const char *object,
                              char ***props)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    *props = NULL;

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

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

    if (qemuMonitorJSONHasError(reply, "DeviceNotFound")) {
        ret = 0;
        goto cleanup;
    }

    ret = qemuMonitorJSONParsePropsList(cmd, reply, NULL, props);
 cleanup:
    virJSONValueFree(reply);
    virJSONValueFree(cmd);
    return ret;
}


char *
qemuMonitorJSONGetTargetArch(qemuMonitorPtr mon)
{
    char *ret = NULL;
    const char *arch;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;

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

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

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

    data = virJSONValueObjectGetObject(reply, "return");

    if (!(arch = virJSONValueObjectGetString(data, "arch"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-target reply was missing arch data"));
        goto cleanup;
    }

    ret = g_strdup(arch);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONGetMigrationCapabilities(qemuMonitorPtr mon,
                                        char ***capabilities)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr caps;
    char **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)
        goto cleanup;

    if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
        ret = 0;
        goto cleanup;
    }

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

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

    list = g_new0(char *, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValuePtr 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"));
            goto cleanup;
        }

        if (!(name = virJSONValueObjectGetString(cap, "capability"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing migration capability name"));
            goto cleanup;
        }

        list[i] = g_strdup(name);
    }

    ret = n;
    *capabilities = list;
    list = NULL;

 cleanup:
    g_strfreev(list);
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONSetMigrationCapabilities(qemuMonitorPtr mon,
                                        virJSONValuePtr *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(qemuMonitorPtr mon,
                                  virGICCapability **capabilities)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr caps;
    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)
        goto cleanup;

    /* 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")) {
        ret = 0;
        goto cleanup;
    }

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

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

    /* If the returned array was empty we have to return successfully */
    if (n == 0) {
        ret = 0;
        goto cleanup;
    }

    list = g_new0(virGICCapability, n);

    for (i = 0; i < n; i++) {
        virJSONValuePtr 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"));
            goto cleanup;
        }

        if (virJSONValueObjectGetNumberInt(cap, "version", &version) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing GIC version"));
            goto cleanup;
        }

        if (virJSONValueObjectGetBoolean(cap, "kernel", &kernel) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing in-kernel GIC information"));
            goto cleanup;
        }

        if (virJSONValueObjectGetBoolean(cap, "emulated", &emulated) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing emulated GIC information"));
            goto cleanup;
        }

        list[i].version = version;
        if (kernel)
            list[i].implementation |= VIR_GIC_IMPLEMENTATION_KERNEL;
        if (emulated)
            list[i].implementation |= VIR_GIC_IMPLEMENTATION_EMULATED;
    }

    ret = n;
    *capabilities = list;
    list = NULL;

 cleanup:
    VIR_FREE(list);
    virJSONValueFree(cmd);
    virJSONValueFree(reply);

    return ret;
}


/**
 * 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(qemuMonitorPtr mon,
                                  virSEVCapability **capabilities)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr 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)
        goto cleanup;

    /* QEMU has only compiled-in support of SEV */
    if (qemuMonitorJSONHasError(reply, "GenericError")) {
        ret = 0;
        goto cleanup;
    }

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    caps = virJSONValueObjectGetObject(reply, "return");

    if (virJSONValueObjectGetNumberUint(caps, "cbitpos", &cbitpos) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-sev-capabilities reply was missing"
                         " 'cbitpos' field"));
        goto cleanup;
    }

    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"));
        goto cleanup;
    }

    if (!(pdh = virJSONValueObjectGetString(caps, "pdh"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-sev-capabilities reply was missing"
                         " 'pdh' field"));
        goto cleanup;
    }

    if (!(cert_chain = virJSONValueObjectGetString(caps, "cert-chain"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("query-sev-capabilities reply was missing"
                         " 'cert-chain' field"));
        goto cleanup;
    }

    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);
    ret = 1;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);

    return ret;
}

static virJSONValuePtr
qemuMonitorJSONBuildInetSocketAddress(const char *host,
                                      const char *port)
{
    virJSONValuePtr addr = NULL;
    virJSONValuePtr data = NULL;

    if (virJSONValueObjectCreate(&data, "s:host", host,
                                        "s:port", port, NULL) < 0)
        return NULL;

    if (virJSONValueObjectCreate(&addr, "s:type", "inet",
                                        "a:data", &data, NULL) < 0) {
        virJSONValueFree(data);
        return NULL;
    }

    return addr;
}

static virJSONValuePtr
qemuMonitorJSONBuildUnixSocketAddress(const char *path)
{
    virJSONValuePtr addr = NULL;
    virJSONValuePtr data = NULL;

    if (virJSONValueObjectCreate(&data, "s:path", path, NULL) < 0)
        return NULL;

    if (virJSONValueObjectCreate(&addr, "s:type", "unix",
                                        "a:data", &data, NULL) < 0) {
        virJSONValueFree(data);
        return NULL;
    }

    return addr;
}

int
qemuMonitorJSONNBDServerStart(qemuMonitorPtr mon,
                              const virStorageNetHostDef *server,
                              const char *tls_alias)
{
    int ret = -1;
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr addr = NULL;
    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"));
        goto cleanup;
    }
    if (!addr)
        goto cleanup;

    if (!(cmd = qemuMonitorJSONMakeCommand("nbd-server-start",
                                           "a:addr", &addr,
                                           "S:tls-creds", tls_alias,
                                           NULL)))
        goto cleanup;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    VIR_FREE(port_str);
    virJSONValueFree(reply);
    virJSONValueFree(cmd);
    virJSONValueFree(addr);
    return ret;
}

int
qemuMonitorJSONNBDServerAdd(qemuMonitorPtr mon,
                            const char *deviceID,
                            const char *export,
                            bool writable,
                            const char *bitmap)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr 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 ret;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int
qemuMonitorJSONNBDServerStop(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("nbd-server-stop",
                                           NULL)))
        return ret;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONBlockExportAdd(qemuMonitorPtr mon,
                              virJSONValuePtr *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(qemuMonitorPtr mon, const char *qmpCmd,
                              char ***array)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    *array = NULL;

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

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

    if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
        ret = 0;
        goto cleanup;
    }

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

    if (!(*array = virJSONValueObjectGetStringArray(reply, "return")))
        goto cleanup;

    ret = g_strv_length(*array);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int qemuMonitorJSONGetTPMModels(qemuMonitorPtr mon,
                                char ***tpmmodels)
{
    return qemuMonitorJSONGetStringArray(mon, "query-tpm-models", tpmmodels);
}


int qemuMonitorJSONGetTPMTypes(qemuMonitorPtr mon,
                               char ***tpmtypes)
{
    return qemuMonitorJSONGetStringArray(mon, "query-tpm-types", tpmtypes);
}

static int
qemuMonitorJSONBuildChrChardevReconnect(virJSONValuePtr object,
                                        const virDomainChrSourceReconnectDef *def)
{
    if (def->enabled != VIR_TRISTATE_BOOL_YES)
        return 0;

    return virJSONValueObjectAppendNumberUint(object, "reconnect", def->timeout);
}

static virJSONValuePtr
qemuMonitorJSONAttachCharDevCommand(const char *chrID,
                                    const virDomainChrSourceDef *chr)
{
    virJSONValuePtr ret = NULL;
    virJSONValuePtr backend = virJSONValueNewObject();
    virJSONValuePtr data = virJSONValueNewObject();
    virJSONValuePtr addr = NULL;
    const char *backend_type = NULL;
    const char *host;
    const char *port;
    char *tlsalias = NULL;
    bool telnet;

    switch ((virDomainChrType)chr->type) {
    case VIR_DOMAIN_CHR_TYPE_NULL:
    case VIR_DOMAIN_CHR_TYPE_VC:
        backend_type = "null";
        break;

    case VIR_DOMAIN_CHR_TYPE_PTY:
        backend_type = "pty";
        break;

    case VIR_DOMAIN_CHR_TYPE_FILE:
        backend_type = "file";
        if (virJSONValueObjectAppendString(data, "out", chr->data.file.path) < 0)
            goto cleanup;
        if (virJSONValueObjectAdd(data,
                                  "T:append", chr->data.file.append,
                                  NULL) < 0)
            goto cleanup;
        break;

    case VIR_DOMAIN_CHR_TYPE_DEV:
        backend_type = STRPREFIX(chrID, "parallel") ? "parallel" : "serial";
        if (virJSONValueObjectAppendString(data, "device",
                                           chr->data.file.path) < 0)
            goto cleanup;
        break;

    case VIR_DOMAIN_CHR_TYPE_TCP:
        backend_type = "socket";
        addr = qemuMonitorJSONBuildInetSocketAddress(chr->data.tcp.host,
                                                     chr->data.tcp.service);
        if (!addr ||
            virJSONValueObjectAppend(data, "addr", addr) < 0)
            goto cleanup;
        addr = NULL;

        telnet = chr->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET;

        if (chr->data.tcp.listen &&
            virJSONValueObjectAppendBoolean(data, "wait", false) < 0)
            goto cleanup;

        if (virJSONValueObjectAppendBoolean(data, "telnet", telnet) < 0 ||
            virJSONValueObjectAppendBoolean(data, "server", chr->data.tcp.listen) < 0)
            goto cleanup;
        if (chr->data.tcp.tlscreds) {
            if (!(tlsalias = qemuAliasTLSObjFromSrcAlias(chrID)))
                goto cleanup;

            if (virJSONValueObjectAppendString(data, "tls-creds", tlsalias) < 0)
                goto cleanup;
        }

        if (qemuMonitorJSONBuildChrChardevReconnect(data, &chr->data.tcp.reconnect) < 0)
            goto cleanup;
        break;

    case VIR_DOMAIN_CHR_TYPE_UDP:
        backend_type = "udp";
        host = chr->data.udp.connectHost;
        if (!host)
            host = "";
        addr = qemuMonitorJSONBuildInetSocketAddress(host,
                                                     chr->data.udp.connectService);
        if (!addr ||
            virJSONValueObjectAppend(data, "remote", addr) < 0)
            goto cleanup;

        host = chr->data.udp.bindHost;
        port = chr->data.udp.bindService;
        if (host || port) {
            if (!host)
                host = "";
            if (!port)
                port = "";
            addr = qemuMonitorJSONBuildInetSocketAddress(host, port);
            if (!addr ||
                virJSONValueObjectAppend(data, "local", addr) < 0)
                goto cleanup;
        }
        addr = NULL;
        break;

    case VIR_DOMAIN_CHR_TYPE_UNIX:
        backend_type = "socket";
        addr = qemuMonitorJSONBuildUnixSocketAddress(chr->data.nix.path);

        if (!addr ||
            virJSONValueObjectAppend(data, "addr", addr) < 0)
            goto cleanup;
        addr = NULL;

        if (chr->data.nix.listen &&
            virJSONValueObjectAppendBoolean(data, "wait", false) < 0)
            goto cleanup;

        if (virJSONValueObjectAppendBoolean(data, "server", chr->data.nix.listen) < 0)
            goto cleanup;

        if (qemuMonitorJSONBuildChrChardevReconnect(data, &chr->data.nix.reconnect) < 0)
            goto cleanup;
        break;

    case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
        backend_type = "spicevmc";

        if (virJSONValueObjectAppendString(data, "type",
                                           virDomainChrSpicevmcTypeToString(chr->data.spicevmc)) < 0)
            goto cleanup;
        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:
    case VIR_DOMAIN_CHR_TYPE_LAST:
        if (virDomainChrTypeToString(chr->type)) {
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("Hotplug unsupported for char device type '%s'"),
                           virDomainChrTypeToString(chr->type));
        } else {
            virReportError(VIR_ERR_OPERATION_FAILED,
                           _("Hotplug unsupported for char device type '%d'"),
                           chr->type);
        }
        goto cleanup;
    }

    if (chr->logfile &&
        virJSONValueObjectAdd(data,
                              "s:logfile", chr->logfile,
                              "T:logappend", chr->logappend,
                              NULL) < 0)
        goto cleanup;

    if (virJSONValueObjectAppendString(backend, "type", backend_type) < 0 ||
        virJSONValueObjectAppend(backend, "data", data) < 0)
        goto cleanup;
    data = NULL;

    if (!(ret = qemuMonitorJSONMakeCommand("chardev-add",
                                           "s:id", chrID,
                                           "a:backend", &backend,
                                           NULL)))
        goto cleanup;

 cleanup:
    VIR_FREE(tlsalias);
    virJSONValueFree(addr);
    virJSONValueFree(data);
    virJSONValueFree(backend);
    return ret;
}


int
qemuMonitorJSONAttachCharDev(qemuMonitorPtr mon,
                             const char *chrID,
                             virDomainChrSourceDefPtr chr)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    if (!(cmd = qemuMonitorJSONAttachCharDevCommand(chrID, chr)))
        return ret;

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

    if (chr->type == VIR_DOMAIN_CHR_TYPE_PTY) {
        if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
            goto cleanup;
    } else {
        if (qemuMonitorJSONCheckError(cmd, reply) < 0)
            goto cleanup;
    }

    if (chr->type == VIR_DOMAIN_CHR_TYPE_PTY) {
        virJSONValuePtr 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"));
            goto cleanup;
        }

        chr->data.file.path = g_strdup(path);
    }

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

int
qemuMonitorJSONDetachCharDev(qemuMonitorPtr mon,
                             const char *chrID)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("chardev-remove",
                                           "s:id", chrID,
                                           NULL)))
        return ret;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONGetDeviceAliases(qemuMonitorPtr mon,
                                char ***aliases)
{
    qemuMonitorJSONListPathPtr *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 = paths[i]->name;
            paths[i]->name = NULL;
            alias++;
        }
    }

    ret = 0;

    for (i = 0; i < n; i++)
        qemuMonitorJSONListPathFree(paths[i]);
    VIR_FREE(paths);
    return ret;
}


static int
qemuMonitorJSONParseCPUx86FeatureWord(virJSONValuePtr 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 virCPUDataPtr
qemuMonitorJSONParseCPUx86Features(virJSONValuePtr data)
{
    virCPUDataPtr cpudata = NULL;
    virCPUx86DataItem item = { 0 };
    size_t i;

    if (!(cpudata = virCPUDataNew(VIR_ARCH_X86_64)))
        goto error;

    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)
            goto error;
    }

    return cpudata;

 error:
    virCPUDataFree(cpudata);
    return NULL;
}


int
qemuMonitorJSONGetCPUx86Data(qemuMonitorPtr mon,
                             const char *property,
                             virCPUDataPtr *cpudata)
{
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    int ret = -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", QOM_CPU_PATH,
                                           "s:property", property,
                                           NULL)))
        goto cleanup;

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

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

    data = virJSONValueObjectGetArray(reply, "return");
    if (!(*cpudata = qemuMonitorJSONParseCPUx86Features(data)))
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/*
 * 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(qemuMonitorPtr mon)
{
    virJSONValuePtr cmd = NULL;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    size_t i;
    size_t n;
    int ret = -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-list",
                                           "s:path", QOM_CPU_PATH,
                                           NULL)))
        goto cleanup;

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

    if ((data = virJSONValueObjectGet(reply, "error"))) {
        const char *klass = virJSONValueObjectGetString(data, "class");
        if (STREQ_NULLABLE(klass, "DeviceNotFound") ||
            STREQ_NULLABLE(klass, "CommandNotFound")) {
            ret = 0;
            goto cleanup;
        }
    }

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

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

    for (i = 0; i < n; i++) {
        virJSONValuePtr element = virJSONValueArrayGet(data, i);
        if (STREQ_NULLABLE(virJSONValueObjectGetString(element, "name"),
                           "feature-words"))
            break;
    }

    if (i == n)
        ret = 0;
    else
        ret = 1;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/**
 * qemuMonitorJSONGetGuestCPUx86:
 * @mon: Pointer to the monitor
 * @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(qemuMonitorPtr mon,
                              virCPUDataPtr *data,
                              virCPUDataPtr *disabled)
{
    virCPUDataPtr cpuEnabled = NULL;
    virCPUDataPtr cpuDisabled = NULL;
    int rc;

    if ((rc = qemuMonitorJSONCheckCPUx86(mon)) < 0)
        return -1;
    else if (!rc)
        return -2;

    if (qemuMonitorJSONGetCPUx86Data(mon, "feature-words",
                                     &cpuEnabled) < 0)
        goto error;

    if (disabled &&
        qemuMonitorJSONGetCPUx86Data(mon, "filtered-features",
                                     &cpuDisabled) < 0)
        goto error;

    *data = cpuEnabled;
    if (disabled)
        *disabled = cpuDisabled;
    return 0;

 error:
    virCPUDataFree(cpuEnabled);
    virCPUDataFree(cpuDisabled);
    return -1;
}


static int
qemuMonitorJSONGetCPUProperties(qemuMonitorPtr mon,
                                char ***props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    *props = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-list",
                                           "s:path", QOM_CPU_PATH,
                                           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(qemuMonitorPtr mon,
                          qemuMonitorCPUFeatureTranslationCallback translate,
                          void *opaque,
                          virCPUDataPtr data)
{
    qemuMonitorJSONObjectProperty prop = { .type = QEMU_MONITOR_OBJECT_PROPERTY_BOOLEAN };
    g_auto(GStrv) props = NULL;
    char **p;

    if (qemuMonitorJSONGetCPUProperties(mon, &props) < 0)
        return -1;

    for (p = props; p && *p; p++) {
        const char *name = *p;

        if (qemuMonitorJSONGetObjectProperty(mon, QOM_CPU_PATH, 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(qemuMonitorPtr mon,
                                  qemuMonitorCPUFeatureTranslationCallback translate,
                                  void *opaque,
                                  virCPUDataPtr data)
{
    g_auto(GStrv) props = NULL;
    char **p;

    if (qemuMonitorJSONGetStringListProperty(mon, QOM_CPU_PATH,
                                             "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
 * @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(qemuMonitorPtr mon,
                           virArch arch,
                           qemuMonitorCPUFeatureTranslationCallback translate,
                           void *opaque,
                           virCPUDataPtr *enabled,
                           virCPUDataPtr *disabled)
{
    virCPUDataPtr cpuEnabled = NULL;
    virCPUDataPtr cpuDisabled = NULL;
    int ret = -1;

    if (!(cpuEnabled = virCPUDataNew(arch)) ||
        !(cpuDisabled = virCPUDataNew(arch)))
        goto cleanup;

    if (qemuMonitorJSONGetCPUData(mon, translate, opaque, cpuEnabled) < 0)
        goto cleanup;

    if (disabled &&
        qemuMonitorJSONGetCPUDataDisabled(mon, translate, opaque, cpuDisabled) < 0)
        goto cleanup;

    *enabled = g_steal_pointer(&cpuEnabled);
    if (disabled)
        *disabled = g_steal_pointer(&cpuDisabled);

    ret = 0;

 cleanup:
    virCPUDataFree(cpuEnabled);
    virCPUDataFree(cpuDisabled);
    return ret;
}


int
qemuMonitorJSONRTCResetReinjection(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("rtc-reset-reinjection",
                                           NULL)))
        return ret;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}

/**
 * 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(qemuMonitorPtr mon,
                            qemuMonitorIOThreadInfoPtr **iothreads,
                            int *niothreads)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;
    qemuMonitorIOThreadInfoPtr *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(qemuMonitorIOThreadInfoPtr, n + 1);

    for (i = 0; i < n; i++) {
        virJSONValuePtr child = virJSONValueArrayGet(data, i);
        const char *tmp;
        qemuMonitorIOThreadInfoPtr 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 = infolist;
    infolist = NULL;
    ret = 0;

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


int
qemuMonitorJSONSetIOThread(qemuMonitorPtr mon,
                           qemuMonitorIOThreadInfoPtr iothreadInfo)
{
    int ret = -1;
    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) \
            goto cleanup; \
    }

    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

    ret = 0;

 cleanup:
    VIR_FREE(path);
    return ret;
}


int
qemuMonitorJSONGetMemoryDeviceInfo(qemuMonitorPtr mon,
                                   GHashTable *info)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data = NULL;
    qemuMonitorMemoryDeviceInfoPtr meminfo = NULL;
    size_t i;

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

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

    if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
        ret = -2;
        goto cleanup;
    }

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

    data = virJSONValueObjectGetArray(reply, "return");

    for (i = 0; i < virJSONValueArraySize(data); i++) {
        virJSONValuePtr elem = virJSONValueArrayGet(data, i);
        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"));
            goto cleanup;
        }

        /* dimm memory devices */
        if (STREQ(type, "dimm")) {
            virJSONValuePtr dimminfo;
            const char *devalias;

            if (!(dimminfo = virJSONValueObjectGetObject(elem, "data"))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("query-memory-devices reply data doesn't "
                                 "contain enum data"));
                goto cleanup;
            }

            if (!(devalias = virJSONValueObjectGetString(dimminfo, "id"))) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("dimm memory info data is missing 'id'"));
                goto cleanup;
            }

            meminfo = g_new0(qemuMonitorMemoryDeviceInfo, 1);

            if (virJSONValueObjectGetNumberUlong(dimminfo, "addr",
                                                 &meminfo->address) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing addr in dimm memory info"));
                goto cleanup;
            }

            if (virJSONValueObjectGetNumberUint(dimminfo, "slot",
                                                &meminfo->slot) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing slot in dimm memory info"));
                goto cleanup;
            }

            if (virJSONValueObjectGetBoolean(dimminfo, "hotplugged",
                                             &meminfo->hotplugged) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing hotplugged in dimm memory info"));
                goto cleanup;

            }

            if (virJSONValueObjectGetBoolean(dimminfo, "hotpluggable",
                                             &meminfo->hotpluggable) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("malformed/missing hotpluggable in dimm memory info"));
                goto cleanup;

            }

            if (virHashAddEntry(info, devalias, meminfo) < 0)
                goto cleanup;

            meminfo = NULL;
        }
    }

    ret = 0;

 cleanup:
    VIR_FREE(meminfo);
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/**
 * 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(qemuMonitorPtr mon,
                                     const char *name,
                                     const char *alias,
                                     char **path)
{
    qemuMonitorJSONListPathPtr *paths = NULL;
    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);
    VIR_FREE(child);
    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(qemuMonitorPtr mon,
                                    const char *curpath,
                                    const char *name,
                                    char **path)
{
    ssize_t i, npaths = 0;
    int ret = -2;
    char *nextpath = NULL;
    qemuMonitorJSONListPathPtr *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<")) {
            nextpath = g_strdup_printf("%s/%s", curpath, paths[i]->name);

            ret = qemuMonitorJSONFindObjectPathByName(mon, nextpath, name, path);
            VIR_FREE(nextpath);
        }
    }

 cleanup:
    for (i = 0; i < npaths; i++)
        qemuMonitorJSONListPathFree(paths[i]);
    VIR_FREE(paths);
    VIR_FREE(nextpath);
    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(qemuMonitorPtr mon,
                            const char *name,
                            const char *alias,
                            char **path)
{
    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);
    VIR_FREE(linkname);
    return ret;
}


int
qemuMonitorJSONMigrateIncoming(qemuMonitorPtr 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(qemuMonitorPtr mon)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("migrate-start-postcopy", NULL)))
        return -1;

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

    ret = qemuMonitorJSONCheckError(cmd, reply);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONMigrateContinue(qemuMonitorPtr mon,
                               qemuMonitorMigrationStatus status)
{
    const char *statusStr = qemuMonitorMigrationStatusTypeToString(status);
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("migrate-continue",
                                           "s:state", statusStr,
                                           NULL)))
        return -1;

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

    ret = qemuMonitorJSONCheckError(cmd, reply);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONGetRTCTime(qemuMonitorPtr mon,
                          struct tm *tm)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", "/machine",
                                           "s:property", "rtc-time",
                                           NULL)))
        return -1;

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

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

    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"));
        goto cleanup;
    }

    ret = 0;
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


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;

        VIR_FREE(entry->type);
        VIR_FREE(entry->qom_path);
        VIR_FREE(entry->alias);
        virJSONValueFree(entry->props);
    }

    VIR_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(virJSONValuePtr vcpu,
                                            struct qemuMonitorQueryHotpluggableCpusEntry *entry)
{
    virJSONValuePtr 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(qemuMonitorPtr mon,
                                   struct qemuMonitorQueryHotpluggableCpusEntry **entries,
                                   size_t *nentries)
{
    struct qemuMonitorQueryHotpluggableCpusEntry *info = NULL;
    size_t ninfo = 0;
    int ret = -1;
    size_t i;
    virJSONValuePtr data;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr 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);
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


virJSONValuePtr
qemuMonitorJSONQueryQMPSchema(qemuMonitorPtr mon)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr ret = NULL;

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

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

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

    ret = virJSONValueObjectStealArray(reply, "return");

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);

    return ret;
}


int
qemuMonitorJSONSetBlockThreshold(qemuMonitorPtr mon,
                                 const char *nodename,
                                 unsigned long long threshold)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    int ret = -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("block-set-write-threshold",
                                           "s:node-name", nodename,
                                           "U:write-threshold", threshold,
                                           NULL)))
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);

    return ret;
}


virJSONValuePtr
qemuMonitorJSONQueryNamedBlockNodes(qemuMonitorPtr 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(qemuMonitorPtr mon,
                                 const char *action)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    int ret = -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("watchdog-set-action",
                                           "s:action", action,
                                           NULL)))
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONBlockdevCreate(qemuMonitorPtr mon,
                              const char *jobname,
                              virJSONValuePtr props)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    cmd = qemuMonitorJSONMakeCommand("blockdev-create",
                                     "s:job-id", jobname,
                                     "a:options", &props,
                                     NULL);
    virJSONValueFree(props);
    if (!cmd)
        return -1;

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

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

    return 0;
}


int
qemuMonitorJSONBlockdevAdd(qemuMonitorPtr mon,
                           virJSONValuePtr *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(qemuMonitorPtr mon,
                              virJSONValuePtr *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(qemuMonitorPtr 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(qemuMonitorPtr mon,
                                const char *id,
                                bool force)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    int ret = -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-open-tray",
                                           "s:id", id,
                                           "b:force", force, NULL)))
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONBlockdevTrayClose(qemuMonitorPtr mon,
                                 const char *id)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    int ret = -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-close-tray",
                                           "s:id", id, NULL)))
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONBlockdevMediumRemove(qemuMonitorPtr mon,
                                    const char *id)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    int ret = -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-remove-medium",
                                           "s:id", id, NULL)))
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONBlockdevMediumInsert(qemuMonitorPtr mon,
                                    const char *id,
                                    const char *nodename)
{
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    int ret = -1;

    if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-insert-medium",
                                           "s:id", id,
                                           "s:node-name", nodename,
                                           NULL)))
        return -1;

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

    if (qemuMonitorJSONCheckError(cmd, reply) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


/**
 * 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(qemuMonitorPtr mon)
{
    const char *tmp;
    char *measurement = NULL;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;
    virJSONValuePtr data;

    if (!(cmd = qemuMonitorJSONMakeCommand("query-sev-launch-measure", NULL)))
         return NULL;

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

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

    data = virJSONValueObjectGetObject(reply, "return");

    if (!(tmp = virJSONValueObjectGetString(data, "data")))
        goto cleanup;

    measurement = g_strdup(tmp);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return measurement;
}


/*
 * Example return data
 *
 * "return": [
 *   { "connected": true, "id": "pr-helper0" }
 *  ]
 */
static int
qemuMonitorJSONExtractPRManagerInfo(virJSONValuePtr reply,
                                    GHashTable *info)
{
    qemuMonitorPRManagerInfoPtr entry = NULL;
    virJSONValuePtr data;
    int ret = -1;
    size_t i;

    data = virJSONValueObjectGetArray(reply, "return");

    for (i = 0; i < virJSONValueArraySize(data); i++) {
        virJSONValuePtr 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)
            goto cleanup;

        entry = NULL;
    }

    ret = 0;
 cleanup:
    VIR_FREE(entry);
    return ret;

 malformed:
    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                   _("malformed prManager reply"));
    goto cleanup;
}


int
qemuMonitorJSONGetPRManagerInfo(qemuMonitorPtr mon,
                                GHashTable *info)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

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

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

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

    ret = qemuMonitorJSONExtractPRManagerInfo(reply, info);
 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;

}


static int
qemuMonitorJSONExtractCurrentMachineInfo(virJSONValuePtr reply,
                                         qemuMonitorCurrentMachineInfoPtr info)
{
    virJSONValuePtr 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(qemuMonitorPtr mon,
                                     qemuMonitorCurrentMachineInfoPtr info)
{
    int ret = -1;
    virJSONValuePtr cmd;
    virJSONValuePtr reply = NULL;

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

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

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

    ret = qemuMonitorJSONExtractCurrentMachineInfo(reply, info);

 cleanup:
    virJSONValueFree(cmd);
    virJSONValueFree(reply);
    return ret;
}


int
qemuMonitorJSONTransactionBitmapAdd(virJSONValuePtr 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(virJSONValuePtr actions,
                                       const char *node,
                                       const char *name)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-remove",
                                         "s:node", node,
                                         "s:name", name,
                                         NULL);
}


int
qemuMonitorJSONTransactionBitmapEnable(virJSONValuePtr actions,
                                       const char *node,
                                       const char *name)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-enable",
                                         "s:node", node,
                                         "s:name", name,
                                         NULL);
}


int
qemuMonitorJSONTransactionBitmapDisable(virJSONValuePtr actions,
                                        const char *node,
                                        const char *name)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-disable",
                                         "s:node", node,
                                         "s:name", name,
                                         NULL);
}


int
qemuMonitorJSONTransactionBitmapMerge(virJSONValuePtr actions,
                                      const char *node,
                                      const char *target,
                                      virJSONValuePtr *sources)
{
    return qemuMonitorJSONTransactionAdd(actions,
                                         "block-dirty-bitmap-merge",
                                         "s:node", node,
                                         "s:target", target,
                                         "a:bitmaps", sources,
                                         NULL);
}


int
qemuMonitorJSONTransactionBitmapMergeSourceAddBitmap(virJSONValuePtr sources,
                                                     const char *sourcenode,
                                                     const char *sourcebitmap)
{
    g_autoptr(virJSONValue) sourceobj = NULL;

    if (virJSONValueObjectCreate(&sourceobj,
                                 "s:node", sourcenode,
                                 "s:name", sourcebitmap,
                                 NULL) < 0)
        return -1;

    if (virJSONValueArrayAppend(sources, sourceobj) < 0)
        return -1;

    sourceobj = NULL;
    return 0;
}


int
qemuMonitorJSONTransactionSnapshotLegacy(virJSONValuePtr 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(virJSONValuePtr 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(virJSONValuePtr 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 qemuMonitorJobInfoPtr
qemuMonitorJSONGetJobInfoOne(virJSONValuePtr 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(qemuMonitorPtr mon,
                          qemuMonitorJobInfoPtr **jobs,
                          size_t *njobs)
{
    virJSONValuePtr 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++) {
        qemuMonitorJobInfoPtr job = NULL;

        if (!(job = qemuMonitorJSONGetJobInfoOne(virJSONValueArrayGet(data, i))))
            return -1;

        if (VIR_APPEND_ELEMENT(*jobs, *njobs, job) < 0)
            return -1;
    }

    return 0;
}


int
qemuMonitorJSONGetCPUMigratable(qemuMonitorPtr mon,
                                bool *migratable)
{
    g_autoptr(virJSONValue) cmd = NULL;
    g_autoptr(virJSONValue) reply = NULL;

    if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
                                           "s:path", QOM_CPU_PATH,
                                           "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);
}
