mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-01-10 14:57:42 +00:00
bc16c1bcf6
Signed-off-by: Peng Liang <tcx4c70@gmail.com> Reviewed-by: Ján Tomko <jtomko@redhat.com>
9009 lines
275 KiB
C
9009 lines
275 KiB
C
/*
|
|
* 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 "viralloc.h"
|
|
#include "virlog.h"
|
|
#include "virerror.h"
|
|
#include "virjson.h"
|
|
#include "virprobe.h"
|
|
#include "virstring.h"
|
|
#include "cpu/cpu_x86.h"
|
|
#include "virenum.h"
|
|
|
|
#ifdef WITH_DTRACE_PROBES
|
|
# include "libvirt_qemu_probes.h"
|
|
#endif
|
|
|
|
#define LIBVIRT_QEMU_MONITOR_PRIV_H_ALLOW
|
|
#include "qemu_monitor_priv.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
VIR_LOG_INIT("qemu.qemu_monitor_json");
|
|
|
|
#define LINE_ENDING "\r\n"
|
|
|
|
static void qemuMonitorJSONHandleShutdown(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleReset(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleStop(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleResume(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleRTCChange(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleWatchdog(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleIOError(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleVNCConnect(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleVNCInitialize(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleSPICEConnect(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleSPICEInitialize(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleSPICEDisconnect(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleTrayChange(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandlePMWakeup(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandlePMSuspend(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleBlockJobCompleted(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleBlockJobCanceled(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleBlockJobReady(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleJobStatusChange(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleBalloonChange(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandlePMSuspendDisk(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleGuestCrashloaded(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleGuestPanic(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleDeviceDeleted(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleNicRxFilterChanged(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleSerialChange(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleSpiceMigrated(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleMigrationStatus(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleMigrationPass(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleAcpiOstInfo(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleBlockThreshold(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleDumpCompleted(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandlePRManagerStatusChanged(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleRdmaGidStatusChanged(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleMemoryFailure(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleMemoryDeviceSizeChange(qemuMonitor *mon, virJSONValue *data);
|
|
static void qemuMonitorJSONHandleDeviceUnplugErr(qemuMonitor *mon, virJSONValue *data);
|
|
|
|
typedef struct {
|
|
const char *type;
|
|
void (*handler)(qemuMonitor *mon, virJSONValue *data);
|
|
} qemuEventHandler;
|
|
|
|
static qemuEventHandler eventHandlers[] = {
|
|
{ "ACPI_DEVICE_OST", qemuMonitorJSONHandleAcpiOstInfo, },
|
|
{ "BALLOON_CHANGE", qemuMonitorJSONHandleBalloonChange, },
|
|
{ "BLOCK_IO_ERROR", qemuMonitorJSONHandleIOError, },
|
|
{ "BLOCK_JOB_CANCELLED", qemuMonitorJSONHandleBlockJobCanceled, },
|
|
{ "BLOCK_JOB_COMPLETED", qemuMonitorJSONHandleBlockJobCompleted, },
|
|
{ "BLOCK_JOB_READY", qemuMonitorJSONHandleBlockJobReady, },
|
|
{ "BLOCK_WRITE_THRESHOLD", qemuMonitorJSONHandleBlockThreshold, },
|
|
{ "DEVICE_DELETED", qemuMonitorJSONHandleDeviceDeleted, },
|
|
{ "DEVICE_TRAY_MOVED", qemuMonitorJSONHandleTrayChange, },
|
|
{ "DEVICE_UNPLUG_GUEST_ERROR", qemuMonitorJSONHandleDeviceUnplugErr, },
|
|
{ "DUMP_COMPLETED", qemuMonitorJSONHandleDumpCompleted, },
|
|
{ "GUEST_CRASHLOADED", qemuMonitorJSONHandleGuestCrashloaded, },
|
|
{ "GUEST_PANICKED", qemuMonitorJSONHandleGuestPanic, },
|
|
{ "JOB_STATUS_CHANGE", qemuMonitorJSONHandleJobStatusChange, },
|
|
{ "MEMORY_DEVICE_SIZE_CHANGE", qemuMonitorJSONHandleMemoryDeviceSizeChange, },
|
|
{ "MEMORY_FAILURE", qemuMonitorJSONHandleMemoryFailure, },
|
|
{ "MIGRATION", qemuMonitorJSONHandleMigrationStatus, },
|
|
{ "MIGRATION_PASS", qemuMonitorJSONHandleMigrationPass, },
|
|
{ "NIC_RX_FILTER_CHANGED", qemuMonitorJSONHandleNicRxFilterChanged, },
|
|
{ "PR_MANAGER_STATUS_CHANGED", qemuMonitorJSONHandlePRManagerStatusChanged, },
|
|
{ "RDMA_GID_STATUS_CHANGED", qemuMonitorJSONHandleRdmaGidStatusChanged, },
|
|
{ "RESET", qemuMonitorJSONHandleReset, },
|
|
{ "RESUME", qemuMonitorJSONHandleResume, },
|
|
{ "RTC_CHANGE", qemuMonitorJSONHandleRTCChange, },
|
|
{ "SHUTDOWN", qemuMonitorJSONHandleShutdown, },
|
|
{ "SPICE_CONNECTED", qemuMonitorJSONHandleSPICEConnect, },
|
|
{ "SPICE_DISCONNECTED", qemuMonitorJSONHandleSPICEDisconnect, },
|
|
{ "SPICE_INITIALIZED", qemuMonitorJSONHandleSPICEInitialize, },
|
|
{ "SPICE_MIGRATE_COMPLETED", qemuMonitorJSONHandleSpiceMigrated, },
|
|
{ "STOP", qemuMonitorJSONHandleStop, },
|
|
{ "SUSPEND", qemuMonitorJSONHandlePMSuspend, },
|
|
{ "SUSPEND_DISK", qemuMonitorJSONHandlePMSuspendDisk, },
|
|
{ "VNC_CONNECTED", qemuMonitorJSONHandleVNCConnect, },
|
|
{ "VNC_DISCONNECTED", qemuMonitorJSONHandleVNCDisconnect, },
|
|
{ "VNC_INITIALIZED", qemuMonitorJSONHandleVNCInitialize, },
|
|
{ "VSERPORT_CHANGE", qemuMonitorJSONHandleSerialChange, },
|
|
{ "WAKEUP", qemuMonitorJSONHandlePMWakeup, },
|
|
{ "WATCHDOG", qemuMonitorJSONHandleWatchdog, },
|
|
/* We use bsearch, so keep this list sorted. */
|
|
};
|
|
|
|
static int
|
|
qemuMonitorEventCompare(const void *key, const void *elt)
|
|
{
|
|
const char *type = key;
|
|
const qemuEventHandler *handler = elt;
|
|
return strcmp(type, handler->type);
|
|
}
|
|
|
|
static int
|
|
qemuMonitorJSONIOProcessEvent(qemuMonitor *mon,
|
|
virJSONValue *obj)
|
|
{
|
|
const char *type;
|
|
qemuEventHandler *handler;
|
|
virJSONValue *data;
|
|
g_autofree char *details = NULL;
|
|
virJSONValue *timestamp;
|
|
long long seconds = -1;
|
|
unsigned int micros = 0;
|
|
|
|
VIR_DEBUG("mon=%p obj=%p", mon, obj);
|
|
|
|
type = virJSONValueObjectGetString(obj, "event");
|
|
if (!type) {
|
|
VIR_WARN("missing event type in message");
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
/* Not all events have data; and event reporting is best-effort only */
|
|
if ((data = virJSONValueObjectGet(obj, "data")))
|
|
details = virJSONValueToString(data, false);
|
|
if ((timestamp = virJSONValueObjectGet(obj, "timestamp"))) {
|
|
ignore_value(virJSONValueObjectGetNumberLong(timestamp, "seconds",
|
|
&seconds));
|
|
ignore_value(virJSONValueObjectGetNumberUint(timestamp, "microseconds",
|
|
µs));
|
|
}
|
|
qemuMonitorEmitEvent(mon, type, seconds, micros, details);
|
|
|
|
handler = bsearch(type, eventHandlers, G_N_ELEMENTS(eventHandlers),
|
|
sizeof(eventHandlers[0]), qemuMonitorEventCompare);
|
|
if (handler) {
|
|
VIR_DEBUG("handle %s handler=%p data=%p", type,
|
|
handler->handler, data);
|
|
(handler->handler)(mon, data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuMonitorJSONIOProcessLine(qemuMonitor *mon,
|
|
const char *line,
|
|
qemuMonitorMessage *msg)
|
|
{
|
|
g_autoptr(virJSONValue) obj = NULL;
|
|
|
|
VIR_DEBUG("Line [%s]", line);
|
|
|
|
if (!(obj = virJSONValueFromString(line)))
|
|
return -1;
|
|
|
|
if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Parsed JSON reply '%s' isn't an object"), line);
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectHasKey(obj, "QMP") == 1) {
|
|
return 0;
|
|
} else if (virJSONValueObjectHasKey(obj, "event") == 1) {
|
|
PROBE(QEMU_MONITOR_RECV_EVENT,
|
|
"mon=%p event=%s", mon, line);
|
|
return qemuMonitorJSONIOProcessEvent(mon, obj);
|
|
} else if (virJSONValueObjectHasKey(obj, "error") == 1 ||
|
|
virJSONValueObjectHasKey(obj, "return") == 1) {
|
|
PROBE(QEMU_MONITOR_RECV_REPLY,
|
|
"mon=%p reply=%s", mon, line);
|
|
if (msg) {
|
|
msg->rxObject = g_steal_pointer(&obj);
|
|
msg->finished = 1;
|
|
return 0;
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unexpected JSON reply '%s'"), line);
|
|
}
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown JSON reply '%s'"), line);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int qemuMonitorJSONIOProcess(qemuMonitor *mon,
|
|
const char *data,
|
|
size_t len,
|
|
qemuMonitorMessage *msg)
|
|
{
|
|
int used = 0;
|
|
/*VIR_DEBUG("Data %d bytes [%s]", len, data);*/
|
|
|
|
while (used < len) {
|
|
char *nl = strstr(data + used, LINE_ENDING);
|
|
|
|
if (nl) {
|
|
int got = nl - (data + used);
|
|
g_autofree char *line = g_strndup(data + used, got);
|
|
|
|
used += got + strlen(LINE_ENDING);
|
|
line[got] = '\0'; /* kill \n */
|
|
if (qemuMonitorJSONIOProcessLine(mon, line, msg) < 0)
|
|
return -1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return used;
|
|
}
|
|
|
|
static int
|
|
qemuMonitorJSONCommandWithFd(qemuMonitor *mon,
|
|
virJSONValue *cmd,
|
|
int scm_fd,
|
|
virJSONValue **reply)
|
|
{
|
|
int ret = -1;
|
|
qemuMonitorMessage msg;
|
|
g_auto(virBuffer) cmdbuf = VIR_BUFFER_INITIALIZER;
|
|
|
|
*reply = NULL;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
|
|
if (virJSONValueObjectHasKey(cmd, "execute") == 1) {
|
|
g_autofree char *id = qemuMonitorNextCommandID(mon);
|
|
|
|
if (virJSONValueObjectAppendString(cmd, "id", id) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Unable to append command 'id' string"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (virJSONValueToBuffer(cmd, &cmdbuf, false) < 0)
|
|
return -1;
|
|
virBufferAddLit(&cmdbuf, "\r\n");
|
|
|
|
msg.txLength = virBufferUse(&cmdbuf);
|
|
msg.txBuffer = virBufferCurrentContent(&cmdbuf);
|
|
msg.txFD = scm_fd;
|
|
|
|
ret = qemuMonitorSend(mon, &msg);
|
|
|
|
if (ret == 0) {
|
|
if (!msg.rxObject) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing monitor reply object"));
|
|
ret = -1;
|
|
} else {
|
|
*reply = msg.rxObject;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONCommand(qemuMonitor *mon,
|
|
virJSONValue *cmd,
|
|
virJSONValue **reply)
|
|
{
|
|
return qemuMonitorJSONCommandWithFd(mon, cmd, -1, reply);
|
|
}
|
|
|
|
/* Ignoring OOM in this method, since we're already reporting
|
|
* a more important error
|
|
*
|
|
* XXX see qerror.h for different klasses & fill out useful params
|
|
*/
|
|
static const char *
|
|
qemuMonitorJSONStringifyError(virJSONValue *error)
|
|
{
|
|
const char *klass = virJSONValueObjectGetString(error, "class");
|
|
const char *detail = NULL;
|
|
|
|
/* The QMP 'desc' field is usually sufficient for our generic
|
|
* error reporting needs.
|
|
*/
|
|
if (klass)
|
|
detail = virJSONValueObjectGetString(error, "desc");
|
|
|
|
|
|
if (!detail)
|
|
detail = "unknown QEMU command error";
|
|
|
|
return detail;
|
|
}
|
|
|
|
static const char *
|
|
qemuMonitorJSONCommandName(virJSONValue *cmd)
|
|
{
|
|
const char *name = virJSONValueObjectGetString(cmd, "execute");
|
|
if (name)
|
|
return name;
|
|
else
|
|
return "<unknown>";
|
|
}
|
|
|
|
static int
|
|
qemuMonitorJSONCheckErrorFull(virJSONValue *cmd,
|
|
virJSONValue *reply,
|
|
bool report)
|
|
{
|
|
if (virJSONValueObjectHasKey(reply, "error")) {
|
|
virJSONValue *error = virJSONValueObjectGet(reply, "error");
|
|
g_autofree char *cmdstr = virJSONValueToString(cmd, false);
|
|
g_autofree char *replystr = virJSONValueToString(reply, false);
|
|
|
|
/* Log the full JSON formatted command & error */
|
|
VIR_DEBUG("unable to execute QEMU command %s: %s",
|
|
NULLSTR(cmdstr), NULLSTR(replystr));
|
|
|
|
if (!report)
|
|
return -1;
|
|
|
|
/* Only send the user the command name + friendly error */
|
|
if (!error)
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unable to execute QEMU command '%s'"),
|
|
qemuMonitorJSONCommandName(cmd));
|
|
else
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unable to execute QEMU command '%s': %s"),
|
|
qemuMonitorJSONCommandName(cmd),
|
|
qemuMonitorJSONStringifyError(error));
|
|
|
|
return -1;
|
|
} else if (!virJSONValueObjectHasKey(reply, "return")) {
|
|
g_autofree char *cmdstr = virJSONValueToString(cmd, false);
|
|
g_autofree char *replystr = virJSONValueToString(reply, false);
|
|
|
|
VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %s: %s",
|
|
NULLSTR(cmdstr), NULLSTR(replystr));
|
|
|
|
if (!report)
|
|
return -1;
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unable to execute QEMU command '%s'"),
|
|
qemuMonitorJSONCommandName(cmd));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONCheckError(virJSONValue *cmd,
|
|
virJSONValue *reply)
|
|
{
|
|
return qemuMonitorJSONCheckErrorFull(cmd, reply, true);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONCheckReply(virJSONValue *cmd,
|
|
virJSONValue *reply,
|
|
virJSONType type)
|
|
{
|
|
virJSONValue *data;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGet(reply, "return");
|
|
if (virJSONValueGetType(data) != type) {
|
|
g_autofree char *cmdstr = virJSONValueToString(cmd, false);
|
|
g_autofree char *retstr = virJSONValueToString(data, false);
|
|
|
|
VIR_DEBUG("Unexpected return type %d (expecting %d) for command %s: %s",
|
|
virJSONValueGetType(data), type, cmdstr, retstr);
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected type returned by QEMU command '%s'"),
|
|
qemuMonitorJSONCommandName(cmd));
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuMonitorJSONErrorIsClass(virJSONValue *error,
|
|
const char *klass)
|
|
{
|
|
return STREQ_NULLABLE(virJSONValueObjectGetString(error, "class"), klass);
|
|
}
|
|
|
|
|
|
static bool
|
|
qemuMonitorJSONHasError(virJSONValue *reply,
|
|
const char *klass)
|
|
{
|
|
virJSONValue *error;
|
|
|
|
if (!(error = virJSONValueObjectGet(reply, "error")))
|
|
return false;
|
|
|
|
return qemuMonitorJSONErrorIsClass(error, klass);
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONTransactionAdd:
|
|
* @actions: array of actions for the 'transaction' command
|
|
* @cmdname: command to add to @actions
|
|
* @...: arguments for @cmdname (see virJSONValueObjectAddVArgs for formatting)
|
|
*
|
|
* Add a new command with arguments to the existing ones. The resulting array
|
|
* is intended to be used as argument for the 'transaction' command.
|
|
*
|
|
* Returns 0 on success and -1 on error.
|
|
*/
|
|
static int
|
|
qemuMonitorJSONTransactionAdd(virJSONValue *actions,
|
|
const char *cmdname,
|
|
...)
|
|
{
|
|
g_autoptr(virJSONValue) entry = NULL;
|
|
g_autoptr(virJSONValue) data = NULL;
|
|
va_list args;
|
|
|
|
va_start(args, cmdname);
|
|
|
|
if (virJSONValueObjectAddVArgs(&data, args) < 0) {
|
|
va_end(args);
|
|
return -1;
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
if (virJSONValueObjectAdd(&entry,
|
|
"s:type", cmdname,
|
|
"A:data", &data, NULL) < 0)
|
|
return -1;
|
|
|
|
if (virJSONValueArrayAppend(actions, &entry) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONMakeCommandInternal:
|
|
* @cmdname: QMP command name
|
|
* @arguments: a JSON object containing command arguments or NULL
|
|
*
|
|
* Create a JSON object used on the QMP monitor to call a command.
|
|
*
|
|
* Note that @arguments is consumed and cleared.
|
|
*/
|
|
static virJSONValue *
|
|
qemuMonitorJSONMakeCommandInternal(const char *cmdname,
|
|
virJSONValue **arguments)
|
|
{
|
|
virJSONValue *ret = NULL;
|
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
"s:execute", cmdname,
|
|
"A:arguments", arguments, NULL));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virJSONValue *G_GNUC_NULL_TERMINATED
|
|
qemuMonitorJSONMakeCommand(const char *cmdname,
|
|
...)
|
|
{
|
|
virJSONValue *obj = NULL;
|
|
g_autoptr(virJSONValue) jargs = NULL;
|
|
va_list args;
|
|
|
|
va_start(args, cmdname);
|
|
|
|
if (virJSONValueObjectAddVArgs(&jargs, args) < 0)
|
|
goto cleanup;
|
|
|
|
obj = qemuMonitorJSONMakeCommandInternal(cmdname, &jargs);
|
|
|
|
cleanup:
|
|
va_end(args);
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandleShutdown(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
bool guest = false;
|
|
virTristateBool guest_initiated = VIR_TRISTATE_BOOL_ABSENT;
|
|
|
|
if (data && virJSONValueObjectGetBoolean(data, "guest", &guest) == 0)
|
|
guest_initiated = virTristateBoolFromBool(guest);
|
|
|
|
qemuMonitorEmitShutdown(mon, guest_initiated);
|
|
}
|
|
|
|
static void qemuMonitorJSONHandleReset(qemuMonitor *mon, virJSONValue *data G_GNUC_UNUSED)
|
|
{
|
|
qemuMonitorEmitReset(mon);
|
|
}
|
|
|
|
static void qemuMonitorJSONHandleStop(qemuMonitor *mon, virJSONValue *data G_GNUC_UNUSED)
|
|
{
|
|
qemuMonitorEmitStop(mon);
|
|
}
|
|
|
|
static void qemuMonitorJSONHandleResume(qemuMonitor *mon, virJSONValue *data G_GNUC_UNUSED)
|
|
{
|
|
qemuMonitorEmitResume(mon);
|
|
}
|
|
|
|
|
|
static qemuMonitorEventPanicInfo *
|
|
qemuMonitorJSONGuestPanicExtractInfoHyperv(virJSONValue *data)
|
|
{
|
|
g_autoptr(qemuMonitorEventPanicInfo) ret = g_new0(qemuMonitorEventPanicInfo, 1);
|
|
|
|
ret->type = QEMU_MONITOR_EVENT_PANIC_INFO_TYPE_HYPERV;
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "arg1", &ret->data.hyperv.arg1) < 0 ||
|
|
virJSONValueObjectGetNumberUlong(data, "arg2", &ret->data.hyperv.arg2) < 0 ||
|
|
virJSONValueObjectGetNumberUlong(data, "arg3", &ret->data.hyperv.arg3) < 0 ||
|
|
virJSONValueObjectGetNumberUlong(data, "arg4", &ret->data.hyperv.arg4) < 0 ||
|
|
virJSONValueObjectGetNumberUlong(data, "arg5", &ret->data.hyperv.arg5) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed hyperv panic data"));
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&ret);
|
|
}
|
|
|
|
static qemuMonitorEventPanicInfo *
|
|
qemuMonitorJSONGuestPanicExtractInfoS390(virJSONValue *data)
|
|
{
|
|
g_autoptr(qemuMonitorEventPanicInfo) ret = NULL;
|
|
int core;
|
|
unsigned long long psw_mask, psw_addr;
|
|
const char *reason = NULL;
|
|
|
|
ret = g_new0(qemuMonitorEventPanicInfo, 1);
|
|
|
|
ret->type = QEMU_MONITOR_EVENT_PANIC_INFO_TYPE_S390;
|
|
|
|
if (virJSONValueObjectGetNumberInt(data, "core", &core) < 0 ||
|
|
virJSONValueObjectGetNumberUlong(data, "psw-mask", &psw_mask) < 0 ||
|
|
virJSONValueObjectGetNumberUlong(data, "psw-addr", &psw_addr) < 0 ||
|
|
!(reason = virJSONValueObjectGetString(data, "reason"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("malformed s390 panic data"));
|
|
return NULL;
|
|
}
|
|
|
|
ret->data.s390.core = core;
|
|
ret->data.s390.psw_mask = psw_mask;
|
|
ret->data.s390.psw_addr = psw_addr;
|
|
|
|
ret->data.s390.reason = g_strdup(reason);
|
|
|
|
return g_steal_pointer(&ret);
|
|
}
|
|
|
|
static qemuMonitorEventPanicInfo *
|
|
qemuMonitorJSONGuestPanicExtractInfo(virJSONValue *data)
|
|
{
|
|
const char *type = virJSONValueObjectGetString(data, "type");
|
|
|
|
if (STREQ_NULLABLE(type, "hyper-v"))
|
|
return qemuMonitorJSONGuestPanicExtractInfoHyperv(data);
|
|
else if (STREQ_NULLABLE(type, "s390"))
|
|
return qemuMonitorJSONGuestPanicExtractInfoS390(data);
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown panic info type '%s'"), NULLSTR(type));
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleGuestPanic(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
virJSONValue *infojson = virJSONValueObjectGetObject(data, "info");
|
|
qemuMonitorEventPanicInfo *info = NULL;
|
|
|
|
if (infojson)
|
|
info = qemuMonitorJSONGuestPanicExtractInfo(infojson);
|
|
|
|
qemuMonitorEmitGuestPanic(mon, info);
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandleRTCChange(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
long long offset = 0;
|
|
if (virJSONValueObjectGetNumberLong(data, "offset", &offset) < 0) {
|
|
VIR_WARN("missing offset in RTC change event");
|
|
offset = 0;
|
|
}
|
|
qemuMonitorEmitRTCChange(mon, offset);
|
|
}
|
|
|
|
VIR_ENUM_DECL(qemuMonitorWatchdogAction);
|
|
VIR_ENUM_IMPL(qemuMonitorWatchdogAction,
|
|
VIR_DOMAIN_EVENT_WATCHDOG_LAST,
|
|
"none", "pause", "reset", "poweroff", "shutdown", "debug", "inject-nmi",
|
|
);
|
|
|
|
static void qemuMonitorJSONHandleWatchdog(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
const char *action;
|
|
int actionID;
|
|
if (!(action = virJSONValueObjectGetString(data, "action")))
|
|
VIR_WARN("missing action in watchdog event");
|
|
if (action) {
|
|
if ((actionID = qemuMonitorWatchdogActionTypeFromString(action)) < 0) {
|
|
VIR_WARN("unknown action %s in watchdog event", action);
|
|
actionID = VIR_DOMAIN_EVENT_WATCHDOG_NONE;
|
|
}
|
|
} else {
|
|
actionID = VIR_DOMAIN_EVENT_WATCHDOG_NONE;
|
|
}
|
|
qemuMonitorEmitWatchdog(mon, actionID);
|
|
}
|
|
|
|
VIR_ENUM_DECL(qemuMonitorIOErrorAction);
|
|
VIR_ENUM_IMPL(qemuMonitorIOErrorAction,
|
|
VIR_DOMAIN_EVENT_IO_ERROR_LAST,
|
|
"ignore", "stop", "report",
|
|
);
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleIOError(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
const char *device;
|
|
const char *nodename;
|
|
const char *action;
|
|
const char *reason = "";
|
|
bool nospc = false;
|
|
int actionID;
|
|
|
|
/* Throughout here we try our best to carry on upon errors,
|
|
since it's important to get as much info as possible out
|
|
to the application */
|
|
|
|
if ((action = virJSONValueObjectGetString(data, "action")) == NULL) {
|
|
VIR_WARN("Missing action in disk io error event");
|
|
action = "ignore";
|
|
}
|
|
|
|
if ((device = virJSONValueObjectGetString(data, "device")) == NULL)
|
|
VIR_WARN("missing device in disk io error event");
|
|
|
|
nodename = virJSONValueObjectGetString(data, "node-name");
|
|
|
|
if (virJSONValueObjectGetBoolean(data, "nospace", &nospc) == 0 && nospc)
|
|
reason = "enospc";
|
|
|
|
if ((actionID = qemuMonitorIOErrorActionTypeFromString(action)) < 0) {
|
|
VIR_WARN("unknown disk io error action '%s'", action);
|
|
actionID = VIR_DOMAIN_EVENT_IO_ERROR_NONE;
|
|
}
|
|
|
|
qemuMonitorEmitIOError(mon, device, nodename, actionID, reason);
|
|
}
|
|
|
|
|
|
VIR_ENUM_DECL(qemuMonitorGraphicsAddressFamily);
|
|
VIR_ENUM_IMPL(qemuMonitorGraphicsAddressFamily,
|
|
VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_LAST,
|
|
"ipv4", "ipv6", "unix",
|
|
);
|
|
|
|
static void
|
|
qemuMonitorJSONHandleGraphicsVNC(qemuMonitor *mon,
|
|
virJSONValue *data,
|
|
int phase)
|
|
{
|
|
const char *localNode, *localService, *localFamily;
|
|
const char *remoteNode, *remoteService, *remoteFamily;
|
|
const char *authScheme, *saslUsername, *x509dname;
|
|
int localFamilyID, remoteFamilyID;
|
|
virJSONValue *client;
|
|
virJSONValue *server;
|
|
|
|
if (!(client = virJSONValueObjectGetObject(data, "client"))) {
|
|
VIR_WARN("missing client info in VNC event");
|
|
return;
|
|
}
|
|
if (!(server = virJSONValueObjectGetObject(data, "server"))) {
|
|
VIR_WARN("missing server info in VNC event");
|
|
return;
|
|
}
|
|
|
|
if (!(authScheme = virJSONValueObjectGetString(server, "auth"))) {
|
|
/* not all events are required to contain auth scheme */
|
|
VIR_DEBUG("missing auth scheme in VNC event");
|
|
authScheme = "";
|
|
}
|
|
|
|
if (!(localFamily = virJSONValueObjectGetString(server, "family"))) {
|
|
VIR_WARN("missing local address family in VNC event");
|
|
return;
|
|
}
|
|
if (!(localNode = virJSONValueObjectGetString(server, "host"))) {
|
|
VIR_WARN("missing local hostname in VNC event");
|
|
return;
|
|
}
|
|
if (!(localService = virJSONValueObjectGetString(server, "service"))) {
|
|
VIR_WARN("missing local service in VNC event");
|
|
return;
|
|
}
|
|
|
|
if (!(remoteFamily = virJSONValueObjectGetString(client, "family"))) {
|
|
VIR_WARN("missing remote address family in VNC event");
|
|
return;
|
|
}
|
|
if (!(remoteNode = virJSONValueObjectGetString(client, "host"))) {
|
|
VIR_WARN("missing remote hostname in VNC event");
|
|
return;
|
|
}
|
|
if (!(remoteService = virJSONValueObjectGetString(client, "service"))) {
|
|
VIR_WARN("missing remote service in VNC event");
|
|
return;
|
|
}
|
|
|
|
saslUsername = virJSONValueObjectGetString(client, "sasl_username");
|
|
x509dname = virJSONValueObjectGetString(client, "x509_dname");
|
|
|
|
if ((localFamilyID = qemuMonitorGraphicsAddressFamilyTypeFromString(localFamily)) < 0) {
|
|
VIR_WARN("unknown address family '%s'", localFamily);
|
|
localFamilyID = VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4;
|
|
}
|
|
if ((remoteFamilyID = qemuMonitorGraphicsAddressFamilyTypeFromString(remoteFamily)) < 0) {
|
|
VIR_WARN("unknown address family '%s'", remoteFamily);
|
|
remoteFamilyID = VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4;
|
|
}
|
|
|
|
qemuMonitorEmitGraphics(mon, phase,
|
|
localFamilyID, localNode, localService,
|
|
remoteFamilyID, remoteNode, remoteService,
|
|
authScheme, x509dname, saslUsername);
|
|
}
|
|
|
|
static void qemuMonitorJSONHandleVNCConnect(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_CONNECT);
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandleVNCInitialize(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE);
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleGraphicsVNC(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleGraphicsSPICE(qemuMonitor *mon,
|
|
virJSONValue *data,
|
|
int phase)
|
|
{
|
|
const char *lhost, *lport, *lfamily;
|
|
const char *rhost, *rport, *rfamily;
|
|
const char *auth = "";
|
|
int lfamilyID, rfamilyID;
|
|
virJSONValue *client;
|
|
virJSONValue *server;
|
|
|
|
if (!(client = virJSONValueObjectGetObject(data, "client")) ||
|
|
!(server = virJSONValueObjectGetObject(data, "server"))) {
|
|
VIR_WARN("missing server or client info in SPICE event");
|
|
return;
|
|
}
|
|
|
|
if (phase == VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE &&
|
|
!(auth = virJSONValueObjectGetString(server, "auth"))) {
|
|
VIR_DEBUG("missing auth scheme in SPICE event");
|
|
auth = "";
|
|
}
|
|
|
|
if (!(lfamily = virJSONValueObjectGetString(server, "family"))) {
|
|
VIR_WARN("missing local address family in SPICE event");
|
|
return;
|
|
}
|
|
if (!(lhost = virJSONValueObjectGetString(server, "host"))) {
|
|
VIR_WARN("missing local hostname in SPICE event");
|
|
return;
|
|
}
|
|
if (!(lport = virJSONValueObjectGetString(server, "port"))) {
|
|
VIR_WARN("missing local port in SPICE event");
|
|
return;
|
|
}
|
|
|
|
if (!(rfamily = virJSONValueObjectGetString(client, "family"))) {
|
|
VIR_WARN("missing remote address family in SPICE event");
|
|
return;
|
|
}
|
|
if (!(rhost = virJSONValueObjectGetString(client, "host"))) {
|
|
VIR_WARN("missing remote hostname in SPICE event");
|
|
return;
|
|
}
|
|
if (!(rport = virJSONValueObjectGetString(client, "port"))) {
|
|
VIR_WARN("missing remote service in SPICE event");
|
|
return;
|
|
}
|
|
|
|
if ((lfamilyID = qemuMonitorGraphicsAddressFamilyTypeFromString(lfamily)) < 0) {
|
|
VIR_WARN("unknown address family '%s'", lfamily);
|
|
lfamilyID = VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4;
|
|
}
|
|
if ((rfamilyID = qemuMonitorGraphicsAddressFamilyTypeFromString(rfamily)) < 0) {
|
|
VIR_WARN("unknown address family '%s'", rfamily);
|
|
rfamilyID = VIR_DOMAIN_EVENT_GRAPHICS_ADDRESS_IPV4;
|
|
}
|
|
|
|
qemuMonitorEmitGraphics(mon, phase, lfamilyID, lhost, lport, rfamilyID,
|
|
rhost, rport, auth, NULL, NULL);
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandleSPICEConnect(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_CONNECT);
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandleSPICEInitialize(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_INITIALIZE);
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandleSPICEDisconnect(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleGraphicsSPICE(mon, data, VIR_DOMAIN_EVENT_GRAPHICS_DISCONNECT);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandleBlockJobImpl(qemuMonitor *mon,
|
|
virJSONValue *data,
|
|
int event)
|
|
{
|
|
const char *device;
|
|
const char *type_str;
|
|
const char *error = NULL;
|
|
int type = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
|
|
unsigned long long offset, len;
|
|
|
|
if ((device = virJSONValueObjectGetString(data, "device")) == NULL) {
|
|
VIR_WARN("missing device in block job event");
|
|
goto out;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "offset", &offset) < 0) {
|
|
VIR_WARN("missing offset in block job event");
|
|
goto out;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "len", &len) < 0) {
|
|
VIR_WARN("missing len in block job event");
|
|
goto out;
|
|
}
|
|
|
|
if ((type_str = virJSONValueObjectGetString(data, "type")) == NULL) {
|
|
VIR_WARN("missing type in block job event");
|
|
goto out;
|
|
}
|
|
|
|
if (STREQ(type_str, "stream"))
|
|
type = VIR_DOMAIN_BLOCK_JOB_TYPE_PULL;
|
|
else if (STREQ(type_str, "commit"))
|
|
type = VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT;
|
|
else if (STREQ(type_str, "mirror"))
|
|
type = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY;
|
|
else if (STREQ(type_str, "backup"))
|
|
type = VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP;
|
|
|
|
switch ((virConnectDomainEventBlockJobStatus) event) {
|
|
case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
|
|
error = virJSONValueObjectGetString(data, "error");
|
|
/* Make sure the whole device has been processed */
|
|
if (offset != len || error)
|
|
event = VIR_DOMAIN_BLOCK_JOB_FAILED;
|
|
break;
|
|
case VIR_DOMAIN_BLOCK_JOB_CANCELED:
|
|
case VIR_DOMAIN_BLOCK_JOB_READY:
|
|
break;
|
|
case VIR_DOMAIN_BLOCK_JOB_FAILED:
|
|
case VIR_DOMAIN_BLOCK_JOB_LAST:
|
|
VIR_DEBUG("should not get here");
|
|
break;
|
|
}
|
|
|
|
out:
|
|
qemuMonitorEmitBlockJob(mon, device, type, event, error);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleJobStatusChange(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
const char *jobname = virJSONValueObjectGetString(data, "id");
|
|
const char *statusstr = virJSONValueObjectGetString(data, "status");
|
|
int status;
|
|
|
|
if (!jobname) {
|
|
VIR_WARN("missing job name in JOB_STATUS_CHANGE event");
|
|
return;
|
|
}
|
|
|
|
if ((status = qemuMonitorJobStatusTypeFromString(statusstr)) < 0) {
|
|
VIR_WARN("unknown job status '%s' for job '%s' in JOB_STATUS_CHANGE event",
|
|
statusstr, jobname);
|
|
return;
|
|
}
|
|
|
|
qemuMonitorEmitJobStatusChange(mon, jobname, status);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleTrayChange(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
const char *devAlias = virJSONValueObjectGetString(data, "device");
|
|
const char *devid = virJSONValueObjectGetString(data, "id");
|
|
bool trayOpened;
|
|
int reason;
|
|
|
|
/* drive alias is always reported but empty for -blockdev */
|
|
if (*devAlias == '\0')
|
|
devAlias = NULL;
|
|
|
|
if (!devAlias && !devid) {
|
|
VIR_WARN("missing device in tray change event");
|
|
return;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(data, "tray-open", &trayOpened) < 0) {
|
|
VIR_WARN("missing tray-open in tray change event");
|
|
return;
|
|
}
|
|
|
|
if (trayOpened)
|
|
reason = VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN;
|
|
else
|
|
reason = VIR_DOMAIN_EVENT_TRAY_CHANGE_CLOSE;
|
|
|
|
qemuMonitorEmitTrayChange(mon, devAlias, devid, reason);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandlePMWakeup(qemuMonitor *mon,
|
|
virJSONValue *data G_GNUC_UNUSED)
|
|
{
|
|
qemuMonitorEmitPMWakeup(mon);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandlePMSuspend(qemuMonitor *mon,
|
|
virJSONValue *data G_GNUC_UNUSED)
|
|
{
|
|
qemuMonitorEmitPMSuspend(mon);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandleBlockJobCompleted(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleBlockJobImpl(mon, data,
|
|
VIR_DOMAIN_BLOCK_JOB_COMPLETED);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandleBlockJobCanceled(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleBlockJobImpl(mon, data,
|
|
VIR_DOMAIN_BLOCK_JOB_CANCELED);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandleBlockJobReady(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
qemuMonitorJSONHandleBlockJobImpl(mon, data,
|
|
VIR_DOMAIN_BLOCK_JOB_READY);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandleBalloonChange(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
unsigned long long actual = 0;
|
|
if (virJSONValueObjectGetNumberUlong(data, "actual", &actual) < 0) {
|
|
VIR_WARN("missing actual in balloon change event");
|
|
return;
|
|
}
|
|
actual = VIR_DIV_UP(actual, 1024);
|
|
qemuMonitorEmitBalloonChange(mon, actual);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandlePMSuspendDisk(qemuMonitor *mon,
|
|
virJSONValue *data G_GNUC_UNUSED)
|
|
{
|
|
qemuMonitorEmitPMSuspendDisk(mon);
|
|
}
|
|
|
|
static void
|
|
qemuMonitorJSONHandleDeviceDeleted(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
const char *device;
|
|
|
|
if (!(device = virJSONValueObjectGetString(data, "device"))) {
|
|
VIR_DEBUG("missing device in device deleted event");
|
|
return;
|
|
}
|
|
|
|
qemuMonitorEmitDeviceDeleted(mon, device);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleDeviceUnplugErr(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
const char *device;
|
|
const char *path;
|
|
|
|
if (!(path = virJSONValueObjectGetString(data, "path"))) {
|
|
VIR_DEBUG("missing path in device unplug guest error event");
|
|
return;
|
|
}
|
|
|
|
device = virJSONValueObjectGetString(data, "device");
|
|
|
|
qemuMonitorEmitDeviceUnplugErr(mon, path, device);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleNicRxFilterChanged(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
const char *name;
|
|
|
|
if (!(name = virJSONValueObjectGetString(data, "name"))) {
|
|
VIR_WARN("missing device in NIC_RX_FILTER_CHANGED event");
|
|
return;
|
|
}
|
|
|
|
qemuMonitorEmitNicRxFilterChanged(mon, name);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleSerialChange(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
const char *name;
|
|
bool connected;
|
|
|
|
if (!(name = virJSONValueObjectGetString(data, "id"))) {
|
|
VIR_WARN("missing device alias in VSERPORT_CHANGE event");
|
|
return;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(data, "open", &connected) < 0) {
|
|
VIR_WARN("missing port state for '%s' in VSERPORT_CHANGE event", name);
|
|
return;
|
|
}
|
|
|
|
qemuMonitorEmitSerialChange(mon, name, connected);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleSpiceMigrated(qemuMonitor *mon,
|
|
virJSONValue *data G_GNUC_UNUSED)
|
|
{
|
|
qemuMonitorEmitSpiceMigrated(mon);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleMemoryDeviceSizeChange(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
const char *name;
|
|
unsigned long long size;
|
|
|
|
if (!(name = virJSONValueObjectGetString(data, "id"))) {
|
|
VIR_WARN("missing device alias in MEMORY_DEVICE_SIZE_CHANGE event");
|
|
return;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "size", &size) < 0) {
|
|
VIR_WARN("missing new size for '%s' in MEMORY_DEVICE_SIZE_CHANGE event", name);
|
|
return;
|
|
}
|
|
|
|
|
|
qemuMonitorEmitMemoryDeviceSizeChange(mon, name, size);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleMemoryFailure(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
virJSONValue *flagsjson = virJSONValueObjectGetObject(data, "flags");
|
|
const char *str;
|
|
int recipient;
|
|
int action;
|
|
bool ar = false;
|
|
bool recursive = false;
|
|
qemuMonitorEventMemoryFailure mf = {0};
|
|
|
|
if (!(str = virJSONValueObjectGetString(data, "recipient"))) {
|
|
VIR_WARN("missing recipient in memory failure event");
|
|
return;
|
|
}
|
|
|
|
recipient = qemuMonitorMemoryFailureRecipientTypeFromString(str);
|
|
if (recipient < 0) {
|
|
VIR_WARN("unknown recipient '%s' in memory_failure event", str);
|
|
return;
|
|
}
|
|
|
|
if (!(str = virJSONValueObjectGetString(data, "action"))) {
|
|
VIR_WARN("missing action in memory failure event");
|
|
return;
|
|
}
|
|
|
|
action = qemuMonitorMemoryFailureActionTypeFromString(str);
|
|
if (action < 0) {
|
|
VIR_WARN("unknown action '%s' in memory_failure event", str);
|
|
return;
|
|
}
|
|
|
|
if (flagsjson) {
|
|
virJSONValueObjectGetBoolean(flagsjson, "action-required", &ar);
|
|
virJSONValueObjectGetBoolean(flagsjson, "recursive", &recursive);
|
|
}
|
|
|
|
mf.recipient = recipient;
|
|
mf.action = action;
|
|
mf.action_required = ar;
|
|
mf.recursive = recursive;
|
|
qemuMonitorEmitMemoryFailure(mon, &mf);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleMigrationStatus(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
const char *str;
|
|
int status;
|
|
|
|
if (!(str = virJSONValueObjectGetString(data, "status"))) {
|
|
VIR_WARN("missing status in migration event");
|
|
return;
|
|
}
|
|
|
|
if ((status = qemuMonitorMigrationStatusTypeFromString(str)) == -1) {
|
|
VIR_WARN("unknown status '%s' in migration event", str);
|
|
return;
|
|
}
|
|
|
|
qemuMonitorEmitMigrationStatus(mon, status);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleMigrationPass(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
int pass;
|
|
|
|
if (virJSONValueObjectGetNumberInt(data, "pass", &pass) < 0) {
|
|
VIR_WARN("missing dirty-sync-count in migration-pass event");
|
|
return;
|
|
}
|
|
|
|
qemuMonitorEmitMigrationPass(mon, pass);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleAcpiOstInfo(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
virJSONValue *info;
|
|
const char *alias;
|
|
const char *slotType;
|
|
const char *slot;
|
|
unsigned int source;
|
|
unsigned int status;
|
|
|
|
if (!(info = virJSONValueObjectGetObject(data, "info")))
|
|
goto error;
|
|
|
|
/* optional */
|
|
alias = virJSONValueObjectGetString(info, "device");
|
|
|
|
if (!(slotType = virJSONValueObjectGetString(info, "slot-type")))
|
|
goto error;
|
|
|
|
if (!(slot = virJSONValueObjectGetString(info, "slot")))
|
|
goto error;
|
|
|
|
if (virJSONValueObjectGetNumberUint(info, "source", &source) < 0)
|
|
goto error;
|
|
|
|
if (virJSONValueObjectGetNumberUint(info, "status", &status) < 0)
|
|
goto error;
|
|
|
|
qemuMonitorEmitAcpiOstInfo(mon, alias, slotType, slot, source, status);
|
|
return;
|
|
|
|
error:
|
|
VIR_WARN("malformed ACPI_DEVICE_OST event");
|
|
return;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleBlockThreshold(qemuMonitor *mon, virJSONValue *data)
|
|
{
|
|
const char *nodename;
|
|
unsigned long long threshold;
|
|
unsigned long long excess;
|
|
|
|
if (!(nodename = virJSONValueObjectGetString(data, "node-name")))
|
|
goto error;
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "write-threshold", &threshold) < 0)
|
|
goto error;
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "amount-exceeded", &excess) < 0)
|
|
goto error;
|
|
|
|
qemuMonitorEmitBlockThreshold(mon, nodename, threshold, excess);
|
|
return;
|
|
|
|
error:
|
|
VIR_WARN("malformed 'BLOCK_WRITE_THRESHOLD' event");
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONExtractDumpStats(virJSONValue *result,
|
|
qemuMonitorDumpStats *ret)
|
|
{
|
|
const char *statusstr;
|
|
|
|
if (!(statusstr = virJSONValueObjectGetString(result, "status"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("incomplete result, failed to get status"));
|
|
return -1;
|
|
}
|
|
|
|
ret->status = qemuMonitorDumpStatusTypeFromString(statusstr);
|
|
if (ret->status < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("incomplete result, unknown status string '%s'"),
|
|
statusstr);
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(result, "completed", &ret->completed) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("incomplete result, failed to get completed"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(result, "total", &ret->total) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("incomplete result, failed to get total"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleDumpCompleted(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
virJSONValue *result;
|
|
int status;
|
|
qemuMonitorDumpStats stats = { 0 };
|
|
const char *error = NULL;
|
|
|
|
if (!(result = virJSONValueObjectGetObject(data, "result"))) {
|
|
VIR_WARN("missing result in dump completed event");
|
|
return;
|
|
}
|
|
|
|
status = qemuMonitorJSONExtractDumpStats(result, &stats);
|
|
|
|
error = virJSONValueObjectGetString(data, "error");
|
|
|
|
qemuMonitorEmitDumpCompleted(mon, status, &stats, error);
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandlePRManagerStatusChanged(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
const char *name;
|
|
bool connected;
|
|
|
|
if (!(name = virJSONValueObjectGetString(data, "id"))) {
|
|
VIR_WARN("missing pr-manager alias in PR_MANAGER_STATUS_CHANGED event");
|
|
return;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(data, "connected", &connected) < 0) {
|
|
VIR_WARN("missing connected state for %s "
|
|
"in PR_MANAGER_STATUS_CHANGED event", name);
|
|
return;
|
|
}
|
|
|
|
qemuMonitorEmitPRManagerStatusChanged(mon, name, connected);
|
|
}
|
|
|
|
|
|
static void qemuMonitorJSONHandleRdmaGidStatusChanged(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
const char *netdev;
|
|
bool gid_status;
|
|
unsigned long long subnet_prefix, interface_id;
|
|
|
|
if (!(netdev = virJSONValueObjectGetString(data, "netdev"))) {
|
|
VIR_WARN("missing netdev in GID_STATUS_CHANGED event");
|
|
return;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(data, "gid-status", &gid_status)) {
|
|
VIR_WARN("missing gid-status in GID_STATUS_CHANGED event");
|
|
return;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "subnet-prefix",
|
|
&subnet_prefix)) {
|
|
VIR_WARN("missing subnet-prefix in GID_STATUS_CHANGED event");
|
|
return;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "interface-id",
|
|
&interface_id)) {
|
|
VIR_WARN("missing interface-id in GID_STATUS_CHANGED event");
|
|
return;
|
|
}
|
|
|
|
qemuMonitorEmitRdmaGidStatusChanged(mon, netdev, gid_status, subnet_prefix,
|
|
interface_id);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONHandleGuestCrashloaded(qemuMonitor *mon,
|
|
virJSONValue *data)
|
|
{
|
|
VIR_DEBUG("qemuMonitorJSONHandleGuestCrashloaded event, mon %p, data %p", mon, data);
|
|
|
|
qemuMonitorEmitGuestCrashloaded(mon);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONHumanCommand(qemuMonitor *mon,
|
|
const char *cmd_str,
|
|
int fd,
|
|
char **reply_str)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *obj;
|
|
const char *data;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("human-monitor-command",
|
|
"s:command-line", cmd_str,
|
|
NULL);
|
|
|
|
if (!cmd || qemuMonitorJSONCommandWithFd(mon, cmd, fd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONHasError(reply, "CommandNotFound")) {
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
|
_("Human monitor command is not available to run %s"),
|
|
cmd_str);
|
|
return -1;
|
|
}
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply))
|
|
return -1;
|
|
|
|
obj = virJSONValueObjectGet(reply, "return");
|
|
data = virJSONValueGetString(obj);
|
|
*reply_str = g_strdup(NULLSTR_EMPTY(data));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetCapabilities(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qmp_capabilities",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONStartCPUs(qemuMonitor *mon)
|
|
{
|
|
int ret;
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("cont", NULL);
|
|
size_t i = 0;
|
|
int timeout = 3;
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
do {
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
ret = qemuMonitorJSONCommand(mon, cmd, &reply);
|
|
|
|
if (ret != 0)
|
|
break;
|
|
|
|
/* If no error, we're done */
|
|
if ((ret = qemuMonitorJSONCheckError(cmd, reply)) == 0)
|
|
break;
|
|
|
|
/* If error class is not MigrationExpected, we're done.
|
|
* Otherwise try 'cont' cmd again */
|
|
if (!qemuMonitorJSONHasError(reply, "MigrationExpected"))
|
|
break;
|
|
|
|
g_usleep(250000);
|
|
} while (++i <= timeout);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONStopCPUs(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("stop", NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetStatus(qemuMonitor *mon,
|
|
bool *running,
|
|
virDomainPausedReason *reason)
|
|
{
|
|
const char *status;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
|
|
if (reason)
|
|
*reason = VIR_DOMAIN_PAUSED_UNKNOWN;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-status", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (virJSONValueObjectGetBoolean(data, "running", running) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-status reply was missing running state"));
|
|
return -1;
|
|
}
|
|
|
|
if ((status = virJSONValueObjectGetString(data, "status"))) {
|
|
if (!*running && reason)
|
|
*reason = qemuMonitorVMStatusToPausedReason(status);
|
|
} else if (!*running) {
|
|
VIR_DEBUG("query-status reply was missing status details");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONSystemPowerdown(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("system_powerdown", NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONSetLink(qemuMonitor *mon,
|
|
const char *name,
|
|
virDomainNetInterfaceLinkState state)
|
|
{
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("set_link",
|
|
"s:name", name,
|
|
"b:up", state != VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN,
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONSystemReset(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("system_reset", NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONExtractCPUS390Info:
|
|
* @jsoncpu: pointer to a single JSON cpu entry
|
|
* @cpu: pointer to a single cpu entry
|
|
*
|
|
* Derive the legacy cpu info 'halted' information
|
|
* from the more accurate s390 cpu state. @cpu is
|
|
* modified only on success.
|
|
*
|
|
* Note: the 'uninitialized' s390 cpu state can't be
|
|
* mapped to halted yes/no.
|
|
*
|
|
* A s390 cpu entry could look like this
|
|
* { "arch": "s390",
|
|
* "cpu-index": 0,
|
|
* "qom-path": "/machine/unattached/device[0]",
|
|
* "thread_id": 3081,
|
|
* "cpu-state": "operating" }
|
|
*
|
|
*/
|
|
static void
|
|
qemuMonitorJSONExtractCPUS390Info(virJSONValue *jsoncpu,
|
|
struct qemuMonitorQueryCpusEntry *cpu)
|
|
{
|
|
const char *cpu_state = virJSONValueObjectGetString(jsoncpu, "cpu-state");
|
|
|
|
if (STREQ_NULLABLE(cpu_state, "operating") ||
|
|
STREQ_NULLABLE(cpu_state, "load"))
|
|
cpu->halted = false;
|
|
else if (STREQ_NULLABLE(cpu_state, "stopped") ||
|
|
STREQ_NULLABLE(cpu_state, "check-stop"))
|
|
cpu->halted = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONExtractCPUInfo:
|
|
* @data: JSON response data
|
|
* @entries: filled with detected cpu entries on success
|
|
* @nentries: number of entries returned
|
|
* @fast: true if this is a response from query-cpus-fast
|
|
*
|
|
* The JSON response @data will have the following format
|
|
* in case @fast == false
|
|
* [{ "arch": "x86",
|
|
* "current": true,
|
|
* "CPU": 0,
|
|
* "qom_path": "/machine/unattached/device[0]",
|
|
* "pc": -2130415978,
|
|
* "halted": true,
|
|
* "thread_id": 2631237,
|
|
* ...},
|
|
* {...}
|
|
* ]
|
|
* and for @fast == true
|
|
* [{ "arch": "x86",
|
|
* "cpu-index": 0,
|
|
* "props": {
|
|
* "core-id": 0,
|
|
* "thread-id": 0,
|
|
* "socket-id": 0
|
|
* },
|
|
* "qom-path": "/machine/unattached/device[0]",
|
|
* "thread-id": 2631237,
|
|
* ...},
|
|
* {...}
|
|
* ]
|
|
* or for s390
|
|
* [{ "arch": "s390",
|
|
* "cpu-index": 0,
|
|
* "props": {
|
|
* "core-id": 0
|
|
* },
|
|
* "qom-path": "/machine/unattached/device[0]",
|
|
* "thread-id": 1237,
|
|
* "cpu-state": "operating",
|
|
* ...},
|
|
* {...}
|
|
* ]
|
|
*
|
|
* Note that since QEMU 2.13.0 the "arch" output member of the
|
|
* "query-cpus-fast" command is replaced by "target".
|
|
*/
|
|
static int
|
|
qemuMonitorJSONExtractCPUInfo(virJSONValue *data,
|
|
struct qemuMonitorQueryCpusEntry **entries,
|
|
size_t *nentries,
|
|
bool fast)
|
|
{
|
|
const char *arch = NULL;
|
|
struct qemuMonitorQueryCpusEntry *cpus = NULL;
|
|
int ret = -1;
|
|
size_t i;
|
|
size_t ncpus;
|
|
|
|
if ((ncpus = virJSONValueArraySize(data)) == 0)
|
|
return -2;
|
|
|
|
cpus = g_new0(struct qemuMonitorQueryCpusEntry, ncpus);
|
|
|
|
for (i = 0; i < ncpus; i++) {
|
|
virJSONValue *entry = virJSONValueArrayGet(data, i);
|
|
int cpuid = -1;
|
|
int thread = 0;
|
|
bool halted = false;
|
|
const char *qom_path;
|
|
if (!entry) {
|
|
ret = -2;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Some older qemu versions don't report the thread_id so treat this as
|
|
* non-fatal, simply returning no data.
|
|
* The return data of query-cpus-fast has different field names
|
|
*/
|
|
if (fast) {
|
|
if (!(arch = virJSONValueObjectGetString(entry, "target")))
|
|
arch = virJSONValueObjectGetString(entry, "arch");
|
|
ignore_value(virJSONValueObjectGetNumberInt(entry, "cpu-index", &cpuid));
|
|
ignore_value(virJSONValueObjectGetNumberInt(entry, "thread-id", &thread));
|
|
qom_path = virJSONValueObjectGetString(entry, "qom-path");
|
|
} else {
|
|
arch = virJSONValueObjectGetString(entry, "arch");
|
|
ignore_value(virJSONValueObjectGetNumberInt(entry, "CPU", &cpuid));
|
|
ignore_value(virJSONValueObjectGetNumberInt(entry, "thread_id", &thread));
|
|
ignore_value(virJSONValueObjectGetBoolean(entry, "halted", &halted));
|
|
qom_path = virJSONValueObjectGetString(entry, "qom_path");
|
|
}
|
|
|
|
cpus[i].qemu_id = cpuid;
|
|
cpus[i].tid = thread;
|
|
cpus[i].halted = halted;
|
|
cpus[i].qom_path = g_strdup(qom_path);
|
|
|
|
/* process optional architecture-specific data */
|
|
if (STREQ_NULLABLE(arch, "s390") || STREQ_NULLABLE(arch, "s390x"))
|
|
qemuMonitorJSONExtractCPUS390Info(entry, cpus + i);
|
|
}
|
|
|
|
*entries = g_steal_pointer(&cpus);
|
|
*nentries = ncpus;
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
qemuMonitorQueryCpusFree(cpus, ncpus);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONQueryCPUs:
|
|
*
|
|
* @mon: monitor object
|
|
* @entries: filled with detected entries on success
|
|
* @nentries: number of entries returned
|
|
* @force: force exit on error
|
|
* @fast: use query-cpus-fast
|
|
*
|
|
* Queries qemu for cpu-related information. Failure to execute the command or
|
|
* extract results does not produce an error as libvirt can continue without
|
|
* this information, unless the caller has specified @force == true.
|
|
*
|
|
* Returns 0 on success, -1 on a fatal error (oom ...) and -2 if the
|
|
* query failed gracefully.
|
|
*/
|
|
int
|
|
qemuMonitorJSONQueryCPUs(qemuMonitor *mon,
|
|
struct qemuMonitorQueryCpusEntry **entries,
|
|
size_t *nentries,
|
|
bool force,
|
|
bool fast)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
|
|
if (fast)
|
|
cmd = qemuMonitorJSONMakeCommand("query-cpus-fast", NULL);
|
|
else
|
|
cmd = qemuMonitorJSONMakeCommand("query-cpus", NULL);
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (force && qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
if (!(data = virJSONValueObjectGetArray(reply, "return")))
|
|
return -2;
|
|
|
|
return qemuMonitorJSONExtractCPUInfo(data, entries, nentries, fast);
|
|
}
|
|
|
|
|
|
/**
|
|
* Loads correct video memory size values from QEMU and update the video
|
|
* definition.
|
|
*
|
|
* Return 0 on success, -1 on failure and set proper error message.
|
|
*/
|
|
int
|
|
qemuMonitorJSONUpdateVideoMemorySize(qemuMonitor *mon,
|
|
virDomainVideoDef *video,
|
|
char *path)
|
|
{
|
|
qemuMonitorJSONObjectProperty prop = {
|
|
QEMU_MONITOR_OBJECT_PROPERTY_ULONG,
|
|
{0}
|
|
};
|
|
|
|
switch (video->type) {
|
|
case VIR_DOMAIN_VIDEO_TYPE_VGA:
|
|
if (qemuMonitorJSONGetObjectProperty(mon, path, "vgamem_mb", &prop) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("QOM Object '%s' has no property 'vgamem_mb'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
video->vram = prop.val.ul * 1024;
|
|
break;
|
|
case VIR_DOMAIN_VIDEO_TYPE_QXL:
|
|
if (qemuMonitorJSONGetObjectProperty(mon, path, "vram_size", &prop) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("QOM Object '%s' has no property 'vram_size'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
video->vram = prop.val.ul / 1024;
|
|
|
|
if (qemuMonitorJSONGetObjectProperty(mon, path, "ram_size", &prop) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("QOM Object '%s' has no property 'ram_size'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
video->ram = prop.val.ul / 1024;
|
|
if (qemuMonitorJSONGetObjectProperty(mon, path, "vgamem_mb", &prop) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("QOM Object '%s' has no property 'vgamem_mb'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
video->vgamem = prop.val.ul * 1024;
|
|
break;
|
|
case VIR_DOMAIN_VIDEO_TYPE_VMVGA:
|
|
if (qemuMonitorJSONGetObjectProperty(mon, path, "vgamem_mb", &prop) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("QOM Object '%s' has no property 'vgamem_mb'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
video->vram = prop.val.ul * 1024;
|
|
break;
|
|
case VIR_DOMAIN_VIDEO_TYPE_CIRRUS:
|
|
case VIR_DOMAIN_VIDEO_TYPE_XEN:
|
|
case VIR_DOMAIN_VIDEO_TYPE_VBOX:
|
|
case VIR_DOMAIN_VIDEO_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Loads correct video vram64 size value from QEMU and update the video
|
|
* definition.
|
|
*
|
|
* Return 0 on success, -1 on failure and set proper error message.
|
|
*/
|
|
int
|
|
qemuMonitorJSONUpdateVideoVram64Size(qemuMonitor *mon,
|
|
virDomainVideoDef *video,
|
|
char *path)
|
|
{
|
|
qemuMonitorJSONObjectProperty prop = {
|
|
QEMU_MONITOR_OBJECT_PROPERTY_ULONG,
|
|
{0}
|
|
};
|
|
|
|
switch (video->type) {
|
|
case VIR_DOMAIN_VIDEO_TYPE_QXL:
|
|
if (video->vram64 != 0) {
|
|
if (qemuMonitorJSONGetObjectProperty(mon, path,
|
|
"vram64_size_mb", &prop) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("QOM Object '%s' has no property 'vram64_size_mb'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
video->vram64 = prop.val.ul * 1024;
|
|
}
|
|
break;
|
|
case VIR_DOMAIN_VIDEO_TYPE_VGA:
|
|
case VIR_DOMAIN_VIDEO_TYPE_VMVGA:
|
|
case VIR_DOMAIN_VIDEO_TYPE_CIRRUS:
|
|
case VIR_DOMAIN_VIDEO_TYPE_XEN:
|
|
case VIR_DOMAIN_VIDEO_TYPE_VBOX:
|
|
case VIR_DOMAIN_VIDEO_TYPE_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetBalloonInfo(qemuMonitor *mon,
|
|
unsigned long long *currmem)
|
|
{
|
|
virJSONValue *data;
|
|
unsigned long long mem;
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-balloon",
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
*currmem = 0;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
/* See if balloon soft-failed */
|
|
if (qemuMonitorJSONHasError(reply, "DeviceNotActive") ||
|
|
qemuMonitorJSONHasError(reply, "KVMMissingCap")) {
|
|
return 0;
|
|
}
|
|
|
|
/* See if any other fatal error occurred */
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (virJSONValueObjectGetNumberUlong(data, "actual", &mem) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("info balloon reply was missing balloon data"));
|
|
return -1;
|
|
}
|
|
|
|
*currmem = (mem/1024);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Process the balloon driver statistics. The request and data returned
|
|
* will be as follows (although the 'child[#]' entry will differ based on
|
|
* where it's run).
|
|
*
|
|
* { "execute": "qom-get","arguments": \
|
|
* { "path": "/machine/i440fx/pci.0/child[7]","property": "guest-stats"} }
|
|
*
|
|
* {"return": {"stats": \
|
|
* {"stat-swap-out": 0,
|
|
* "stat-free-memory": 686350336,
|
|
* "stat-minor-faults": 697283,
|
|
* "stat-major-faults": 951,
|
|
* "stat-total-memory": 1019924480,
|
|
* "stat-swap-in": 0},
|
|
* "last-update": 1371221540}}
|
|
*
|
|
* A value in "stats" can be -1 indicating it's never been collected/stored.
|
|
* The 'last-update' value could be used in the future in order to determine
|
|
* rates and/or whether data has been collected since a previous cycle.
|
|
* It's currently unused.
|
|
*/
|
|
#define GET_BALLOON_STATS(OBJECT, FIELD, TAG, DIVISOR) \
|
|
if (virJSONValueObjectHasKey(OBJECT, FIELD) && \
|
|
(got < nr_stats)) { \
|
|
if (virJSONValueObjectGetNumberUlong(OBJECT, FIELD, &mem) < 0) { \
|
|
VIR_DEBUG("Failed to get '%s' value", FIELD); \
|
|
} else { \
|
|
/* Not being collected? No point in providing bad data */ \
|
|
if (mem != -1UL) { \
|
|
stats[got].tag = TAG; \
|
|
stats[got].val = mem / DIVISOR; \
|
|
got++; \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetMemoryStats(qemuMonitor *mon,
|
|
char *balloonpath,
|
|
virDomainMemoryStatPtr stats,
|
|
unsigned int nr_stats)
|
|
{
|
|
int ret = -1;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
virJSONValue *statsdata;
|
|
unsigned long long mem;
|
|
int got = 0;
|
|
|
|
ret = qemuMonitorJSONGetBalloonInfo(mon, &mem);
|
|
if (ret == 1 && (got < nr_stats)) {
|
|
stats[got].tag = VIR_DOMAIN_MEMORY_STAT_ACTUAL_BALLOON;
|
|
stats[got].val = mem;
|
|
got++;
|
|
}
|
|
|
|
if (!balloonpath)
|
|
return got;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
|
|
"s:path", balloonpath,
|
|
"s:property", "guest-stats",
|
|
NULL)))
|
|
return got;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return got;
|
|
|
|
if ((data = virJSONValueObjectGetObject(reply, "error"))) {
|
|
const char *klass = virJSONValueObjectGetString(data, "class");
|
|
const char *desc = virJSONValueObjectGetString(data, "desc");
|
|
|
|
if (STREQ_NULLABLE(klass, "GenericError") &&
|
|
STREQ_NULLABLE(desc, "guest hasn't updated any stats yet")) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
_("the guest hasn't updated any stats yet"));
|
|
return got;
|
|
}
|
|
}
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return got;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (!(statsdata = virJSONValueObjectGet(data, "stats"))) {
|
|
VIR_DEBUG("data does not include 'stats'");
|
|
return got;
|
|
}
|
|
|
|
GET_BALLOON_STATS(statsdata, "stat-swap-in",
|
|
VIR_DOMAIN_MEMORY_STAT_SWAP_IN, 1024);
|
|
GET_BALLOON_STATS(statsdata, "stat-swap-out",
|
|
VIR_DOMAIN_MEMORY_STAT_SWAP_OUT, 1024);
|
|
GET_BALLOON_STATS(statsdata, "stat-major-faults",
|
|
VIR_DOMAIN_MEMORY_STAT_MAJOR_FAULT, 1);
|
|
GET_BALLOON_STATS(statsdata, "stat-minor-faults",
|
|
VIR_DOMAIN_MEMORY_STAT_MINOR_FAULT, 1);
|
|
GET_BALLOON_STATS(statsdata, "stat-free-memory",
|
|
VIR_DOMAIN_MEMORY_STAT_UNUSED, 1024);
|
|
GET_BALLOON_STATS(statsdata, "stat-total-memory",
|
|
VIR_DOMAIN_MEMORY_STAT_AVAILABLE, 1024);
|
|
GET_BALLOON_STATS(statsdata, "stat-available-memory",
|
|
VIR_DOMAIN_MEMORY_STAT_USABLE, 1024);
|
|
GET_BALLOON_STATS(data, "last-update",
|
|
VIR_DOMAIN_MEMORY_STAT_LAST_UPDATE, 1);
|
|
GET_BALLOON_STATS(statsdata, "stat-disk-caches",
|
|
VIR_DOMAIN_MEMORY_STAT_DISK_CACHES, 1024);
|
|
GET_BALLOON_STATS(statsdata, "stat-htlb-pgalloc",
|
|
VIR_DOMAIN_MEMORY_STAT_HUGETLB_PGALLOC, 1);
|
|
GET_BALLOON_STATS(statsdata, "stat-htlb-pgfail",
|
|
VIR_DOMAIN_MEMORY_STAT_HUGETLB_PGFAIL, 1);
|
|
|
|
return got;
|
|
}
|
|
#undef GET_BALLOON_STATS
|
|
|
|
|
|
/*
|
|
* Using the provided balloonpath, determine if we need to set the
|
|
* collection interval property to enable statistics gathering.
|
|
*/
|
|
int
|
|
qemuMonitorJSONSetMemoryStatsPeriod(qemuMonitor *mon,
|
|
char *balloonpath,
|
|
int period)
|
|
{
|
|
qemuMonitorJSONObjectProperty prop;
|
|
|
|
/* Set to the value in memballoon (could enable or disable) */
|
|
memset(&prop, 0, sizeof(qemuMonitorJSONObjectProperty));
|
|
prop.type = QEMU_MONITOR_OBJECT_PROPERTY_INT;
|
|
prop.val.iv = period;
|
|
if (qemuMonitorJSONSetObjectProperty(mon, balloonpath,
|
|
"guest-stats-polling-interval",
|
|
&prop) < 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetDBusVMStateIdList(qemuMonitor *mon,
|
|
const char *vmstatepath,
|
|
const char *idstr)
|
|
{
|
|
qemuMonitorJSONObjectProperty prop = {
|
|
.type = QEMU_MONITOR_OBJECT_PROPERTY_STRING,
|
|
.val.str = (char *) idstr,
|
|
};
|
|
|
|
return qemuMonitorJSONSetObjectProperty(mon, vmstatepath, "id-list", &prop);
|
|
}
|
|
|
|
|
|
/* qemuMonitorJSONQueryBlock:
|
|
* @mon: Monitor pointer
|
|
*
|
|
* This helper will attempt to make a "query-block" call and check for
|
|
* errors before returning with the reply.
|
|
*
|
|
* Returns: NULL on error, reply on success
|
|
*/
|
|
static virJSONValue *
|
|
qemuMonitorJSONQueryBlock(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-block", NULL)))
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0 ||
|
|
qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return NULL;
|
|
|
|
return virJSONValueObjectStealArray(reply, "return");
|
|
}
|
|
|
|
|
|
static virJSONValue *
|
|
qemuMonitorJSONGetBlockDev(virJSONValue *devices,
|
|
size_t idx)
|
|
{
|
|
virJSONValue *dev = virJSONValueArrayGet(devices, idx);
|
|
|
|
if (!dev || virJSONValueGetType(dev) != VIR_JSON_TYPE_OBJECT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-block device entry was not in expected format"));
|
|
return NULL;
|
|
}
|
|
return dev;
|
|
}
|
|
|
|
|
|
static const char *
|
|
qemuMonitorJSONGetBlockDevDevice(virJSONValue *dev)
|
|
{
|
|
const char *thisdev;
|
|
|
|
if (!(thisdev = virJSONValueObjectGetString(dev, "device"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-block device entry was not in expected format"));
|
|
return NULL;
|
|
}
|
|
|
|
return thisdev;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONBlockInfoAdd(GHashTable *table,
|
|
struct qemuDomainDiskInfo *info,
|
|
const char *entryname)
|
|
{
|
|
struct qemuDomainDiskInfo *tmp = NULL;
|
|
|
|
if (g_hash_table_contains(table, entryname)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Duplicate block info for '%s'"), entryname);
|
|
return -1;
|
|
}
|
|
|
|
tmp = g_new0(struct qemuDomainDiskInfo, 1);
|
|
|
|
*tmp = *info;
|
|
tmp->nodename = g_strdup(info->nodename);
|
|
|
|
g_hash_table_insert(table, g_strdup(entryname), tmp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONGetBlockInfo(qemuMonitor *mon,
|
|
GHashTable *table)
|
|
{
|
|
size_t i;
|
|
g_autoptr(virJSONValue) devices = NULL;
|
|
|
|
if (!(devices = qemuMonitorJSONQueryBlock(mon)))
|
|
return -1;
|
|
|
|
for (i = 0; i < virJSONValueArraySize(devices); i++) {
|
|
virJSONValue *dev;
|
|
virJSONValue *image;
|
|
struct qemuDomainDiskInfo info = { false };
|
|
const char *thisdev;
|
|
const char *status;
|
|
const char *qdev;
|
|
|
|
if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
|
|
return -1;
|
|
|
|
if (!(thisdev = qemuMonitorJSONGetBlockDevDevice(dev)))
|
|
return -1;
|
|
|
|
thisdev = qemuAliasDiskDriveSkipPrefix(thisdev);
|
|
qdev = virJSONValueObjectGetString(dev, "qdev");
|
|
|
|
if (*thisdev == '\0')
|
|
thisdev = NULL;
|
|
|
|
if (!qdev && !thisdev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-block device entry was not in expected format"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(dev, "removable", &info.removable) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot read %s value"),
|
|
"removable");
|
|
return -1;
|
|
}
|
|
|
|
/* 'tray_open' is present only if the device has a tray */
|
|
if (virJSONValueObjectGetBoolean(dev, "tray_open", &info.tray_open) == 0)
|
|
info.tray = true;
|
|
|
|
/* presence of 'inserted' notifies that a medium is in the device */
|
|
if ((image = virJSONValueObjectGetObject(dev, "inserted"))) {
|
|
info.nodename = (char *) virJSONValueObjectGetString(image, "node-name");
|
|
} else {
|
|
info.empty = true;
|
|
}
|
|
|
|
/* Missing io-status indicates no error */
|
|
if ((status = virJSONValueObjectGetString(dev, "io-status"))) {
|
|
info.io_status = qemuMonitorBlockIOStatusToError(status);
|
|
if (info.io_status < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (thisdev &&
|
|
qemuMonitorJSONBlockInfoAdd(table, &info, thisdev) < 0)
|
|
return -1;
|
|
|
|
if (qdev && STRNEQ_NULLABLE(thisdev, qdev) &&
|
|
qemuMonitorJSONBlockInfoAdd(table, &info, qdev) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static qemuBlockStats *
|
|
qemuMonitorJSONBlockStatsCollectData(virJSONValue *dev,
|
|
int *nstats)
|
|
{
|
|
g_autofree qemuBlockStats *bstats = NULL;
|
|
virJSONValue *parent;
|
|
virJSONValue *parentstats;
|
|
virJSONValue *stats;
|
|
|
|
if ((stats = virJSONValueObjectGetObject(dev, "stats")) == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("blockstats stats entry was not "
|
|
"in expected format"));
|
|
return NULL;
|
|
}
|
|
|
|
bstats = g_new0(qemuBlockStats, 1);
|
|
|
|
#define QEMU_MONITOR_BLOCK_STAT_GET(NAME, VAR, MANDATORY) \
|
|
if (MANDATORY || virJSONValueObjectHasKey(stats, NAME)) { \
|
|
(*nstats)++; \
|
|
if (virJSONValueObjectGetNumberUlong(stats, NAME, &VAR) < 0) { \
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, \
|
|
_("cannot read %s statistic"), NAME); \
|
|
return NULL; \
|
|
} \
|
|
}
|
|
QEMU_MONITOR_BLOCK_STAT_GET("rd_bytes", bstats->rd_bytes, true);
|
|
QEMU_MONITOR_BLOCK_STAT_GET("wr_bytes", bstats->wr_bytes, true);
|
|
QEMU_MONITOR_BLOCK_STAT_GET("rd_operations", bstats->rd_req, true);
|
|
QEMU_MONITOR_BLOCK_STAT_GET("wr_operations", bstats->wr_req, true);
|
|
QEMU_MONITOR_BLOCK_STAT_GET("rd_total_time_ns", bstats->rd_total_times, false);
|
|
QEMU_MONITOR_BLOCK_STAT_GET("wr_total_time_ns", bstats->wr_total_times, false);
|
|
QEMU_MONITOR_BLOCK_STAT_GET("flush_operations", bstats->flush_req, false);
|
|
QEMU_MONITOR_BLOCK_STAT_GET("flush_total_time_ns", bstats->flush_total_times, false);
|
|
#undef QEMU_MONITOR_BLOCK_STAT_GET
|
|
|
|
if ((parent = virJSONValueObjectGetObject(dev, "parent")) &&
|
|
(parentstats = virJSONValueObjectGetObject(parent, "stats"))) {
|
|
if (virJSONValueObjectGetNumberUlong(parentstats, "wr_highest_offset",
|
|
&bstats->wr_highest_offset) == 0)
|
|
bstats->wr_highest_offset_valid = true;
|
|
}
|
|
|
|
return g_steal_pointer(&bstats);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONAddOneBlockStatsInfo(qemuBlockStats *bstats,
|
|
const char *name,
|
|
GHashTable *stats)
|
|
{
|
|
qemuBlockStats *copy = NULL;
|
|
|
|
copy = g_new0(qemuBlockStats, 1);
|
|
|
|
if (bstats)
|
|
*copy = *bstats;
|
|
|
|
if (virHashAddEntry(stats, name, copy) < 0) {
|
|
VIR_FREE(copy);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetOneBlockStatsInfo(virJSONValue *dev,
|
|
const char *dev_name,
|
|
int depth,
|
|
GHashTable *hash)
|
|
{
|
|
g_autofree qemuBlockStats *bstats = NULL;
|
|
int nstats = 0;
|
|
const char *qdevname = NULL;
|
|
const char *nodename = NULL;
|
|
g_autofree char *devicename = NULL;
|
|
virJSONValue *backing;
|
|
|
|
if (dev_name &&
|
|
!(devicename = qemuDomainStorageAlias(dev_name, depth)))
|
|
return -1;
|
|
|
|
qdevname = virJSONValueObjectGetString(dev, "qdev");
|
|
nodename = virJSONValueObjectGetString(dev, "node-name");
|
|
|
|
if (!devicename && !qdevname && !nodename) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("blockstats device entry was not in expected format"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(bstats = qemuMonitorJSONBlockStatsCollectData(dev, &nstats)))
|
|
return -1;
|
|
|
|
if (devicename &&
|
|
qemuMonitorJSONAddOneBlockStatsInfo(bstats, devicename, hash) < 0)
|
|
return -1;
|
|
|
|
if (qdevname && STRNEQ_NULLABLE(qdevname, devicename) &&
|
|
qemuMonitorJSONAddOneBlockStatsInfo(bstats, qdevname, hash) < 0)
|
|
return -1;
|
|
|
|
if (nodename &&
|
|
qemuMonitorJSONAddOneBlockStatsInfo(bstats, nodename, hash) < 0)
|
|
return -1;
|
|
|
|
if ((backing = virJSONValueObjectGetObject(dev, "backing")) &&
|
|
qemuMonitorJSONGetOneBlockStatsInfo(backing, dev_name, depth + 1, hash) < 0)
|
|
return -1;
|
|
|
|
return nstats;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetOneBlockStatsNodeInfo(virJSONValue *dev,
|
|
GHashTable *hash)
|
|
{
|
|
qemuBlockStats *bstats = NULL;
|
|
int nstats = 0;
|
|
const char *nodename = NULL;
|
|
|
|
if (!(nodename = virJSONValueObjectGetString(dev, "node-name")))
|
|
return 0;
|
|
|
|
/* we already have the stats */
|
|
if (g_hash_table_contains(hash, nodename))
|
|
return 0;
|
|
|
|
if (!(bstats = qemuMonitorJSONBlockStatsCollectData(dev, &nstats)))
|
|
return -1;
|
|
|
|
g_hash_table_insert(hash, g_strdup(nodename), bstats);
|
|
|
|
return nstats;
|
|
}
|
|
|
|
|
|
virJSONValue *
|
|
qemuMonitorJSONQueryBlockstats(qemuMonitor *mon,
|
|
bool queryNodes)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-blockstats",
|
|
"B:query-nodes", queryNodes,
|
|
NULL)))
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return NULL;
|
|
|
|
return virJSONValueObjectStealArray(reply, "return");
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetAllBlockStatsInfo(qemuMonitor *mon,
|
|
GHashTable *hash)
|
|
{
|
|
int nstats = 0;
|
|
int rc;
|
|
size_t i;
|
|
g_autoptr(virJSONValue) blockstatsDevices = NULL;
|
|
g_autoptr(virJSONValue) blockstatsNodes = NULL;
|
|
|
|
if (!(blockstatsDevices = qemuMonitorJSONQueryBlockstats(mon, false)))
|
|
return -1;
|
|
|
|
for (i = 0; i < virJSONValueArraySize(blockstatsDevices); i++) {
|
|
virJSONValue *dev = virJSONValueArrayGet(blockstatsDevices, i);
|
|
const char *dev_name;
|
|
|
|
if (!dev || virJSONValueGetType(dev) != VIR_JSON_TYPE_OBJECT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("blockstats device entry was not "
|
|
"in expected format"));
|
|
return -1;
|
|
}
|
|
|
|
if ((dev_name = virJSONValueObjectGetString(dev, "device"))) {
|
|
if (*dev_name == '\0')
|
|
dev_name = NULL;
|
|
}
|
|
|
|
rc = qemuMonitorJSONGetOneBlockStatsInfo(dev, dev_name, 0, hash);
|
|
|
|
if (rc < 0)
|
|
return -1;
|
|
|
|
if (rc > nstats)
|
|
nstats = rc;
|
|
}
|
|
|
|
if (!(blockstatsNodes = qemuMonitorJSONQueryBlockstats(mon, true)))
|
|
return -1;
|
|
|
|
for (i = 0; i < virJSONValueArraySize(blockstatsNodes); i++) {
|
|
virJSONValue *dev = virJSONValueArrayGet(blockstatsNodes, i);
|
|
|
|
if (!dev || virJSONValueGetType(dev) != VIR_JSON_TYPE_OBJECT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("blockstats device entry was not in expected format"));
|
|
return -1;
|
|
}
|
|
|
|
if ((rc = qemuMonitorJSONGetOneBlockStatsNodeInfo(dev, hash)) < 0)
|
|
return -1;
|
|
|
|
if (rc > nstats)
|
|
nstats = rc;
|
|
}
|
|
|
|
return nstats;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONBlockStatsUpdateCapacityData(virJSONValue *image,
|
|
const char *name,
|
|
GHashTable *stats,
|
|
qemuBlockStats **entry)
|
|
{
|
|
qemuBlockStats *bstats;
|
|
|
|
if (!(bstats = virHashLookup(stats, name))) {
|
|
bstats = g_new0(qemuBlockStats, 1);
|
|
|
|
if (virHashAddEntry(stats, name, bstats) < 0) {
|
|
VIR_FREE(bstats);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (entry)
|
|
*entry = bstats;
|
|
|
|
/* failures can be ignored after this point */
|
|
if (virJSONValueObjectGetNumberUlong(image, "virtual-size",
|
|
&bstats->capacity) < 0)
|
|
return 0;
|
|
|
|
/* if actual-size is missing, image is not thin provisioned */
|
|
if (virJSONValueObjectGetNumberUlong(image, "actual-size",
|
|
&bstats->physical) < 0)
|
|
bstats->physical = bstats->capacity;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONBlockStatsUpdateCapacityOne(virJSONValue *image,
|
|
const char *dev_name,
|
|
int depth,
|
|
GHashTable *stats)
|
|
{
|
|
g_autofree char *entry_name = qemuDomainStorageAlias(dev_name, depth);
|
|
virJSONValue *backing;
|
|
|
|
if (qemuMonitorJSONBlockStatsUpdateCapacityData(image, entry_name,
|
|
stats, NULL) < 0)
|
|
return -1;
|
|
|
|
if ((backing = virJSONValueObjectGetObject(image, "backing-image")) &&
|
|
qemuMonitorJSONBlockStatsUpdateCapacityOne(backing,
|
|
dev_name,
|
|
depth + 1,
|
|
stats) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockStatsUpdateCapacity(qemuMonitor *mon,
|
|
GHashTable *stats)
|
|
{
|
|
size_t i;
|
|
g_autoptr(virJSONValue) devices = NULL;
|
|
|
|
if (!(devices = qemuMonitorJSONQueryBlock(mon)))
|
|
return -1;
|
|
|
|
for (i = 0; i < virJSONValueArraySize(devices); i++) {
|
|
virJSONValue *dev;
|
|
virJSONValue *inserted;
|
|
virJSONValue *image;
|
|
const char *dev_name;
|
|
|
|
if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
|
|
return -1;
|
|
|
|
if (!(dev_name = qemuMonitorJSONGetBlockDevDevice(dev)))
|
|
return -1;
|
|
|
|
/* drive may be empty */
|
|
if (!(inserted = virJSONValueObjectGetObject(dev, "inserted")) ||
|
|
!(image = virJSONValueObjectGetObject(inserted, "image")))
|
|
continue;
|
|
|
|
if (qemuMonitorJSONBlockStatsUpdateCapacityOne(image, dev_name, 0, stats) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONBlockStatsUpdateCapacityBlockdevWorker(size_t pos G_GNUC_UNUSED,
|
|
virJSONValue *val,
|
|
void *opaque)
|
|
{
|
|
GHashTable *stats = opaque;
|
|
virJSONValue *image;
|
|
const char *nodename;
|
|
qemuBlockStats *entry;
|
|
|
|
if (!(nodename = virJSONValueObjectGetString(val, "node-name")) ||
|
|
!(image = virJSONValueObjectGetObject(val, "image"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-named-block-nodes entry was not in expected format"));
|
|
return -1;
|
|
}
|
|
|
|
if (qemuMonitorJSONBlockStatsUpdateCapacityData(image, nodename, stats, &entry) < 0)
|
|
return -1;
|
|
|
|
if (entry)
|
|
ignore_value(virJSONValueObjectGetNumberUlong(val, "write_threshold",
|
|
&entry->write_threshold));
|
|
|
|
return 1; /* we don't want to steal the value from the JSON array */
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockStatsUpdateCapacityBlockdev(qemuMonitor *mon,
|
|
GHashTable *stats)
|
|
{
|
|
g_autoptr(virJSONValue) nodes = NULL;
|
|
|
|
if (!(nodes = qemuMonitorJSONQueryNamedBlockNodes(mon, false)))
|
|
return -1;
|
|
|
|
if (virJSONValueArrayForeachSteal(nodes,
|
|
qemuMonitorJSONBlockStatsUpdateCapacityBlockdevWorker,
|
|
stats) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONBlockNamedNodeDataBitmapFree(qemuBlockNamedNodeDataBitmap *bitmap)
|
|
{
|
|
if (!bitmap)
|
|
return;
|
|
|
|
g_free(bitmap->name);
|
|
g_free(bitmap);
|
|
}
|
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockNamedNodeDataBitmap,
|
|
qemuMonitorJSONBlockNamedNodeDataBitmapFree);
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONBlockNamedNodeDataFree(qemuBlockNamedNodeData *data)
|
|
{
|
|
size_t i;
|
|
|
|
if (!data)
|
|
return;
|
|
|
|
for (i = 0; i < data->nbitmaps; i++)
|
|
qemuMonitorJSONBlockNamedNodeDataBitmapFree(data->bitmaps[i]);
|
|
g_free(data->bitmaps);
|
|
g_free(data);
|
|
}
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuBlockNamedNodeData, qemuMonitorJSONBlockNamedNodeDataFree);
|
|
|
|
|
|
static qemuBlockNamedNodeDataBitmap *
|
|
qemuMonitorJSONBlockGetNamedNodeDataBitmapOne(virJSONValue *val)
|
|
{
|
|
g_autoptr(qemuBlockNamedNodeDataBitmap) bitmap = NULL;
|
|
const char *name;
|
|
|
|
bitmap = g_new0(qemuBlockNamedNodeDataBitmap, 1);
|
|
|
|
if (!(name = virJSONValueObjectGetString(val, "name")))
|
|
return NULL;
|
|
|
|
bitmap->name = g_strdup(name);
|
|
|
|
ignore_value(virJSONValueObjectGetBoolean(val, "recording", &bitmap->recording));
|
|
ignore_value(virJSONValueObjectGetBoolean(val, "persistent", &bitmap->persistent));
|
|
ignore_value(virJSONValueObjectGetBoolean(val, "busy", &bitmap->busy));
|
|
ignore_value(virJSONValueObjectGetBoolean(val, "inconsistent", &bitmap->inconsistent));
|
|
ignore_value(virJSONValueObjectGetNumberUlong(val, "granularity", &bitmap->granularity));
|
|
ignore_value(virJSONValueObjectGetNumberUlong(val, "count", &bitmap->dirtybytes));
|
|
|
|
return g_steal_pointer(&bitmap);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuMonitorJSONBlockGetNamedNodeDataBitmaps(virJSONValue *bitmaps,
|
|
qemuBlockNamedNodeData *data)
|
|
{
|
|
size_t nbitmaps = virJSONValueArraySize(bitmaps);
|
|
size_t i;
|
|
|
|
data->bitmaps = g_new0(qemuBlockNamedNodeDataBitmap *, nbitmaps);
|
|
|
|
for (i = 0; i < nbitmaps; i++) {
|
|
virJSONValue *bitmap = virJSONValueArrayGet(bitmaps, i);
|
|
qemuBlockNamedNodeDataBitmap *tmp;
|
|
|
|
if (!bitmap)
|
|
continue;
|
|
|
|
if (!(tmp = qemuMonitorJSONBlockGetNamedNodeDataBitmapOne(bitmap)))
|
|
continue;
|
|
|
|
data->bitmaps[data->nbitmaps++] = tmp;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONBlockGetNamedNodeDataWorker(size_t pos G_GNUC_UNUSED,
|
|
virJSONValue *val,
|
|
void *opaque)
|
|
{
|
|
GHashTable *nodes = opaque;
|
|
virJSONValue *img;
|
|
virJSONValue *bitmaps;
|
|
virJSONValue *format_specific;
|
|
const char *nodename;
|
|
g_autoptr(qemuBlockNamedNodeData) ent = NULL;
|
|
|
|
ent = g_new0(qemuBlockNamedNodeData, 1);
|
|
|
|
if (!(nodename = virJSONValueObjectGetString(val, "node-name")) ||
|
|
!(img = virJSONValueObjectGetObject(val, "image")))
|
|
goto broken;
|
|
|
|
if (virJSONValueObjectGetNumberUlong(img, "virtual-size", &ent->capacity) < 0)
|
|
goto broken;
|
|
|
|
/* if actual-size is missing, image is not thin provisioned */
|
|
if (virJSONValueObjectGetNumberUlong(img, "actual-size", &ent->physical) < 0)
|
|
ent->physical = ent->capacity;
|
|
|
|
/* try looking up the cluster size */
|
|
ignore_value(virJSONValueObjectGetNumberUlong(img, "cluster-size", &ent->clusterSize));
|
|
|
|
if ((bitmaps = virJSONValueObjectGetArray(val, "dirty-bitmaps")))
|
|
qemuMonitorJSONBlockGetNamedNodeDataBitmaps(bitmaps, ent);
|
|
|
|
/* query qcow2 format specific props */
|
|
if ((format_specific = virJSONValueObjectGetObject(img, "format-specific")) &&
|
|
STREQ_NULLABLE(virJSONValueObjectGetString(format_specific, "type"), "qcow2")) {
|
|
virJSONValue *qcow2props = virJSONValueObjectGetObject(format_specific, "data");
|
|
|
|
if (qcow2props) {
|
|
if (STREQ_NULLABLE(virJSONValueObjectGetString(qcow2props, "compat"), "0.10"))
|
|
ent->qcow2v2 = true;
|
|
|
|
ignore_value(virJSONValueObjectGetBoolean(qcow2props, "extended-l2",
|
|
&ent->qcow2extendedL2));
|
|
}
|
|
}
|
|
|
|
if (virHashAddEntry(nodes, nodename, ent) < 0)
|
|
return -1;
|
|
|
|
ent = NULL;
|
|
|
|
return 1; /* we don't want to steal the value from the JSON array */
|
|
|
|
broken:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-named-block-nodes entry was not in expected format"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
GHashTable *
|
|
qemuMonitorJSONBlockGetNamedNodeDataJSON(virJSONValue *nodes)
|
|
{
|
|
g_autoptr(GHashTable) ret = NULL;
|
|
|
|
ret = virHashNew((GDestroyNotify) qemuMonitorJSONBlockNamedNodeDataFree);
|
|
|
|
if (virJSONValueArrayForeachSteal(nodes,
|
|
qemuMonitorJSONBlockGetNamedNodeDataWorker,
|
|
ret) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&ret);
|
|
}
|
|
|
|
|
|
GHashTable *
|
|
qemuMonitorJSONBlockGetNamedNodeData(qemuMonitor *mon,
|
|
bool supports_flat)
|
|
{
|
|
g_autoptr(virJSONValue) nodes = NULL;
|
|
|
|
if (!(nodes = qemuMonitorJSONQueryNamedBlockNodes(mon, supports_flat)))
|
|
return NULL;
|
|
|
|
return qemuMonitorJSONBlockGetNamedNodeDataJSON(nodes);
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONBlockResize(qemuMonitor *mon,
|
|
const char *device,
|
|
const char *nodename,
|
|
unsigned long long size)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("block_resize",
|
|
"S:device", device,
|
|
"S:node-name", nodename,
|
|
"U:size", size,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONSetPassword(qemuMonitor *mon,
|
|
const char *protocol,
|
|
const char *password,
|
|
const char *action_if_connected)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("set_password",
|
|
"s:protocol", protocol,
|
|
"s:password", password,
|
|
"s:connected", action_if_connected,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONExpirePassword(qemuMonitor *mon,
|
|
const char *protocol,
|
|
const char *expire_time)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("expire_password",
|
|
"s:protocol", protocol,
|
|
"s:time", expire_time,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetBalloon(qemuMonitor *mon,
|
|
unsigned long long newmem)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("balloon",
|
|
"U:value", newmem * 1024,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
/* See if balloon soft-failed */
|
|
if (qemuMonitorJSONHasError(reply, "DeviceNotActive") ||
|
|
qemuMonitorJSONHasError(reply, "KVMMissingCap")) {
|
|
return 0;
|
|
}
|
|
|
|
/* See if any other fatal error occurred */
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
/* Real success */
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Run QMP command to eject a media from ejectable device.
|
|
*
|
|
* Returns:
|
|
* -1 on error
|
|
* 0 on success
|
|
*/
|
|
int qemuMonitorJSONEjectMedia(qemuMonitor *mon,
|
|
const char *dev_name,
|
|
bool force)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("eject",
|
|
"s:device", dev_name,
|
|
"b:force", force,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONChangeMedia(qemuMonitor *mon,
|
|
const char *dev_name,
|
|
const char *newmedia,
|
|
const char *format)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("change",
|
|
"s:device", dev_name,
|
|
"s:target", newmedia,
|
|
"S:arg", format,
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int qemuMonitorJSONSaveMemory(qemuMonitor *mon,
|
|
const char *cmdtype,
|
|
unsigned long long offset,
|
|
unsigned long long length,
|
|
const char *path)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand(cmdtype,
|
|
"U:val", offset,
|
|
"U:size", length,
|
|
"s:filename", path,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSaveVirtualMemory(qemuMonitor *mon,
|
|
unsigned long long offset,
|
|
unsigned long long length,
|
|
const char *path)
|
|
{
|
|
return qemuMonitorJSONSaveMemory(mon, "memsave", offset, length, path);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSavePhysicalMemory(qemuMonitor *mon,
|
|
unsigned long long offset,
|
|
unsigned long long length,
|
|
const char *path)
|
|
{
|
|
return qemuMonitorJSONSaveMemory(mon, "pmemsave", offset, length, path);
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONSetMigrationSpeed(qemuMonitor *mon,
|
|
unsigned long bandwidth)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("migrate_set_speed",
|
|
"U:value", bandwidth * 1024ULL * 1024ULL,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONSetMigrationDowntime(qemuMonitor *mon,
|
|
unsigned long long downtime)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("migrate_set_downtime",
|
|
"d:value", downtime / 1000.0,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetMigrationCacheSize(qemuMonitor *mon,
|
|
unsigned long long *cacheSize)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
*cacheSize = 0;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("query-migrate-cache-size", NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_NUMBER) < 0)
|
|
return -1;
|
|
|
|
if (virJSONValueObjectGetNumberUlong(reply, "return", cacheSize) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid cache size in query-migrate-cache-size reply"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetMigrationCacheSize(qemuMonitor *mon,
|
|
unsigned long long cacheSize)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("migrate-set-cache-size",
|
|
"U:value", cacheSize,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetMigrationParams(qemuMonitor *mon,
|
|
virJSONValue **params)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
*params = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-migrate-parameters", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
*params = virJSONValueObjectStealObject(reply, "return");
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuMonitorJSONSetMigrationParams(qemuMonitor *mon,
|
|
virJSONValue **params)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("migrate-set-parameters", params)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetMigrationStatsReply(virJSONValue *reply,
|
|
qemuMonitorMigrationStats *stats,
|
|
char **error)
|
|
{
|
|
virJSONValue *ret;
|
|
virJSONValue *ram;
|
|
virJSONValue *disk;
|
|
virJSONValue *comp;
|
|
const char *statusstr;
|
|
int rc;
|
|
double mbps;
|
|
const char *tmp;
|
|
|
|
ret = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (!(statusstr = virJSONValueObjectGetString(ret, "status"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("info migration reply was missing return status"));
|
|
return -1;
|
|
}
|
|
|
|
stats->status = qemuMonitorMigrationStatusTypeFromString(statusstr);
|
|
if (stats->status < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unexpected migration status in %s"), statusstr);
|
|
return -1;
|
|
}
|
|
|
|
ignore_value(virJSONValueObjectGetNumberUlong(ret, "total-time",
|
|
&stats->total_time));
|
|
if (stats->status == QEMU_MONITOR_MIGRATION_STATUS_COMPLETED) {
|
|
rc = virJSONValueObjectGetNumberUlong(ret, "downtime",
|
|
&stats->downtime);
|
|
} else {
|
|
rc = virJSONValueObjectGetNumberUlong(ret, "expected-downtime",
|
|
&stats->downtime);
|
|
}
|
|
if (rc == 0)
|
|
stats->downtime_set = true;
|
|
|
|
if (virJSONValueObjectGetNumberUlong(ret, "setup-time",
|
|
&stats->setup_time) == 0)
|
|
stats->setup_time_set = true;
|
|
|
|
ignore_value(virJSONValueObjectGetNumberInt(ret, "cpu-throttle-percentage",
|
|
&stats->cpu_throttle_percentage));
|
|
|
|
switch ((qemuMonitorMigrationStatus) stats->status) {
|
|
case QEMU_MONITOR_MIGRATION_STATUS_INACTIVE:
|
|
case QEMU_MONITOR_MIGRATION_STATUS_SETUP:
|
|
case QEMU_MONITOR_MIGRATION_STATUS_CANCELLED:
|
|
case QEMU_MONITOR_MIGRATION_STATUS_WAIT_UNPLUG:
|
|
case QEMU_MONITOR_MIGRATION_STATUS_LAST:
|
|
break;
|
|
|
|
case QEMU_MONITOR_MIGRATION_STATUS_ERROR:
|
|
if (error) {
|
|
tmp = virJSONValueObjectGetString(ret, "error-desc");
|
|
if (tmp)
|
|
*error = g_strdup(tmp);
|
|
}
|
|
break;
|
|
|
|
case QEMU_MONITOR_MIGRATION_STATUS_ACTIVE:
|
|
case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY:
|
|
case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY_PAUSED:
|
|
case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY_RECOVER:
|
|
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) {
|
|
if (virJSONValueObjectGetNumberUlong(ram, "transferred",
|
|
&stats->ram_transferred) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("migration was active, but RAM 'transferred' data was missing"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetNumberUlong(ram, "remaining",
|
|
&stats->ram_remaining) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("migration was active, but RAM 'remaining' data was missing"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetNumberUlong(ram, "total",
|
|
&stats->ram_total) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("migration was active, but RAM 'total' data was missing"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberDouble(ram, "mbps", &mbps) == 0 &&
|
|
mbps > 0) {
|
|
/* mpbs from QEMU reports Mbits/s (M as in 10^6 not Mi as 2^20) */
|
|
stats->ram_bps = mbps * (1000 * 1000 / 8);
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(ram, "duplicate",
|
|
&stats->ram_duplicate) == 0)
|
|
stats->ram_duplicate_set = true;
|
|
ignore_value(virJSONValueObjectGetNumberUlong(ram, "normal",
|
|
&stats->ram_normal));
|
|
ignore_value(virJSONValueObjectGetNumberUlong(ram, "normal-bytes",
|
|
&stats->ram_normal_bytes));
|
|
ignore_value(virJSONValueObjectGetNumberUlong(ram, "dirty-pages-rate",
|
|
&stats->ram_dirty_rate));
|
|
ignore_value(virJSONValueObjectGetNumberUlong(ram, "page-size",
|
|
&stats->ram_page_size));
|
|
ignore_value(virJSONValueObjectGetNumberUlong(ram, "dirty-sync-count",
|
|
&stats->ram_iteration));
|
|
ignore_value(virJSONValueObjectGetNumberUlong(ram, "postcopy-requests",
|
|
&stats->ram_postcopy_reqs));
|
|
}
|
|
|
|
disk = virJSONValueObjectGetObject(ret, "disk");
|
|
if (disk) {
|
|
rc = virJSONValueObjectGetNumberUlong(disk, "transferred",
|
|
&stats->disk_transferred);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("disk migration was active, but "
|
|
"'transferred' data was missing"));
|
|
return -1;
|
|
}
|
|
|
|
rc = virJSONValueObjectGetNumberUlong(disk, "remaining",
|
|
&stats->disk_remaining);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("disk migration was active, but 'remaining' "
|
|
"data was missing"));
|
|
return -1;
|
|
}
|
|
|
|
rc = virJSONValueObjectGetNumberUlong(disk, "total",
|
|
&stats->disk_total);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("disk migration was active, but 'total' "
|
|
"data was missing"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberDouble(disk, "mbps", &mbps) == 0 &&
|
|
mbps > 0) {
|
|
/* mpbs from QEMU reports Mbits/s (M as in 10^6 not Mi as 2^20) */
|
|
stats->disk_bps = mbps * (1000 * 1000 / 8);
|
|
}
|
|
}
|
|
|
|
comp = virJSONValueObjectGetObject(ret, "xbzrle-cache");
|
|
if (comp) {
|
|
stats->xbzrle_set = true;
|
|
rc = virJSONValueObjectGetNumberUlong(comp, "cache-size",
|
|
&stats->xbzrle_cache_size);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("XBZRLE is active, but 'cache-size' data "
|
|
"was missing"));
|
|
return -1;
|
|
}
|
|
|
|
rc = virJSONValueObjectGetNumberUlong(comp, "bytes",
|
|
&stats->xbzrle_bytes);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("XBZRLE is active, but 'bytes' data "
|
|
"was missing"));
|
|
return -1;
|
|
}
|
|
|
|
rc = virJSONValueObjectGetNumberUlong(comp, "pages",
|
|
&stats->xbzrle_pages);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("XBZRLE is active, but 'pages' data "
|
|
"was missing"));
|
|
return -1;
|
|
}
|
|
|
|
rc = virJSONValueObjectGetNumberUlong(comp, "cache-miss",
|
|
&stats->xbzrle_cache_miss);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("XBZRLE is active, but 'cache-miss' data "
|
|
"was missing"));
|
|
return -1;
|
|
}
|
|
|
|
rc = virJSONValueObjectGetNumberUlong(comp, "overflow",
|
|
&stats->xbzrle_overflow);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("XBZRLE is active, but 'overflow' data "
|
|
"was missing"));
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONGetMigrationStats(qemuMonitor *mon,
|
|
qemuMonitorMigrationStats *stats,
|
|
char **error)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-migrate",
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
memset(stats, 0, sizeof(*stats));
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONGetMigrationStatsReply(reply, stats, error) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONMigrate(qemuMonitor *mon,
|
|
unsigned int flags,
|
|
const char *uri)
|
|
{
|
|
bool detach = !!(flags & QEMU_MONITOR_MIGRATE_BACKGROUND);
|
|
bool blk = !!(flags & QEMU_MONITOR_MIGRATE_NON_SHARED_DISK);
|
|
bool inc = !!(flags & QEMU_MONITOR_MIGRATE_NON_SHARED_INC);
|
|
bool resume = !!(flags & QEMU_MONITOR_MIGRATE_RESUME);
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("migrate",
|
|
"b:detach", detach,
|
|
"b:blk", blk,
|
|
"b:inc", inc,
|
|
"b:resume", resume,
|
|
"s:uri", uri,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONMigrateCancel(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("migrate_cancel", NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONMigratePause(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("migrate-pause", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* qemuMonitorJSONQueryDump:
|
|
* @mon: Monitor pointer
|
|
* @stats: Monitor dump stats
|
|
*
|
|
* Attempt to make a "query-dump" call, check for errors, and get/return
|
|
* the current from the reply
|
|
*
|
|
* Returns: 0 on success, -1 on failure
|
|
*/
|
|
int
|
|
qemuMonitorJSONQueryDump(qemuMonitor *mon,
|
|
qemuMonitorDumpStats *stats)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-dump", NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *result = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
result = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
return qemuMonitorJSONExtractDumpStats(result, stats);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetDumpGuestMemoryCapability(qemuMonitor *mon,
|
|
const char *capability)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *caps;
|
|
virJSONValue *formats;
|
|
size_t i;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-dump-guest-memory-capability",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
caps = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (!(formats = virJSONValueObjectGetArray(caps, "formats"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing supported dump formats"));
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < virJSONValueArraySize(formats); i++) {
|
|
virJSONValue *dumpformat = virJSONValueArrayGet(formats, i);
|
|
|
|
if (!dumpformat || virJSONValueGetType(dumpformat) != VIR_JSON_TYPE_STRING) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing entry in supported dump formats"));
|
|
return -1;
|
|
}
|
|
|
|
if (STREQ(virJSONValueGetString(dumpformat), capability))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuMonitorJSONDump(qemuMonitor *mon,
|
|
const char *protocol,
|
|
const char *dumpformat,
|
|
bool detach)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("dump-guest-memory",
|
|
"b:paging", false,
|
|
"s:protocol", protocol,
|
|
"S:format", dumpformat,
|
|
"B:detach", detach,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONGraphicsRelocate(qemuMonitor *mon,
|
|
int type,
|
|
const char *hostname,
|
|
int port,
|
|
int tlsPort,
|
|
const char *tlsSubject)
|
|
{
|
|
const char *protocol = "vnc";
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE)
|
|
protocol = "spice";
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("client_migrate_info",
|
|
"s:protocol", protocol,
|
|
"s:hostname", hostname,
|
|
"i:port", port,
|
|
"i:tls-port", tlsPort,
|
|
"S:cert-subject", tlsSubject,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONAddFileHandleToSet(qemuMonitor *mon,
|
|
int fd,
|
|
int fdset,
|
|
const char *opaque)
|
|
{
|
|
g_autoptr(virJSONValue) args = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
|
|
if (virJSONValueObjectAdd(&args, "S:opaque", opaque, NULL) < 0)
|
|
return -1;
|
|
|
|
if (fdset >= 0 &&
|
|
virJSONValueObjectAdd(&args, "j:fdset-id", fdset, NULL) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("add-fd", &args)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommandWithFd(mon, cmd, fd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONQueryFdsetsParse(virJSONValue *msg,
|
|
qemuMonitorFdsets **fdsets)
|
|
{
|
|
virJSONValue *returnArray;
|
|
virJSONValue *entry;
|
|
size_t i;
|
|
g_autoptr(qemuMonitorFdsets) sets = g_new0(qemuMonitorFdsets, 1);
|
|
int ninfo;
|
|
|
|
returnArray = virJSONValueObjectGetArray(msg, "return");
|
|
|
|
ninfo = virJSONValueArraySize(returnArray);
|
|
if (ninfo > 0)
|
|
sets->fdsets = g_new0(qemuMonitorFdsetInfo, ninfo);
|
|
sets->nfdsets = ninfo;
|
|
|
|
for (i = 0; i < ninfo; i++) {
|
|
size_t j;
|
|
virJSONValue *fdarray;
|
|
qemuMonitorFdsetInfo *fdsetinfo = &sets->fdsets[i];
|
|
|
|
if (!(entry = virJSONValueArrayGet(returnArray, i))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-fdsets return data missing fdset array element"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUint(entry, "fdset-id", &fdsetinfo->id) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-fdsets reply was missing 'fdset-id'"));
|
|
return -1;
|
|
|
|
}
|
|
|
|
if ((fdarray = virJSONValueObjectGetArray(entry, "fds"))) {
|
|
fdsetinfo->nfds = virJSONValueArraySize(fdarray);
|
|
if (fdsetinfo->nfds > 0)
|
|
fdsetinfo->fds = g_new0(qemuMonitorFdsetFdInfo, fdsetinfo->nfds);
|
|
|
|
for (j = 0; j < fdsetinfo->nfds; j++) {
|
|
qemuMonitorFdsetFdInfo *fdinfo = &fdsetinfo->fds[j];
|
|
virJSONValue *fdentry;
|
|
|
|
if (!(fdentry = virJSONValueArrayGet(fdarray, j))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-fdsets return data missing fd array element"));
|
|
return -1;
|
|
}
|
|
|
|
/* opaque is optional and may be missing */
|
|
fdinfo->opaque = g_strdup(virJSONValueObjectGetString(fdentry, "opaque"));
|
|
}
|
|
}
|
|
}
|
|
|
|
*fdsets = g_steal_pointer(&sets);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONQueryFdsets(qemuMonitor *mon,
|
|
qemuMonitorFdsets **fdsets)
|
|
{
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-fdsets",
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONQueryFdsetsParse(reply, fdsets) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONRemoveFdset(qemuMonitor *mon,
|
|
unsigned int fdset)
|
|
{
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("remove-fd",
|
|
"u:fdset-id", fdset,
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONSendFileHandle(qemuMonitor *mon,
|
|
const char *fdname,
|
|
int fd)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("getfd",
|
|
"s:fdname", fdname,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommandWithFd(mon, cmd, fd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONCloseFileHandle(qemuMonitor *mon,
|
|
const char *fdname)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("closefd",
|
|
"s:fdname", fdname,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONAddNetdev(qemuMonitor *mon,
|
|
virJSONValue **props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("netdev_add", props)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONRemoveNetdev(qemuMonitor *mon,
|
|
const char *alias)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("netdev_del",
|
|
"s:id", alias,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONQueryRxFilterParse(virJSONValue *msg,
|
|
virNetDevRxFilter **filter)
|
|
{
|
|
const char *tmp;
|
|
virJSONValue *returnArray;
|
|
virJSONValue *entry;
|
|
virJSONValue *table;
|
|
virJSONValue *element;
|
|
size_t nTable;
|
|
size_t i;
|
|
g_autoptr(virNetDevRxFilter) fil = virNetDevRxFilterNew();
|
|
|
|
if (!fil)
|
|
return -1;
|
|
|
|
returnArray = virJSONValueObjectGetArray(msg, "return");
|
|
|
|
if (!(entry = virJSONValueArrayGet(returnArray, 0))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-rx-filter return data missing array element"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(entry, "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid name "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
fil->name = g_strdup(tmp);
|
|
if ((!(tmp = virJSONValueObjectGetString(entry, "main-mac"))) ||
|
|
virMacAddrParse(tmp, &fil->mac) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'main-mac' "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetBoolean(entry, "promiscuous",
|
|
&fil->promiscuous) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'promiscuous' "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetBoolean(entry, "broadcast-allowed",
|
|
&fil->broadcastAllowed) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'broadcast-allowed' "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
|
|
if ((!(tmp = virJSONValueObjectGetString(entry, "unicast"))) ||
|
|
((fil->unicast.mode
|
|
= virNetDevRxFilterModeTypeFromString(tmp)) < 0)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'unicast' "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetBoolean(entry, "unicast-overflow",
|
|
&fil->unicast.overflow) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'unicast-overflow' "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
if ((!(table = virJSONValueObjectGet(entry, "unicast-table"))) ||
|
|
(!virJSONValueIsArray(table))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'unicast-table' array "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
nTable = virJSONValueArraySize(table);
|
|
fil->unicast.table = g_new0(virMacAddr, nTable);
|
|
for (i = 0; i < nTable; i++) {
|
|
if (!(element = virJSONValueArrayGet(table, i)) ||
|
|
!(tmp = virJSONValueGetString(element))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Missing or invalid element %zu of 'unicast' "
|
|
"list in query-rx-filter response"), i);
|
|
return -1;
|
|
}
|
|
if (virMacAddrParse(tmp, &fil->unicast.table[i]) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("invalid mac address '%s' in 'unicast-table' "
|
|
"array in query-rx-filter response"), tmp);
|
|
return -1;
|
|
}
|
|
}
|
|
fil->unicast.nTable = nTable;
|
|
|
|
if ((!(tmp = virJSONValueObjectGetString(entry, "multicast"))) ||
|
|
((fil->multicast.mode
|
|
= virNetDevRxFilterModeTypeFromString(tmp)) < 0)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'multicast' "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetBoolean(entry, "multicast-overflow",
|
|
&fil->multicast.overflow) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'multicast-overflow' "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
if ((!(table = virJSONValueObjectGet(entry, "multicast-table"))) ||
|
|
(!virJSONValueIsArray(table))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'multicast-table' array "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
nTable = virJSONValueArraySize(table);
|
|
fil->multicast.table = g_new0(virMacAddr, nTable);
|
|
for (i = 0; i < nTable; i++) {
|
|
if (!(element = virJSONValueArrayGet(table, i)) ||
|
|
!(tmp = virJSONValueGetString(element))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Missing or invalid element %zu of 'multicast' "
|
|
"list in query-rx-filter response"), i);
|
|
return -1;
|
|
}
|
|
if (virMacAddrParse(tmp, &fil->multicast.table[i]) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("invalid mac address '%s' in 'multicast-table' "
|
|
"array in query-rx-filter response"), tmp);
|
|
return -1;
|
|
}
|
|
}
|
|
fil->multicast.nTable = nTable;
|
|
|
|
if ((!(tmp = virJSONValueObjectGetString(entry, "vlan"))) ||
|
|
((fil->vlan.mode
|
|
= virNetDevRxFilterModeTypeFromString(tmp)) < 0)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'vlan' "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
if ((!(table = virJSONValueObjectGet(entry, "vlan-table"))) ||
|
|
(!virJSONValueIsArray(table))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Missing or invalid 'vlan-table' array "
|
|
"in query-rx-filter response"));
|
|
return -1;
|
|
}
|
|
nTable = virJSONValueArraySize(table);
|
|
fil->vlan.table = g_new0(unsigned int, nTable);
|
|
for (i = 0; i < nTable; i++) {
|
|
if (!(element = virJSONValueArrayGet(table, i)) ||
|
|
virJSONValueGetNumberUint(element, &fil->vlan.table[i]) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Missing or invalid element %zu of 'vlan-table' "
|
|
"array in query-rx-filter response"), i);
|
|
return -1;
|
|
}
|
|
}
|
|
fil->vlan.nTable = nTable;
|
|
|
|
*filter = g_steal_pointer(&fil);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONQueryRxFilter(qemuMonitor *mon, const char *alias,
|
|
virNetDevRxFilter **filter)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-rx-filter",
|
|
"s:name", alias,
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONQueryRxFilterParse(reply, filter) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Example return data
|
|
*
|
|
* {"return": [
|
|
* {"filename": "stdio", "label": "monitor"},
|
|
* {"filename": "pty:/dev/pts/6", "label": "serial0", "frontend-open": true},
|
|
* {"filename": "pty:/dev/pts/7", "label": "parallel0"}
|
|
* ]}
|
|
*
|
|
*/
|
|
static int
|
|
qemuMonitorJSONExtractChardevInfo(virJSONValue *reply,
|
|
GHashTable *info)
|
|
{
|
|
virJSONValue *data;
|
|
int ret = -1;
|
|
size_t i;
|
|
qemuMonitorChardevInfo *entry = NULL;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
|
|
for (i = 0; i < virJSONValueArraySize(data); i++) {
|
|
virJSONValue *chardev = virJSONValueArrayGet(data, i);
|
|
const char *type;
|
|
const char *alias;
|
|
bool connected;
|
|
|
|
if (!chardev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("character device information was missing array element"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(alias = virJSONValueObjectGetString(chardev, "label"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("character device information was missing label"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(type = virJSONValueObjectGetString(chardev, "filename"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("character device information was missing filename"));
|
|
goto cleanup;
|
|
}
|
|
|
|
entry = g_new0(qemuMonitorChardevInfo, 1);
|
|
|
|
if (STRPREFIX(type, "pty:"))
|
|
entry->ptyPath = g_strdup(type + strlen("pty:"));
|
|
|
|
if (virJSONValueObjectGetBoolean(chardev, "frontend-open", &connected) == 0) {
|
|
if (connected)
|
|
entry->state = VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED;
|
|
else
|
|
entry->state = VIR_DOMAIN_CHR_DEVICE_STATE_DISCONNECTED;
|
|
}
|
|
|
|
if (virHashAddEntry(info, alias, entry) < 0) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("failed to add chardev '%s' info"), alias);
|
|
goto cleanup;
|
|
}
|
|
|
|
entry = NULL;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (entry) {
|
|
VIR_FREE(entry->ptyPath);
|
|
VIR_FREE(entry);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetChardevInfo(qemuMonitor *mon,
|
|
GHashTable *info)
|
|
|
|
{
|
|
g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("query-chardev",
|
|
NULL);
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONExtractChardevInfo(reply, info);
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONDelDevice(qemuMonitor *mon,
|
|
const char *devalias)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("device_del",
|
|
"s:id", devalias,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONHasError(reply, "DeviceNotFound"))
|
|
return -2;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONAddDeviceProps(qemuMonitor *mon,
|
|
virJSONValue **props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("device_add", props)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONAddObject(qemuMonitor *mon,
|
|
virJSONValue **props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("object-add", props)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONDelObject(qemuMonitor *mon,
|
|
const char *objalias,
|
|
bool report_error)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("object-del", "s:id", objalias, NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckErrorFull(cmd, reply, report_error) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* speed is in bytes/sec */
|
|
int
|
|
qemuMonitorJSONDriveMirror(qemuMonitor *mon,
|
|
const char *device, const char *file,
|
|
const char *format, unsigned long long speed,
|
|
unsigned int granularity,
|
|
unsigned long long buf_size,
|
|
bool shallow,
|
|
bool reuse)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
const char *syncmode = "full";
|
|
const char *mode = "absolute-paths";
|
|
|
|
if (shallow)
|
|
syncmode = "top";
|
|
|
|
if (reuse)
|
|
mode = "existing";
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("drive-mirror",
|
|
"s:device", device,
|
|
"s:target", file,
|
|
"Y:speed", speed,
|
|
"z:granularity", granularity,
|
|
"P:buf-size", buf_size,
|
|
"s:sync", syncmode,
|
|
"s:mode", mode,
|
|
"S:format", format,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONCheckError(cmd, reply);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevMirror(qemuMonitor *mon,
|
|
const char *jobname,
|
|
bool persistjob,
|
|
const char *device,
|
|
const char *target,
|
|
unsigned long long speed,
|
|
unsigned int granularity,
|
|
unsigned long long buf_size,
|
|
bool shallow,
|
|
bool syncWrite)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virTristateBool autofinalize = VIR_TRISTATE_BOOL_ABSENT;
|
|
virTristateBool autodismiss = VIR_TRISTATE_BOOL_ABSENT;
|
|
const char *syncmode = "full";
|
|
const char *copymode = NULL;
|
|
|
|
if (shallow)
|
|
syncmode = "top";
|
|
|
|
if (syncWrite)
|
|
copymode = "write-blocking";
|
|
|
|
if (persistjob) {
|
|
autofinalize = VIR_TRISTATE_BOOL_YES;
|
|
autodismiss = VIR_TRISTATE_BOOL_NO;
|
|
}
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("blockdev-mirror",
|
|
"S:job-id", jobname,
|
|
"s:device", device,
|
|
"s:target", target,
|
|
"Y:speed", speed,
|
|
"z:granularity", granularity,
|
|
"P:buf-size", buf_size,
|
|
"s:sync", syncmode,
|
|
"S:copy-mode", copymode,
|
|
"T:auto-finalize", autofinalize,
|
|
"T:auto-dismiss", autodismiss,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONCheckError(cmd, reply);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransaction(qemuMonitor *mon, virJSONValue **actions)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("transaction",
|
|
"a:actions", actions,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* speed is in bytes/sec. Returns 0 on success, -1 with error message
|
|
* emitted on failure. */
|
|
int
|
|
qemuMonitorJSONBlockCommit(qemuMonitor *mon,
|
|
const char *device,
|
|
const char *jobname,
|
|
bool persistjob,
|
|
const char *top,
|
|
const char *topNode,
|
|
const char *base,
|
|
const char *baseNode,
|
|
const char *backingName,
|
|
unsigned long long speed)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virTristateBool autofinalize = VIR_TRISTATE_BOOL_ABSENT;
|
|
virTristateBool autodismiss = VIR_TRISTATE_BOOL_ABSENT;
|
|
|
|
if (persistjob) {
|
|
autofinalize = VIR_TRISTATE_BOOL_YES;
|
|
autodismiss = VIR_TRISTATE_BOOL_NO;
|
|
}
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("block-commit",
|
|
"s:device", device,
|
|
"S:job-id", jobname,
|
|
"Y:speed", speed,
|
|
"S:top", top,
|
|
"S:top-node", topNode,
|
|
"S:base", base,
|
|
"S:base-node", baseNode,
|
|
"S:backing-file", backingName,
|
|
"T:auto-finalize", autofinalize,
|
|
"T:auto-dismiss", autodismiss,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static char *
|
|
qemuMonitorJSONDiskNameLookupOne(virJSONValue *image,
|
|
virStorageSource *top,
|
|
virStorageSource *target)
|
|
{
|
|
virJSONValue *backing;
|
|
char *ret;
|
|
|
|
/* The caller will report a generic message if we return NULL
|
|
* without an error; but in some cases we can improve by reporting
|
|
* a more specific message. */
|
|
if (!top || !image)
|
|
return NULL;
|
|
if (top != target) {
|
|
backing = virJSONValueObjectGetObject(image, "backing-image");
|
|
return qemuMonitorJSONDiskNameLookupOne(backing, top->backingStore,
|
|
target);
|
|
}
|
|
ret = g_strdup(virJSONValueObjectGetString(image, "filename"));
|
|
/* Sanity check - the name qemu gave us should resolve to the same
|
|
file tracked by our target description. */
|
|
if (virStorageSourceIsLocalStorage(target) &&
|
|
STRNEQ(ret, target->path) &&
|
|
!virFileLinkPointsTo(ret, target->path)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("qemu block name '%s' doesn't match expected '%s'"),
|
|
ret, target->path);
|
|
VIR_FREE(ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
char *
|
|
qemuMonitorJSONDiskNameLookup(qemuMonitor *mon,
|
|
const char *device,
|
|
virStorageSource *top,
|
|
virStorageSource *target)
|
|
{
|
|
g_autoptr(virJSONValue) devices = NULL;
|
|
size_t i;
|
|
|
|
if (!(devices = qemuMonitorJSONQueryBlock(mon)))
|
|
return NULL;
|
|
|
|
for (i = 0; i < virJSONValueArraySize(devices); i++) {
|
|
virJSONValue *dev;
|
|
virJSONValue *inserted;
|
|
virJSONValue *image;
|
|
const char *thisdev;
|
|
|
|
if (!(dev = qemuMonitorJSONGetBlockDev(devices, i)))
|
|
return NULL;
|
|
|
|
if (!(thisdev = qemuMonitorJSONGetBlockDevDevice(dev)))
|
|
return NULL;
|
|
|
|
if (STREQ(thisdev, device)) {
|
|
if ((inserted = virJSONValueObjectGetObject(dev, "inserted")) &&
|
|
(image = virJSONValueObjectGetObject(inserted, "image"))) {
|
|
return qemuMonitorJSONDiskNameLookupOne(image, top, target);
|
|
}
|
|
}
|
|
}
|
|
/* Guarantee an error when returning NULL, but don't override a
|
|
* more specific error if one was already generated. */
|
|
if (virGetLastErrorCode() == VIR_ERR_OK) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unable to find backing name for device %s"),
|
|
device);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONArbitraryCommand(qemuMonitor *mon,
|
|
const char *cmd_str,
|
|
int fd,
|
|
char **reply_str)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = virJSONValueFromString(cmd_str)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommandWithFd(mon, cmd, fd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (!(*reply_str = virJSONValueToString(reply, false)))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONInjectNMI(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("inject-nmi", NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONSendKey(qemuMonitor *mon,
|
|
unsigned int holdtime,
|
|
unsigned int *keycodes,
|
|
unsigned int nkeycodes)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
g_autoptr(virJSONValue) keys = NULL;
|
|
size_t i;
|
|
|
|
/* create the key data array */
|
|
keys = virJSONValueNewArray();
|
|
|
|
for (i = 0; i < nkeycodes; i++) {
|
|
g_autoptr(virJSONValue) key = NULL;
|
|
|
|
if (keycodes[i] > 0xffff) {
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("keycode %zu is invalid: 0x%X"), i, keycodes[i]);
|
|
return -1;
|
|
}
|
|
|
|
/* create single key object */
|
|
key = virJSONValueNewObject();
|
|
|
|
/* Union KeyValue has two types, use the generic one */
|
|
if (virJSONValueObjectAppendString(key, "type", "number") < 0)
|
|
return -1;
|
|
|
|
/* with the keycode */
|
|
if (virJSONValueObjectAppendNumberInt(key, "data", keycodes[i]) < 0)
|
|
return -1;
|
|
|
|
if (virJSONValueArrayAppend(keys, &key) < 0)
|
|
return -1;
|
|
}
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("send-key",
|
|
"a:keys", &keys,
|
|
"p:hold-time", holdtime,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONScreendump(qemuMonitor *mon,
|
|
const char *device,
|
|
unsigned int head,
|
|
const char *file)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("screendump",
|
|
"s:filename", file,
|
|
"S:device", device,
|
|
"p:head", head,
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONParseBlockJobInfo(GHashTable *blockJobs,
|
|
virJSONValue *entry,
|
|
bool rawjobname)
|
|
{
|
|
qemuMonitorBlockJobInfo *info = NULL;
|
|
const char *device;
|
|
const char *type;
|
|
|
|
if (!(device = virJSONValueObjectGetString(entry, "device"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("entry was missing 'device'"));
|
|
return -1;
|
|
}
|
|
|
|
if (!rawjobname)
|
|
device = qemuAliasDiskDriveSkipPrefix(device);
|
|
|
|
info = g_new0(qemuMonitorBlockJobInfo, 1);
|
|
|
|
if (virHashAddEntry(blockJobs, device, info) < 0) {
|
|
VIR_FREE(info);
|
|
return -1;
|
|
}
|
|
|
|
if (!(type = virJSONValueObjectGetString(entry, "type"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("entry was missing 'type'"));
|
|
return -1;
|
|
}
|
|
if (STREQ(type, "stream"))
|
|
info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_PULL;
|
|
else if (STREQ(type, "commit"))
|
|
info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT;
|
|
else if (STREQ(type, "mirror"))
|
|
info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_COPY;
|
|
else if (STREQ(type, "backup"))
|
|
info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_BACKUP;
|
|
else
|
|
info->type = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
|
|
|
|
if (virJSONValueObjectGetNumberUlong(entry, "speed", &info->bandwidth) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("entry was missing 'speed'"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(entry, "offset", &info->cur) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("entry was missing 'offset'"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(entry, "len", &info->end) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("entry was missing 'len'"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(entry, "ready", &info->ready) == 0)
|
|
info->ready_present = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
GHashTable *
|
|
qemuMonitorJSONGetAllBlockJobInfo(qemuMonitor *mon,
|
|
bool rawjobname)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
size_t nr_results;
|
|
size_t i;
|
|
g_autoptr(GHashTable) blockJobs = virHashNew(g_free);
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("query-block-jobs", NULL);
|
|
if (!cmd)
|
|
return NULL;
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return NULL;
|
|
|
|
if ((data = virJSONValueObjectGetArray(reply, "return")) == NULL) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("reply was missing return data"));
|
|
return NULL;
|
|
}
|
|
|
|
nr_results = virJSONValueArraySize(data);
|
|
|
|
for (i = 0; i < nr_results; i++) {
|
|
virJSONValue *entry = virJSONValueArrayGet(data, i);
|
|
if (!entry) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing array element"));
|
|
return NULL;
|
|
}
|
|
if (qemuMonitorJSONParseBlockJobInfo(blockJobs, entry, rawjobname) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&blockJobs);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONBlockJobError(virJSONValue *cmd,
|
|
virJSONValue *reply,
|
|
const char *jobname)
|
|
{
|
|
virJSONValue *error;
|
|
|
|
if ((error = virJSONValueObjectGet(reply, "error")) &&
|
|
(qemuMonitorJSONErrorIsClass(error, "DeviceNotActive"))) {
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
|
_("No active block job '%s'"), jobname);
|
|
return -1;
|
|
}
|
|
|
|
return qemuMonitorJSONCheckError(cmd, reply);
|
|
}
|
|
|
|
|
|
/* speed is in bytes/sec */
|
|
int
|
|
qemuMonitorJSONBlockStream(qemuMonitor *mon,
|
|
const char *device,
|
|
const char *jobname,
|
|
bool persistjob,
|
|
const char *base,
|
|
const char *baseNode,
|
|
const char *backingName,
|
|
unsigned long long speed)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virTristateBool autofinalize = VIR_TRISTATE_BOOL_ABSENT;
|
|
virTristateBool autodismiss = VIR_TRISTATE_BOOL_ABSENT;
|
|
|
|
if (persistjob) {
|
|
autofinalize = VIR_TRISTATE_BOOL_YES;
|
|
autodismiss = VIR_TRISTATE_BOOL_NO;
|
|
}
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("block-stream",
|
|
"s:device", device,
|
|
"S:job-id", jobname,
|
|
"Y:speed", speed,
|
|
"S:base", base,
|
|
"S:base-node", baseNode,
|
|
"S:backing-file", backingName,
|
|
"T:auto-finalize", autofinalize,
|
|
"T:auto-dismiss", autodismiss,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockJobCancel(qemuMonitor *mon,
|
|
const char *jobname,
|
|
bool force)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("block-job-cancel",
|
|
"s:device", jobname,
|
|
"B:force", force,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockJobSetSpeed(qemuMonitor *mon,
|
|
const char *jobname,
|
|
unsigned long long speed)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("block-job-set-speed",
|
|
"s:device", jobname,
|
|
"J:speed", speed,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONDrivePivot(qemuMonitor *mon,
|
|
const char *jobname)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("block-job-complete",
|
|
"s:device", jobname,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONJobDismiss(qemuMonitor *mon,
|
|
const char *jobname)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("job-dismiss",
|
|
"s:id", jobname,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONJobComplete(qemuMonitor *mon,
|
|
const char *jobname)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("job-complete",
|
|
"s:id", jobname,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONBlockJobError(cmd, reply, jobname) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONOpenGraphics(qemuMonitor *mon,
|
|
const char *protocol,
|
|
const char *fdname,
|
|
bool skipauth)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("add_client",
|
|
"s:protocol", protocol,
|
|
"s:fdname", fdname,
|
|
"b:skipauth", skipauth,
|
|
NULL);
|
|
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define GET_THROTTLE_STATS_OPTIONAL(FIELD, STORE) \
|
|
if (virJSONValueObjectGetNumberUlong(inserted, \
|
|
FIELD, \
|
|
&reply->STORE) < 0) { \
|
|
reply->STORE = 0; \
|
|
}
|
|
#define GET_THROTTLE_STATS(FIELD, STORE) \
|
|
if (virJSONValueObjectGetNumberUlong(inserted, \
|
|
FIELD, \
|
|
&reply->STORE) < 0) { \
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, \
|
|
_("block_io_throttle field '%s' missing " \
|
|
"in qemu's output"), \
|
|
#STORE); \
|
|
return -1; \
|
|
}
|
|
static int
|
|
qemuMonitorJSONBlockIoThrottleInfo(virJSONValue *io_throttle,
|
|
const char *drivealias,
|
|
const char *qdevid,
|
|
virDomainBlockIoTuneInfo *reply)
|
|
{
|
|
size_t i;
|
|
bool found = false;
|
|
|
|
for (i = 0; i < virJSONValueArraySize(io_throttle); i++) {
|
|
virJSONValue *temp_dev = virJSONValueArrayGet(io_throttle, i);
|
|
virJSONValue *inserted;
|
|
const char *current_drive;
|
|
const char *current_qdev;
|
|
|
|
if (!temp_dev || virJSONValueGetType(temp_dev) != VIR_JSON_TYPE_OBJECT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("block_io_throttle device entry "
|
|
"was not in expected format"));
|
|
return -1;
|
|
}
|
|
|
|
current_qdev = virJSONValueObjectGetString(temp_dev, "qdev");
|
|
current_drive = virJSONValueObjectGetString(temp_dev, "device");
|
|
|
|
if (!current_drive && !current_qdev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("block_io_throttle device entry "
|
|
"was not in expected format"));
|
|
return -1;
|
|
}
|
|
|
|
if ((drivealias && current_drive && STRNEQ(current_drive, drivealias)) ||
|
|
(qdevid && current_qdev && STRNEQ(current_qdev, qdevid)))
|
|
continue;
|
|
|
|
found = true;
|
|
if (!(inserted = virJSONValueObjectGetObject(temp_dev, "inserted"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("block_io_throttle inserted entry "
|
|
"was not in expected format"));
|
|
return -1;
|
|
}
|
|
GET_THROTTLE_STATS("bps", total_bytes_sec);
|
|
GET_THROTTLE_STATS("bps_rd", read_bytes_sec);
|
|
GET_THROTTLE_STATS("bps_wr", write_bytes_sec);
|
|
GET_THROTTLE_STATS("iops", total_iops_sec);
|
|
GET_THROTTLE_STATS("iops_rd", read_iops_sec);
|
|
GET_THROTTLE_STATS("iops_wr", write_iops_sec);
|
|
GET_THROTTLE_STATS_OPTIONAL("bps_max", total_bytes_sec_max);
|
|
GET_THROTTLE_STATS_OPTIONAL("bps_rd_max", read_bytes_sec_max);
|
|
GET_THROTTLE_STATS_OPTIONAL("bps_wr_max", write_bytes_sec_max);
|
|
GET_THROTTLE_STATS_OPTIONAL("iops_max", total_iops_sec_max);
|
|
GET_THROTTLE_STATS_OPTIONAL("iops_rd_max", read_iops_sec_max);
|
|
GET_THROTTLE_STATS_OPTIONAL("iops_wr_max", write_iops_sec_max);
|
|
GET_THROTTLE_STATS_OPTIONAL("iops_size", size_iops_sec);
|
|
|
|
reply->group_name = g_strdup(virJSONValueObjectGetString(inserted, "group"));
|
|
|
|
GET_THROTTLE_STATS_OPTIONAL("bps_max_length", total_bytes_sec_max_length);
|
|
GET_THROTTLE_STATS_OPTIONAL("bps_rd_max_length", read_bytes_sec_max_length);
|
|
GET_THROTTLE_STATS_OPTIONAL("bps_wr_max_length", write_bytes_sec_max_length);
|
|
GET_THROTTLE_STATS_OPTIONAL("iops_max_length", total_iops_sec_max_length);
|
|
GET_THROTTLE_STATS_OPTIONAL("iops_rd_max_length", read_iops_sec_max_length);
|
|
GET_THROTTLE_STATS_OPTIONAL("iops_wr_max_length", write_iops_sec_max_length);
|
|
|
|
break;
|
|
}
|
|
|
|
if (!found) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot find throttling info for device '%s'"),
|
|
drivealias ? drivealias : qdevid);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#undef GET_THROTTLE_STATS
|
|
#undef GET_THROTTLE_STATS_OPTIONAL
|
|
|
|
int qemuMonitorJSONSetBlockIoThrottle(qemuMonitor *mon,
|
|
const char *drivealias,
|
|
const char *qomid,
|
|
virDomainBlockIoTuneInfo *info)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) result = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("block_set_io_throttle",
|
|
"S:device", drivealias,
|
|
"S:id", qomid,
|
|
"U:bps", info->total_bytes_sec,
|
|
"U:bps_rd", info->read_bytes_sec,
|
|
"U:bps_wr", info->write_bytes_sec,
|
|
"U:iops", info->total_iops_sec,
|
|
"U:iops_rd", info->read_iops_sec,
|
|
"U:iops_wr", info->write_iops_sec,
|
|
"U:bps_max", info->total_bytes_sec_max,
|
|
"U:bps_rd_max", info->read_bytes_sec_max,
|
|
"U:bps_wr_max", info->write_bytes_sec_max,
|
|
"U:iops_max", info->total_iops_sec_max,
|
|
"U:iops_rd_max", info->read_iops_sec_max,
|
|
"U:iops_wr_max", info->write_iops_sec_max,
|
|
"U:iops_size", info->size_iops_sec,
|
|
"S:group", info->group_name,
|
|
"P:bps_max_length", info->total_bytes_sec_max_length,
|
|
"P:bps_rd_max_length", info->read_bytes_sec_max_length,
|
|
"P:bps_wr_max_length", info->write_bytes_sec_max_length,
|
|
"P:iops_max_length", info->total_iops_sec_max_length,
|
|
"P:iops_rd_max_length", info->read_iops_sec_max_length,
|
|
"P:iops_wr_max_length", info->write_iops_sec_max_length,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &result) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, result) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONGetBlockIoThrottle(qemuMonitor *mon,
|
|
const char *drivealias,
|
|
const char *qdevid,
|
|
virDomainBlockIoTuneInfo *reply)
|
|
{
|
|
g_autoptr(virJSONValue) devices = NULL;
|
|
|
|
if (!(devices = qemuMonitorJSONQueryBlock(mon)))
|
|
return -1;
|
|
|
|
return qemuMonitorJSONBlockIoThrottleInfo(devices, drivealias, qdevid, reply);
|
|
}
|
|
|
|
int qemuMonitorJSONSystemWakeup(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("system_wakeup", NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONGetVersion(qemuMonitor *mon,
|
|
int *major,
|
|
int *minor,
|
|
int *micro,
|
|
char **package)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
virJSONValue *qemu;
|
|
|
|
*major = *minor = *micro = 0;
|
|
if (package)
|
|
*package = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-version", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (!(qemu = virJSONValueObjectGetObject(data, "qemu"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-version reply was missing 'qemu' data"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberInt(qemu, "major", major) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-version reply was missing 'major' version"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetNumberInt(qemu, "minor", minor) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-version reply was missing 'minor' version"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetNumberInt(qemu, "micro", micro) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-version reply was missing 'micro' version"));
|
|
return -1;
|
|
}
|
|
|
|
if (package) {
|
|
const char *tmp;
|
|
if (!(tmp = virJSONValueObjectGetString(data, "package"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-version reply was missing 'package' version"));
|
|
return -1;
|
|
}
|
|
*package = g_strdup(tmp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONGetMachines(qemuMonitor *mon,
|
|
qemuMonitorMachineInfo ***machines)
|
|
{
|
|
int ret = -1;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
qemuMonitorMachineInfo **infolist = NULL;
|
|
size_t n = 0;
|
|
size_t i;
|
|
|
|
*machines = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-machines", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
goto cleanup;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(data);
|
|
|
|
/* null-terminated list */
|
|
infolist = g_new0(qemuMonitorMachineInfo *, n + 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *child = virJSONValueArrayGet(data, i);
|
|
const char *tmp;
|
|
qemuMonitorMachineInfo *info;
|
|
|
|
info = g_new0(qemuMonitorMachineInfo, 1);
|
|
|
|
infolist[i] = info;
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-machines reply data was missing 'name'"));
|
|
goto cleanup;
|
|
}
|
|
|
|
info->name = g_strdup(tmp);
|
|
|
|
if (virJSONValueObjectHasKey(child, "is-default") &&
|
|
virJSONValueObjectGetBoolean(child, "is-default", &info->isDefault) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-machines reply has malformed 'is-default' data"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virJSONValueObjectHasKey(child, "alias")) {
|
|
if (!(tmp = virJSONValueObjectGetString(child, "alias"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-machines reply has malformed 'alias' data"));
|
|
goto cleanup;
|
|
}
|
|
info->alias = g_strdup(tmp);
|
|
}
|
|
if (virJSONValueObjectHasKey(child, "cpu-max") &&
|
|
virJSONValueObjectGetNumberUint(child, "cpu-max", &info->maxCpus) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-machines reply has malformed 'cpu-max' data"));
|
|
goto cleanup;
|
|
}
|
|
|
|
ignore_value(virJSONValueObjectGetBoolean(child, "hotpluggable-cpus",
|
|
&info->hotplugCpus));
|
|
|
|
if (virJSONValueObjectHasKey(child, "default-cpu-type")) {
|
|
if (!(tmp = virJSONValueObjectGetString(child, "default-cpu-type"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-machines reply has malformed "
|
|
"'default-cpu-type' data"));
|
|
goto cleanup;
|
|
}
|
|
|
|
info->defaultCPU = g_strdup(tmp);
|
|
}
|
|
|
|
if (virJSONValueObjectHasKey(child, "numa-mem-supported")) {
|
|
if (virJSONValueObjectGetBoolean(child, "numa-mem-supported", &info->numaMemSupported) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("qemu-machines reply has malformed "
|
|
"'numa-mem-supported' data"));
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
info->numaMemSupported = true;
|
|
}
|
|
|
|
if (virJSONValueObjectHasKey(child, "default-ram-id")) {
|
|
if (!(tmp = virJSONValueObjectGetString(child, "default-ram-id"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-machines reply has malformed "
|
|
"'default-ram-id' data"));
|
|
goto cleanup;
|
|
}
|
|
|
|
info->defaultRAMid = g_strdup(tmp);
|
|
}
|
|
|
|
if (virJSONValueObjectHasKey(child, "deprecated") &&
|
|
virJSONValueObjectGetBoolean(child, "deprecated", &info->deprecated) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = n;
|
|
*machines = g_steal_pointer(&infolist);
|
|
|
|
cleanup:
|
|
if (infolist) {
|
|
for (i = 0; i < n; i++)
|
|
qemuMonitorMachineInfoFree(infolist[i]);
|
|
VIR_FREE(infolist);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetCPUDefinitions(qemuMonitor *mon,
|
|
qemuMonitorCPUDefs **cpuDefs)
|
|
{
|
|
g_autoptr(qemuMonitorCPUDefs) defs = NULL;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
size_t ncpus;
|
|
size_t i;
|
|
|
|
*cpuDefs = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-definitions", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
/* Urgh, some QEMU architectures have the query-cpu-definitions
|
|
* command, but return 'GenericError' with string "Not supported",
|
|
* instead of simply omitting the command entirely :-(
|
|
*/
|
|
if (qemuMonitorJSONHasError(reply, "GenericError"))
|
|
return 0;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
ncpus = virJSONValueArraySize(data);
|
|
|
|
if (!(defs = qemuMonitorCPUDefsNew(ncpus)))
|
|
return -1;
|
|
|
|
for (i = 0; i < defs->ncpus; i++) {
|
|
virJSONValue *child = virJSONValueArrayGet(data, i);
|
|
const char *tmp;
|
|
qemuMonitorCPUDefInfo *cpu = defs->cpus + i;
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-cpu-definitions reply data was missing 'name'"));
|
|
return -1;
|
|
}
|
|
|
|
cpu->name = g_strdup(tmp);
|
|
|
|
if ((tmp = virJSONValueObjectGetString(child, "typename")) && *tmp)
|
|
cpu->type = g_strdup(tmp);
|
|
|
|
if (virJSONValueObjectHasKey(child, "unavailable-features")) {
|
|
if (!(cpu->blockers = virJSONValueObjectGetStringArray(child,
|
|
"unavailable-features")))
|
|
return -1;
|
|
|
|
if (g_strv_length(cpu->blockers) == 0) {
|
|
cpu->usable = VIR_DOMCAPS_CPU_USABLE_YES;
|
|
g_clear_pointer(&cpu->blockers, g_strfreev);
|
|
} else {
|
|
cpu->usable = VIR_DOMCAPS_CPU_USABLE_NO;
|
|
}
|
|
}
|
|
|
|
if (virJSONValueObjectHasKey(child, "deprecated") &&
|
|
virJSONValueObjectGetBoolean(child, "deprecated", &cpu->deprecated) < 0)
|
|
return -1;
|
|
}
|
|
|
|
*cpuDefs = g_steal_pointer(&defs);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONParseCPUModelProperty(const char *key,
|
|
virJSONValue *value,
|
|
void *opaque)
|
|
{
|
|
qemuMonitorCPUModelInfo *machine_model = opaque;
|
|
qemuMonitorCPUProperty *prop;
|
|
|
|
prop = machine_model->props + machine_model->nprops;
|
|
|
|
switch ((virJSONType)virJSONValueGetType(value)) {
|
|
case VIR_JSON_TYPE_STRING:
|
|
prop->value.string = g_strdup(virJSONValueGetString(value));
|
|
prop->type = QEMU_MONITOR_CPU_PROPERTY_STRING;
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_NUMBER:
|
|
/* Ignore numbers which cannot be parsed as unsigned long long */
|
|
if (virJSONValueGetNumberLong(value, &prop->value.number) < 0)
|
|
return 0;
|
|
prop->type = QEMU_MONITOR_CPU_PROPERTY_NUMBER;
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_BOOLEAN:
|
|
virJSONValueGetBoolean(value, &prop->value.boolean);
|
|
prop->type = QEMU_MONITOR_CPU_PROPERTY_BOOLEAN;
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_OBJECT:
|
|
case VIR_JSON_TYPE_ARRAY:
|
|
case VIR_JSON_TYPE_NULL:
|
|
return 0;
|
|
}
|
|
|
|
machine_model->nprops++;
|
|
prop->name = g_strdup(key);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virJSONValue *
|
|
qemuMonitorJSONMakeCPUModel(virCPUDef *cpu,
|
|
bool migratable)
|
|
{
|
|
g_autoptr(virJSONValue) model = virJSONValueNewObject();
|
|
size_t i;
|
|
|
|
if (virJSONValueObjectAppendString(model, "name", cpu->model) < 0)
|
|
return NULL;
|
|
|
|
if (cpu->nfeatures || !migratable) {
|
|
g_autoptr(virJSONValue) props = virJSONValueNewObject();
|
|
|
|
for (i = 0; i < cpu->nfeatures; i++) {
|
|
char *name = cpu->features[i].name;
|
|
bool enabled = false;
|
|
|
|
/* policy may be reported as -1 if the CPU def is a host model */
|
|
if (cpu->features[i].policy == VIR_CPU_FEATURE_REQUIRE ||
|
|
cpu->features[i].policy == VIR_CPU_FEATURE_FORCE ||
|
|
cpu->features[i].policy == -1)
|
|
enabled = true;
|
|
|
|
if (virJSONValueObjectAppendBoolean(props, name, enabled) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (!migratable &&
|
|
virJSONValueObjectAppendBoolean(props, "migratable", false) < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(model, "props", &props) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&model);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONParseCPUModelData(virJSONValue *data,
|
|
const char *cmd_name,
|
|
bool fail_no_props,
|
|
virJSONValue **cpu_model,
|
|
virJSONValue **cpu_props,
|
|
const char **cpu_name)
|
|
{
|
|
if (!(*cpu_model = virJSONValueObjectGetObject(data, "model"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("%s reply data was missing 'model'"), cmd_name);
|
|
return -1;
|
|
}
|
|
|
|
if (!(*cpu_name = virJSONValueObjectGetString(*cpu_model, "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("%s reply data was missing 'name'"), cmd_name);
|
|
return -1;
|
|
}
|
|
|
|
if (!(*cpu_props = virJSONValueObjectGetObject(*cpu_model, "props")) &&
|
|
fail_no_props) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("%s reply data was missing 'props'"), cmd_name);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONParseCPUModel(const char *cpu_name,
|
|
virJSONValue *cpu_props,
|
|
qemuMonitorCPUModelInfo **model_info)
|
|
{
|
|
g_autoptr(qemuMonitorCPUModelInfo) machine_model = NULL;
|
|
|
|
machine_model = g_new0(qemuMonitorCPUModelInfo, 1);
|
|
machine_model->name = g_strdup(cpu_name);
|
|
|
|
if (cpu_props) {
|
|
size_t nprops = virJSONValueObjectKeysNumber(cpu_props);
|
|
|
|
machine_model->props = g_new0(qemuMonitorCPUProperty, nprops);
|
|
|
|
if (virJSONValueObjectForeachKeyValue(cpu_props,
|
|
qemuMonitorJSONParseCPUModelProperty,
|
|
machine_model) < 0)
|
|
return -1;
|
|
}
|
|
|
|
*model_info = g_steal_pointer(&machine_model);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONQueryCPUModelExpansionOne(qemuMonitor *mon,
|
|
qemuMonitorCPUModelExpansionType type,
|
|
virJSONValue **model,
|
|
virJSONValue **data)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
const char *typeStr = "";
|
|
|
|
switch (type) {
|
|
case QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC:
|
|
case QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC_FULL:
|
|
typeStr = "static";
|
|
break;
|
|
|
|
case QEMU_MONITOR_CPU_MODEL_EXPANSION_FULL:
|
|
typeStr = "full";
|
|
break;
|
|
}
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-model-expansion",
|
|
"s:type", typeStr,
|
|
"a:model", model,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
/* Even though query-cpu-model-expansion is advertised by query-commands it
|
|
* may just return GenericError if it is not implemented for the requested
|
|
* guest architecture or it is not supported in the host environment.
|
|
*/
|
|
if (qemuMonitorJSONHasError(reply, "GenericError"))
|
|
return 0;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
*data = virJSONValueObjectStealObject(reply, "return");
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetCPUModelExpansion(qemuMonitor *mon,
|
|
qemuMonitorCPUModelExpansionType type,
|
|
virCPUDef *cpu,
|
|
bool migratable,
|
|
bool fail_no_props,
|
|
qemuMonitorCPUModelInfo **model_info)
|
|
{
|
|
g_autoptr(virJSONValue) model = NULL;
|
|
g_autoptr(virJSONValue) data = NULL;
|
|
g_autoptr(virJSONValue) fullData = NULL;
|
|
virJSONValue *cpu_model;
|
|
virJSONValue *cpu_props = NULL;
|
|
const char *cpu_name = "";
|
|
int rc;
|
|
|
|
*model_info = NULL;
|
|
|
|
if (!(model = qemuMonitorJSONMakeCPUModel(cpu, migratable)))
|
|
return -1;
|
|
|
|
if ((rc = qemuMonitorJSONQueryCPUModelExpansionOne(mon, type, &model, &data)) <= 0)
|
|
return rc;
|
|
|
|
if (qemuMonitorJSONParseCPUModelData(data, "query-cpu-model-expansion",
|
|
fail_no_props, &cpu_model, &cpu_props,
|
|
&cpu_name) < 0)
|
|
return -1;
|
|
|
|
/* QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC_FULL requests "full" expansion
|
|
* on the result of the initial "static" expansion. */
|
|
if (type == QEMU_MONITOR_CPU_MODEL_EXPANSION_STATIC_FULL) {
|
|
g_autoptr(virJSONValue) fullModel = virJSONValueCopy(cpu_model);
|
|
|
|
if (!fullModel)
|
|
return -1;
|
|
|
|
type = QEMU_MONITOR_CPU_MODEL_EXPANSION_FULL;
|
|
|
|
if ((rc = qemuMonitorJSONQueryCPUModelExpansionOne(mon, type, &fullModel, &fullData)) <= 0)
|
|
return rc;
|
|
|
|
if (qemuMonitorJSONParseCPUModelData(fullData, "query-cpu-model-expansion",
|
|
fail_no_props, &cpu_model, &cpu_props,
|
|
&cpu_name) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return qemuMonitorJSONParseCPUModel(cpu_name, cpu_props, model_info);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetCPUModelBaseline(qemuMonitor *mon,
|
|
virCPUDef *cpu_a,
|
|
virCPUDef *cpu_b,
|
|
qemuMonitorCPUModelInfo **baseline)
|
|
{
|
|
g_autoptr(virJSONValue) model_a = NULL;
|
|
g_autoptr(virJSONValue) model_b = NULL;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
virJSONValue *cpu_model;
|
|
virJSONValue *cpu_props = NULL;
|
|
const char *cpu_name = "";
|
|
|
|
if (!(model_a = qemuMonitorJSONMakeCPUModel(cpu_a, true)) ||
|
|
!(model_b = qemuMonitorJSONMakeCPUModel(cpu_b, true)))
|
|
return -1;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-model-baseline",
|
|
"a:modela", &model_a,
|
|
"a:modelb", &model_b,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (qemuMonitorJSONParseCPUModelData(data, "query-cpu-model-baseline",
|
|
false, &cpu_model, &cpu_props,
|
|
&cpu_name) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONParseCPUModel(cpu_name, cpu_props, baseline);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetCPUModelComparison(qemuMonitor *mon,
|
|
virCPUDef *cpu_a,
|
|
virCPUDef *cpu_b,
|
|
char **result)
|
|
{
|
|
g_autoptr(virJSONValue) model_a = NULL;
|
|
g_autoptr(virJSONValue) model_b = NULL;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
const char *data_result;
|
|
virJSONValue *data;
|
|
|
|
if (!(model_a = qemuMonitorJSONMakeCPUModel(cpu_a, true)) ||
|
|
!(model_b = qemuMonitorJSONMakeCPUModel(cpu_b, true)))
|
|
return -1;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-cpu-model-comparison",
|
|
"a:modela", &model_a,
|
|
"a:modelb", &model_b,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (!(data_result = virJSONValueObjectGetString(data, "result"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-cpu-model-comparison reply data was missing "
|
|
"'result'"));
|
|
return -1;
|
|
}
|
|
|
|
*result = g_strdup(data_result);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONGetCommands(qemuMonitor *mon,
|
|
char ***commands)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
g_auto(GStrv) commandlist = NULL;
|
|
size_t n = 0;
|
|
size_t i;
|
|
|
|
*commands = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-commands", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(data);
|
|
|
|
/* null-terminated list */
|
|
commandlist = g_new0(char *, n + 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *child = virJSONValueArrayGet(data, i);
|
|
const char *tmp;
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-commands reply data was missing 'name'"));
|
|
return -1;
|
|
}
|
|
|
|
commandlist[i] = g_strdup(tmp);
|
|
}
|
|
|
|
*commands = g_steal_pointer(&commandlist);
|
|
return n;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetCommandLineOptionsWorker(size_t pos G_GNUC_UNUSED,
|
|
virJSONValue *item,
|
|
void *opaque)
|
|
{
|
|
const char *name = virJSONValueObjectGetString(item, "option");
|
|
g_autoptr(virJSONValue) parameters = NULL;
|
|
GHashTable *options = opaque;
|
|
|
|
if (!name ||
|
|
virJSONValueObjectRemoveKey(item, "parameters", ¶meters) <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("reply data was missing 'option' name or parameters"));
|
|
return -1;
|
|
}
|
|
|
|
g_hash_table_insert(options, g_strdup(name), parameters);
|
|
parameters = NULL;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
GHashTable *
|
|
qemuMonitorJSONGetCommandLineOptions(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(GHashTable) ret = virHashNew(virJSONValueHashFree);
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-command-line-options", NULL)))
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return NULL;
|
|
|
|
if (virJSONValueArrayForeachSteal(virJSONValueObjectGetArray(reply, "return"),
|
|
qemuMonitorJSONGetCommandLineOptionsWorker,
|
|
ret) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&ret);
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONGetKVMState(qemuMonitor *mon,
|
|
bool *enabled,
|
|
bool *present)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data = NULL;
|
|
|
|
/* Safe defaults */
|
|
*enabled = *present = false;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-kvm", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (virJSONValueObjectGetBoolean(data, "enabled", enabled) < 0 ||
|
|
virJSONValueObjectGetBoolean(data, "present", present) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-kvm replied unexpected data"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetObjectTypes(qemuMonitor *mon,
|
|
char ***types)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
g_auto(GStrv) typelist = NULL;
|
|
size_t n = 0;
|
|
size_t i;
|
|
|
|
*types = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-list-types", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(data);
|
|
|
|
/* null-terminated list */
|
|
typelist = g_new0(char *, n + 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *child = virJSONValueArrayGet(data, i);
|
|
const char *tmp;
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("qom-list-types reply data was missing 'name'"));
|
|
return -1;
|
|
}
|
|
|
|
typelist[i] = g_strdup(tmp);
|
|
}
|
|
|
|
*types = g_steal_pointer(&typelist);
|
|
return n;
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONGetObjectListPaths(qemuMonitor *mon,
|
|
const char *path,
|
|
qemuMonitorJSONListPath ***paths)
|
|
{
|
|
int ret = -1;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
qemuMonitorJSONListPath **pathlist = NULL;
|
|
size_t n = 0;
|
|
size_t i;
|
|
|
|
*paths = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-list",
|
|
"s:path", path,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
goto cleanup;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(data);
|
|
|
|
/* null-terminated list */
|
|
pathlist = g_new0(qemuMonitorJSONListPath *, n + 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *child = virJSONValueArrayGet(data, i);
|
|
const char *tmp;
|
|
qemuMonitorJSONListPath *info;
|
|
|
|
info = g_new0(qemuMonitorJSONListPath, 1);
|
|
|
|
pathlist[i] = info;
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("qom-list reply data was missing 'name'"));
|
|
goto cleanup;
|
|
}
|
|
|
|
info->name = g_strdup(tmp);
|
|
|
|
if (virJSONValueObjectHasKey(child, "type")) {
|
|
if (!(tmp = virJSONValueObjectGetString(child, "type"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("qom-list reply has malformed 'type' data"));
|
|
goto cleanup;
|
|
}
|
|
info->type = g_strdup(tmp);
|
|
}
|
|
}
|
|
|
|
ret = n;
|
|
*paths = g_steal_pointer(&pathlist);
|
|
|
|
cleanup:
|
|
if (pathlist) {
|
|
for (i = 0; i < n; i++)
|
|
qemuMonitorJSONListPathFree(pathlist[i]);
|
|
VIR_FREE(pathlist);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void qemuMonitorJSONListPathFree(qemuMonitorJSONListPath *paths)
|
|
{
|
|
if (!paths)
|
|
return;
|
|
g_free(paths->name);
|
|
g_free(paths->type);
|
|
g_free(paths);
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONGetObjectProperty(qemuMonitor *mon,
|
|
const char *path,
|
|
const char *property,
|
|
qemuMonitorJSONObjectProperty *prop)
|
|
{
|
|
int ret = -1;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
const char *tmp;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
|
|
"s:path", path,
|
|
"s:property", property,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGet(reply, "return");
|
|
|
|
switch ((qemuMonitorJSONObjectPropertyType) prop->type) {
|
|
/* Simple cases of boolean, int, long, uint, ulong, double, and string
|
|
* will receive return value as part of {"return": xxx} statement
|
|
*/
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_BOOLEAN:
|
|
ret = virJSONValueGetBoolean(data, &prop->val.b);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_INT:
|
|
ret = virJSONValueGetNumberInt(data, &prop->val.iv);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_LONG:
|
|
ret = virJSONValueGetNumberLong(data, &prop->val.l);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_UINT:
|
|
ret = virJSONValueGetNumberUint(data, &prop->val.ui);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_ULONG:
|
|
ret = virJSONValueGetNumberUlong(data, &prop->val.ul);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_DOUBLE:
|
|
ret = virJSONValueGetNumberDouble(data, &prop->val.d);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_STRING:
|
|
tmp = virJSONValueGetString(data);
|
|
if (tmp)
|
|
prop->val.str = g_strdup(tmp);
|
|
if (tmp)
|
|
ret = 0;
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("qom-get invalid object property type %d"),
|
|
prop->type);
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
if (ret == -1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("qom-get reply was missing return data"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetStringListProperty(qemuMonitor *mon,
|
|
const char *path,
|
|
const char *property,
|
|
char ***strList)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
*strList = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
|
|
"s:path", path,
|
|
"s:property", property,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
if (!(*strList = virJSONValueObjectGetStringArray(reply, "return")))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define MAKE_SET_CMD(STRING, VALUE) \
|
|
cmd = qemuMonitorJSONMakeCommand("qom-set", \
|
|
"s:path", path, \
|
|
"s:property", property, \
|
|
STRING, VALUE, \
|
|
NULL)
|
|
int qemuMonitorJSONSetObjectProperty(qemuMonitor *mon,
|
|
const char *path,
|
|
const char *property,
|
|
qemuMonitorJSONObjectProperty *prop)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
switch ((qemuMonitorJSONObjectPropertyType) prop->type) {
|
|
/* Simple cases of boolean, int, long, uint, ulong, double, and string
|
|
* will receive return value as part of {"return": xxx} statement
|
|
*/
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_BOOLEAN:
|
|
MAKE_SET_CMD("b:value", prop->val.b);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_INT:
|
|
MAKE_SET_CMD("i:value", prop->val.iv);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_LONG:
|
|
MAKE_SET_CMD("I:value", prop->val.l);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_UINT:
|
|
MAKE_SET_CMD("u:value", prop->val.ui);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_ULONG:
|
|
MAKE_SET_CMD("U:value", prop->val.ul);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_DOUBLE:
|
|
MAKE_SET_CMD("d:value", prop->val.d);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_STRING:
|
|
MAKE_SET_CMD("s:value", prop->val.str);
|
|
break;
|
|
case QEMU_MONITOR_OBJECT_PROPERTY_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("qom-set invalid object property type %d"),
|
|
prop->type);
|
|
return -1;
|
|
|
|
}
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#undef MAKE_SET_CMD
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONParsePropsList(virJSONValue *cmd,
|
|
virJSONValue *reply,
|
|
const char *type,
|
|
char ***props)
|
|
{
|
|
virJSONValue *data;
|
|
g_auto(GStrv) proplist = NULL;
|
|
size_t n = 0;
|
|
size_t count = 0;
|
|
size_t i;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(data);
|
|
|
|
/* null-terminated list */
|
|
proplist = g_new0(char *, n + 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *child = virJSONValueArrayGet(data, i);
|
|
const char *tmp;
|
|
|
|
if (type &&
|
|
STRNEQ_NULLABLE(virJSONValueObjectGetString(child, "type"), type))
|
|
continue;
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("reply data was missing 'name'"));
|
|
return -1;
|
|
}
|
|
|
|
proplist[count++] = g_strdup(tmp);
|
|
}
|
|
|
|
*props = g_steal_pointer(&proplist);
|
|
return count;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetDevicePropsWorker(size_t pos G_GNUC_UNUSED,
|
|
virJSONValue *item,
|
|
void *opaque)
|
|
{
|
|
const char *name = virJSONValueObjectGetString(item, "name");
|
|
GHashTable *devices = opaque;
|
|
|
|
if (!name) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("reply data was missing 'name'"));
|
|
return -1;
|
|
}
|
|
|
|
if (virHashAddEntry(devices, name, item) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
GHashTable *
|
|
qemuMonitorJSONGetDeviceProps(qemuMonitor *mon,
|
|
const char *device)
|
|
{
|
|
g_autoptr(GHashTable) props = virHashNew(virJSONValueHashFree);
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("device-list-properties",
|
|
"s:typename", device,
|
|
NULL)))
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return NULL;
|
|
|
|
/* return empty hash */
|
|
if (qemuMonitorJSONHasError(reply, "DeviceNotFound"))
|
|
return g_steal_pointer(&props);
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return NULL;
|
|
|
|
if (virJSONValueArrayForeachSteal(virJSONValueObjectGetArray(reply, "return"),
|
|
qemuMonitorJSONGetDevicePropsWorker,
|
|
props) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&props);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetObjectProps(qemuMonitor *mon,
|
|
const char *object,
|
|
char ***props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
*props = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-list-properties",
|
|
"s:typename", object,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONHasError(reply, "DeviceNotFound"))
|
|
return 0;
|
|
|
|
return qemuMonitorJSONParsePropsList(cmd, reply, NULL, props);
|
|
}
|
|
|
|
|
|
char *
|
|
qemuMonitorJSONGetTargetArch(qemuMonitor *mon)
|
|
{
|
|
const char *arch;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-target", NULL)))
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return NULL;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (!(arch = virJSONValueObjectGetString(data, "arch"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-target reply was missing arch data"));
|
|
return NULL;
|
|
}
|
|
|
|
return g_strdup(arch);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetMigrationCapabilities(qemuMonitor *mon,
|
|
char ***capabilities)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *caps;
|
|
g_auto(GStrv) list = NULL;
|
|
size_t i;
|
|
size_t n;
|
|
|
|
*capabilities = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-migrate-capabilities",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
caps = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(caps);
|
|
|
|
list = g_new0(char *, n + 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *cap = virJSONValueArrayGet(caps, i);
|
|
const char *name;
|
|
|
|
if (!cap || virJSONValueGetType(cap) != VIR_JSON_TYPE_OBJECT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing entry in migration capabilities list"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(name = virJSONValueObjectGetString(cap, "capability"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing migration capability name"));
|
|
return -1;
|
|
}
|
|
|
|
list[i] = g_strdup(name);
|
|
}
|
|
|
|
*capabilities = g_steal_pointer(&list);
|
|
return n;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetMigrationCapabilities(qemuMonitor *mon,
|
|
virJSONValue **caps)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("migrate-set-capabilities",
|
|
"a:capabilities", caps,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONGetGICCapabilities:
|
|
* @mon: QEMU JSON monitor
|
|
* @capabilities: where to store the GIC capabilities
|
|
*
|
|
* Use @mon to obtain information about the GIC capabilities for the
|
|
* corresponding QEMU binary, and store them in @capabilities.
|
|
*
|
|
* If the QEMU binary has no GIC capabilities, or if GIC capabilities could
|
|
* not be determined due to the lack of 'query-gic-capabilities' QMP command,
|
|
* a NULL pointer will be returned instead of an empty array.
|
|
*
|
|
* Returns: the number of GIC capabilities obtained from the monitor,
|
|
* <0 on failure
|
|
*/
|
|
int
|
|
qemuMonitorJSONGetGICCapabilities(qemuMonitor *mon,
|
|
virGICCapability **capabilities)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *caps;
|
|
g_autofree virGICCapability *list = NULL;
|
|
size_t i;
|
|
size_t n;
|
|
|
|
*capabilities = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-gic-capabilities",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
/* If the 'query-gic-capabilities' QMP command was not available
|
|
* we simply successfully return zero capabilities.
|
|
* This is the case for QEMU <2.6 and all non-ARM architectures */
|
|
if (qemuMonitorJSONHasError(reply, "CommandNotFound"))
|
|
return 0;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
caps = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(caps);
|
|
|
|
/* If the returned array was empty we have to return successfully */
|
|
if (n == 0)
|
|
return 0;
|
|
|
|
list = g_new0(virGICCapability, n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *cap = virJSONValueArrayGet(caps, i);
|
|
int version;
|
|
bool kernel;
|
|
bool emulated;
|
|
|
|
if (!cap || virJSONValueGetType(cap) != VIR_JSON_TYPE_OBJECT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing entry in GIC capabilities list"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberInt(cap, "version", &version) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing GIC version"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(cap, "kernel", &kernel) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing in-kernel GIC information"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(cap, "emulated", &emulated) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing emulated GIC information"));
|
|
return -1;
|
|
}
|
|
|
|
list[i].version = version;
|
|
if (kernel)
|
|
list[i].implementation |= VIR_GIC_IMPLEMENTATION_KERNEL;
|
|
if (emulated)
|
|
list[i].implementation |= VIR_GIC_IMPLEMENTATION_EMULATED;
|
|
}
|
|
|
|
*capabilities = g_steal_pointer(&list);
|
|
return n;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONGetSEVCapabilities:
|
|
* @mon: qemu monitor object
|
|
* @capabilities: pointer to pointer to a SEV capability structure to be filled
|
|
*
|
|
* This function queries and fills in AMD's SEV platform-specific data.
|
|
* Note that from QEMU's POV both -object sev-guest and query-sev-capabilities
|
|
* can be present even if SEV is not available, which basically leaves us with
|
|
* checking for JSON "GenericError" in order to differentiate between
|
|
* compiled-in support and actual SEV support on the platform.
|
|
*
|
|
* Returns -1 on error, 0 if SEV is not supported, and 1 if SEV is supported on
|
|
* the platform.
|
|
*/
|
|
int
|
|
qemuMonitorJSONGetSEVCapabilities(qemuMonitor *mon,
|
|
virSEVCapability **capabilities)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *caps;
|
|
const char *pdh = NULL;
|
|
const char *cert_chain = NULL;
|
|
const char *cpu0_id = NULL;
|
|
unsigned int cbitpos;
|
|
unsigned int reduced_phys_bits;
|
|
g_autoptr(virSEVCapability) capability = NULL;
|
|
|
|
*capabilities = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-sev-capabilities",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
/* QEMU has only compiled-in support of SEV */
|
|
if (qemuMonitorJSONHasError(reply, "GenericError"))
|
|
return 0;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
caps = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (virJSONValueObjectGetNumberUint(caps, "cbitpos", &cbitpos) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-sev-capabilities reply was missing"
|
|
" 'cbitpos' field"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUint(caps, "reduced-phys-bits",
|
|
&reduced_phys_bits) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-sev-capabilities reply was missing"
|
|
" 'reduced-phys-bits' field"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(pdh = virJSONValueObjectGetString(caps, "pdh"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-sev-capabilities reply was missing"
|
|
" 'pdh' field"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(cert_chain = virJSONValueObjectGetString(caps, "cert-chain"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-sev-capabilities reply was missing"
|
|
" 'cert-chain' field"));
|
|
return -1;
|
|
}
|
|
|
|
capability = g_new0(virSEVCapability, 1);
|
|
|
|
capability->pdh = g_strdup(pdh);
|
|
|
|
capability->cert_chain = g_strdup(cert_chain);
|
|
|
|
cpu0_id = virJSONValueObjectGetString(caps, "cpu0-id");
|
|
if (cpu0_id != NULL) {
|
|
capability->cpu0_id = g_strdup(cpu0_id);
|
|
}
|
|
|
|
capability->cbitpos = cbitpos;
|
|
capability->reduced_phys_bits = reduced_phys_bits;
|
|
*capabilities = g_steal_pointer(&capability);
|
|
return 1;
|
|
}
|
|
|
|
static virJSONValue *
|
|
qemuMonitorJSONBuildInetSocketAddress(const char *host,
|
|
const char *port)
|
|
{
|
|
g_autoptr(virJSONValue) addr = NULL;
|
|
g_autoptr(virJSONValue) data = NULL;
|
|
|
|
if (virJSONValueObjectAdd(&data,
|
|
"s:host", host,
|
|
"s:port", port,
|
|
NULL) < 0)
|
|
return NULL;
|
|
|
|
if (virJSONValueObjectAdd(&addr,
|
|
"s:type", "inet",
|
|
"a:data", &data,
|
|
NULL) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&addr);
|
|
}
|
|
|
|
static virJSONValue *
|
|
qemuMonitorJSONBuildUnixSocketAddress(const char *path)
|
|
{
|
|
g_autoptr(virJSONValue) addr = NULL;
|
|
g_autoptr(virJSONValue) data = NULL;
|
|
|
|
if (virJSONValueObjectAdd(&data, "s:path", path, NULL) < 0)
|
|
return NULL;
|
|
|
|
if (virJSONValueObjectAdd(&addr,
|
|
"s:type", "unix",
|
|
"a:data", &data, NULL) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&addr);
|
|
}
|
|
|
|
|
|
static virJSONValue *
|
|
qemuMonitorJSONBuildFDSocketAddress(const char *fdname)
|
|
{
|
|
g_autoptr(virJSONValue) addr = NULL;
|
|
g_autoptr(virJSONValue) data = NULL;
|
|
|
|
if (virJSONValueObjectAdd(&data, "s:str", fdname, NULL) < 0)
|
|
return NULL;
|
|
|
|
if (virJSONValueObjectAdd(&addr,
|
|
"s:type", "fd",
|
|
"a:data", &data, NULL) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&addr);
|
|
}
|
|
int
|
|
qemuMonitorJSONNBDServerStart(qemuMonitor *mon,
|
|
const virStorageNetHostDef *server,
|
|
const char *tls_alias)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
g_autoptr(virJSONValue) addr = NULL;
|
|
g_autofree char *port_str = NULL;
|
|
|
|
switch ((virStorageNetHostTransport)server->transport) {
|
|
case VIR_STORAGE_NET_HOST_TRANS_TCP:
|
|
port_str = g_strdup_printf("%u", server->port);
|
|
addr = qemuMonitorJSONBuildInetSocketAddress(server->name, port_str);
|
|
break;
|
|
case VIR_STORAGE_NET_HOST_TRANS_UNIX:
|
|
addr = qemuMonitorJSONBuildUnixSocketAddress(server->socket);
|
|
break;
|
|
case VIR_STORAGE_NET_HOST_TRANS_RDMA:
|
|
case VIR_STORAGE_NET_HOST_TRANS_LAST:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("invalid server address"));
|
|
return -1;
|
|
}
|
|
if (!addr)
|
|
return -1;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("nbd-server-start",
|
|
"a:addr", &addr,
|
|
"S:tls-creds", tls_alias,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuMonitorJSONNBDServerAdd(qemuMonitor *mon,
|
|
const char *deviceID,
|
|
const char *export,
|
|
bool writable,
|
|
const char *bitmap)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
/* Note: bitmap must be NULL if QEMU_CAPS_NBD_BITMAP is lacking */
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("nbd-server-add",
|
|
"s:device", deviceID,
|
|
"S:name", export,
|
|
"b:writable", writable,
|
|
"S:bitmap", bitmap,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuMonitorJSONNBDServerStop(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("nbd-server-stop",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockExportAdd(qemuMonitor *mon,
|
|
virJSONValue **props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("block-export-add", props)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetStringArray(qemuMonitor *mon,
|
|
const char *qmpCmd,
|
|
char ***array)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
*array = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand(qmpCmd, NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONHasError(reply, "CommandNotFound"))
|
|
return 0;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
if (!(*array = virJSONValueObjectGetStringArray(reply, "return")))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemuMonitorJSONGetTPMModels(qemuMonitor *mon,
|
|
char ***tpmmodels)
|
|
{
|
|
return qemuMonitorJSONGetStringArray(mon, "query-tpm-models", tpmmodels);
|
|
}
|
|
|
|
|
|
int qemuMonitorJSONGetTPMTypes(qemuMonitor *mon,
|
|
char ***tpmtypes)
|
|
{
|
|
return qemuMonitorJSONGetStringArray(mon, "query-tpm-types", tpmtypes);
|
|
}
|
|
|
|
|
|
static virJSONValue *
|
|
qemuMonitorJSONAttachCharDevGetProps(const char *chrID,
|
|
const virDomainChrSourceDef *chr)
|
|
{
|
|
qemuDomainChrSourcePrivate *chrSourcePriv = QEMU_DOMAIN_CHR_SOURCE_PRIVATE(chr);
|
|
g_autoptr(virJSONValue) props = NULL;
|
|
g_autoptr(virJSONValue) backend = NULL;
|
|
g_autoptr(virJSONValue) backendData = virJSONValueNewObject();
|
|
const char *backendType = NULL;
|
|
|
|
switch ((virDomainChrType)chr->type) {
|
|
case VIR_DOMAIN_CHR_TYPE_NULL:
|
|
case VIR_DOMAIN_CHR_TYPE_VC:
|
|
case VIR_DOMAIN_CHR_TYPE_PTY:
|
|
backendType = virDomainChrTypeToString(chr->type);
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_FILE: {
|
|
const char *path = chr->data.file.path;
|
|
virTristateSwitch append = chr->data.file.append;
|
|
backendType = "file";
|
|
|
|
if (chrSourcePriv->sourcefd) {
|
|
path = qemuFDPassGetPath(chrSourcePriv->sourcefd);
|
|
append = VIR_TRISTATE_SWITCH_ON;
|
|
}
|
|
|
|
if (virJSONValueObjectAdd(&backendData,
|
|
"s:out", path,
|
|
"T:append", append,
|
|
NULL) < 0)
|
|
return NULL;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_DEV:
|
|
if (STRPREFIX(chrID, "parallel"))
|
|
backendType = "parallel";
|
|
else
|
|
backendType = "serial";
|
|
|
|
if (virJSONValueObjectAdd(&backendData,
|
|
"s:device", chr->data.file.path,
|
|
NULL) < 0)
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
|
case VIR_DOMAIN_CHR_TYPE_TCP: {
|
|
const char *tlsalias = NULL;
|
|
g_autoptr(virJSONValue) addr = NULL;
|
|
virTristateBool waitval = VIR_TRISTATE_BOOL_ABSENT;
|
|
virTristateBool telnet = VIR_TRISTATE_BOOL_ABSENT;
|
|
bool server = false;
|
|
int reconnect = -1;
|
|
|
|
backendType = "socket";
|
|
|
|
if (chr->type == VIR_DOMAIN_CHR_TYPE_TCP) {
|
|
telnet = virTristateBoolFromBool(chr->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET);
|
|
|
|
if (chr->data.tcp.listen) {
|
|
server = true;
|
|
waitval = VIR_TRISTATE_BOOL_NO;
|
|
}
|
|
|
|
tlsalias = chrSourcePriv->tlsCredsAlias;
|
|
|
|
if (!(addr = qemuMonitorJSONBuildInetSocketAddress(chr->data.tcp.host,
|
|
chr->data.tcp.service)))
|
|
return NULL;
|
|
|
|
if (chr->data.tcp.reconnect.enabled == VIR_TRISTATE_BOOL_YES)
|
|
reconnect = chr->data.tcp.reconnect.timeout;
|
|
else if (chr->data.tcp.reconnect.enabled == VIR_TRISTATE_BOOL_NO)
|
|
reconnect = 0;
|
|
} else {
|
|
if (chr->data.nix.listen) {
|
|
server = true;
|
|
waitval = VIR_TRISTATE_BOOL_NO;
|
|
}
|
|
|
|
if (chrSourcePriv->directfd) {
|
|
if (!(addr = qemuMonitorJSONBuildFDSocketAddress(qemuFDPassDirectGetPath(chrSourcePriv->directfd))))
|
|
return NULL;
|
|
} else {
|
|
if (!(addr = qemuMonitorJSONBuildUnixSocketAddress(chr->data.nix.path)))
|
|
return NULL;
|
|
|
|
if (chr->data.nix.reconnect.enabled == VIR_TRISTATE_BOOL_YES)
|
|
reconnect = chr->data.tcp.reconnect.timeout;
|
|
else if (chr->data.nix.reconnect.enabled == VIR_TRISTATE_BOOL_NO)
|
|
reconnect = 0;
|
|
}
|
|
}
|
|
|
|
if (virJSONValueObjectAdd(&backendData,
|
|
"a:addr", &addr,
|
|
"T:wait", waitval,
|
|
"T:telnet", telnet,
|
|
"b:server", server,
|
|
"S:tls-creds", tlsalias,
|
|
"k:reconnect", reconnect,
|
|
NULL) < 0)
|
|
return NULL;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UDP: {
|
|
g_autoptr(virJSONValue) local = NULL;
|
|
g_autoptr(virJSONValue) remote = NULL;
|
|
|
|
backendType = "udp";
|
|
|
|
if (!(remote = qemuMonitorJSONBuildInetSocketAddress(NULLSTR_EMPTY(chr->data.udp.connectHost),
|
|
chr->data.udp.connectService)))
|
|
return NULL;
|
|
|
|
if (chr->data.udp.bindHost || chr->data.udp.bindService) {
|
|
if (!(local = qemuMonitorJSONBuildInetSocketAddress(NULLSTR_EMPTY(chr->data.udp.bindHost),
|
|
NULLSTR_EMPTY(chr->data.udp.bindService))))
|
|
return NULL;
|
|
}
|
|
|
|
if (virJSONValueObjectAdd(&backendData,
|
|
"a:remote", &remote,
|
|
"A:local", &local,
|
|
NULL) < 0)
|
|
return NULL;
|
|
}
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
|
|
backendType = "spicevmc";
|
|
|
|
if (virJSONValueObjectAdd(&backendData,
|
|
"s:type", virDomainChrSpicevmcTypeToString(chr->data.spicevmc),
|
|
NULL) < 0)
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT: {
|
|
virTristateBool mouse = VIR_TRISTATE_BOOL_ABSENT;
|
|
switch (chr->data.qemuVdagent.mouse) {
|
|
case VIR_DOMAIN_MOUSE_MODE_CLIENT:
|
|
mouse = VIR_TRISTATE_BOOL_YES;
|
|
break;
|
|
case VIR_DOMAIN_MOUSE_MODE_SERVER:
|
|
mouse = VIR_TRISTATE_BOOL_NO;
|
|
break;
|
|
case VIR_DOMAIN_MOUSE_MODE_DEFAULT:
|
|
break;
|
|
case VIR_DOMAIN_MOUSE_MODE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainMouseMode,
|
|
chr->data.qemuVdagent.mouse);
|
|
return NULL;
|
|
}
|
|
backendType = "qemu-vdagent";
|
|
|
|
if (virJSONValueObjectAdd(&backendData,
|
|
"T:clipboard", chr->data.qemuVdagent.clipboard,
|
|
"T:mouse", mouse,
|
|
NULL) < 0)
|
|
return NULL;
|
|
break;
|
|
}
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_DBUS:
|
|
backendType = "dbus";
|
|
|
|
if (virJSONValueObjectAdd(&backendData,
|
|
"s:name", chr->data.dbus.channel,
|
|
NULL) < 0)
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
|
|
case VIR_DOMAIN_CHR_TYPE_PIPE:
|
|
case VIR_DOMAIN_CHR_TYPE_STDIO:
|
|
case VIR_DOMAIN_CHR_TYPE_NMDM:
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
_("Hotplug unsupported for char device type '%s'"),
|
|
virDomainChrTypeToString(chr->type));
|
|
return NULL;
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_LAST:
|
|
default:
|
|
virReportEnumRangeError(virDomainChrType, chr->type);
|
|
return NULL;
|
|
}
|
|
|
|
if (chr->logfile) {
|
|
const char *path = chr->logfile;
|
|
virTristateSwitch append = chr->logappend;
|
|
|
|
if (chrSourcePriv->logfd) {
|
|
path = qemuFDPassGetPath(chrSourcePriv->logfd);
|
|
append = VIR_TRISTATE_SWITCH_ON;
|
|
}
|
|
|
|
if (virJSONValueObjectAdd(&backendData,
|
|
"s:logfile", path,
|
|
"T:logappend", append,
|
|
NULL) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
if (virJSONValueObjectAdd(&backend,
|
|
"s:type", backendType,
|
|
"A:data", &backendData,
|
|
NULL) < 0)
|
|
return NULL;
|
|
|
|
if (virJSONValueObjectAdd(&props,
|
|
"s:id", chrID,
|
|
"a:backend", &backend,
|
|
NULL) < 0)
|
|
return NULL;
|
|
|
|
return g_steal_pointer(&props);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONAttachCharDev(qemuMonitor *mon,
|
|
const char *chrID,
|
|
virDomainChrSourceDef *chr)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
g_autoptr(virJSONValue) props = NULL;
|
|
|
|
if (!(props = qemuMonitorJSONAttachCharDevGetProps(chrID, chr)))
|
|
return -1;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("chardev-add", &props)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (chr->type == VIR_DOMAIN_CHR_TYPE_PTY) {
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
} else {
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (chr->type == VIR_DOMAIN_CHR_TYPE_PTY) {
|
|
virJSONValue *data = virJSONValueObjectGetObject(reply, "return");
|
|
const char *path;
|
|
|
|
if (!(path = virJSONValueObjectGetString(data, "pty"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("chardev-add reply was missing pty path"));
|
|
return -1;
|
|
}
|
|
|
|
chr->data.file.path = g_strdup(path);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
qemuMonitorJSONDetachCharDev(qemuMonitor *mon,
|
|
const char *chrID)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("chardev-remove",
|
|
"s:id", chrID,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetDeviceAliases(qemuMonitor *mon,
|
|
char ***aliases)
|
|
{
|
|
qemuMonitorJSONListPath **paths = NULL;
|
|
char **alias;
|
|
int ret = -1;
|
|
size_t i;
|
|
int n;
|
|
|
|
*aliases = NULL;
|
|
|
|
n = qemuMonitorJSONGetObjectListPaths(mon, "/machine/peripheral", &paths);
|
|
if (n < 0)
|
|
return -1;
|
|
|
|
*aliases = g_new0(char *, n + 1);
|
|
|
|
alias = *aliases;
|
|
for (i = 0; i < n; i++) {
|
|
if (STRPREFIX(paths[i]->type, "child<")) {
|
|
*alias = g_steal_pointer(&paths[i]->name);
|
|
alias++;
|
|
}
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
for (i = 0; i < n; i++)
|
|
qemuMonitorJSONListPathFree(paths[i]);
|
|
VIR_FREE(paths);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONParseCPUx86FeatureWord(virJSONValue *data,
|
|
virCPUx86CPUID *cpuid)
|
|
{
|
|
const char *reg;
|
|
unsigned long long eax_in;
|
|
unsigned long long ecx_in = 0;
|
|
unsigned long long features;
|
|
|
|
memset(cpuid, 0, sizeof(*cpuid));
|
|
|
|
if (!(reg = virJSONValueObjectGetString(data, "cpuid-register"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing cpuid-register in CPU data"));
|
|
return -1;
|
|
}
|
|
if (virJSONValueObjectGetNumberUlong(data, "cpuid-input-eax", &eax_in) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing or invalid cpuid-input-eax in CPU data"));
|
|
return -1;
|
|
}
|
|
ignore_value(virJSONValueObjectGetNumberUlong(data, "cpuid-input-ecx",
|
|
&ecx_in));
|
|
if (virJSONValueObjectGetNumberUlong(data, "features", &features) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing or invalid features in CPU data"));
|
|
return -1;
|
|
}
|
|
|
|
cpuid->eax_in = eax_in;
|
|
cpuid->ecx_in = ecx_in;
|
|
if (STREQ(reg, "EAX")) {
|
|
cpuid->eax = features;
|
|
} else if (STREQ(reg, "EBX")) {
|
|
cpuid->ebx = features;
|
|
} else if (STREQ(reg, "ECX")) {
|
|
cpuid->ecx = features;
|
|
} else if (STREQ(reg, "EDX")) {
|
|
cpuid->edx = features;
|
|
} else {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown CPU register '%s'"), reg);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virCPUData *
|
|
qemuMonitorJSONParseCPUx86Features(virJSONValue *data)
|
|
{
|
|
g_autoptr(virCPUData) cpudata = NULL;
|
|
virCPUx86DataItem item = { 0 };
|
|
size_t i;
|
|
|
|
if (!(cpudata = virCPUDataNew(VIR_ARCH_X86_64)))
|
|
return NULL;
|
|
|
|
item.type = VIR_CPU_X86_DATA_CPUID;
|
|
for (i = 0; i < virJSONValueArraySize(data); i++) {
|
|
if (qemuMonitorJSONParseCPUx86FeatureWord(virJSONValueArrayGet(data, i),
|
|
&item.data.cpuid) < 0 ||
|
|
virCPUx86DataAdd(cpudata, &item) < 0)
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer(&cpudata);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetCPUx86Data(qemuMonitor *mon,
|
|
const char *cpuQOMPath,
|
|
const char *property,
|
|
virCPUData **cpudata)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
|
|
"s:path", cpuQOMPath,
|
|
"s:property", property,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
if (!(*cpudata = qemuMonitorJSONParseCPUx86Features(data)))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns -1 on error, 0 if QEMU does not support reporting CPUID features
|
|
* of a guest CPU, and 1 if the feature is supported.
|
|
*/
|
|
static int
|
|
qemuMonitorJSONCheckCPUx86(qemuMonitor *mon,
|
|
const char *cpuQOMPath)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
size_t i;
|
|
size_t n;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-list",
|
|
"s:path", cpuQOMPath,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if ((data = virJSONValueObjectGet(reply, "error"))) {
|
|
const char *klass = virJSONValueObjectGetString(data, "class");
|
|
if (STREQ_NULLABLE(klass, "DeviceNotFound") ||
|
|
STREQ_NULLABLE(klass, "CommandNotFound")) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(data);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *element = virJSONValueArrayGet(data, i);
|
|
if (STREQ_NULLABLE(virJSONValueObjectGetString(element, "name"),
|
|
"feature-words"))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONGetGuestCPUx86:
|
|
* @mon: Pointer to the monitor
|
|
* @cpuQOMPath: QOM path of a CPU to probe
|
|
* @data: returns the cpu data of the guest
|
|
* @disabled: returns the CPU data for features which were disabled by QEMU
|
|
*
|
|
* Retrieve the definition of the guest CPU from a running qemu instance.
|
|
*
|
|
* Returns 0 on success, -2 if guest doesn't support this feature,
|
|
* -1 on other errors.
|
|
*/
|
|
int
|
|
qemuMonitorJSONGetGuestCPUx86(qemuMonitor *mon,
|
|
const char *cpuQOMPath,
|
|
virCPUData **data,
|
|
virCPUData **disabled)
|
|
{
|
|
g_autoptr(virCPUData) cpuEnabled = NULL;
|
|
g_autoptr(virCPUData) cpuDisabled = NULL;
|
|
int rc;
|
|
|
|
if ((rc = qemuMonitorJSONCheckCPUx86(mon, cpuQOMPath)) < 0)
|
|
return -1;
|
|
else if (!rc)
|
|
return -2;
|
|
|
|
if (qemuMonitorJSONGetCPUx86Data(mon, cpuQOMPath, "feature-words",
|
|
&cpuEnabled) < 0)
|
|
return -1;
|
|
|
|
if (disabled &&
|
|
qemuMonitorJSONGetCPUx86Data(mon, cpuQOMPath, "filtered-features",
|
|
&cpuDisabled) < 0)
|
|
return -1;
|
|
|
|
*data = g_steal_pointer(&cpuEnabled);
|
|
if (disabled)
|
|
*disabled = g_steal_pointer(&cpuDisabled);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetCPUProperties(qemuMonitor *mon,
|
|
const char *cpuQOMPath,
|
|
char ***props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
*props = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-list",
|
|
"s:path", cpuQOMPath,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONHasError(reply, "DeviceNotFound"))
|
|
return 0;
|
|
|
|
return qemuMonitorJSONParsePropsList(cmd, reply, "bool", props);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetCPUData(qemuMonitor *mon,
|
|
const char *cpuQOMPath,
|
|
qemuMonitorCPUFeatureTranslationCallback translate,
|
|
void *opaque,
|
|
virCPUData *data)
|
|
{
|
|
qemuMonitorJSONObjectProperty prop = { .type = QEMU_MONITOR_OBJECT_PROPERTY_BOOLEAN };
|
|
g_auto(GStrv) props = NULL;
|
|
char **p;
|
|
|
|
if (qemuMonitorJSONGetCPUProperties(mon, cpuQOMPath, &props) < 0)
|
|
return -1;
|
|
|
|
for (p = props; p && *p; p++) {
|
|
const char *name = *p;
|
|
|
|
if (qemuMonitorJSONGetObjectProperty(mon, cpuQOMPath, name, &prop) < 0)
|
|
return -1;
|
|
|
|
if (!prop.val.b)
|
|
continue;
|
|
|
|
if (translate)
|
|
name = translate(name, opaque);
|
|
|
|
if (virCPUDataAddFeature(data, name) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONGetCPUDataDisabled(qemuMonitor *mon,
|
|
const char *cpuQOMPath,
|
|
qemuMonitorCPUFeatureTranslationCallback translate,
|
|
void *opaque,
|
|
virCPUData *data)
|
|
{
|
|
g_auto(GStrv) props = NULL;
|
|
char **p;
|
|
|
|
if (qemuMonitorJSONGetStringListProperty(mon, cpuQOMPath,
|
|
"unavailable-features", &props) < 0)
|
|
return -1;
|
|
|
|
for (p = props; p && *p; p++) {
|
|
const char *name = *p;
|
|
|
|
if (translate)
|
|
name = translate(name, opaque);
|
|
|
|
if (virCPUDataAddFeature(data, name) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* qemuMonitorJSONGetGuestCPU:
|
|
* @mon: Pointer to the monitor
|
|
* @arch: CPU architecture
|
|
* @cpuQOMPath: QOM path of a CPU to probe
|
|
* @translate: callback for translating CPU feature names from QEMU to libvirt
|
|
* @opaque: data for @translate callback
|
|
* @enabled: returns the CPU data for all enabled features
|
|
* @disabled: returns the CPU data for features which we asked for
|
|
* (either explicitly or via a named CPU model) but QEMU disabled them
|
|
*
|
|
* Retrieve the definition of the guest CPU from a running QEMU instance.
|
|
*
|
|
* Returns 0 on success, -1 on error.
|
|
*/
|
|
int
|
|
qemuMonitorJSONGetGuestCPU(qemuMonitor *mon,
|
|
virArch arch,
|
|
const char *cpuQOMPath,
|
|
qemuMonitorCPUFeatureTranslationCallback translate,
|
|
void *opaque,
|
|
virCPUData **enabled,
|
|
virCPUData **disabled)
|
|
{
|
|
g_autoptr(virCPUData) cpuEnabled = NULL;
|
|
g_autoptr(virCPUData) cpuDisabled = NULL;
|
|
|
|
if (!(cpuEnabled = virCPUDataNew(arch)) ||
|
|
!(cpuDisabled = virCPUDataNew(arch)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONGetCPUData(mon, cpuQOMPath, translate, opaque, cpuEnabled) < 0)
|
|
return -1;
|
|
|
|
if (disabled &&
|
|
qemuMonitorJSONGetCPUDataDisabled(mon, cpuQOMPath, translate, opaque, cpuDisabled) < 0)
|
|
return -1;
|
|
|
|
*enabled = g_steal_pointer(&cpuEnabled);
|
|
if (disabled)
|
|
*disabled = g_steal_pointer(&cpuDisabled);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONRTCResetReinjection(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("rtc-reset-reinjection",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Query and parse returned array of data such as:
|
|
*
|
|
* {u'return': [{u'id': u'iothread1', u'thread-id': 30992}, \
|
|
* {u'id': u'iothread2', u'thread-id': 30993}]}
|
|
*/
|
|
int
|
|
qemuMonitorJSONGetIOThreads(qemuMonitor *mon,
|
|
qemuMonitorIOThreadInfo ***iothreads,
|
|
int *niothreads)
|
|
{
|
|
int ret = -1;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
qemuMonitorIOThreadInfo **infolist = NULL;
|
|
size_t n = 0;
|
|
size_t i;
|
|
|
|
*iothreads = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-iothreads", NULL)))
|
|
return ret;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
goto cleanup;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
n = virJSONValueArraySize(data);
|
|
|
|
/* null-terminated list */
|
|
infolist = g_new0(qemuMonitorIOThreadInfo *, n + 1);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
virJSONValue *child = virJSONValueArrayGet(data, i);
|
|
const char *tmp;
|
|
qemuMonitorIOThreadInfo *info;
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(child, "id"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-iothreads reply data was missing 'id'"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!STRPREFIX(tmp, "iothread"))
|
|
continue;
|
|
|
|
info = g_new0(qemuMonitorIOThreadInfo, 1);
|
|
|
|
infolist[i] = info;
|
|
|
|
if (virStrToLong_ui(tmp + strlen("iothread"),
|
|
NULL, 10, &info->iothread_id) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("failed to find iothread id for '%s'"),
|
|
tmp);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberInt(child, "thread-id",
|
|
&info->thread_id) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-iothreads reply has malformed "
|
|
"'thread-id' data"));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Fetch poll values (since QEMU 2.9 ) if available. QEMU
|
|
* stores these values as int64_t's; however, the qapi type
|
|
* is an int. The qapi/misc.json also mis-describes the grow
|
|
* and shrink values as pure add/remove values. The source
|
|
* util/aio-posix.c function aio_poll uses them as a factor
|
|
* or divisor in it's calculation. We will fetch and store
|
|
* them as defined in our structures. */
|
|
if (virJSONValueObjectGetNumberUlong(child, "poll-max-ns",
|
|
&info->poll_max_ns) == 0 &&
|
|
virJSONValueObjectGetNumberUint(child, "poll-grow",
|
|
&info->poll_grow) == 0 &&
|
|
virJSONValueObjectGetNumberUint(child, "poll-shrink",
|
|
&info->poll_shrink) == 0)
|
|
info->poll_valid = true;
|
|
}
|
|
|
|
*niothreads = n;
|
|
*iothreads = g_steal_pointer(&infolist);
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (infolist) {
|
|
for (i = 0; i < n; i++)
|
|
VIR_FREE(infolist[i]);
|
|
VIR_FREE(infolist);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetIOThread(qemuMonitor *mon,
|
|
qemuMonitorIOThreadInfo *iothreadInfo)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
qemuMonitorJSONObjectProperty prop;
|
|
|
|
path = g_strdup_printf("/objects/iothread%u", iothreadInfo->iothread_id);
|
|
|
|
#define VIR_IOTHREAD_SET_PROP(propName, propVal) \
|
|
if (iothreadInfo->set_##propVal) { \
|
|
memset(&prop, 0, sizeof(qemuMonitorJSONObjectProperty)); \
|
|
prop.type = QEMU_MONITOR_OBJECT_PROPERTY_INT; \
|
|
prop.val.iv = iothreadInfo->propVal; \
|
|
if (qemuMonitorJSONSetObjectProperty(mon, path, propName, &prop) < 0) \
|
|
return -1; \
|
|
}
|
|
|
|
VIR_IOTHREAD_SET_PROP("poll-max-ns", poll_max_ns);
|
|
VIR_IOTHREAD_SET_PROP("poll-grow", poll_grow);
|
|
VIR_IOTHREAD_SET_PROP("poll-shrink", poll_shrink);
|
|
VIR_IOTHREAD_SET_PROP("thread-pool-min", thread_pool_min);
|
|
VIR_IOTHREAD_SET_PROP("thread-pool-max", thread_pool_max);
|
|
|
|
#undef VIR_IOTHREAD_SET_PROP
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetMemoryDeviceInfo(qemuMonitor *mon,
|
|
GHashTable *info)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data = NULL;
|
|
size_t i;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-memory-devices", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
|
|
for (i = 0; i < virJSONValueArraySize(data); i++) {
|
|
virJSONValue *elem = virJSONValueArrayGet(data, i);
|
|
g_autofree qemuMonitorMemoryDeviceInfo *meminfo = NULL;
|
|
virJSONValue *dimminfo;
|
|
const char *devalias;
|
|
const char *type;
|
|
|
|
if (!(type = virJSONValueObjectGetString(elem, "type"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-memory-devices reply data doesn't contain "
|
|
"enum type discriminator"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(dimminfo = virJSONValueObjectGetObject(elem, "data"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-memory-devices reply data doesn't "
|
|
"contain enum data"));
|
|
return -1;
|
|
}
|
|
|
|
/* While 'id' attribute is marked as optional in QEMU's QAPI
|
|
* specification, Libvirt always sets it. Thus we can fail if not
|
|
* present. */
|
|
if (!(devalias = virJSONValueObjectGetString(dimminfo, "id"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("dimm memory info data is missing 'id'"));
|
|
return -1;
|
|
}
|
|
|
|
meminfo = g_new0(qemuMonitorMemoryDeviceInfo, 1);
|
|
|
|
/* dimm memory devices */
|
|
if (STREQ(type, "dimm") || STREQ(type, "nvdimm")) {
|
|
if (virJSONValueObjectGetNumberUlong(dimminfo, "addr",
|
|
&meminfo->address) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed/missing addr in dimm memory info"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUint(dimminfo, "slot",
|
|
&meminfo->slot) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed/missing slot in dimm memory info"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(dimminfo, "hotplugged",
|
|
&meminfo->hotplugged) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed/missing hotplugged in dimm memory info"));
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (virJSONValueObjectGetBoolean(dimminfo, "hotpluggable",
|
|
&meminfo->hotpluggable) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed/missing hotpluggable in dimm memory info"));
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (STREQ(type, "virtio-mem")) {
|
|
if (virJSONValueObjectGetNumberUlong(dimminfo, "size",
|
|
&meminfo->size) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed/missing size in virtio memory info"));
|
|
return -1;
|
|
}
|
|
} else {
|
|
/* type not handled yet */
|
|
continue;
|
|
}
|
|
|
|
if (virHashAddEntry(info, devalias, meminfo) < 0)
|
|
return -1;
|
|
|
|
meminfo = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Search for a QOM object link by alias and name.
|
|
*
|
|
* For @alias and @name, this function tries to find QOM object named @name
|
|
* with id @alias in /machine/peripheral.
|
|
*
|
|
* Returns:
|
|
* 0 - Found
|
|
* -1 - Error - bail out
|
|
* -2 - Not found
|
|
*/
|
|
static int
|
|
qemuMonitorJSONFindObjectPathByAlias(qemuMonitor *mon,
|
|
const char *name,
|
|
const char *alias,
|
|
char **path)
|
|
{
|
|
qemuMonitorJSONListPath **paths = NULL;
|
|
g_autofree char *child = NULL;
|
|
int npaths;
|
|
int ret = -1;
|
|
size_t i;
|
|
|
|
npaths = qemuMonitorJSONGetObjectListPaths(mon, "/machine/peripheral", &paths);
|
|
if (npaths < 0)
|
|
return -1;
|
|
|
|
child = g_strdup_printf("child<%s>", name);
|
|
|
|
for (i = 0; i < npaths; i++) {
|
|
if (STREQ(paths[i]->name, alias) && STREQ(paths[i]->type, child)) {
|
|
*path = g_strdup_printf("/machine/peripheral/%s", alias);
|
|
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ret = -2;
|
|
|
|
cleanup:
|
|
for (i = 0; i < npaths; i++)
|
|
qemuMonitorJSONListPathFree(paths[i]);
|
|
VIR_FREE(paths);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Recursively search for a QOM object link only by name.
|
|
*
|
|
* For @name, this function finds the first QOM object
|
|
* named @name, recursively going through all the "child<>"
|
|
* entries, starting from @curpath.
|
|
*
|
|
* Returns:
|
|
* 0 - Found
|
|
* -1 - Error - bail out
|
|
* -2 - Not found
|
|
*/
|
|
static int
|
|
qemuMonitorJSONFindObjectPathByName(qemuMonitor *mon,
|
|
const char *curpath,
|
|
const char *name,
|
|
char **path)
|
|
{
|
|
ssize_t i, npaths = 0;
|
|
int ret = -2;
|
|
qemuMonitorJSONListPath **paths = NULL;
|
|
|
|
VIR_DEBUG("Searching for '%s' Object Path starting at '%s'", name, curpath);
|
|
|
|
npaths = qemuMonitorJSONGetObjectListPaths(mon, curpath, &paths);
|
|
if (npaths < 0)
|
|
goto cleanup;
|
|
|
|
for (i = 0; i < npaths && ret == -2; i++) {
|
|
|
|
if (STREQ_NULLABLE(paths[i]->type, name)) {
|
|
VIR_DEBUG("Path to '%s' is '%s/%s'", name, curpath, paths[i]->name);
|
|
ret = 0;
|
|
*path = g_strdup_printf("%s/%s", curpath, paths[i]->name);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Type entries that begin with "child<" are a branch that can be
|
|
* traversed looking for more entries
|
|
*/
|
|
if (paths[i]->type && STRPREFIX(paths[i]->type, "child<")) {
|
|
g_autofree char *nextpath = g_strdup_printf("%s/%s", curpath, paths[i]->name);
|
|
|
|
ret = qemuMonitorJSONFindObjectPathByName(mon, nextpath, name, path);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
for (i = 0; i < npaths; i++)
|
|
qemuMonitorJSONListPathFree(paths[i]);
|
|
VIR_FREE(paths);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Recursively search for a QOM object link.
|
|
*
|
|
* For @name and @alias, this function finds the first QOM object.
|
|
* The search is done at first by @alias and @name and if nothing was found
|
|
* it continues recursively only with @name.
|
|
*
|
|
* Returns:
|
|
* 0 - Found
|
|
* -1 - Error
|
|
* -2 - Not found
|
|
*/
|
|
int
|
|
qemuMonitorJSONFindLinkPath(qemuMonitor *mon,
|
|
const char *name,
|
|
const char *alias,
|
|
char **path)
|
|
{
|
|
g_autofree char *linkname = NULL;
|
|
int ret = -1;
|
|
|
|
if (alias) {
|
|
ret = qemuMonitorJSONFindObjectPathByAlias(mon, name, alias, path);
|
|
if (ret == -1 || ret == 0)
|
|
return ret;
|
|
}
|
|
|
|
linkname = g_strdup_printf("link<%s>", name);
|
|
|
|
ret = qemuMonitorJSONFindObjectPathByName(mon, "/", linkname, path);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONMigrateIncoming(qemuMonitor *mon,
|
|
const char *uri)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("migrate-incoming",
|
|
"s:uri", uri,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONCheckError(cmd, reply);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONMigrateStartPostCopy(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("migrate-start-postcopy", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONCheckError(cmd, reply);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONMigrateContinue(qemuMonitor *mon,
|
|
qemuMonitorMigrationStatus status)
|
|
{
|
|
const char *statusStr = qemuMonitorMigrationStatusTypeToString(status);
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("migrate-continue",
|
|
"s:state", statusStr,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONCheckError(cmd, reply);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetRTCTime(qemuMonitor *mon,
|
|
struct tm *tm)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
|
|
"s:path", "/machine",
|
|
"s:property", "rtc-time",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGet(reply, "return");
|
|
|
|
if (virJSONValueObjectGetNumberInt(data, "tm_year", &tm->tm_year) < 0 ||
|
|
virJSONValueObjectGetNumberInt(data, "tm_mon", &tm->tm_mon) < 0 ||
|
|
virJSONValueObjectGetNumberInt(data, "tm_mday", &tm->tm_mday) < 0 ||
|
|
virJSONValueObjectGetNumberInt(data, "tm_hour", &tm->tm_hour) < 0 ||
|
|
virJSONValueObjectGetNumberInt(data, "tm_min", &tm->tm_min) < 0 ||
|
|
virJSONValueObjectGetNumberInt(data, "tm_sec", &tm->tm_sec) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("qemu returned malformed time"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
qemuMonitorQueryHotpluggableCpusFree(struct qemuMonitorQueryHotpluggableCpusEntry *entries,
|
|
size_t nentries)
|
|
{
|
|
struct qemuMonitorQueryHotpluggableCpusEntry *entry;
|
|
size_t i;
|
|
|
|
if (!entries)
|
|
return;
|
|
|
|
for (i = 0; i < nentries; i++) {
|
|
entry = entries + i;
|
|
|
|
g_free(entry->type);
|
|
g_free(entry->qom_path);
|
|
g_free(entry->alias);
|
|
virJSONValueFree(entry->props);
|
|
}
|
|
|
|
g_free(entries);
|
|
}
|
|
|
|
|
|
/**
|
|
* [{
|
|
* "props": {
|
|
* "core-id": 0,
|
|
* "thread-id": 0,
|
|
* "socket-id": 0
|
|
* },
|
|
* "vcpus-count": 1,
|
|
* "qom-path": "/machine/unattached/device[0]",
|
|
* "type": "qemu64-x86_64-cpu"
|
|
* },
|
|
* {...}
|
|
* ]
|
|
*/
|
|
static int
|
|
qemuMonitorJSONProcessHotpluggableCpusReply(virJSONValue *vcpu,
|
|
struct qemuMonitorQueryHotpluggableCpusEntry *entry)
|
|
{
|
|
virJSONValue *props;
|
|
const char *tmp;
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(vcpu, "type"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-hotpluggable-cpus didn't return device type"));
|
|
return -1;
|
|
}
|
|
|
|
entry->type = g_strdup(tmp);
|
|
|
|
if (virJSONValueObjectGetNumberUint(vcpu, "vcpus-count", &entry->vcpus) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-hotpluggable-cpus didn't return vcpus-count"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(props = virJSONValueObjectGetObject(vcpu, "props"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-hotpluggable-cpus didn't return device props"));
|
|
return -1;
|
|
}
|
|
|
|
if (!(entry->props = virJSONValueCopy(props)))
|
|
return -1;
|
|
|
|
entry->node_id = -1;
|
|
entry->socket_id = -1;
|
|
entry->die_id = -1;
|
|
entry->core_id = -1;
|
|
entry->thread_id = -1;
|
|
|
|
ignore_value(virJSONValueObjectGetNumberInt(props, "node-id", &entry->node_id));
|
|
ignore_value(virJSONValueObjectGetNumberInt(props, "socket-id", &entry->socket_id));
|
|
ignore_value(virJSONValueObjectGetNumberInt(props, "die-id", &entry->die_id));
|
|
ignore_value(virJSONValueObjectGetNumberInt(props, "core-id", &entry->core_id));
|
|
ignore_value(virJSONValueObjectGetNumberInt(props, "thread-id", &entry->thread_id));
|
|
|
|
if (entry->node_id == -1 && entry->socket_id == -1 &&
|
|
entry->core_id == -1 && entry->thread_id == -1) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-hotpluggable-cpus entry doesn't report "
|
|
"topology information"));
|
|
return -1;
|
|
}
|
|
|
|
/* qom path is not present unless the vCPU is online */
|
|
if ((tmp = virJSONValueObjectGetString(vcpu, "qom-path"))) {
|
|
entry->qom_path = g_strdup(tmp);
|
|
|
|
/* alias is the part after last slash having a "vcpu" prefix */
|
|
if ((tmp = strrchr(tmp, '/')) && STRPREFIX(tmp + 1, "vcpu"))
|
|
entry->alias = g_strdup(tmp + 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorQueryHotpluggableCpusEntrySort(const void *p1,
|
|
const void *p2)
|
|
{
|
|
const struct qemuMonitorQueryHotpluggableCpusEntry *a = p1;
|
|
const struct qemuMonitorQueryHotpluggableCpusEntry *b = p2;
|
|
|
|
if (a->socket_id != b->socket_id)
|
|
return a->socket_id - b->socket_id;
|
|
|
|
if (a->die_id != b->die_id)
|
|
return a->die_id - b->die_id;
|
|
|
|
if (a->core_id != b->core_id)
|
|
return a->core_id - b->core_id;
|
|
|
|
return a->thread_id - b->thread_id;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetHotpluggableCPUs(qemuMonitor *mon,
|
|
struct qemuMonitorQueryHotpluggableCpusEntry **entries,
|
|
size_t *nentries)
|
|
{
|
|
struct qemuMonitorQueryHotpluggableCpusEntry *info = NULL;
|
|
size_t ninfo = 0;
|
|
int ret = -1;
|
|
size_t i;
|
|
virJSONValue *data;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *vcpu;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-hotpluggable-cpus", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
goto cleanup;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
goto cleanup;
|
|
|
|
data = virJSONValueObjectGet(reply, "return");
|
|
ninfo = virJSONValueArraySize(data);
|
|
|
|
info = g_new0(struct qemuMonitorQueryHotpluggableCpusEntry, ninfo);
|
|
|
|
for (i = 0; i < ninfo; i++) {
|
|
vcpu = virJSONValueArrayGet(data, i);
|
|
|
|
if (qemuMonitorJSONProcessHotpluggableCpusReply(vcpu, info + i) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
qsort(info, ninfo, sizeof(*info), qemuMonitorQueryHotpluggableCpusEntrySort);
|
|
|
|
*entries = g_steal_pointer(&info);
|
|
*nentries = ninfo;
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
qemuMonitorQueryHotpluggableCpusFree(info, ninfo);
|
|
return ret;
|
|
}
|
|
|
|
|
|
virJSONValue *
|
|
qemuMonitorJSONQueryQMPSchema(qemuMonitor *mon)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-qmp-schema", NULL)))
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return NULL;
|
|
|
|
return virJSONValueObjectStealArray(reply, "return");
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetBlockThreshold(qemuMonitor *mon,
|
|
const char *nodename,
|
|
unsigned long long threshold)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("block-set-write-threshold",
|
|
"s:node-name", nodename,
|
|
"U:write-threshold", threshold,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
virJSONValue *
|
|
qemuMonitorJSONQueryNamedBlockNodes(qemuMonitor *mon,
|
|
bool flat)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-named-block-nodes",
|
|
"B:flat", flat,
|
|
NULL)))
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return NULL;
|
|
|
|
return virJSONValueObjectStealArray(reply, "return");
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetWatchdogAction(qemuMonitor *mon,
|
|
const char *action)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("watchdog-set-action",
|
|
"s:action", action,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevCreate(qemuMonitor *mon,
|
|
const char *jobname,
|
|
virJSONValue **props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
cmd = qemuMonitorJSONMakeCommand("blockdev-create",
|
|
"s:job-id", jobname,
|
|
"a:options", props,
|
|
NULL);
|
|
if (!cmd)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevAdd(qemuMonitor *mon,
|
|
virJSONValue **props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("blockdev-add", props)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevReopen(qemuMonitor *mon,
|
|
virJSONValue **props)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommandInternal("blockdev-reopen", props)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevDel(qemuMonitor *mon,
|
|
const char *nodename)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-del",
|
|
"s:node-name", nodename,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevTrayOpen(qemuMonitor *mon,
|
|
const char *id,
|
|
bool force)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-open-tray",
|
|
"s:id", id,
|
|
"b:force", force, NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevTrayClose(qemuMonitor *mon,
|
|
const char *id)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-close-tray",
|
|
"s:id", id, NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevMediumRemove(qemuMonitor *mon,
|
|
const char *id)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-remove-medium",
|
|
"s:id", id, NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBlockdevMediumInsert(qemuMonitor *mon,
|
|
const char *id,
|
|
const char *nodename)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("blockdev-insert-medium",
|
|
"s:id", id,
|
|
"s:node-name", nodename,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* The function is used to retrieve the measurement of a SEV guest.
|
|
* The measurement is signature of the memory contents that was encrypted
|
|
* through the SEV launch flow.
|
|
*
|
|
* A example JSON output:
|
|
*
|
|
* { "execute" : "query-sev-launch-measure" }
|
|
* { "return" : { "data" : "4l8LXeNlSPUDlXPJG5966/8%YZ" } }
|
|
*/
|
|
char *
|
|
qemuMonitorJSONGetSEVMeasurement(qemuMonitor *mon)
|
|
{
|
|
const char *tmp;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-sev-launch-measure", NULL)))
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return NULL;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return NULL;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (!(tmp = virJSONValueObjectGetString(data, "data")))
|
|
return NULL;
|
|
|
|
return g_strdup(tmp);
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve info about the SEV setup, returning those fields that
|
|
* are required to do a launch attestation, as per
|
|
*
|
|
* HMAC(0x04 || API_MAJOR || API_MINOR || BUILD || GCTX.POLICY || GCTX.LD || MNONCE; GCTX.TIK)
|
|
*
|
|
* specified in section 6.5.1 of AMD Secure Encrypted
|
|
* Virtualization API.
|
|
*
|
|
* { "execute": "query-sev" }
|
|
* { "return": { "enabled": true, "api-major" : 0, "api-minor" : 0,
|
|
* "build-id" : 0, "policy" : 0, "state" : "running",
|
|
* "handle" : 1 } }
|
|
*/
|
|
int
|
|
qemuMonitorJSONGetSEVInfo(qemuMonitor *mon,
|
|
unsigned int *apiMajor,
|
|
unsigned int *apiMinor,
|
|
unsigned int *buildID,
|
|
unsigned int *policy)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-sev", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
|
|
if (virJSONValueObjectGetNumberUint(data, "api-major", apiMajor) < 0 ||
|
|
virJSONValueObjectGetNumberUint(data, "api-minor", apiMinor) < 0 ||
|
|
virJSONValueObjectGetNumberUint(data, "build-id", buildID) < 0 ||
|
|
virJSONValueObjectGetNumberUint(data, "policy", policy) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set a launch secret in guest memory
|
|
*
|
|
* Example JSON:
|
|
*
|
|
* { "execute" : "sev-inject-launch-secret",
|
|
* "data": { "packet-header": "str", "secret": "str", "gpa": "uint64" } }
|
|
*
|
|
* The guest physical address (gpa) parameter is optional
|
|
*/
|
|
int
|
|
qemuMonitorJSONSetLaunchSecurityState(qemuMonitor *mon,
|
|
const char *secrethdr,
|
|
const char *secret,
|
|
unsigned long long setaddr,
|
|
bool hasSetaddr)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (hasSetaddr) {
|
|
cmd = qemuMonitorJSONMakeCommand("sev-inject-launch-secret",
|
|
"s:packet-header", secrethdr,
|
|
"s:secret", secret,
|
|
"U:gpa", setaddr,
|
|
NULL);
|
|
} else {
|
|
cmd = qemuMonitorJSONMakeCommand("sev-inject-launch-secret",
|
|
"s:packet-header", secrethdr,
|
|
"s:secret", secret,
|
|
NULL);
|
|
}
|
|
if (cmd == NULL)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Example return data
|
|
*
|
|
* "return": [
|
|
* { "connected": true, "id": "pr-helper0" }
|
|
* ]
|
|
*/
|
|
static int
|
|
qemuMonitorJSONExtractPRManagerInfo(virJSONValue *reply,
|
|
GHashTable *info)
|
|
{
|
|
virJSONValue *data;
|
|
size_t i;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
|
|
for (i = 0; i < virJSONValueArraySize(data); i++) {
|
|
g_autofree qemuMonitorPRManagerInfo *entry = NULL;
|
|
virJSONValue *prManager = virJSONValueArrayGet(data, i);
|
|
const char *alias;
|
|
|
|
if (!(alias = virJSONValueObjectGetString(prManager, "id")))
|
|
goto malformed;
|
|
|
|
entry = g_new0(qemuMonitorPRManagerInfo, 1);
|
|
|
|
if (virJSONValueObjectGetBoolean(prManager,
|
|
"connected",
|
|
&entry->connected) < 0) {
|
|
goto malformed;
|
|
}
|
|
|
|
if (virHashAddEntry(info, alias, entry) < 0)
|
|
return -1;
|
|
|
|
entry = NULL;
|
|
}
|
|
|
|
return 0;
|
|
|
|
malformed:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed prManager reply"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetPRManagerInfo(qemuMonitor *mon,
|
|
GHashTable *info)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-pr-managers",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONExtractPRManagerInfo(reply, info);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuMonitorJSONExtractCurrentMachineInfo(virJSONValue *reply,
|
|
qemuMonitorCurrentMachineInfo *info)
|
|
{
|
|
virJSONValue *data;
|
|
|
|
data = virJSONValueObjectGetObject(reply, "return");
|
|
if (!data)
|
|
goto malformed;
|
|
|
|
if (virJSONValueObjectGetBoolean(data, "wakeup-suspend-support",
|
|
&info->wakeupSuspendSupport) < 0)
|
|
goto malformed;
|
|
|
|
return 0;
|
|
|
|
malformed:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed qemu-current-machine reply"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetCurrentMachineInfo(qemuMonitor *mon,
|
|
qemuMonitorCurrentMachineInfo *info)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-current-machine",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_OBJECT) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONExtractCurrentMachineInfo(reply, info);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransactionBitmapAdd(virJSONValue *actions,
|
|
const char *node,
|
|
const char *name,
|
|
bool persistent,
|
|
bool disabled,
|
|
unsigned long long granularity)
|
|
{
|
|
return qemuMonitorJSONTransactionAdd(actions,
|
|
"block-dirty-bitmap-add",
|
|
"s:node", node,
|
|
"s:name", name,
|
|
"b:persistent", persistent,
|
|
"b:disabled", disabled,
|
|
"P:granularity", granularity,
|
|
NULL);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransactionBitmapRemove(virJSONValue *actions,
|
|
const char *node,
|
|
const char *name)
|
|
{
|
|
return qemuMonitorJSONTransactionAdd(actions,
|
|
"block-dirty-bitmap-remove",
|
|
"s:node", node,
|
|
"s:name", name,
|
|
NULL);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONBitmapRemove(qemuMonitor *mon,
|
|
const char *node,
|
|
const char *name)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-remove",
|
|
"s:node", node,
|
|
"s:name", name,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransactionBitmapEnable(virJSONValue *actions,
|
|
const char *node,
|
|
const char *name)
|
|
{
|
|
return qemuMonitorJSONTransactionAdd(actions,
|
|
"block-dirty-bitmap-enable",
|
|
"s:node", node,
|
|
"s:name", name,
|
|
NULL);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransactionBitmapDisable(virJSONValue *actions,
|
|
const char *node,
|
|
const char *name)
|
|
{
|
|
return qemuMonitorJSONTransactionAdd(actions,
|
|
"block-dirty-bitmap-disable",
|
|
"s:node", node,
|
|
"s:name", name,
|
|
NULL);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransactionBitmapMerge(virJSONValue *actions,
|
|
const char *node,
|
|
const char *target,
|
|
virJSONValue **sources)
|
|
{
|
|
return qemuMonitorJSONTransactionAdd(actions,
|
|
"block-dirty-bitmap-merge",
|
|
"s:node", node,
|
|
"s:target", target,
|
|
"a:bitmaps", sources,
|
|
NULL);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransactionBitmapMergeSourceAddBitmap(virJSONValue *sources,
|
|
const char *sourcenode,
|
|
const char *sourcebitmap)
|
|
{
|
|
g_autoptr(virJSONValue) sourceobj = NULL;
|
|
|
|
if (virJSONValueObjectAdd(&sourceobj,
|
|
"s:node", sourcenode,
|
|
"s:name", sourcebitmap,
|
|
NULL) < 0)
|
|
return -1;
|
|
|
|
if (virJSONValueArrayAppend(sources, &sourceobj) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransactionSnapshotLegacy(virJSONValue *actions,
|
|
const char *device,
|
|
const char *path,
|
|
const char *format,
|
|
bool existing)
|
|
{
|
|
const char *mode = NULL;
|
|
|
|
if (existing)
|
|
mode = "existing";
|
|
|
|
return qemuMonitorJSONTransactionAdd(actions,
|
|
"blockdev-snapshot-sync",
|
|
"s:device", device,
|
|
"s:snapshot-file", path,
|
|
"s:format", format,
|
|
"S:mode", mode,
|
|
NULL);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONTransactionSnapshotBlockdev(virJSONValue *actions,
|
|
const char *node,
|
|
const char *overlay)
|
|
{
|
|
return qemuMonitorJSONTransactionAdd(actions,
|
|
"blockdev-snapshot",
|
|
"s:node", node,
|
|
"s:overlay", overlay,
|
|
NULL);
|
|
}
|
|
|
|
VIR_ENUM_DECL(qemuMonitorTransactionBackupSyncMode);
|
|
VIR_ENUM_IMPL(qemuMonitorTransactionBackupSyncMode,
|
|
QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_LAST,
|
|
"none",
|
|
"incremental",
|
|
"full");
|
|
|
|
int
|
|
qemuMonitorJSONTransactionBackup(virJSONValue *actions,
|
|
const char *device,
|
|
const char *jobname,
|
|
const char *target,
|
|
const char *bitmap,
|
|
qemuMonitorTransactionBackupSyncMode syncmode)
|
|
{
|
|
const char *syncmodestr = qemuMonitorTransactionBackupSyncModeTypeToString(syncmode);
|
|
|
|
return qemuMonitorJSONTransactionAdd(actions,
|
|
"blockdev-backup",
|
|
"s:device", device,
|
|
"s:job-id", jobname,
|
|
"s:target", target,
|
|
"s:sync", syncmodestr,
|
|
"S:bitmap", bitmap,
|
|
"T:auto-finalize", VIR_TRISTATE_BOOL_YES,
|
|
"T:auto-dismiss", VIR_TRISTATE_BOOL_NO,
|
|
NULL);
|
|
}
|
|
|
|
|
|
static qemuMonitorJobInfo *
|
|
qemuMonitorJSONGetJobInfoOne(virJSONValue *data)
|
|
{
|
|
const char *id = virJSONValueObjectGetString(data, "id");
|
|
const char *type = virJSONValueObjectGetString(data, "type");
|
|
const char *status = virJSONValueObjectGetString(data, "status");
|
|
const char *errmsg = virJSONValueObjectGetString(data, "error");
|
|
int tmp;
|
|
g_autoptr(qemuMonitorJobInfo) job = NULL;
|
|
|
|
job = g_new0(qemuMonitorJobInfo, 1);
|
|
|
|
if ((tmp = qemuMonitorJobTypeFromString(type)) < 0)
|
|
tmp = QEMU_MONITOR_JOB_TYPE_UNKNOWN;
|
|
|
|
job->type = tmp;
|
|
|
|
if ((tmp = qemuMonitorJobStatusTypeFromString(status)) < 0)
|
|
tmp = QEMU_MONITOR_JOB_STATUS_UNKNOWN;
|
|
|
|
job->status = tmp;
|
|
|
|
job->id = g_strdup(id);
|
|
job->error = g_strdup(errmsg);
|
|
|
|
/* failure to fetch progress stats is not fatal */
|
|
ignore_value(virJSONValueObjectGetNumberUlong(data, "current-progress",
|
|
&job->progressCurrent));
|
|
ignore_value(virJSONValueObjectGetNumberUlong(data, "total-progress",
|
|
&job->progressTotal));
|
|
|
|
return g_steal_pointer(&job);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetJobInfo(qemuMonitor *mon,
|
|
qemuMonitorJobInfo ***jobs,
|
|
size_t *njobs)
|
|
{
|
|
virJSONValue *data;
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
size_t i;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-jobs", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_ARRAY) < 0)
|
|
return -1;
|
|
|
|
data = virJSONValueObjectGetArray(reply, "return");
|
|
|
|
for (i = 0; i < virJSONValueArraySize(data); i++) {
|
|
qemuMonitorJobInfo *job = NULL;
|
|
|
|
if (!(job = qemuMonitorJSONGetJobInfoOne(virJSONValueArrayGet(data, i))))
|
|
return -1;
|
|
|
|
VIR_APPEND_ELEMENT(*jobs, *njobs, job);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONGetCPUMigratable(qemuMonitor *mon,
|
|
const char *cpuQOMPath,
|
|
bool *migratable)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("qom-get",
|
|
"s:path", cpuQOMPath,
|
|
"s:property", "migratable",
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONHasError(reply, "GenericError"))
|
|
return 1;
|
|
|
|
if (qemuMonitorJSONCheckReply(cmd, reply, VIR_JSON_TYPE_BOOLEAN) < 0)
|
|
return -1;
|
|
|
|
return virJSONValueGetBoolean(virJSONValueObjectGet(reply, "return"),
|
|
migratable);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONStartDirtyRateCalc(qemuMonitor *mon,
|
|
int seconds,
|
|
qemuMonitorDirtyRateCalcMode mode)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
const char *modestr = NULL;
|
|
|
|
if (mode != QEMU_MONITOR_DIRTYRATE_CALC_MODE_PAGE_SAMPLING)
|
|
modestr = qemuMonitorDirtyRateCalcModeTypeToString(mode);
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("calc-dirty-rate",
|
|
"i:calc-time", seconds,
|
|
"S:mode", modestr,
|
|
NULL))) {
|
|
return -1;
|
|
}
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ENUM_DECL(qemuMonitorDirtyRateStatus);
|
|
VIR_ENUM_IMPL(qemuMonitorDirtyRateStatus,
|
|
VIR_DOMAIN_DIRTYRATE_LAST,
|
|
"unstarted",
|
|
"measuring",
|
|
"measured");
|
|
|
|
static int
|
|
qemuMonitorJSONExtractVcpuDirtyRate(virJSONValue *data,
|
|
qemuMonitorDirtyRateInfo *info)
|
|
{
|
|
size_t nvcpus;
|
|
size_t i;
|
|
|
|
nvcpus = virJSONValueArraySize(data);
|
|
info->nvcpus = nvcpus;
|
|
info->rates = g_new0(qemuMonitorDirtyRateVcpu, nvcpus);
|
|
|
|
for (i = 0; i < nvcpus; i++) {
|
|
virJSONValue *entry = virJSONValueArrayGet(data, i);
|
|
if (virJSONValueObjectGetNumberInt(entry, "id",
|
|
&info->rates[i].idx) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-dirty-rate reply was missing 'id' data"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberUlong(entry, "dirty-rate",
|
|
&info->rates[i].value) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-dirty-rate reply was missing 'dirty-rate' data"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
qemuMonitorJSONExtractDirtyRateInfo(virJSONValue *data,
|
|
qemuMonitorDirtyRateInfo *info)
|
|
{
|
|
const char *statusstr;
|
|
const char *modestr;
|
|
int status;
|
|
int mode;
|
|
virJSONValue *rates = NULL;
|
|
|
|
if (!(statusstr = virJSONValueObjectGetString(data, "status"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-dirty-rate reply was missing 'status' data"));
|
|
return -1;
|
|
}
|
|
|
|
if ((status = qemuMonitorDirtyRateStatusTypeFromString(statusstr)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown dirty rate status: %s"), statusstr);
|
|
return -1;
|
|
}
|
|
info->status = status;
|
|
|
|
/* `query-dirty-rate` replies `dirty-rate` data only if the status of the latest
|
|
* calculation is `measured`.
|
|
*/
|
|
if ((info->status == VIR_DOMAIN_DIRTYRATE_MEASURED) &&
|
|
(virJSONValueObjectGetNumberLong(data, "dirty-rate", &info->dirtyRate) < 0)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-dirty-rate reply was missing 'dirty-rate' data"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberLong(data, "start-time", &info->startTime) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-dirty-rate reply was missing 'start-time' data"));
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectGetNumberInt(data, "calc-time", &info->calcTime) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-dirty-rate reply was missing 'calc-time' data"));
|
|
return -1;
|
|
}
|
|
|
|
if ((modestr = virJSONValueObjectGetString(data, "mode"))) {
|
|
if ((mode = qemuMonitorDirtyRateCalcModeTypeFromString(modestr)) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unknown dirty page rate calculation mode: %s"), modestr);
|
|
return -1;
|
|
}
|
|
info->mode = mode;
|
|
} else {
|
|
info->mode = QEMU_MONITOR_DIRTYRATE_CALC_MODE_PAGE_SAMPLING;
|
|
}
|
|
|
|
if ((rates = virJSONValueObjectGetArray(data, "vcpu-dirty-rate"))) {
|
|
if (qemuMonitorJSONExtractVcpuDirtyRate(rates, info) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-dirty-rate parsing 'vcpu-dirty-rate' in failure"));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONQueryDirtyRate(qemuMonitor *mon,
|
|
qemuMonitorDirtyRateInfo *info)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
virJSONValue *data = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("query-dirty-rate", NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
if (!(data = virJSONValueObjectGetObject(reply, "return"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("query-dirty-rate reply was missing 'return' data"));
|
|
return -1;
|
|
}
|
|
|
|
return qemuMonitorJSONExtractDirtyRateInfo(data, info);
|
|
}
|
|
|
|
|
|
VIR_ENUM_DECL(qemuMonitorActionShutdown);
|
|
VIR_ENUM_IMPL(qemuMonitorActionShutdown,
|
|
QEMU_MONITOR_ACTION_SHUTDOWN_LAST,
|
|
"",
|
|
"poweroff",
|
|
"pause");
|
|
|
|
VIR_ENUM_DECL(qemuMonitorActionReboot);
|
|
VIR_ENUM_IMPL(qemuMonitorActionReboot,
|
|
QEMU_MONITOR_ACTION_REBOOT_LAST,
|
|
"",
|
|
"reset",
|
|
"shutdown");
|
|
|
|
VIR_ENUM_DECL(qemuMonitorActionWatchdog);
|
|
VIR_ENUM_IMPL(qemuMonitorActionWatchdog,
|
|
QEMU_MONITOR_ACTION_WATCHDOG_LAST,
|
|
"",
|
|
"reset",
|
|
"shutdown",
|
|
"poweroff",
|
|
"pause",
|
|
"debug",
|
|
"none",
|
|
"inject-nmi");
|
|
|
|
VIR_ENUM_DECL(qemuMonitorActionPanic);
|
|
VIR_ENUM_IMPL(qemuMonitorActionPanic,
|
|
QEMU_MONITOR_ACTION_PANIC_LAST,
|
|
"",
|
|
"pause",
|
|
"shutdown",
|
|
"none");
|
|
|
|
|
|
int
|
|
qemuMonitorJSONSetAction(qemuMonitor *mon,
|
|
qemuMonitorActionShutdown shutdown,
|
|
qemuMonitorActionReboot reboot,
|
|
qemuMonitorActionWatchdog watchdog,
|
|
qemuMonitorActionPanic panic)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
const char *actionShutdown = NULL;
|
|
const char *actionReboot = NULL;
|
|
const char *actionWatchdog = NULL;
|
|
const char *actionPanic = NULL;
|
|
|
|
if (shutdown != QEMU_MONITOR_ACTION_SHUTDOWN_KEEP)
|
|
actionShutdown = qemuMonitorActionShutdownTypeToString(shutdown);
|
|
|
|
if (reboot != QEMU_MONITOR_ACTION_REBOOT_KEEP)
|
|
actionReboot = qemuMonitorActionRebootTypeToString(reboot);
|
|
|
|
if (watchdog != QEMU_MONITOR_ACTION_WATCHDOG_KEEP)
|
|
actionWatchdog = qemuMonitorActionWatchdogTypeToString(watchdog);
|
|
|
|
if (panic != QEMU_MONITOR_ACTION_PANIC_KEEP)
|
|
actionPanic = qemuMonitorActionPanicTypeToString(panic);
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("set-action",
|
|
"S:shutdown", actionShutdown,
|
|
"S:reboot", actionReboot,
|
|
"S:watchdog", actionWatchdog,
|
|
"S:panic", actionPanic,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCheckError(cmd, reply) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONChangeMemoryRequestedSize(qemuMonitor *mon,
|
|
const char *alias,
|
|
unsigned long long requestedsize)
|
|
{
|
|
g_autofree char *path = g_strdup_printf("/machine/peripheral/%s", alias);
|
|
qemuMonitorJSONObjectProperty prop = {
|
|
.type = QEMU_MONITOR_OBJECT_PROPERTY_ULONG,
|
|
.val.ul = requestedsize * 1024, /* monitor needs bytes */
|
|
};
|
|
|
|
return qemuMonitorJSONSetObjectProperty(mon, path, "requested-size", &prop);
|
|
}
|
|
|
|
|
|
int
|
|
qemuMonitorJSONMigrateRecover(qemuMonitor *mon,
|
|
const char *uri)
|
|
{
|
|
g_autoptr(virJSONValue) cmd = NULL;
|
|
g_autoptr(virJSONValue) reply = NULL;
|
|
|
|
if (!(cmd = qemuMonitorJSONMakeCommand("migrate-recover",
|
|
"s:uri", uri,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0)
|
|
return -1;
|
|
|
|
return qemuMonitorJSONCheckError(cmd, reply);
|
|
}
|