As it could be shared with libxl which now allows channels to
be created. Also changed filename to match others in the same
directory namely to virqemuagent.{h,c}
Signed-off-by: Joao Martins <joao.m.martins(a)oracle.com>
---
po/POTFILES.in | 2 +-
src/Makefile.am | 2 +-
src/libvirt_private.syms | 21 +
src/qemu/qemu_agent.c | 2248 ------------------------------------------
src/qemu/qemu_agent.h | 123 ---
src/qemu/qemu_domain.h | 2 +-
src/qemu/qemu_driver.c | 2 +-
src/util/virqemuagent.c | 2248 ++++++++++++++++++++++++++++++++++++++++++
src/util/virqemuagent.h | 123 +++
tests/qemuagenttest.c | 2 +-
tests/qemumonitortestutils.c | 2 +-
tests/qemumonitortestutils.h | 2 +-
12 files changed, 2399 insertions(+), 2378 deletions(-)
delete mode 100644 src/qemu/qemu_agent.c
delete mode 100644 src/qemu/qemu_agent.h
create mode 100644 src/util/virqemuagent.c
create mode 100644 src/util/virqemuagent.h
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 365ea66..ebb247b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -122,7 +122,6 @@ src/openvz/openvz_conf.c
src/openvz/openvz_driver.c
src/openvz/openvz_util.c
src/phyp/phyp_driver.c
-src/qemu/qemu_agent.c
src/qemu/qemu_alias.c
src/qemu/qemu_capabilities.c
src/qemu/qemu_cgroup.c
@@ -239,6 +238,7 @@ src/util/virpolkit.c
src/util/virportallocator.c
src/util/virprocess.c
src/util/virqemu.c
+src/util/virqemuagent.c
src/util/virrandom.c
src/util/virrotatingfile.c
src/util/virscsi.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 2f32d41..62c8733 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -161,6 +161,7 @@ UTIL_SOURCES = \
util/virprobe.h \
util/virprocess.c util/virprocess.h \
util/virqemu.c util/virqemu.h \
+ util/virqemuagent.c util/virqemuagent.h \
util/virrandom.h util/virrandom.c \
util/virrotatingfile.h util/virrotatingfile.c \
util/virscsi.c util/virscsi.h \
@@ -815,7 +816,6 @@ VBOX_DRIVER_EXTRA_DIST = \
vbox/vbox_XPCOMCGlue.c vbox/vbox_XPCOMCGlue.h
QEMU_DRIVER_SOURCES = \
- qemu/qemu_agent.c qemu/qemu_agent.h \
qemu/qemu_alias.c qemu/qemu_alias.h \
qemu/qemu_blockjob.c qemu/qemu_blockjob.h \
qemu/qemu_capabilities.c qemu/qemu_capabilities.h \
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index d556c7d..a5a1313 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2306,6 +2306,27 @@ virQEMUBuildLuksOpts;
virQEMUBuildObjectCommandlineFromJSON;
+# util/virqemuagent.h
+qemuAgentArbitraryCommand;
+qemuAgentClose;
+qemuAgentFSFreeze;
+qemuAgentFSThaw;
+qemuAgentFSTrim;
+qemuAgentGetFSInfo;
+qemuAgentGetInterfaces;
+qemuAgentGetTime;
+qemuAgentGetVCPUs;
+qemuAgentNotifyClose;
+qemuAgentNotifyEvent;
+qemuAgentOpen;
+qemuAgentSetVCPUs;
+qemuAgentSetUserPassword;
+qemuAgentSetTime;
+qemuAgentShutdown;
+qemuAgentSuspend;
+qemuAgentUpdateCPUInfo;
+
+
# util/virrandom.h
virRandom;
virRandomBits;
diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c
deleted file mode 100644
index 46cad53..0000000
--- a/src/qemu/qemu_agent.c
+++ /dev/null
@@ -1,2248 +0,0 @@
-/*
- * qemu_agent.c: interaction with QEMU guest agent
- *
- * Copyright (C) 2006-2014 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/>.
- *
- * Author: Daniel P. Berrange <berrange(a)redhat.com>
- */
-
-#include <config.h>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <poll.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <sys/time.h>
-
-#include "qemu_agent.h"
-#include "viralloc.h"
-#include "virlog.h"
-#include "virerror.h"
-#include "virjson.h"
-#include "virfile.h"
-#include "virprocess.h"
-#include "virtime.h"
-#include "virobject.h"
-#include "virstring.h"
-#include "base64.h"
-
-#define VIR_FROM_THIS VIR_FROM_QEMU
-
-VIR_LOG_INIT("qemu.qemu_agent");
-
-#define LINE_ENDING "\n"
-
-#define DEBUG_IO 0
-#define DEBUG_RAW_IO 0
-
-/* When you are the first to uncomment this,
- * don't forget to uncomment the corresponding
- * part in qemuAgentIOProcessEvent as well.
- *
-static struct {
- const char *type;
- void (*handler)(qemuAgentPtr mon, virJSONValuePtr data);
-} eventHandlers[] = {
-};
-*/
-
-typedef struct _qemuAgentMessage qemuAgentMessage;
-typedef qemuAgentMessage *qemuAgentMessagePtr;
-
-struct _qemuAgentMessage {
- char *txBuffer;
- int txOffset;
- int txLength;
-
- /* Used by the JSON monitor to hold reply / error */
- char *rxBuffer;
- int rxLength;
- void *rxObject;
-
- /* True if rxBuffer / rxObject are ready, or a
- * fatal error occurred on the monitor channel
- */
- bool finished;
- /* true for sync command */
- bool sync;
- /* id of the issued sync comand */
- unsigned long long id;
- bool first;
-};
-
-
-struct _qemuAgent {
- virObjectLockable parent;
-
- virCond notify;
-
- int fd;
- int watch;
-
- bool connectPending;
- bool running;
-
- virDomainObjPtr vm;
-
- qemuAgentCallbacksPtr cb;
-
- /* If there's a command being processed this will be
- * non-NULL */
- qemuAgentMessagePtr msg;
-
- /* Buffer incoming data ready for Agent monitor
- * code to process & find message boundaries */
- size_t bufferOffset;
- size_t bufferLength;
- char *buffer;
-
- /* If anything went wrong, this will be fed back
- * the next monitor msg */
- virError lastError;
-
- /* Some guest agent commands don't return anything
- * but fire up an event on qemu monitor instead.
- * Take that as indication of successful completion */
- qemuAgentEvent await_event;
-};
-
-static virClassPtr qemuAgentClass;
-static void qemuAgentDispose(void *obj);
-
-static int qemuAgentOnceInit(void)
-{
- if (!(qemuAgentClass = virClassNew(virClassForObjectLockable(),
- "qemuAgent",
- sizeof(qemuAgent),
- qemuAgentDispose)))
- return -1;
-
- return 0;
-}
-
-VIR_ONCE_GLOBAL_INIT(qemuAgent)
-
-
-#if DEBUG_RAW_IO
-# include <c-ctype.h>
-static char *
-qemuAgentEscapeNonPrintable(const char *text)
-{
- size_t i;
- virBuffer buf = VIR_BUFFER_INITIALIZER;
- for (i = 0; text[i] != '\0'; i++) {
- if (text[i] == '\\')
- virBufferAddLit(&buf, "\\\\");
- else if (c_isprint(text[i]) || text[i] == '\n' ||
- (text[i] == '\r' && text[i+1] == '\n'))
- virBufferAddChar(&buf, text[i]);
- else
- virBufferAsprintf(&buf, "\\x%02x", text[i]);
- }
- return virBufferContentAndReset(&buf);
-}
-#endif
-
-
-static void qemuAgentDispose(void *obj)
-{
- qemuAgentPtr mon = obj;
- VIR_DEBUG("mon=%p", mon);
- if (mon->cb && mon->cb->destroy)
- (mon->cb->destroy)(mon, mon->vm);
- virCondDestroy(&mon->notify);
- VIR_FREE(mon->buffer);
- virResetError(&mon->lastError);
-}
-
-static int
-qemuAgentOpenUnix(const char *monitor, pid_t cpid, bool *inProgress)
-{
- struct sockaddr_un addr;
- int monfd;
- virTimeBackOffVar timeout;
- int ret = -1;
-
- *inProgress = false;
-
- if ((monfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
- virReportSystemError(errno,
- "%s", _("failed to create socket"));
- return -1;
- }
-
- if (virSetNonBlock(monfd) < 0) {
- virReportSystemError(errno, "%s",
- _("Unable to put monitor "
- "into non-blocking mode"));
- goto error;
- }
-
- if (virSetCloseExec(monfd) < 0) {
- virReportSystemError(errno, "%s",
- _("Unable to set monitor "
- "close-on-exec flag"));
- goto error;
- }
-
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- if (virStrcpyStatic(addr.sun_path, monitor) == NULL) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("Agent path %s too big for destination"), monitor);
- goto error;
- }
-
- if (virTimeBackOffStart(&timeout, 1, 3*1000 /* ms */) < 0)
- goto error;
- while (virTimeBackOffWait(&timeout)) {
- ret = connect(monfd, (struct sockaddr *) &addr, sizeof(addr));
-
- if (ret == 0)
- break;
-
- if ((errno == ENOENT || errno == ECONNREFUSED) &&
- virProcessKill(cpid, 0) == 0) {
- /* ENOENT : Socket may not have shown up yet
- * ECONNREFUSED : Leftover socket hasn't been removed yet */
- continue;
- }
-
- if ((errno == EINPROGRESS) ||
- (errno == EAGAIN)) {
- VIR_DEBUG("Connection attempt continuing in background");
- *inProgress = true;
- ret = 0;
- break;
- }
-
- virReportSystemError(errno, "%s",
- _("failed to connect to monitor socket"));
- goto error;
-
- }
-
- if (ret != 0) {
- virReportSystemError(errno, "%s",
- _("monitor socket did not show up"));
- goto error;
- }
-
- return monfd;
-
- error:
- VIR_FORCE_CLOSE(monfd);
- return -1;
-}
-
-static int
-qemuAgentOpenPty(const char *monitor)
-{
- int monfd;
-
- if ((monfd = open(monitor, O_RDWR | O_NONBLOCK)) < 0) {
- virReportSystemError(errno,
- _("Unable to open monitor path %s"), monitor);
- return -1;
- }
-
- if (virSetCloseExec(monfd) < 0) {
- virReportSystemError(errno, "%s",
- _("Unable to set monitor close-on-exec flag"));
- goto error;
- }
-
- return monfd;
-
- error:
- VIR_FORCE_CLOSE(monfd);
- return -1;
-}
-
-
-static int
-qemuAgentIOProcessEvent(qemuAgentPtr mon,
- virJSONValuePtr obj)
-{
- const char *type;
- 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;
- }
-
-/*
- for (i = 0; i < ARRAY_CARDINALITY(eventHandlers); i++) {
- if (STREQ(eventHandlers[i].type, type)) {
- virJSONValuePtr data = virJSONValueObjectGet(obj, "data");
- VIR_DEBUG("handle %s handler=%p data=%p", type,
- eventHandlers[i].handler, data);
- (eventHandlers[i].handler)(mon, data);
- break;
- }
- }
-*/
- return 0;
-}
-
-static int
-qemuAgentIOProcessLine(qemuAgentPtr mon,
- const char *line,
- qemuAgentMessagePtr msg)
-{
- virJSONValuePtr obj = NULL;
- int ret = -1;
-
- VIR_DEBUG("Line [%s]", line);
-
- if (!(obj = virJSONValueFromString(line))) {
- /* receiving garbage on first sync is regular situation */
- if (msg && msg->sync && msg->first) {
- VIR_DEBUG("Received garbage on sync");
- msg->finished = 1;
- return 0;
- }
-
- goto cleanup;
- }
-
- if (obj->type != VIR_JSON_TYPE_OBJECT) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("Parsed JSON reply '%s' isn't an object"),
line);
- goto cleanup;
- }
-
- if (virJSONValueObjectHasKey(obj, "QMP") == 1) {
- ret = 0;
- } else if (virJSONValueObjectHasKey(obj, "event") == 1) {
- ret = qemuAgentIOProcessEvent(mon, obj);
- } else if (virJSONValueObjectHasKey(obj, "error") == 1 ||
- virJSONValueObjectHasKey(obj, "return") == 1) {
- if (msg) {
- if (msg->sync) {
- unsigned long long id;
-
- if (virJSONValueObjectGetNumberUlong(obj, "return", &id)
< 0) {
- VIR_DEBUG("Ignoring delayed reply on sync");
- ret = 0;
- goto cleanup;
- }
-
- VIR_DEBUG("Guest returned ID: %llu", id);
-
- if (msg->id != id) {
- VIR_DEBUG("Guest agent returned ID: %llu instead of %llu",
- id, msg->id);
- ret = 0;
- goto cleanup;
- }
- }
- msg->rxObject = obj;
- msg->finished = 1;
- obj = NULL;
- } else {
- /* we are out of sync */
- VIR_DEBUG("Ignoring delayed reply");
- }
- ret = 0;
- } else {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("Unknown JSON reply '%s'"), line);
- }
-
- cleanup:
- virJSONValueFree(obj);
- return ret;
-}
-
-static int qemuAgentIOProcessData(qemuAgentPtr mon,
- char *data,
- size_t len,
- qemuAgentMessagePtr msg)
-{
- int used = 0;
- size_t i = 0;
-#if DEBUG_IO
-# if DEBUG_RAW_IO
- char *str1 = qemuAgentEscapeNonPrintable(data);
- VIR_ERROR("[%s]", str1);
- VIR_FREE(str1);
-# else
- VIR_DEBUG("Data %zu bytes [%s]", len, data);
-# endif
-#endif
-
- while (used < len) {
- char *nl = strstr(data + used, LINE_ENDING);
-
- if (nl) {
- int got = nl - (data + used);
- for (i = 0; i < strlen(LINE_ENDING); i++)
- data[used + got + i] = '\0';
- if (qemuAgentIOProcessLine(mon, data + used, msg) < 0)
- return -1;
- used += got + strlen(LINE_ENDING);
- } else {
- break;
- }
- }
-
- VIR_DEBUG("Total used %d bytes out of %zd available in buffer", used,
len);
- return used;
-}
-
-/* This method processes data that has been received
- * from the monitor. Looking for async events and
- * replies/errors.
- */
-static int
-qemuAgentIOProcess(qemuAgentPtr mon)
-{
- int len;
- qemuAgentMessagePtr msg = NULL;
-
- /* See if there's a message ready for reply; that is,
- * one that has completed writing all its data.
- */
- if (mon->msg && mon->msg->txOffset == mon->msg->txLength)
- msg = mon->msg;
-
-#if DEBUG_IO
-# if DEBUG_RAW_IO
- char *str1 = qemuAgentEscapeNonPrintable(msg ? msg->txBuffer : "");
- char *str2 = qemuAgentEscapeNonPrintable(mon->buffer);
- VIR_ERROR(_("Process %zu %p %p [[[%s]]][[[%s]]]"),
- mon->bufferOffset, mon->msg, msg, str1, str2);
- VIR_FREE(str1);
- VIR_FREE(str2);
-# else
- VIR_DEBUG("Process %zu", mon->bufferOffset);
-# endif
-#endif
-
- len = qemuAgentIOProcessData(mon,
- mon->buffer, mon->bufferOffset,
- msg);
-
- if (len < 0)
- return -1;
-
- if (len < mon->bufferOffset) {
- memmove(mon->buffer, mon->buffer + len, mon->bufferOffset - len);
- mon->bufferOffset -= len;
- } else {
- VIR_FREE(mon->buffer);
- mon->bufferOffset = mon->bufferLength = 0;
- }
-#if DEBUG_IO
- VIR_DEBUG("Process done %zu used %d", mon->bufferOffset, len);
-#endif
- if (msg && msg->finished)
- virCondBroadcast(&mon->notify);
- return len;
-}
-
-
-static int
-qemuAgentIOConnect(qemuAgentPtr mon)
-{
- int optval;
- socklen_t optlen;
-
- VIR_DEBUG("Checking on background connection status");
-
- mon->connectPending = false;
-
- optlen = sizeof(optval);
-
- if (getsockopt(mon->fd, SOL_SOCKET, SO_ERROR,
- &optval, &optlen) < 0) {
- virReportSystemError(errno, "%s",
- _("Cannot check socket connection status"));
- return -1;
- }
-
- if (optval != 0) {
- virReportSystemError(optval, "%s",
- _("Cannot connect to agent socket"));
- return -1;
- }
-
- VIR_DEBUG("Agent is now connected");
- return 0;
-}
-
-/*
- * Called when the monitor is able to write data
- * Call this function while holding the monitor lock.
- */
-static int
-qemuAgentIOWrite(qemuAgentPtr mon)
-{
- int done;
-
- /* If no active message, or fully transmitted, then no-op */
- if (!mon->msg || mon->msg->txOffset == mon->msg->txLength)
- return 0;
-
- done = safewrite(mon->fd,
- mon->msg->txBuffer + mon->msg->txOffset,
- mon->msg->txLength - mon->msg->txOffset);
-
- if (done < 0) {
- if (errno == EAGAIN)
- return 0;
-
- virReportSystemError(errno, "%s",
- _("Unable to write to monitor"));
- return -1;
- }
- mon->msg->txOffset += done;
- return done;
-}
-
-/*
- * Called when the monitor has incoming data to read
- * Call this function while holding the monitor lock.
- *
- * Returns -1 on error, or number of bytes read
- */
-static int
-qemuAgentIORead(qemuAgentPtr mon)
-{
- size_t avail = mon->bufferLength - mon->bufferOffset;
- int ret = 0;
-
- if (avail < 1024) {
- if (VIR_REALLOC_N(mon->buffer,
- mon->bufferLength + 1024) < 0)
- return -1;
- mon->bufferLength += 1024;
- avail += 1024;
- }
-
- /* Read as much as we can get into our buffer,
- until we block on EAGAIN, or hit EOF */
- while (avail > 1) {
- int got;
- got = read(mon->fd,
- mon->buffer + mon->bufferOffset,
- avail - 1);
- if (got < 0) {
- if (errno == EAGAIN)
- break;
- virReportSystemError(errno, "%s",
- _("Unable to read from monitor"));
- ret = -1;
- break;
- }
- if (got == 0)
- break;
-
- ret += got;
- avail -= got;
- mon->bufferOffset += got;
- mon->buffer[mon->bufferOffset] = '\0';
- }
-
-#if DEBUG_IO
- VIR_DEBUG("Now read %zu bytes of data", mon->bufferOffset);
-#endif
-
- return ret;
-}
-
-
-static void qemuAgentUpdateWatch(qemuAgentPtr mon)
-{
- int events =
- VIR_EVENT_HANDLE_HANGUP |
- VIR_EVENT_HANDLE_ERROR;
-
- if (mon->lastError.code == VIR_ERR_OK) {
- events |= VIR_EVENT_HANDLE_READABLE;
-
- if (mon->msg && mon->msg->txOffset <
mon->msg->txLength)
- events |= VIR_EVENT_HANDLE_WRITABLE;
- }
-
- virEventUpdateHandle(mon->watch, events);
-}
-
-
-static void
-qemuAgentIO(int watch, int fd, int events, void *opaque)
-{
- qemuAgentPtr mon = opaque;
- bool error = false;
- bool eof = false;
-
- virObjectRef(mon);
- /* lock access to the monitor and protect fd */
- virObjectLock(mon);
-#if DEBUG_IO
- VIR_DEBUG("Agent %p I/O on watch %d fd %d events %d", mon, watch, fd,
events);
-#endif
-
- if (mon->fd != fd || mon->watch != watch) {
- if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR))
- eof = true;
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("event from unexpected fd %d!=%d / watch %d!=%d"),
- mon->fd, fd, mon->watch, watch);
- error = true;
- } else if (mon->lastError.code != VIR_ERR_OK) {
- if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR))
- eof = true;
- error = true;
- } else {
- if (events & VIR_EVENT_HANDLE_WRITABLE) {
- if (mon->connectPending) {
- if (qemuAgentIOConnect(mon) < 0)
- error = true;
- } else {
- if (qemuAgentIOWrite(mon) < 0)
- error = true;
- }
- events &= ~VIR_EVENT_HANDLE_WRITABLE;
- }
-
- if (!error &&
- events & VIR_EVENT_HANDLE_READABLE) {
- int got = qemuAgentIORead(mon);
- events &= ~VIR_EVENT_HANDLE_READABLE;
- if (got < 0) {
- error = true;
- } else if (got == 0) {
- eof = true;
- } else {
- /* Ignore hangup/error events if we read some data, to
- * give time for that data to be consumed */
- events = 0;
-
- if (qemuAgentIOProcess(mon) < 0)
- error = true;
- }
- }
-
- if (!error &&
- events & VIR_EVENT_HANDLE_HANGUP) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("End of file from agent monitor"));
- eof = true;
- events &= ~VIR_EVENT_HANDLE_HANGUP;
- }
-
- if (!error && !eof &&
- events & VIR_EVENT_HANDLE_ERROR) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("Invalid file descriptor while waiting for
monitor"));
- eof = true;
- events &= ~VIR_EVENT_HANDLE_ERROR;
- }
- if (!error && events) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("Unhandled event %d for monitor fd %d"),
- events, mon->fd);
- error = true;
- }
- }
-
- if (error || eof) {
- if (mon->lastError.code != VIR_ERR_OK) {
- /* Already have an error, so clear any new error */
- virResetLastError();
- } else {
- virErrorPtr err = virGetLastError();
- if (!err)
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("Error while processing monitor IO"));
- virCopyLastError(&mon->lastError);
- virResetLastError();
- }
-
- VIR_DEBUG("Error on monitor %s", NULLSTR(mon->lastError.message));
- /* If IO process resulted in an error & we have a message,
- * then wakeup that waiter */
- if (mon->msg && !mon->msg->finished) {
- mon->msg->finished = 1;
- virCondSignal(&mon->notify);
- }
- }
-
- qemuAgentUpdateWatch(mon);
-
- /* We have to unlock to avoid deadlock against command thread,
- * but is this safe ? I think it is, because the callback
- * will try to acquire the virDomainObjPtr mutex next */
- if (eof) {
- void (*eofNotify)(qemuAgentPtr, virDomainObjPtr)
- = mon->cb->eofNotify;
- virDomainObjPtr vm = mon->vm;
-
- /* Make sure anyone waiting wakes up now */
- virCondSignal(&mon->notify);
- virObjectUnlock(mon);
- virObjectUnref(mon);
- VIR_DEBUG("Triggering EOF callback");
- (eofNotify)(mon, vm);
- } else if (error) {
- void (*errorNotify)(qemuAgentPtr, virDomainObjPtr)
- = mon->cb->errorNotify;
- virDomainObjPtr vm = mon->vm;
-
- /* Make sure anyone waiting wakes up now */
- virCondSignal(&mon->notify);
- virObjectUnlock(mon);
- virObjectUnref(mon);
- VIR_DEBUG("Triggering error callback");
- (errorNotify)(mon, vm);
- } else {
- virObjectUnlock(mon);
- virObjectUnref(mon);
- }
-}
-
-
-qemuAgentPtr
-qemuAgentOpen(virDomainObjPtr vm,
- const virDomainChrSourceDef *config,
- qemuAgentCallbacksPtr cb)
-{
- qemuAgentPtr mon;
-
- if (!cb || !cb->eofNotify) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("EOF notify callback must be supplied"));
- return NULL;
- }
-
- if (qemuAgentInitialize() < 0)
- return NULL;
-
- if (!(mon = virObjectLockableNew(qemuAgentClass)))
- return NULL;
-
- mon->fd = -1;
- if (virCondInit(&mon->notify) < 0) {
- virReportSystemError(errno, "%s",
- _("cannot initialize monitor condition"));
- virObjectUnref(mon);
- return NULL;
- }
- mon->vm = vm;
- mon->cb = cb;
-
- switch (config->type) {
- case VIR_DOMAIN_CHR_TYPE_UNIX:
- mon->fd = qemuAgentOpenUnix(config->data.nix.path, vm->pid,
- &mon->connectPending);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_PTY:
- mon->fd = qemuAgentOpenPty(config->data.file.path);
- break;
-
- default:
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("unable to handle monitor type: %s"),
- virDomainChrTypeToString(config->type));
- goto cleanup;
- }
-
- if (mon->fd == -1)
- goto cleanup;
-
- virObjectRef(mon);
- if ((mon->watch = virEventAddHandle(mon->fd,
- VIR_EVENT_HANDLE_HANGUP |
- VIR_EVENT_HANDLE_ERROR |
- VIR_EVENT_HANDLE_READABLE |
- (mon->connectPending ?
- VIR_EVENT_HANDLE_WRITABLE :
- 0),
- qemuAgentIO,
- mon,
- virObjectFreeCallback)) < 0) {
- virObjectUnref(mon);
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("unable to register monitor events"));
- goto cleanup;
- }
-
- mon->running = true;
- VIR_DEBUG("New mon %p fd =%d watch=%d", mon, mon->fd, mon->watch);
-
- return mon;
-
- cleanup:
- /* We don't want the 'destroy' callback invoked during
- * cleanup from construction failure, because that can
- * give a double-unref on virDomainObjPtr in the caller,
- * so kill the callbacks now.
- */
- mon->cb = NULL;
- qemuAgentClose(mon);
- return NULL;
-}
-
-
-static void
-qemuAgentNotifyCloseLocked(qemuAgentPtr mon)
-{
- if (mon) {
- mon->running = false;
-
- /* If there is somebody waiting for a message
- * wake him up. No message will arrive anyway. */
- if (mon->msg && !mon->msg->finished) {
- mon->msg->finished = 1;
- virCondSignal(&mon->notify);
- }
- }
-}
-
-
-void
-qemuAgentNotifyClose(qemuAgentPtr mon)
-{
- if (!mon)
- return;
-
- VIR_DEBUG("mon=%p", mon);
-
- virObjectLock(mon);
- qemuAgentNotifyCloseLocked(mon);
- virObjectUnlock(mon);
-}
-
-
-void qemuAgentClose(qemuAgentPtr mon)
-{
- if (!mon)
- return;
-
- VIR_DEBUG("mon=%p", mon);
-
- virObjectLock(mon);
-
- if (mon->fd >= 0) {
- if (mon->watch)
- virEventRemoveHandle(mon->watch);
- VIR_FORCE_CLOSE(mon->fd);
- }
-
- qemuAgentNotifyCloseLocked(mon);
- virObjectUnlock(mon);
-
- virObjectUnref(mon);
-}
-
-#define QEMU_AGENT_WAIT_TIME 5
-
-/**
- * qemuAgentSend:
- * @mon: Monitor
- * @msg: Message
- * @seconds: number of seconds to wait for the result, it can be either
- * -2, -1, 0 or positive.
- *
- * Send @msg to agent @mon. If @seconds is equal to
- * VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK(-2), this function will block forever
- * waiting for the result. The value of
- * VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT(-1) means use default timeout value
- * and VIR_DOMAIN_QEMU_AGENT_COMMAND_NOWAIT(0) makes this this function return
- * immediately without waiting. Any positive value means the number of seconds
- * to wait for the result.
- *
- * Returns: 0 on success,
- * -2 on timeout,
- * -1 otherwise
- */
-static int qemuAgentSend(qemuAgentPtr mon,
- qemuAgentMessagePtr msg,
- int seconds)
-{
- int ret = -1;
- unsigned long long then = 0;
-
- /* Check whether qemu quit unexpectedly */
- if (mon->lastError.code != VIR_ERR_OK) {
- VIR_DEBUG("Attempt to send command while error is set %s",
- NULLSTR(mon->lastError.message));
- virSetError(&mon->lastError);
- return -1;
- }
-
- if (seconds > VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) {
- unsigned long long now;
- if (virTimeMillisNow(&now) < 0)
- return -1;
- if (seconds == VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT)
- seconds = QEMU_AGENT_WAIT_TIME;
- then = now + seconds * 1000ull;
- }
-
- mon->msg = msg;
- qemuAgentUpdateWatch(mon);
-
- while (!mon->msg->finished) {
- if ((then && virCondWaitUntil(&mon->notify,
&mon->parent.lock, then) < 0) ||
- (!then && virCondWait(&mon->notify, &mon->parent.lock)
< 0)) {
- if (errno == ETIMEDOUT) {
- virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
- _("Guest agent not available for now"));
- ret = -2;
- } else {
- virReportSystemError(errno, "%s",
- _("Unable to wait on agent monitor "
- "condition"));
- }
- goto cleanup;
- }
- }
-
- if (mon->lastError.code != VIR_ERR_OK) {
- VIR_DEBUG("Send command resulted in error %s",
- NULLSTR(mon->lastError.message));
- virSetError(&mon->lastError);
- goto cleanup;
- }
-
- ret = 0;
-
- cleanup:
- mon->msg = NULL;
- qemuAgentUpdateWatch(mon);
-
- return ret;
-}
-
-
-/**
- * qemuAgentGuestSync:
- * @mon: Monitor
- *
- * Send guest-sync with unique ID
- * and wait for reply. If we get one, check if
- * received ID is equal to given.
- *
- * Returns: 0 on success,
- * -1 otherwise
- */
-static int
-qemuAgentGuestSync(qemuAgentPtr mon)
-{
- int ret = -1;
- int send_ret;
- unsigned long long id;
- qemuAgentMessage sync_msg;
-
- memset(&sync_msg, 0, sizeof(sync_msg));
- /* set only on first sync */
- sync_msg.first = true;
-
- retry:
- if (virTimeMillisNow(&id) < 0)
- return -1;
-
- if (virAsprintf(&sync_msg.txBuffer,
- "{\"execute\":\"guest-sync\", "
- "\"arguments\":{\"id\":%llu}}\n", id)
< 0)
- return -1;
-
- sync_msg.txLength = strlen(sync_msg.txBuffer);
- sync_msg.sync = true;
- sync_msg.id = id;
-
- VIR_DEBUG("Sending guest-sync command with ID: %llu", id);
-
- send_ret = qemuAgentSend(mon, &sync_msg,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT);
-
- VIR_DEBUG("qemuAgentSend returned: %d", send_ret);
-
- if (send_ret < 0)
- goto cleanup;
-
- if (!sync_msg.rxObject) {
- if (sync_msg.first) {
- VIR_FREE(sync_msg.txBuffer);
- memset(&sync_msg, 0, sizeof(sync_msg));
- goto retry;
- } else {
- if (mon->running)
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("Missing monitor reply object"));
- else
- virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
- _("Guest agent disappeared while executing
command"));
- goto cleanup;
- }
- }
-
- ret = 0;
-
- cleanup:
- virJSONValueFree(sync_msg.rxObject);
- VIR_FREE(sync_msg.txBuffer);
- return ret;
-}
-
-static const char *
-qemuAgentStringifyErrorClass(const char *klass)
-{
- if (STREQ_NULLABLE(klass, "BufferOverrun"))
- return "Buffer overrun";
- else if (STREQ_NULLABLE(klass, "CommandDisabled"))
- return "The command has been disabled for this instance";
- else if (STREQ_NULLABLE(klass, "CommandNotFound"))
- return "The command has not been found";
- else if (STREQ_NULLABLE(klass, "FdNotFound"))
- return "File descriptor not found";
- else if (STREQ_NULLABLE(klass, "InvalidParameter"))
- return "Invalid parameter";
- else if (STREQ_NULLABLE(klass, "InvalidParameterType"))
- return "Invalid parameter type";
- else if (STREQ_NULLABLE(klass, "InvalidParameterValue"))
- return "Invalid parameter value";
- else if (STREQ_NULLABLE(klass, "OpenFileFailed"))
- return "Cannot open file";
- else if (STREQ_NULLABLE(klass, "QgaCommandFailed"))
- return "Guest agent command failed";
- else if (STREQ_NULLABLE(klass, "QMPBadInputObjectMember"))
- return "Bad QMP input object member";
- else if (STREQ_NULLABLE(klass, "QMPExtraInputObjectMember"))
- return "Unexpected extra object member";
- else if (STREQ_NULLABLE(klass, "UndefinedError"))
- return "An undefined error has occurred";
- else if (STREQ_NULLABLE(klass, "Unsupported"))
- return "this feature or command is not currently supported";
- else if (klass)
- return klass;
- else
- return "unknown QEMU command error";
-}
-
-/* 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 *
-qemuAgentStringifyError(virJSONValuePtr error)
-{
- const char *klass = virJSONValueObjectGetString(error, "class");
- const char *detail = virJSONValueObjectGetString(error, "desc");
-
- /* The QMP 'desc' field is usually sufficient for our generic
- * error reporting needs. However, if not present, translate
- * the class into something readable.
- */
- if (!detail)
- detail = qemuAgentStringifyErrorClass(klass);
-
- return detail;
-}
-
-static const char *
-qemuAgentCommandName(virJSONValuePtr cmd)
-{
- const char *name = virJSONValueObjectGetString(cmd, "execute");
- if (name)
- return name;
- else
- return "<unknown>";
-}
-
-static int
-qemuAgentCheckError(virJSONValuePtr cmd,
- virJSONValuePtr reply)
-{
- if (virJSONValueObjectHasKey(reply, "error")) {
- virJSONValuePtr error = virJSONValueObjectGet(reply, "error");
- char *cmdstr = virJSONValueToString(cmd, false);
- char *replystr = virJSONValueToString(reply, false);
-
- /* Log the full JSON formatted command & error */
- VIR_DEBUG("unable to execute QEMU agent command %s: %s",
- NULLSTR(cmdstr), NULLSTR(replystr));
-
- /* Only send the user the command name + friendly error */
- if (!error)
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("unable to execute QEMU agent command
'%s'"),
- qemuAgentCommandName(cmd));
- else
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("unable to execute QEMU agent command '%s':
%s"),
- qemuAgentCommandName(cmd),
- qemuAgentStringifyError(error));
-
- VIR_FREE(cmdstr);
- VIR_FREE(replystr);
- return -1;
- } else if (!virJSONValueObjectHasKey(reply, "return")) {
- char *cmdstr = virJSONValueToString(cmd, false);
- char *replystr = virJSONValueToString(reply, false);
-
- VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON
reply %s: %s",
- NULLSTR(cmdstr), NULLSTR(replystr));
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("unable to execute QEMU agent command '%s'"),
- qemuAgentCommandName(cmd));
- VIR_FREE(cmdstr);
- VIR_FREE(replystr);
- return -1;
- }
- return 0;
-}
-
-static int
-qemuAgentCommand(qemuAgentPtr mon,
- virJSONValuePtr cmd,
- virJSONValuePtr *reply,
- bool needReply,
- int seconds)
-{
- int ret = -1;
- qemuAgentMessage msg;
- char *cmdstr = NULL;
- int await_event = mon->await_event;
-
- *reply = NULL;
-
- if (!mon->running) {
- virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
- _("Guest agent disappeared while executing command"));
- return -1;
- }
-
- if (qemuAgentGuestSync(mon) < 0)
- return -1;
-
- memset(&msg, 0, sizeof(msg));
-
- if (!(cmdstr = virJSONValueToString(cmd, false)))
- goto cleanup;
- if (virAsprintf(&msg.txBuffer, "%s" LINE_ENDING, cmdstr) < 0)
- goto cleanup;
- msg.txLength = strlen(msg.txBuffer);
-
- VIR_DEBUG("Send command '%s' for write, seconds = %d", cmdstr,
seconds);
-
- ret = qemuAgentSend(mon, &msg, seconds);
-
- VIR_DEBUG("Receive command reply ret=%d rxObject=%p",
- ret, msg.rxObject);
-
- if (ret == 0) {
- /* If we haven't obtained any reply but we wait for an
- * event, then don't report this as error */
- if (!msg.rxObject) {
- if (await_event && !needReply) {
- VIR_DEBUG("Woken up by event %d", await_event);
- } else {
- if (mon->running)
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("Missing monitor reply object"));
- else
- virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
- _("Guest agent disappeared while executing
command"));
- ret = -1;
- }
- } else {
- *reply = msg.rxObject;
- ret = qemuAgentCheckError(cmd, *reply);
- }
- }
-
- cleanup:
- VIR_FREE(cmdstr);
- VIR_FREE(msg.txBuffer);
-
- return ret;
-}
-
-static virJSONValuePtr ATTRIBUTE_SENTINEL
-qemuAgentMakeCommand(const char *cmdname,
- ...)
-{
- virJSONValuePtr obj;
- virJSONValuePtr jargs = NULL;
- va_list args;
-
- va_start(args, cmdname);
-
- if (!(obj = virJSONValueNewObject()))
- goto error;
-
- if (virJSONValueObjectAppendString(obj, "execute", cmdname) < 0)
- goto error;
-
- if (virJSONValueObjectCreateVArgs(&jargs, args) < 0)
- goto error;
-
- if (jargs &&
- virJSONValueObjectAppend(obj, "arguments", jargs) < 0)
- goto error;
-
- va_end(args);
-
- return obj;
-
- error:
- virJSONValueFree(obj);
- virJSONValueFree(jargs);
- va_end(args);
- return NULL;
-}
-
-static virJSONValuePtr
-qemuAgentMakeStringsArray(const char **strings, unsigned int len)
-{
- size_t i;
- virJSONValuePtr ret = virJSONValueNewArray(), str;
-
- if (!ret)
- return NULL;
-
- for (i = 0; i < len; i++) {
- str = virJSONValueNewString(strings[i]);
- if (!str)
- goto error;
-
- if (virJSONValueArrayAppend(ret, str) < 0) {
- virJSONValueFree(str);
- goto error;
- }
- }
- return ret;
-
- error:
- virJSONValueFree(ret);
- return NULL;
-}
-
-void qemuAgentNotifyEvent(qemuAgentPtr mon,
- qemuAgentEvent event)
-{
- virObjectLock(mon);
-
- VIR_DEBUG("mon=%p event=%d await_event=%d", mon, event,
mon->await_event);
- if (mon->await_event == event) {
- mon->await_event = QEMU_AGENT_EVENT_NONE;
- /* somebody waiting for this event, wake him up. */
- if (mon->msg && !mon->msg->finished) {
- mon->msg->finished = 1;
- virCondSignal(&mon->notify);
- }
- }
-
- virObjectUnlock(mon);
-}
-
-VIR_ENUM_DECL(qemuAgentShutdownMode);
-
-VIR_ENUM_IMPL(qemuAgentShutdownMode,
- QEMU_AGENT_SHUTDOWN_LAST,
- "powerdown", "reboot", "halt");
-
-int qemuAgentShutdown(qemuAgentPtr mon,
- qemuAgentShutdownMode mode)
-{
- int ret = -1;
- virJSONValuePtr cmd;
- virJSONValuePtr reply = NULL;
-
- cmd = qemuAgentMakeCommand("guest-shutdown",
- "s:mode",
qemuAgentShutdownModeTypeToString(mode),
- NULL);
- if (!cmd)
- return -1;
-
- if (mode == QEMU_AGENT_SHUTDOWN_REBOOT)
- mon->await_event = QEMU_AGENT_EVENT_RESET;
- else
- mon->await_event = QEMU_AGENT_EVENT_SHUTDOWN;
- ret = qemuAgentCommand(mon, cmd, &reply, false,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_SHUTDOWN);
-
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-/*
- * qemuAgentFSFreeze:
- * @mon: Agent
- * @mountpoints: Array of mountpoint paths to be frozen, or NULL for all
- * @nmountpoints: Number of mountpoints to be frozen, or 0 for all
- *
- * Issue guest-fsfreeze-freeze command to guest agent,
- * which freezes file systems mounted on specified mountpoints
- * (or all file systems when @mountpoints is NULL), and returns
- * number of frozen file systems on success.
- *
- * Returns: number of file system frozen on success,
- * -1 on error.
- */
-int qemuAgentFSFreeze(qemuAgentPtr mon, const char **mountpoints,
- unsigned int nmountpoints)
-{
- int ret = -1;
- virJSONValuePtr cmd, arg = NULL;
- virJSONValuePtr reply = NULL;
-
- if (mountpoints && nmountpoints) {
- arg = qemuAgentMakeStringsArray(mountpoints, nmountpoints);
- if (!arg)
- return -1;
-
- cmd = qemuAgentMakeCommand("guest-fsfreeze-freeze-list",
- "a:mountpoints", arg, NULL);
- } else {
- cmd = qemuAgentMakeCommand("guest-fsfreeze-freeze", NULL);
- }
-
- if (!cmd)
- goto cleanup;
- arg = NULL;
-
- if (qemuAgentCommand(mon, cmd, &reply, true,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
- goto cleanup;
-
- if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("malformed return value"));
- }
-
- cleanup:
- virJSONValueFree(arg);
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-/*
- * qemuAgentFSThaw:
- * @mon: Agent
- *
- * Issue guest-fsfreeze-thaw command to guest agent,
- * which unfreezes all mounted file systems and returns
- * number of thawed file systems on success.
- *
- * Returns: number of file system thawed on success,
- * -1 on error.
- */
-int qemuAgentFSThaw(qemuAgentPtr mon)
-{
- int ret = -1;
- virJSONValuePtr cmd;
- virJSONValuePtr reply = NULL;
-
- cmd = qemuAgentMakeCommand("guest-fsfreeze-thaw", NULL);
-
- if (!cmd)
- return -1;
-
- if (qemuAgentCommand(mon, cmd, &reply, true,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
- goto cleanup;
-
- if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("malformed return value"));
- }
-
- cleanup:
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-VIR_ENUM_DECL(qemuAgentSuspendMode);
-
-VIR_ENUM_IMPL(qemuAgentSuspendMode,
- VIR_NODE_SUSPEND_TARGET_LAST,
- "guest-suspend-ram",
- "guest-suspend-disk",
- "guest-suspend-hybrid");
-
-int
-qemuAgentSuspend(qemuAgentPtr mon,
- unsigned int target)
-{
- int ret = -1;
- virJSONValuePtr cmd;
- virJSONValuePtr reply = NULL;
-
- cmd = qemuAgentMakeCommand(qemuAgentSuspendModeTypeToString(target),
- NULL);
- if (!cmd)
- return -1;
-
- mon->await_event = QEMU_AGENT_EVENT_SUSPEND;
- ret = qemuAgentCommand(mon, cmd, &reply, false,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK);
-
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-int
-qemuAgentArbitraryCommand(qemuAgentPtr mon,
- const char *cmd_str,
- char **result,
- int timeout)
-{
- int ret = -1;
- virJSONValuePtr cmd = NULL;
- virJSONValuePtr reply = NULL;
-
- *result = NULL;
- if (timeout < VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN) {
- virReportError(VIR_ERR_INVALID_ARG,
- _("guest agent timeout '%d' is "
- "less than the minimum '%d'"),
- timeout, VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN);
- goto cleanup;
- }
-
- if (!(cmd = virJSONValueFromString(cmd_str)))
- goto cleanup;
-
- if ((ret = qemuAgentCommand(mon, cmd, &reply, true, timeout)) < 0)
- goto cleanup;
-
- if (!(*result = virJSONValueToString(reply, false)))
- ret = -1;
-
-
- cleanup:
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-int
-qemuAgentFSTrim(qemuAgentPtr mon,
- unsigned long long minimum)
-{
- int ret = -1;
- virJSONValuePtr cmd;
- virJSONValuePtr reply = NULL;
-
- cmd = qemuAgentMakeCommand("guest-fstrim",
- "U:minimum", minimum,
- NULL);
- if (!cmd)
- return ret;
-
- ret = qemuAgentCommand(mon, cmd, &reply, false,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK);
-
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-int
-qemuAgentGetVCPUs(qemuAgentPtr mon,
- qemuAgentCPUInfoPtr *info)
-{
- int ret = -1;
- size_t i;
- virJSONValuePtr cmd;
- virJSONValuePtr reply = NULL;
- virJSONValuePtr data = NULL;
- ssize_t ndata;
-
- if (!(cmd = qemuAgentMakeCommand("guest-get-vcpus", NULL)))
- return -1;
-
- if (qemuAgentCommand(mon, cmd, &reply, true,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
- goto cleanup;
-
- if (!(data = virJSONValueObjectGet(reply, "return"))) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("guest-get-vcpus reply was missing return data"));
- goto cleanup;
- }
-
- if (data->type != VIR_JSON_TYPE_ARRAY) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("guest-get-vcpus return information was not an
array"));
- goto cleanup;
- }
-
- ndata = virJSONValueArraySize(data);
-
- if (VIR_ALLOC_N(*info, ndata) < 0)
- goto cleanup;
-
- for (i = 0; i < ndata; i++) {
- virJSONValuePtr entry = virJSONValueArrayGet(data, i);
- qemuAgentCPUInfoPtr in = *info + i;
-
- if (!entry) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("array element missing in guest-get-vcpus return
"
- "value"));
- goto cleanup;
- }
-
- if (virJSONValueObjectGetNumberUint(entry, "logical-id",
&in->id) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("'logical-id' missing in reply of
guest-get-vcpus"));
- goto cleanup;
- }
-
- if (virJSONValueObjectGetBoolean(entry, "online", &in->online)
< 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("'online' missing in reply of
guest-get-vcpus"));
- goto cleanup;
- }
-
- if (virJSONValueObjectGetBoolean(entry, "can-offline",
- &in->offlinable) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("'can-offline' missing in reply of
guest-get-vcpus"));
- goto cleanup;
- }
- }
-
- ret = ndata;
-
- cleanup:
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-
-/* returns the value provided by the guest agent or -1 on internal error */
-static int
-qemuAgentSetVCPUsCommand(qemuAgentPtr mon,
- qemuAgentCPUInfoPtr info,
- size_t ninfo,
- int *nmodified)
-{
- int ret = -1;
- virJSONValuePtr cmd = NULL;
- virJSONValuePtr reply = NULL;
- virJSONValuePtr cpus = NULL;
- virJSONValuePtr cpu = NULL;
- size_t i;
-
- *nmodified = 0;
-
- /* create the key data array */
- if (!(cpus = virJSONValueNewArray()))
- goto cleanup;
-
- for (i = 0; i < ninfo; i++) {
- qemuAgentCPUInfoPtr in = &info[i];
-
- /* don't set state for cpus that were not touched */
- if (!in->modified)
- continue;
-
- (*nmodified)++;
-
- /* create single cpu object */
- if (!(cpu = virJSONValueNewObject()))
- goto cleanup;
-
- if (virJSONValueObjectAppendNumberInt(cpu, "logical-id", in->id)
< 0)
- goto cleanup;
-
- if (virJSONValueObjectAppendBoolean(cpu, "online", in->online) <
0)
- goto cleanup;
-
- if (virJSONValueArrayAppend(cpus, cpu) < 0)
- goto cleanup;
-
- cpu = NULL;
- }
-
- if (*nmodified == 0) {
- ret = 0;
- goto cleanup;
- }
-
- if (!(cmd = qemuAgentMakeCommand("guest-set-vcpus",
- "a:vcpus", cpus,
- NULL)))
- goto cleanup;
-
- cpus = NULL;
-
- if (qemuAgentCommand(mon, cmd, &reply, true,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
- goto cleanup;
-
- if (qemuAgentCheckError(cmd, reply) < 0)
- goto cleanup;
-
- /* All negative values are invalid. Return of 0 is bogus since we wouldn't
- * call the guest agent so that 0 cpus would be set successfully. Reporting
- * more successfully set vcpus that we've asked for is invalid. */
- if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0 ||
- ret <= 0 || ret > *nmodified) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("guest agent returned malformed or invalid return
value"));
- ret = -1;
- }
-
- cleanup:
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- virJSONValueFree(cpu);
- virJSONValueFree(cpus);
- return ret;
-}
-
-
-/**
- * Set the VCPU state using guest agent.
- *
- * Attempts to set the guest agent state for all cpus or until a proper error is
- * reported by the guest agent. This may require multiple calls.
- *
- * Returns -1 on error, 0 on success.
- */
-int
-qemuAgentSetVCPUs(qemuAgentPtr mon,
- qemuAgentCPUInfoPtr info,
- size_t ninfo)
-{
- int rv;
- int nmodified;
- size_t i;
-
- do {
- if ((rv = qemuAgentSetVCPUsCommand(mon, info, ninfo, &nmodified)) < 0)
- return -1;
-
- /* all vcpus were set successfully */
- if (rv == nmodified)
- return 0;
-
- /* un-mark vcpus that were already set */
- for (i = 0; i < ninfo && rv > 0; i++) {
- if (!info[i].modified)
- continue;
-
- info[i].modified = false;
- rv--;
- }
- } while (1);
-
- return 0;
-}
-
-
-/* modify the cpu info structure to set the correct amount of cpus */
-int
-qemuAgentUpdateCPUInfo(unsigned int nvcpus,
- qemuAgentCPUInfoPtr cpuinfo,
- int ncpuinfo)
-{
- size_t i;
- int nonline = 0;
- int nofflinable = 0;
- ssize_t cpu0 = -1;
-
- /* count the active and offlinable cpus */
- for (i = 0; i < ncpuinfo; i++) {
- if (cpuinfo[i].id == 0)
- cpu0 = i;
-
- if (cpuinfo[i].online)
- nonline++;
-
- if (cpuinfo[i].offlinable && cpuinfo[i].online)
- nofflinable++;
-
- /* This shouldn't happen, but we can't trust the guest agent */
- if (!cpuinfo[i].online && !cpuinfo[i].offlinable) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("Invalid data provided by guest agent"));
- return -1;
- }
- }
-
- /* CPU0 was made offlinable in linux a while ago, but certain parts (suspend
- * to ram) of the kernel still don't cope well with that. Make sure that if
- * all remaining vCPUs are offlinable, vCPU0 will not be selected to be
- * offlined automatically */
- if (nofflinable == nonline && cpu0 >= 0 && cpuinfo[cpu0].online)
{
- cpuinfo[cpu0].offlinable = false;
- nofflinable--;
- }
-
- /* the guest agent reported less cpus than requested */
- if (nvcpus > ncpuinfo) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("guest agent reports less cpu than requested"));
- return -1;
- }
-
- /* not enough offlinable CPUs to support the request */
- if (nvcpus < nonline - nofflinable) {
- virReportError(VIR_ERR_INVALID_ARG, "%s",
- _("Cannot offline enough CPUs"));
- return -1;
- }
-
- for (i = 0; i < ncpuinfo; i++) {
- if (nvcpus < nonline) {
- /* unplug */
- if (cpuinfo[i].offlinable && cpuinfo[i].online) {
- cpuinfo[i].online = false;
- cpuinfo[i].modified = true;
- nonline--;
- }
- } else if (nvcpus > nonline) {
- /* plug */
- if (!cpuinfo[i].online) {
- cpuinfo[i].online = true;
- cpuinfo[i].modified = true;
- nonline++;
- }
- } else {
- /* done */
- break;
- }
- }
-
- return 0;
-}
-
-
-int
-qemuAgentGetTime(qemuAgentPtr mon,
- long long *seconds,
- unsigned int *nseconds)
-{
- int ret = -1;
- unsigned long long json_time;
- virJSONValuePtr cmd;
- virJSONValuePtr reply = NULL;
-
- cmd = qemuAgentMakeCommand("guest-get-time",
- NULL);
- if (!cmd)
- return ret;
-
- if (qemuAgentCommand(mon, cmd, &reply, true,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
- goto cleanup;
-
- if (virJSONValueObjectGetNumberUlong(reply, "return", &json_time) <
0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("malformed return value"));
- goto cleanup;
- }
-
- /* guest agent returns time in nanoseconds,
- * we need it in seconds here */
- *seconds = json_time / 1000000000LL;
- *nseconds = json_time % 1000000000LL;
- ret = 0;
-
- cleanup:
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-
-/**
- * qemuAgentSetTime:
- * @setTime: time to set
- * @sync: let guest agent to read domain's RTC (@setTime is ignored)
- */
-int
-qemuAgentSetTime(qemuAgentPtr mon,
- long long seconds,
- unsigned int nseconds,
- bool rtcSync)
-{
- int ret = -1;
- virJSONValuePtr cmd;
- virJSONValuePtr reply = NULL;
-
- if (rtcSync) {
- cmd = qemuAgentMakeCommand("guest-set-time", NULL);
- } else {
- /* guest agent expect time with nanosecond granularity.
- * Impressing. */
- long long json_time;
-
- /* Check if we overflow. For some reason qemu doesn't handle unsigned
- * long long on the monitor well as it silently truncates numbers to
- * signed long long. Therefore we must check overflow against LLONG_MAX
- * not ULLONG_MAX. */
- if (seconds > LLONG_MAX / 1000000000LL) {
- virReportError(VIR_ERR_INVALID_ARG,
- _("Time '%lld' is too big for guest
agent"),
- seconds);
- return ret;
- }
-
- json_time = seconds * 1000000000LL;
- json_time += nseconds;
- cmd = qemuAgentMakeCommand("guest-set-time",
- "I:time", json_time,
- NULL);
- }
-
- if (!cmd)
- return ret;
-
- if (qemuAgentCommand(mon, cmd, &reply, true,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
- goto cleanup;
-
- ret = 0;
- cleanup:
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-
-int
-qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info,
- virDomainDefPtr vmdef)
-{
- size_t i, j, k;
- int ret = -1;
- ssize_t ndata = 0, ndisk;
- char **alias;
- virJSONValuePtr cmd;
- virJSONValuePtr reply = NULL;
- virJSONValuePtr data;
- virDomainFSInfoPtr *info_ret = NULL;
- virPCIDeviceAddress pci_address;
-
- cmd = qemuAgentMakeCommand("guest-get-fsinfo", NULL);
- if (!cmd)
- return ret;
-
- if (qemuAgentCommand(mon, cmd, &reply, true,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
- goto cleanup;
-
- if (!(data = virJSONValueObjectGet(reply, "return"))) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("guest-get-fsinfo reply was missing return data"));
- goto cleanup;
- }
-
- if (data->type != VIR_JSON_TYPE_ARRAY) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("guest-get-fsinfo return information was not "
- "an array"));
- goto cleanup;
- }
-
- ndata = virJSONValueArraySize(data);
- if (!ndata) {
- ret = 0;
- *info = NULL;
- goto cleanup;
- }
- if (VIR_ALLOC_N(info_ret, ndata) < 0)
- goto cleanup;
-
- for (i = 0; i < ndata; i++) {
- /* Reverse the order to arrange in mount order */
- virJSONValuePtr entry = virJSONValueArrayGet(data, ndata - 1 - i);
-
- if (!entry) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("array element '%zd' of '%zd' missing
in "
- "guest-get-fsinfo return data"),
- i, ndata);
- goto cleanup;
- }
-
- if (VIR_ALLOC(info_ret[i]) < 0)
- goto cleanup;
-
- if (VIR_STRDUP(info_ret[i]->mountpoint,
- virJSONValueObjectGetString(entry, "mountpoint")) <
0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("'mountpoint' missing in reply of "
- "guest-get-fsinfo"));
- goto cleanup;
- }
-
- if (VIR_STRDUP(info_ret[i]->name,
- virJSONValueObjectGetString(entry, "name")) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("'name' missing in reply of
guest-get-fsinfo"));
- goto cleanup;
- }
-
- if (VIR_STRDUP(info_ret[i]->fstype,
- virJSONValueObjectGetString(entry, "type")) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("'type' missing in reply of
guest-get-fsinfo"));
- goto cleanup;
- }
-
- if (!(entry = virJSONValueObjectGet(entry, "disk"))) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("'disk' missing in reply of
guest-get-fsinfo"));
- goto cleanup;
- }
-
- if (entry->type != VIR_JSON_TYPE_ARRAY) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("guest-get-fsinfo 'disk' data was not an
array"));
- goto cleanup;
- }
-
- ndisk = virJSONValueArraySize(entry);
- if (!ndisk)
- continue;
- if (VIR_ALLOC_N(info_ret[i]->devAlias, ndisk) < 0)
- goto cleanup;
-
- alias = info_ret[i]->devAlias;
- info_ret[i]->ndevAlias = 0;
- for (j = 0; j < ndisk; j++) {
- virJSONValuePtr disk = virJSONValueArrayGet(entry, j);
- virJSONValuePtr pci;
- int diskaddr[3], pciaddr[4];
- const char *diskaddr_comp[] = {"bus", "target",
"unit"};
- const char *pciaddr_comp[] = {"domain", "bus",
"slot", "function"};
- virDomainDiskDefPtr diskDef;
-
- if (!disk) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("array element '%zd' of '%zd'
missing in "
- "guest-get-fsinfo 'disk' data"),
- j, ndisk);
- goto cleanup;
- }
-
- if (!(pci = virJSONValueObjectGet(disk, "pci-controller"))) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("'pci-controller' missing in
guest-get-fsinfo "
- "'disk' data"));
- goto cleanup;
- }
-
- for (k = 0; k < 3; k++) {
- if (virJSONValueObjectGetNumberInt(
- disk, diskaddr_comp[k], &diskaddr[k]) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("'%s' missing in guest-get-fsinfo
"
- "'disk' data"),
diskaddr_comp[k]);
- goto cleanup;
- }
- }
- for (k = 0; k < 4; k++) {
- if (virJSONValueObjectGetNumberInt(
- pci, pciaddr_comp[k], &pciaddr[k]) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("'%s' missing in guest-get-fsinfo
"
- "'pci-address' data"),
pciaddr_comp[k]);
- goto cleanup;
- }
- }
-
- pci_address.domain = pciaddr[0];
- pci_address.bus = pciaddr[1];
- pci_address.slot = pciaddr[2];
- pci_address.function = pciaddr[3];
- if (!(diskDef = virDomainDiskByAddress(
- vmdef, &pci_address,
- diskaddr[0], diskaddr[1], diskaddr[2])))
- continue;
-
- if (VIR_STRDUP(*alias, diskDef->dst) < 0)
- goto cleanup;
-
- if (*alias) {
- alias++;
- info_ret[i]->ndevAlias++;
- }
- }
- }
-
- *info = info_ret;
- info_ret = NULL;
- ret = ndata;
-
- cleanup:
- if (info_ret) {
- for (i = 0; i < ndata; i++)
- virDomainFSInfoFree(info_ret[i]);
- VIR_FREE(info_ret);
- }
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- return ret;
-}
-
-/*
- * qemuAgentGetInterfaces:
- * @mon: Agent monitor
- * @ifaces: pointer to an array of pointers pointing to interface objects
- *
- * Issue guest-network-get-interfaces to guest agent, which returns a
- * list of interfaces of a running domain along with their IP and MAC
- * addresses.
- *
- * Returns: number of interfaces on success, -1 on error.
- */
-int
-qemuAgentGetInterfaces(qemuAgentPtr mon,
- virDomainInterfacePtr **ifaces)
-{
- int ret = -1;
- size_t i, j;
- ssize_t size = -1;
- virJSONValuePtr cmd = NULL;
- virJSONValuePtr reply = NULL;
- virJSONValuePtr ret_array = NULL;
- size_t ifaces_count = 0;
- size_t addrs_count = 0;
- virDomainInterfacePtr *ifaces_ret = NULL;
- virHashTablePtr ifaces_store = NULL;
- char **ifname = NULL;
-
- /* Hash table to handle the interface alias */
- if (!(ifaces_store = virHashCreate(ifaces_count, NULL))) {
- virHashFree(ifaces_store);
- return -1;
- }
-
- if (!(cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL)))
- goto cleanup;
-
- if (qemuAgentCommand(mon, cmd, &reply, false,
VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0 ||
- qemuAgentCheckError(cmd, reply) < 0) {
- goto cleanup;
- }
-
- if (!(ret_array = virJSONValueObjectGet(reply, "return"))) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("qemu agent didn't provide 'return'
field"));
- goto cleanup;
- }
-
- if ((size = virJSONValueArraySize(ret_array)) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("qemu agent didn't return an array of
interfaces"));
- goto cleanup;
- }
-
- for (i = 0; i < size; i++) {
- virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i);
- virJSONValuePtr ip_addr_arr = NULL;
- const char *hwaddr, *ifname_s, *name = NULL;
- ssize_t ip_addr_arr_size;
- virDomainInterfacePtr iface = NULL;
-
- /* Shouldn't happen but doesn't hurt to check neither */
- if (!tmp_iface) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("qemu agent reply missing interface entry in
array"));
- goto error;
- }
-
- /* interface name is required to be presented */
- name = virJSONValueObjectGetString(tmp_iface, "name");
- if (!name) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("qemu agent didn't provide 'name'
field"));
- goto error;
- }
-
- /* Handle interface alias (<ifname>:<alias>) */
- ifname = virStringSplit(name, ":", 2);
- ifname_s = ifname[0];
-
- iface = virHashLookup(ifaces_store, ifname_s);
-
- /* If the hash table doesn't contain this iface, add it */
- if (!iface) {
- if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0)
- goto error;
-
- if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0)
- goto error;
-
- if (virHashAddEntry(ifaces_store, ifname_s,
- ifaces_ret[ifaces_count - 1]) < 0)
- goto error;
-
- iface = ifaces_ret[ifaces_count - 1];
- iface->naddrs = 0;
-
- if (VIR_STRDUP(iface->name, ifname_s) < 0)
- goto error;
-
- hwaddr = virJSONValueObjectGetString(tmp_iface,
"hardware-address");
- if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0)
- goto error;
- }
-
- /* Has to be freed for each interface. */
- virStringListFree(ifname);
-
- /* as well as IP address which - moreover -
- * can be presented multiple times */
- ip_addr_arr = virJSONValueObjectGet(tmp_iface, "ip-addresses");
- if (!ip_addr_arr)
- continue;
-
- if ((ip_addr_arr_size = virJSONValueArraySize(ip_addr_arr)) < 0)
- /* Mmm, empty 'ip-address'? */
- goto error;
-
- /* If current iface already exists, continue with the count */
- addrs_count = iface->naddrs;
-
- for (j = 0; j < ip_addr_arr_size; j++) {
- const char *type, *addr;
- virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j);
- virDomainIPAddressPtr ip_addr;
-
- if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0)
- goto error;
-
- ip_addr = &iface->addrs[addrs_count - 1];
-
- /* Shouldn't happen but doesn't hurt to check neither */
- if (!ip_addr_obj) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("qemu agent reply missing IP addr in
array"));
- goto error;
- }
-
- type = virJSONValueObjectGetString(ip_addr_obj,
"ip-address-type");
- if (!type) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("qemu agent didn't provide
'ip-address-type'"
- " field for interface '%s'"), name);
- goto error;
- } else if (STREQ(type, "ipv4")) {
- ip_addr->type = VIR_IP_ADDR_TYPE_IPV4;
- } else if (STREQ(type, "ipv6")) {
- ip_addr->type = VIR_IP_ADDR_TYPE_IPV6;
- } else {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("unknown ip address type '%s'"),
- type);
- goto error;
- }
-
- addr = virJSONValueObjectGetString(ip_addr_obj, "ip-address");
- if (!addr) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("qemu agent didn't provide
'ip-address'"
- " field for interface '%s'"), name);
- goto error;
- }
- if (VIR_STRDUP(ip_addr->addr, addr) < 0)
- goto error;
-
- if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix",
- &ip_addr->prefix) < 0) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("malformed 'prefix' field"));
- goto error;
- }
- }
-
- iface->naddrs = addrs_count;
- }
-
- *ifaces = ifaces_ret;
- ifaces_ret = NULL;
- ret = ifaces_count;
-
- cleanup:
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- virHashFree(ifaces_store);
- return ret;
-
- error:
- if (ifaces_ret) {
- for (i = 0; i < ifaces_count; i++)
- virDomainInterfaceFree(ifaces_ret[i]);
- }
- VIR_FREE(ifaces_ret);
- virStringListFree(ifname);
-
- goto cleanup;
-}
-
-
-int
-qemuAgentSetUserPassword(qemuAgentPtr mon,
- const char *user,
- const char *password,
- bool crypted)
-{
- int ret = -1;
- virJSONValuePtr cmd = NULL;
- virJSONValuePtr reply = NULL;
- char *password64 = NULL;
-
- if (!(password64 = virStringEncodeBase64((unsigned char *) password,
- strlen(password))))
- goto cleanup;
-
- if (!(cmd = qemuAgentMakeCommand("guest-set-user-password",
- "b:crypted", crypted,
- "s:username", user,
- "s:password", password64,
- NULL)))
- goto cleanup;
-
- if (qemuAgentCommand(mon, cmd, &reply, true,
- VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
- goto cleanup;
-
- ret = 0;
-
- cleanup:
- virJSONValueFree(cmd);
- virJSONValueFree(reply);
- VIR_FREE(password64);
- return ret;
-}
diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h
deleted file mode 100644
index 6dd9c70..0000000
--- a/src/qemu/qemu_agent.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * qemu_agent.h: interaction with QEMU guest agent
- *
- * Copyright (C) 2006-2012 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/>.
- *
- * Author: Daniel P. Berrange <berrange(a)redhat.com>
- */
-
-
-#ifndef __QEMU_AGENT_H__
-# define __QEMU_AGENT_H__
-
-# include "internal.h"
-# include "domain_conf.h"
-
-typedef struct _qemuAgent qemuAgent;
-typedef qemuAgent *qemuAgentPtr;
-
-typedef struct _qemuAgentCallbacks qemuAgentCallbacks;
-typedef qemuAgentCallbacks *qemuAgentCallbacksPtr;
-struct _qemuAgentCallbacks {
- void (*destroy)(qemuAgentPtr mon,
- virDomainObjPtr vm);
- void (*eofNotify)(qemuAgentPtr mon,
- virDomainObjPtr vm);
- void (*errorNotify)(qemuAgentPtr mon,
- virDomainObjPtr vm);
-};
-
-
-qemuAgentPtr qemuAgentOpen(virDomainObjPtr vm,
- const virDomainChrSourceDef *config,
- qemuAgentCallbacksPtr cb);
-
-void qemuAgentClose(qemuAgentPtr mon);
-
-void qemuAgentNotifyClose(qemuAgentPtr mon);
-
-typedef enum {
- QEMU_AGENT_EVENT_NONE = 0,
- QEMU_AGENT_EVENT_SHUTDOWN,
- QEMU_AGENT_EVENT_SUSPEND,
- QEMU_AGENT_EVENT_RESET,
-} qemuAgentEvent;
-
-void qemuAgentNotifyEvent(qemuAgentPtr mon,
- qemuAgentEvent event);
-
-typedef enum {
- QEMU_AGENT_SHUTDOWN_POWERDOWN,
- QEMU_AGENT_SHUTDOWN_REBOOT,
- QEMU_AGENT_SHUTDOWN_HALT,
-
- QEMU_AGENT_SHUTDOWN_LAST,
-} qemuAgentShutdownMode;
-
-int qemuAgentShutdown(qemuAgentPtr mon,
- qemuAgentShutdownMode mode);
-
-int qemuAgentFSFreeze(qemuAgentPtr mon,
- const char **mountpoints, unsigned int nmountpoints);
-int qemuAgentFSThaw(qemuAgentPtr mon);
-int qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info,
- virDomainDefPtr vmdef);
-
-int qemuAgentSuspend(qemuAgentPtr mon,
- unsigned int target);
-
-int qemuAgentArbitraryCommand(qemuAgentPtr mon,
- const char *cmd,
- char **result,
- int timeout);
-int qemuAgentFSTrim(qemuAgentPtr mon,
- unsigned long long minimum);
-
-
-typedef struct _qemuAgentCPUInfo qemuAgentCPUInfo;
-typedef qemuAgentCPUInfo *qemuAgentCPUInfoPtr;
-struct _qemuAgentCPUInfo {
- unsigned int id; /* logical cpu ID */
- bool online; /* true if the CPU is activated */
- bool offlinable; /* true if the CPU can be offlined */
-
- bool modified; /* set to true if the vcpu state needs to be changed */
-};
-
-int qemuAgentGetVCPUs(qemuAgentPtr mon, qemuAgentCPUInfoPtr *info);
-int qemuAgentSetVCPUs(qemuAgentPtr mon, qemuAgentCPUInfoPtr cpus, size_t ncpus);
-int qemuAgentUpdateCPUInfo(unsigned int nvcpus,
- qemuAgentCPUInfoPtr cpuinfo,
- int ncpuinfo);
-
-int qemuAgentGetTime(qemuAgentPtr mon,
- long long *seconds,
- unsigned int *nseconds);
-int qemuAgentSetTime(qemuAgentPtr mon,
- long long seconds,
- unsigned int nseconds,
- bool sync);
-
-int qemuAgentGetInterfaces(qemuAgentPtr mon,
- virDomainInterfacePtr **ifaces);
-
-int qemuAgentSetUserPassword(qemuAgentPtr mon,
- const char *user,
- const char *password,
- bool crypted);
-#endif /* __QEMU_AGENT_H__ */
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
index 3973182..8d8fd95 100644
--- a/src/qemu/qemu_domain.h
+++ b/src/qemu/qemu_domain.h
@@ -31,7 +31,7 @@
# include "domain_conf.h"
# include "snapshot_conf.h"
# include "qemu_monitor.h"
-# include "qemu_agent.h"
+# include "virqemuagent.h"
# include "qemu_conf.h"
# include "qemu_capabilities.h"
# include "virchrdev.h"
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 37ccfdf..177ca51 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -44,7 +44,6 @@
#include "qemu_driver.h"
-#include "qemu_agent.h"
#include "qemu_alias.h"
#include "qemu_conf.h"
#include "qemu_capabilities.h"
@@ -59,6 +58,7 @@
#include "qemu_blockjob.h"
#include "qemu_security.h"
+#include "virqemuagent.h"
#include "virerror.h"
#include "virlog.h"
#include "datatypes.h"
diff --git a/src/util/virqemuagent.c b/src/util/virqemuagent.c
new file mode 100644
index 0000000..caabae0
--- /dev/null
+++ b/src/util/virqemuagent.c
@@ -0,0 +1,2248 @@
+/*
+ * virqemuagent.c: interaction with QEMU guest agent
+ *
+ * Copyright (C) 2006-2014 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/>.
+ *
+ * Author: Daniel P. Berrange <berrange(a)redhat.com>
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <poll.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "virqemuagent.h"
+#include "viralloc.h"
+#include "virlog.h"
+#include "virerror.h"
+#include "virjson.h"
+#include "virfile.h"
+#include "virprocess.h"
+#include "virtime.h"
+#include "virobject.h"
+#include "virstring.h"
+#include "base64.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("qemu.qemu_agent");
+
+#define LINE_ENDING "\n"
+
+#define DEBUG_IO 0
+#define DEBUG_RAW_IO 0
+
+/* When you are the first to uncomment this,
+ * don't forget to uncomment the corresponding
+ * part in qemuAgentIOProcessEvent as well.
+ *
+static struct {
+ const char *type;
+ void (*handler)(qemuAgentPtr mon, virJSONValuePtr data);
+} eventHandlers[] = {
+};
+*/
+
+typedef struct _qemuAgentMessage qemuAgentMessage;
+typedef qemuAgentMessage *qemuAgentMessagePtr;
+
+struct _qemuAgentMessage {
+ char *txBuffer;
+ int txOffset;
+ int txLength;
+
+ /* Used by the JSON monitor to hold reply / error */
+ char *rxBuffer;
+ int rxLength;
+ void *rxObject;
+
+ /* True if rxBuffer / rxObject are ready, or a
+ * fatal error occurred on the monitor channel
+ */
+ bool finished;
+ /* true for sync command */
+ bool sync;
+ /* id of the issued sync comand */
+ unsigned long long id;
+ bool first;
+};
+
+
+struct _qemuAgent {
+ virObjectLockable parent;
+
+ virCond notify;
+
+ int fd;
+ int watch;
+
+ bool connectPending;
+ bool running;
+
+ virDomainObjPtr vm;
+
+ qemuAgentCallbacksPtr cb;
+
+ /* If there's a command being processed this will be
+ * non-NULL */
+ qemuAgentMessagePtr msg;
+
+ /* Buffer incoming data ready for Agent monitor
+ * code to process & find message boundaries */
+ size_t bufferOffset;
+ size_t bufferLength;
+ char *buffer;
+
+ /* If anything went wrong, this will be fed back
+ * the next monitor msg */
+ virError lastError;
+
+ /* Some guest agent commands don't return anything
+ * but fire up an event on qemu monitor instead.
+ * Take that as indication of successful completion */
+ qemuAgentEvent await_event;
+};
+
+static virClassPtr qemuAgentClass;
+static void qemuAgentDispose(void *obj);
+
+static int qemuAgentOnceInit(void)
+{
+ if (!(qemuAgentClass = virClassNew(virClassForObjectLockable(),
+ "qemuAgent",
+ sizeof(qemuAgent),
+ qemuAgentDispose)))
+ return -1;
+
+ return 0;
+}
+
+VIR_ONCE_GLOBAL_INIT(qemuAgent)
+
+
+#if DEBUG_RAW_IO
+# include <c-ctype.h>
+static char *
+qemuAgentEscapeNonPrintable(const char *text)
+{
+ size_t i;
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+ for (i = 0; text[i] != '\0'; i++) {
+ if (text[i] == '\\')
+ virBufferAddLit(&buf, "\\\\");
+ else if (c_isprint(text[i]) || text[i] == '\n' ||
+ (text[i] == '\r' && text[i+1] == '\n'))
+ virBufferAddChar(&buf, text[i]);
+ else
+ virBufferAsprintf(&buf, "\\x%02x", text[i]);
+ }
+ return virBufferContentAndReset(&buf);
+}
+#endif
+
+
+static void qemuAgentDispose(void *obj)
+{
+ qemuAgentPtr mon = obj;
+ VIR_DEBUG("mon=%p", mon);
+ if (mon->cb && mon->cb->destroy)
+ (mon->cb->destroy)(mon, mon->vm);
+ virCondDestroy(&mon->notify);
+ VIR_FREE(mon->buffer);
+ virResetError(&mon->lastError);
+}
+
+static int
+qemuAgentOpenUnix(const char *monitor, pid_t cpid, bool *inProgress)
+{
+ struct sockaddr_un addr;
+ int monfd;
+ virTimeBackOffVar timeout;
+ int ret = -1;
+
+ *inProgress = false;
+
+ if ((monfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ virReportSystemError(errno,
+ "%s", _("failed to create socket"));
+ return -1;
+ }
+
+ if (virSetNonBlock(monfd) < 0) {
+ virReportSystemError(errno, "%s",
+ _("Unable to put monitor "
+ "into non-blocking mode"));
+ goto error;
+ }
+
+ if (virSetCloseExec(monfd) < 0) {
+ virReportSystemError(errno, "%s",
+ _("Unable to set monitor "
+ "close-on-exec flag"));
+ goto error;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ if (virStrcpyStatic(addr.sun_path, monitor) == NULL) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Agent path %s too big for destination"), monitor);
+ goto error;
+ }
+
+ if (virTimeBackOffStart(&timeout, 1, 3*1000 /* ms */) < 0)
+ goto error;
+ while (virTimeBackOffWait(&timeout)) {
+ ret = connect(monfd, (struct sockaddr *) &addr, sizeof(addr));
+
+ if (ret == 0)
+ break;
+
+ if ((errno == ENOENT || errno == ECONNREFUSED) &&
+ virProcessKill(cpid, 0) == 0) {
+ /* ENOENT : Socket may not have shown up yet
+ * ECONNREFUSED : Leftover socket hasn't been removed yet */
+ continue;
+ }
+
+ if ((errno == EINPROGRESS) ||
+ (errno == EAGAIN)) {
+ VIR_DEBUG("Connection attempt continuing in background");
+ *inProgress = true;
+ ret = 0;
+ break;
+ }
+
+ virReportSystemError(errno, "%s",
+ _("failed to connect to monitor socket"));
+ goto error;
+
+ }
+
+ if (ret != 0) {
+ virReportSystemError(errno, "%s",
+ _("monitor socket did not show up"));
+ goto error;
+ }
+
+ return monfd;
+
+ error:
+ VIR_FORCE_CLOSE(monfd);
+ return -1;
+}
+
+static int
+qemuAgentOpenPty(const char *monitor)
+{
+ int monfd;
+
+ if ((monfd = open(monitor, O_RDWR | O_NONBLOCK)) < 0) {
+ virReportSystemError(errno,
+ _("Unable to open monitor path %s"), monitor);
+ return -1;
+ }
+
+ if (virSetCloseExec(monfd) < 0) {
+ virReportSystemError(errno, "%s",
+ _("Unable to set monitor close-on-exec flag"));
+ goto error;
+ }
+
+ return monfd;
+
+ error:
+ VIR_FORCE_CLOSE(monfd);
+ return -1;
+}
+
+
+static int
+qemuAgentIOProcessEvent(qemuAgentPtr mon,
+ virJSONValuePtr obj)
+{
+ const char *type;
+ 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;
+ }
+
+/*
+ for (i = 0; i < ARRAY_CARDINALITY(eventHandlers); i++) {
+ if (STREQ(eventHandlers[i].type, type)) {
+ virJSONValuePtr data = virJSONValueObjectGet(obj, "data");
+ VIR_DEBUG("handle %s handler=%p data=%p", type,
+ eventHandlers[i].handler, data);
+ (eventHandlers[i].handler)(mon, data);
+ break;
+ }
+ }
+*/
+ return 0;
+}
+
+static int
+qemuAgentIOProcessLine(qemuAgentPtr mon,
+ const char *line,
+ qemuAgentMessagePtr msg)
+{
+ virJSONValuePtr obj = NULL;
+ int ret = -1;
+
+ VIR_DEBUG("Line [%s]", line);
+
+ if (!(obj = virJSONValueFromString(line))) {
+ /* receiving garbage on first sync is regular situation */
+ if (msg && msg->sync && msg->first) {
+ VIR_DEBUG("Received garbage on sync");
+ msg->finished = 1;
+ return 0;
+ }
+
+ goto cleanup;
+ }
+
+ if (obj->type != VIR_JSON_TYPE_OBJECT) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Parsed JSON reply '%s' isn't an object"),
line);
+ goto cleanup;
+ }
+
+ if (virJSONValueObjectHasKey(obj, "QMP") == 1) {
+ ret = 0;
+ } else if (virJSONValueObjectHasKey(obj, "event") == 1) {
+ ret = qemuAgentIOProcessEvent(mon, obj);
+ } else if (virJSONValueObjectHasKey(obj, "error") == 1 ||
+ virJSONValueObjectHasKey(obj, "return") == 1) {
+ if (msg) {
+ if (msg->sync) {
+ unsigned long long id;
+
+ if (virJSONValueObjectGetNumberUlong(obj, "return", &id)
< 0) {
+ VIR_DEBUG("Ignoring delayed reply on sync");
+ ret = 0;
+ goto cleanup;
+ }
+
+ VIR_DEBUG("Guest returned ID: %llu", id);
+
+ if (msg->id != id) {
+ VIR_DEBUG("Guest agent returned ID: %llu instead of %llu",
+ id, msg->id);
+ ret = 0;
+ goto cleanup;
+ }
+ }
+ msg->rxObject = obj;
+ msg->finished = 1;
+ obj = NULL;
+ } else {
+ /* we are out of sync */
+ VIR_DEBUG("Ignoring delayed reply");
+ }
+ ret = 0;
+ } else {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unknown JSON reply '%s'"), line);
+ }
+
+ cleanup:
+ virJSONValueFree(obj);
+ return ret;
+}
+
+static int qemuAgentIOProcessData(qemuAgentPtr mon,
+ char *data,
+ size_t len,
+ qemuAgentMessagePtr msg)
+{
+ int used = 0;
+ size_t i = 0;
+#if DEBUG_IO
+# if DEBUG_RAW_IO
+ char *str1 = qemuAgentEscapeNonPrintable(data);
+ VIR_ERROR("[%s]", str1);
+ VIR_FREE(str1);
+# else
+ VIR_DEBUG("Data %zu bytes [%s]", len, data);
+# endif
+#endif
+
+ while (used < len) {
+ char *nl = strstr(data + used, LINE_ENDING);
+
+ if (nl) {
+ int got = nl - (data + used);
+ for (i = 0; i < strlen(LINE_ENDING); i++)
+ data[used + got + i] = '\0';
+ if (qemuAgentIOProcessLine(mon, data + used, msg) < 0)
+ return -1;
+ used += got + strlen(LINE_ENDING);
+ } else {
+ break;
+ }
+ }
+
+ VIR_DEBUG("Total used %d bytes out of %zd available in buffer", used,
len);
+ return used;
+}
+
+/* This method processes data that has been received
+ * from the monitor. Looking for async events and
+ * replies/errors.
+ */
+static int
+qemuAgentIOProcess(qemuAgentPtr mon)
+{
+ int len;
+ qemuAgentMessagePtr msg = NULL;
+
+ /* See if there's a message ready for reply; that is,
+ * one that has completed writing all its data.
+ */
+ if (mon->msg && mon->msg->txOffset == mon->msg->txLength)
+ msg = mon->msg;
+
+#if DEBUG_IO
+# if DEBUG_RAW_IO
+ char *str1 = qemuAgentEscapeNonPrintable(msg ? msg->txBuffer : "");
+ char *str2 = qemuAgentEscapeNonPrintable(mon->buffer);
+ VIR_ERROR(_("Process %zu %p %p [[[%s]]][[[%s]]]"),
+ mon->bufferOffset, mon->msg, msg, str1, str2);
+ VIR_FREE(str1);
+ VIR_FREE(str2);
+# else
+ VIR_DEBUG("Process %zu", mon->bufferOffset);
+# endif
+#endif
+
+ len = qemuAgentIOProcessData(mon,
+ mon->buffer, mon->bufferOffset,
+ msg);
+
+ if (len < 0)
+ return -1;
+
+ if (len < mon->bufferOffset) {
+ memmove(mon->buffer, mon->buffer + len, mon->bufferOffset - len);
+ mon->bufferOffset -= len;
+ } else {
+ VIR_FREE(mon->buffer);
+ mon->bufferOffset = mon->bufferLength = 0;
+ }
+#if DEBUG_IO
+ VIR_DEBUG("Process done %zu used %d", mon->bufferOffset, len);
+#endif
+ if (msg && msg->finished)
+ virCondBroadcast(&mon->notify);
+ return len;
+}
+
+
+static int
+qemuAgentIOConnect(qemuAgentPtr mon)
+{
+ int optval;
+ socklen_t optlen;
+
+ VIR_DEBUG("Checking on background connection status");
+
+ mon->connectPending = false;
+
+ optlen = sizeof(optval);
+
+ if (getsockopt(mon->fd, SOL_SOCKET, SO_ERROR,
+ &optval, &optlen) < 0) {
+ virReportSystemError(errno, "%s",
+ _("Cannot check socket connection status"));
+ return -1;
+ }
+
+ if (optval != 0) {
+ virReportSystemError(optval, "%s",
+ _("Cannot connect to agent socket"));
+ return -1;
+ }
+
+ VIR_DEBUG("Agent is now connected");
+ return 0;
+}
+
+/*
+ * Called when the monitor is able to write data
+ * Call this function while holding the monitor lock.
+ */
+static int
+qemuAgentIOWrite(qemuAgentPtr mon)
+{
+ int done;
+
+ /* If no active message, or fully transmitted, then no-op */
+ if (!mon->msg || mon->msg->txOffset == mon->msg->txLength)
+ return 0;
+
+ done = safewrite(mon->fd,
+ mon->msg->txBuffer + mon->msg->txOffset,
+ mon->msg->txLength - mon->msg->txOffset);
+
+ if (done < 0) {
+ if (errno == EAGAIN)
+ return 0;
+
+ virReportSystemError(errno, "%s",
+ _("Unable to write to monitor"));
+ return -1;
+ }
+ mon->msg->txOffset += done;
+ return done;
+}
+
+/*
+ * Called when the monitor has incoming data to read
+ * Call this function while holding the monitor lock.
+ *
+ * Returns -1 on error, or number of bytes read
+ */
+static int
+qemuAgentIORead(qemuAgentPtr mon)
+{
+ size_t avail = mon->bufferLength - mon->bufferOffset;
+ int ret = 0;
+
+ if (avail < 1024) {
+ if (VIR_REALLOC_N(mon->buffer,
+ mon->bufferLength + 1024) < 0)
+ return -1;
+ mon->bufferLength += 1024;
+ avail += 1024;
+ }
+
+ /* Read as much as we can get into our buffer,
+ until we block on EAGAIN, or hit EOF */
+ while (avail > 1) {
+ int got;
+ got = read(mon->fd,
+ mon->buffer + mon->bufferOffset,
+ avail - 1);
+ if (got < 0) {
+ if (errno == EAGAIN)
+ break;
+ virReportSystemError(errno, "%s",
+ _("Unable to read from monitor"));
+ ret = -1;
+ break;
+ }
+ if (got == 0)
+ break;
+
+ ret += got;
+ avail -= got;
+ mon->bufferOffset += got;
+ mon->buffer[mon->bufferOffset] = '\0';
+ }
+
+#if DEBUG_IO
+ VIR_DEBUG("Now read %zu bytes of data", mon->bufferOffset);
+#endif
+
+ return ret;
+}
+
+
+static void qemuAgentUpdateWatch(qemuAgentPtr mon)
+{
+ int events =
+ VIR_EVENT_HANDLE_HANGUP |
+ VIR_EVENT_HANDLE_ERROR;
+
+ if (mon->lastError.code == VIR_ERR_OK) {
+ events |= VIR_EVENT_HANDLE_READABLE;
+
+ if (mon->msg && mon->msg->txOffset <
mon->msg->txLength)
+ events |= VIR_EVENT_HANDLE_WRITABLE;
+ }
+
+ virEventUpdateHandle(mon->watch, events);
+}
+
+
+static void
+qemuAgentIO(int watch, int fd, int events, void *opaque)
+{
+ qemuAgentPtr mon = opaque;
+ bool error = false;
+ bool eof = false;
+
+ virObjectRef(mon);
+ /* lock access to the monitor and protect fd */
+ virObjectLock(mon);
+#if DEBUG_IO
+ VIR_DEBUG("Agent %p I/O on watch %d fd %d events %d", mon, watch, fd,
events);
+#endif
+
+ if (mon->fd != fd || mon->watch != watch) {
+ if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR))
+ eof = true;
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("event from unexpected fd %d!=%d / watch %d!=%d"),
+ mon->fd, fd, mon->watch, watch);
+ error = true;
+ } else if (mon->lastError.code != VIR_ERR_OK) {
+ if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR))
+ eof = true;
+ error = true;
+ } else {
+ if (events & VIR_EVENT_HANDLE_WRITABLE) {
+ if (mon->connectPending) {
+ if (qemuAgentIOConnect(mon) < 0)
+ error = true;
+ } else {
+ if (qemuAgentIOWrite(mon) < 0)
+ error = true;
+ }
+ events &= ~VIR_EVENT_HANDLE_WRITABLE;
+ }
+
+ if (!error &&
+ events & VIR_EVENT_HANDLE_READABLE) {
+ int got = qemuAgentIORead(mon);
+ events &= ~VIR_EVENT_HANDLE_READABLE;
+ if (got < 0) {
+ error = true;
+ } else if (got == 0) {
+ eof = true;
+ } else {
+ /* Ignore hangup/error events if we read some data, to
+ * give time for that data to be consumed */
+ events = 0;
+
+ if (qemuAgentIOProcess(mon) < 0)
+ error = true;
+ }
+ }
+
+ if (!error &&
+ events & VIR_EVENT_HANDLE_HANGUP) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("End of file from agent monitor"));
+ eof = true;
+ events &= ~VIR_EVENT_HANDLE_HANGUP;
+ }
+
+ if (!error && !eof &&
+ events & VIR_EVENT_HANDLE_ERROR) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Invalid file descriptor while waiting for
monitor"));
+ eof = true;
+ events &= ~VIR_EVENT_HANDLE_ERROR;
+ }
+ if (!error && events) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unhandled event %d for monitor fd %d"),
+ events, mon->fd);
+ error = true;
+ }
+ }
+
+ if (error || eof) {
+ if (mon->lastError.code != VIR_ERR_OK) {
+ /* Already have an error, so clear any new error */
+ virResetLastError();
+ } else {
+ virErrorPtr err = virGetLastError();
+ if (!err)
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Error while processing monitor IO"));
+ virCopyLastError(&mon->lastError);
+ virResetLastError();
+ }
+
+ VIR_DEBUG("Error on monitor %s", NULLSTR(mon->lastError.message));
+ /* If IO process resulted in an error & we have a message,
+ * then wakeup that waiter */
+ if (mon->msg && !mon->msg->finished) {
+ mon->msg->finished = 1;
+ virCondSignal(&mon->notify);
+ }
+ }
+
+ qemuAgentUpdateWatch(mon);
+
+ /* We have to unlock to avoid deadlock against command thread,
+ * but is this safe ? I think it is, because the callback
+ * will try to acquire the virDomainObjPtr mutex next */
+ if (eof) {
+ void (*eofNotify)(qemuAgentPtr, virDomainObjPtr)
+ = mon->cb->eofNotify;
+ virDomainObjPtr vm = mon->vm;
+
+ /* Make sure anyone waiting wakes up now */
+ virCondSignal(&mon->notify);
+ virObjectUnlock(mon);
+ virObjectUnref(mon);
+ VIR_DEBUG("Triggering EOF callback");
+ (eofNotify)(mon, vm);
+ } else if (error) {
+ void (*errorNotify)(qemuAgentPtr, virDomainObjPtr)
+ = mon->cb->errorNotify;
+ virDomainObjPtr vm = mon->vm;
+
+ /* Make sure anyone waiting wakes up now */
+ virCondSignal(&mon->notify);
+ virObjectUnlock(mon);
+ virObjectUnref(mon);
+ VIR_DEBUG("Triggering error callback");
+ (errorNotify)(mon, vm);
+ } else {
+ virObjectUnlock(mon);
+ virObjectUnref(mon);
+ }
+}
+
+
+qemuAgentPtr
+qemuAgentOpen(virDomainObjPtr vm,
+ const virDomainChrSourceDef *config,
+ qemuAgentCallbacksPtr cb)
+{
+ qemuAgentPtr mon;
+
+ if (!cb || !cb->eofNotify) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("EOF notify callback must be supplied"));
+ return NULL;
+ }
+
+ if (qemuAgentInitialize() < 0)
+ return NULL;
+
+ if (!(mon = virObjectLockableNew(qemuAgentClass)))
+ return NULL;
+
+ mon->fd = -1;
+ if (virCondInit(&mon->notify) < 0) {
+ virReportSystemError(errno, "%s",
+ _("cannot initialize monitor condition"));
+ virObjectUnref(mon);
+ return NULL;
+ }
+ mon->vm = vm;
+ mon->cb = cb;
+
+ switch (config->type) {
+ case VIR_DOMAIN_CHR_TYPE_UNIX:
+ mon->fd = qemuAgentOpenUnix(config->data.nix.path, vm->pid,
+ &mon->connectPending);
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_PTY:
+ mon->fd = qemuAgentOpenPty(config->data.file.path);
+ break;
+
+ default:
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to handle monitor type: %s"),
+ virDomainChrTypeToString(config->type));
+ goto cleanup;
+ }
+
+ if (mon->fd == -1)
+ goto cleanup;
+
+ virObjectRef(mon);
+ if ((mon->watch = virEventAddHandle(mon->fd,
+ VIR_EVENT_HANDLE_HANGUP |
+ VIR_EVENT_HANDLE_ERROR |
+ VIR_EVENT_HANDLE_READABLE |
+ (mon->connectPending ?
+ VIR_EVENT_HANDLE_WRITABLE :
+ 0),
+ qemuAgentIO,
+ mon,
+ virObjectFreeCallback)) < 0) {
+ virObjectUnref(mon);
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("unable to register monitor events"));
+ goto cleanup;
+ }
+
+ mon->running = true;
+ VIR_DEBUG("New mon %p fd =%d watch=%d", mon, mon->fd, mon->watch);
+
+ return mon;
+
+ cleanup:
+ /* We don't want the 'destroy' callback invoked during
+ * cleanup from construction failure, because that can
+ * give a double-unref on virDomainObjPtr in the caller,
+ * so kill the callbacks now.
+ */
+ mon->cb = NULL;
+ qemuAgentClose(mon);
+ return NULL;
+}
+
+
+static void
+qemuAgentNotifyCloseLocked(qemuAgentPtr mon)
+{
+ if (mon) {
+ mon->running = false;
+
+ /* If there is somebody waiting for a message
+ * wake him up. No message will arrive anyway. */
+ if (mon->msg && !mon->msg->finished) {
+ mon->msg->finished = 1;
+ virCondSignal(&mon->notify);
+ }
+ }
+}
+
+
+void
+qemuAgentNotifyClose(qemuAgentPtr mon)
+{
+ if (!mon)
+ return;
+
+ VIR_DEBUG("mon=%p", mon);
+
+ virObjectLock(mon);
+ qemuAgentNotifyCloseLocked(mon);
+ virObjectUnlock(mon);
+}
+
+
+void qemuAgentClose(qemuAgentPtr mon)
+{
+ if (!mon)
+ return;
+
+ VIR_DEBUG("mon=%p", mon);
+
+ virObjectLock(mon);
+
+ if (mon->fd >= 0) {
+ if (mon->watch)
+ virEventRemoveHandle(mon->watch);
+ VIR_FORCE_CLOSE(mon->fd);
+ }
+
+ qemuAgentNotifyCloseLocked(mon);
+ virObjectUnlock(mon);
+
+ virObjectUnref(mon);
+}
+
+#define QEMU_AGENT_WAIT_TIME 5
+
+/**
+ * qemuAgentSend:
+ * @mon: Monitor
+ * @msg: Message
+ * @seconds: number of seconds to wait for the result, it can be either
+ * -2, -1, 0 or positive.
+ *
+ * Send @msg to agent @mon. If @seconds is equal to
+ * VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK(-2), this function will block forever
+ * waiting for the result. The value of
+ * VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT(-1) means use default timeout value
+ * and VIR_DOMAIN_QEMU_AGENT_COMMAND_NOWAIT(0) makes this this function return
+ * immediately without waiting. Any positive value means the number of seconds
+ * to wait for the result.
+ *
+ * Returns: 0 on success,
+ * -2 on timeout,
+ * -1 otherwise
+ */
+static int qemuAgentSend(qemuAgentPtr mon,
+ qemuAgentMessagePtr msg,
+ int seconds)
+{
+ int ret = -1;
+ unsigned long long then = 0;
+
+ /* Check whether qemu quit unexpectedly */
+ if (mon->lastError.code != VIR_ERR_OK) {
+ VIR_DEBUG("Attempt to send command while error is set %s",
+ NULLSTR(mon->lastError.message));
+ virSetError(&mon->lastError);
+ return -1;
+ }
+
+ if (seconds > VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) {
+ unsigned long long now;
+ if (virTimeMillisNow(&now) < 0)
+ return -1;
+ if (seconds == VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT)
+ seconds = QEMU_AGENT_WAIT_TIME;
+ then = now + seconds * 1000ull;
+ }
+
+ mon->msg = msg;
+ qemuAgentUpdateWatch(mon);
+
+ while (!mon->msg->finished) {
+ if ((then && virCondWaitUntil(&mon->notify,
&mon->parent.lock, then) < 0) ||
+ (!then && virCondWait(&mon->notify, &mon->parent.lock)
< 0)) {
+ if (errno == ETIMEDOUT) {
+ virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+ _("Guest agent not available for now"));
+ ret = -2;
+ } else {
+ virReportSystemError(errno, "%s",
+ _("Unable to wait on agent monitor "
+ "condition"));
+ }
+ goto cleanup;
+ }
+ }
+
+ if (mon->lastError.code != VIR_ERR_OK) {
+ VIR_DEBUG("Send command resulted in error %s",
+ NULLSTR(mon->lastError.message));
+ virSetError(&mon->lastError);
+ goto cleanup;
+ }
+
+ ret = 0;
+
+ cleanup:
+ mon->msg = NULL;
+ qemuAgentUpdateWatch(mon);
+
+ return ret;
+}
+
+
+/**
+ * qemuAgentGuestSync:
+ * @mon: Monitor
+ *
+ * Send guest-sync with unique ID
+ * and wait for reply. If we get one, check if
+ * received ID is equal to given.
+ *
+ * Returns: 0 on success,
+ * -1 otherwise
+ */
+static int
+qemuAgentGuestSync(qemuAgentPtr mon)
+{
+ int ret = -1;
+ int send_ret;
+ unsigned long long id;
+ qemuAgentMessage sync_msg;
+
+ memset(&sync_msg, 0, sizeof(sync_msg));
+ /* set only on first sync */
+ sync_msg.first = true;
+
+ retry:
+ if (virTimeMillisNow(&id) < 0)
+ return -1;
+
+ if (virAsprintf(&sync_msg.txBuffer,
+ "{\"execute\":\"guest-sync\", "
+ "\"arguments\":{\"id\":%llu}}\n", id)
< 0)
+ return -1;
+
+ sync_msg.txLength = strlen(sync_msg.txBuffer);
+ sync_msg.sync = true;
+ sync_msg.id = id;
+
+ VIR_DEBUG("Sending guest-sync command with ID: %llu", id);
+
+ send_ret = qemuAgentSend(mon, &sync_msg,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT);
+
+ VIR_DEBUG("qemuAgentSend returned: %d", send_ret);
+
+ if (send_ret < 0)
+ goto cleanup;
+
+ if (!sync_msg.rxObject) {
+ if (sync_msg.first) {
+ VIR_FREE(sync_msg.txBuffer);
+ memset(&sync_msg, 0, sizeof(sync_msg));
+ goto retry;
+ } else {
+ if (mon->running)
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Missing monitor reply object"));
+ else
+ virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+ _("Guest agent disappeared while executing
command"));
+ goto cleanup;
+ }
+ }
+
+ ret = 0;
+
+ cleanup:
+ virJSONValueFree(sync_msg.rxObject);
+ VIR_FREE(sync_msg.txBuffer);
+ return ret;
+}
+
+static const char *
+qemuAgentStringifyErrorClass(const char *klass)
+{
+ if (STREQ_NULLABLE(klass, "BufferOverrun"))
+ return "Buffer overrun";
+ else if (STREQ_NULLABLE(klass, "CommandDisabled"))
+ return "The command has been disabled for this instance";
+ else if (STREQ_NULLABLE(klass, "CommandNotFound"))
+ return "The command has not been found";
+ else if (STREQ_NULLABLE(klass, "FdNotFound"))
+ return "File descriptor not found";
+ else if (STREQ_NULLABLE(klass, "InvalidParameter"))
+ return "Invalid parameter";
+ else if (STREQ_NULLABLE(klass, "InvalidParameterType"))
+ return "Invalid parameter type";
+ else if (STREQ_NULLABLE(klass, "InvalidParameterValue"))
+ return "Invalid parameter value";
+ else if (STREQ_NULLABLE(klass, "OpenFileFailed"))
+ return "Cannot open file";
+ else if (STREQ_NULLABLE(klass, "QgaCommandFailed"))
+ return "Guest agent command failed";
+ else if (STREQ_NULLABLE(klass, "QMPBadInputObjectMember"))
+ return "Bad QMP input object member";
+ else if (STREQ_NULLABLE(klass, "QMPExtraInputObjectMember"))
+ return "Unexpected extra object member";
+ else if (STREQ_NULLABLE(klass, "UndefinedError"))
+ return "An undefined error has occurred";
+ else if (STREQ_NULLABLE(klass, "Unsupported"))
+ return "this feature or command is not currently supported";
+ else if (klass)
+ return klass;
+ else
+ return "unknown QEMU command error";
+}
+
+/* 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 *
+qemuAgentStringifyError(virJSONValuePtr error)
+{
+ const char *klass = virJSONValueObjectGetString(error, "class");
+ const char *detail = virJSONValueObjectGetString(error, "desc");
+
+ /* The QMP 'desc' field is usually sufficient for our generic
+ * error reporting needs. However, if not present, translate
+ * the class into something readable.
+ */
+ if (!detail)
+ detail = qemuAgentStringifyErrorClass(klass);
+
+ return detail;
+}
+
+static const char *
+qemuAgentCommandName(virJSONValuePtr cmd)
+{
+ const char *name = virJSONValueObjectGetString(cmd, "execute");
+ if (name)
+ return name;
+ else
+ return "<unknown>";
+}
+
+static int
+qemuAgentCheckError(virJSONValuePtr cmd,
+ virJSONValuePtr reply)
+{
+ if (virJSONValueObjectHasKey(reply, "error")) {
+ virJSONValuePtr error = virJSONValueObjectGet(reply, "error");
+ char *cmdstr = virJSONValueToString(cmd, false);
+ char *replystr = virJSONValueToString(reply, false);
+
+ /* Log the full JSON formatted command & error */
+ VIR_DEBUG("unable to execute QEMU agent command %s: %s",
+ NULLSTR(cmdstr), NULLSTR(replystr));
+
+ /* Only send the user the command name + friendly error */
+ if (!error)
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to execute QEMU agent command
'%s'"),
+ qemuAgentCommandName(cmd));
+ else
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to execute QEMU agent command '%s':
%s"),
+ qemuAgentCommandName(cmd),
+ qemuAgentStringifyError(error));
+
+ VIR_FREE(cmdstr);
+ VIR_FREE(replystr);
+ return -1;
+ } else if (!virJSONValueObjectHasKey(reply, "return")) {
+ char *cmdstr = virJSONValueToString(cmd, false);
+ char *replystr = virJSONValueToString(reply, false);
+
+ VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON
reply %s: %s",
+ NULLSTR(cmdstr), NULLSTR(replystr));
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to execute QEMU agent command '%s'"),
+ qemuAgentCommandName(cmd));
+ VIR_FREE(cmdstr);
+ VIR_FREE(replystr);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+qemuAgentCommand(qemuAgentPtr mon,
+ virJSONValuePtr cmd,
+ virJSONValuePtr *reply,
+ bool needReply,
+ int seconds)
+{
+ int ret = -1;
+ qemuAgentMessage msg;
+ char *cmdstr = NULL;
+ int await_event = mon->await_event;
+
+ *reply = NULL;
+
+ if (!mon->running) {
+ virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+ _("Guest agent disappeared while executing command"));
+ return -1;
+ }
+
+ if (qemuAgentGuestSync(mon) < 0)
+ return -1;
+
+ memset(&msg, 0, sizeof(msg));
+
+ if (!(cmdstr = virJSONValueToString(cmd, false)))
+ goto cleanup;
+ if (virAsprintf(&msg.txBuffer, "%s" LINE_ENDING, cmdstr) < 0)
+ goto cleanup;
+ msg.txLength = strlen(msg.txBuffer);
+
+ VIR_DEBUG("Send command '%s' for write, seconds = %d", cmdstr,
seconds);
+
+ ret = qemuAgentSend(mon, &msg, seconds);
+
+ VIR_DEBUG("Receive command reply ret=%d rxObject=%p",
+ ret, msg.rxObject);
+
+ if (ret == 0) {
+ /* If we haven't obtained any reply but we wait for an
+ * event, then don't report this as error */
+ if (!msg.rxObject) {
+ if (await_event && !needReply) {
+ VIR_DEBUG("Woken up by event %d", await_event);
+ } else {
+ if (mon->running)
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Missing monitor reply object"));
+ else
+ virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+ _("Guest agent disappeared while executing
command"));
+ ret = -1;
+ }
+ } else {
+ *reply = msg.rxObject;
+ ret = qemuAgentCheckError(cmd, *reply);
+ }
+ }
+
+ cleanup:
+ VIR_FREE(cmdstr);
+ VIR_FREE(msg.txBuffer);
+
+ return ret;
+}
+
+static virJSONValuePtr ATTRIBUTE_SENTINEL
+qemuAgentMakeCommand(const char *cmdname,
+ ...)
+{
+ virJSONValuePtr obj;
+ virJSONValuePtr jargs = NULL;
+ va_list args;
+
+ va_start(args, cmdname);
+
+ if (!(obj = virJSONValueNewObject()))
+ goto error;
+
+ if (virJSONValueObjectAppendString(obj, "execute", cmdname) < 0)
+ goto error;
+
+ if (virJSONValueObjectCreateVArgs(&jargs, args) < 0)
+ goto error;
+
+ if (jargs &&
+ virJSONValueObjectAppend(obj, "arguments", jargs) < 0)
+ goto error;
+
+ va_end(args);
+
+ return obj;
+
+ error:
+ virJSONValueFree(obj);
+ virJSONValueFree(jargs);
+ va_end(args);
+ return NULL;
+}
+
+static virJSONValuePtr
+qemuAgentMakeStringsArray(const char **strings, unsigned int len)
+{
+ size_t i;
+ virJSONValuePtr ret = virJSONValueNewArray(), str;
+
+ if (!ret)
+ return NULL;
+
+ for (i = 0; i < len; i++) {
+ str = virJSONValueNewString(strings[i]);
+ if (!str)
+ goto error;
+
+ if (virJSONValueArrayAppend(ret, str) < 0) {
+ virJSONValueFree(str);
+ goto error;
+ }
+ }
+ return ret;
+
+ error:
+ virJSONValueFree(ret);
+ return NULL;
+}
+
+void qemuAgentNotifyEvent(qemuAgentPtr mon,
+ qemuAgentEvent event)
+{
+ virObjectLock(mon);
+
+ VIR_DEBUG("mon=%p event=%d await_event=%d", mon, event,
mon->await_event);
+ if (mon->await_event == event) {
+ mon->await_event = QEMU_AGENT_EVENT_NONE;
+ /* somebody waiting for this event, wake him up. */
+ if (mon->msg && !mon->msg->finished) {
+ mon->msg->finished = 1;
+ virCondSignal(&mon->notify);
+ }
+ }
+
+ virObjectUnlock(mon);
+}
+
+VIR_ENUM_DECL(qemuAgentShutdownMode);
+
+VIR_ENUM_IMPL(qemuAgentShutdownMode,
+ QEMU_AGENT_SHUTDOWN_LAST,
+ "powerdown", "reboot", "halt");
+
+int qemuAgentShutdown(qemuAgentPtr mon,
+ qemuAgentShutdownMode mode)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuAgentMakeCommand("guest-shutdown",
+ "s:mode",
qemuAgentShutdownModeTypeToString(mode),
+ NULL);
+ if (!cmd)
+ return -1;
+
+ if (mode == QEMU_AGENT_SHUTDOWN_REBOOT)
+ mon->await_event = QEMU_AGENT_EVENT_RESET;
+ else
+ mon->await_event = QEMU_AGENT_EVENT_SHUTDOWN;
+ ret = qemuAgentCommand(mon, cmd, &reply, false,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_SHUTDOWN);
+
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+/*
+ * qemuAgentFSFreeze:
+ * @mon: Agent
+ * @mountpoints: Array of mountpoint paths to be frozen, or NULL for all
+ * @nmountpoints: Number of mountpoints to be frozen, or 0 for all
+ *
+ * Issue guest-fsfreeze-freeze command to guest agent,
+ * which freezes file systems mounted on specified mountpoints
+ * (or all file systems when @mountpoints is NULL), and returns
+ * number of frozen file systems on success.
+ *
+ * Returns: number of file system frozen on success,
+ * -1 on error.
+ */
+int qemuAgentFSFreeze(qemuAgentPtr mon, const char **mountpoints,
+ unsigned int nmountpoints)
+{
+ int ret = -1;
+ virJSONValuePtr cmd, arg = NULL;
+ virJSONValuePtr reply = NULL;
+
+ if (mountpoints && nmountpoints) {
+ arg = qemuAgentMakeStringsArray(mountpoints, nmountpoints);
+ if (!arg)
+ return -1;
+
+ cmd = qemuAgentMakeCommand("guest-fsfreeze-freeze-list",
+ "a:mountpoints", arg, NULL);
+ } else {
+ cmd = qemuAgentMakeCommand("guest-fsfreeze-freeze", NULL);
+ }
+
+ if (!cmd)
+ goto cleanup;
+ arg = NULL;
+
+ if (qemuAgentCommand(mon, cmd, &reply, true,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
+ goto cleanup;
+
+ if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("malformed return value"));
+ }
+
+ cleanup:
+ virJSONValueFree(arg);
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+/*
+ * qemuAgentFSThaw:
+ * @mon: Agent
+ *
+ * Issue guest-fsfreeze-thaw command to guest agent,
+ * which unfreezes all mounted file systems and returns
+ * number of thawed file systems on success.
+ *
+ * Returns: number of file system thawed on success,
+ * -1 on error.
+ */
+int qemuAgentFSThaw(qemuAgentPtr mon)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuAgentMakeCommand("guest-fsfreeze-thaw", NULL);
+
+ if (!cmd)
+ return -1;
+
+ if (qemuAgentCommand(mon, cmd, &reply, true,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
+ goto cleanup;
+
+ if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("malformed return value"));
+ }
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+VIR_ENUM_DECL(qemuAgentSuspendMode);
+
+VIR_ENUM_IMPL(qemuAgentSuspendMode,
+ VIR_NODE_SUSPEND_TARGET_LAST,
+ "guest-suspend-ram",
+ "guest-suspend-disk",
+ "guest-suspend-hybrid");
+
+int
+qemuAgentSuspend(qemuAgentPtr mon,
+ unsigned int target)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuAgentMakeCommand(qemuAgentSuspendModeTypeToString(target),
+ NULL);
+ if (!cmd)
+ return -1;
+
+ mon->await_event = QEMU_AGENT_EVENT_SUSPEND;
+ ret = qemuAgentCommand(mon, cmd, &reply, false,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK);
+
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+int
+qemuAgentArbitraryCommand(qemuAgentPtr mon,
+ const char *cmd_str,
+ char **result,
+ int timeout)
+{
+ int ret = -1;
+ virJSONValuePtr cmd = NULL;
+ virJSONValuePtr reply = NULL;
+
+ *result = NULL;
+ if (timeout < VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN) {
+ virReportError(VIR_ERR_INVALID_ARG,
+ _("guest agent timeout '%d' is "
+ "less than the minimum '%d'"),
+ timeout, VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN);
+ goto cleanup;
+ }
+
+ if (!(cmd = virJSONValueFromString(cmd_str)))
+ goto cleanup;
+
+ if ((ret = qemuAgentCommand(mon, cmd, &reply, true, timeout)) < 0)
+ goto cleanup;
+
+ if (!(*result = virJSONValueToString(reply, false)))
+ ret = -1;
+
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+int
+qemuAgentFSTrim(qemuAgentPtr mon,
+ unsigned long long minimum)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuAgentMakeCommand("guest-fstrim",
+ "U:minimum", minimum,
+ NULL);
+ if (!cmd)
+ return ret;
+
+ ret = qemuAgentCommand(mon, cmd, &reply, false,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK);
+
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+int
+qemuAgentGetVCPUs(qemuAgentPtr mon,
+ qemuAgentCPUInfoPtr *info)
+{
+ int ret = -1;
+ size_t i;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+ virJSONValuePtr data = NULL;
+ ssize_t ndata;
+
+ if (!(cmd = qemuAgentMakeCommand("guest-get-vcpus", NULL)))
+ return -1;
+
+ if (qemuAgentCommand(mon, cmd, &reply, true,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
+ goto cleanup;
+
+ if (!(data = virJSONValueObjectGet(reply, "return"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest-get-vcpus reply was missing return data"));
+ goto cleanup;
+ }
+
+ if (data->type != VIR_JSON_TYPE_ARRAY) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest-get-vcpus return information was not an
array"));
+ goto cleanup;
+ }
+
+ ndata = virJSONValueArraySize(data);
+
+ if (VIR_ALLOC_N(*info, ndata) < 0)
+ goto cleanup;
+
+ for (i = 0; i < ndata; i++) {
+ virJSONValuePtr entry = virJSONValueArrayGet(data, i);
+ qemuAgentCPUInfoPtr in = *info + i;
+
+ if (!entry) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("array element missing in guest-get-vcpus return
"
+ "value"));
+ goto cleanup;
+ }
+
+ if (virJSONValueObjectGetNumberUint(entry, "logical-id",
&in->id) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'logical-id' missing in reply of
guest-get-vcpus"));
+ goto cleanup;
+ }
+
+ if (virJSONValueObjectGetBoolean(entry, "online", &in->online)
< 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'online' missing in reply of
guest-get-vcpus"));
+ goto cleanup;
+ }
+
+ if (virJSONValueObjectGetBoolean(entry, "can-offline",
+ &in->offlinable) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'can-offline' missing in reply of
guest-get-vcpus"));
+ goto cleanup;
+ }
+ }
+
+ ret = ndata;
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+
+/* returns the value provided by the guest agent or -1 on internal error */
+static int
+qemuAgentSetVCPUsCommand(qemuAgentPtr mon,
+ qemuAgentCPUInfoPtr info,
+ size_t ninfo,
+ int *nmodified)
+{
+ int ret = -1;
+ virJSONValuePtr cmd = NULL;
+ virJSONValuePtr reply = NULL;
+ virJSONValuePtr cpus = NULL;
+ virJSONValuePtr cpu = NULL;
+ size_t i;
+
+ *nmodified = 0;
+
+ /* create the key data array */
+ if (!(cpus = virJSONValueNewArray()))
+ goto cleanup;
+
+ for (i = 0; i < ninfo; i++) {
+ qemuAgentCPUInfoPtr in = &info[i];
+
+ /* don't set state for cpus that were not touched */
+ if (!in->modified)
+ continue;
+
+ (*nmodified)++;
+
+ /* create single cpu object */
+ if (!(cpu = virJSONValueNewObject()))
+ goto cleanup;
+
+ if (virJSONValueObjectAppendNumberInt(cpu, "logical-id", in->id)
< 0)
+ goto cleanup;
+
+ if (virJSONValueObjectAppendBoolean(cpu, "online", in->online) <
0)
+ goto cleanup;
+
+ if (virJSONValueArrayAppend(cpus, cpu) < 0)
+ goto cleanup;
+
+ cpu = NULL;
+ }
+
+ if (*nmodified == 0) {
+ ret = 0;
+ goto cleanup;
+ }
+
+ if (!(cmd = qemuAgentMakeCommand("guest-set-vcpus",
+ "a:vcpus", cpus,
+ NULL)))
+ goto cleanup;
+
+ cpus = NULL;
+
+ if (qemuAgentCommand(mon, cmd, &reply, true,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
+ goto cleanup;
+
+ if (qemuAgentCheckError(cmd, reply) < 0)
+ goto cleanup;
+
+ /* All negative values are invalid. Return of 0 is bogus since we wouldn't
+ * call the guest agent so that 0 cpus would be set successfully. Reporting
+ * more successfully set vcpus that we've asked for is invalid. */
+ if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0 ||
+ ret <= 0 || ret > *nmodified) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest agent returned malformed or invalid return
value"));
+ ret = -1;
+ }
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ virJSONValueFree(cpu);
+ virJSONValueFree(cpus);
+ return ret;
+}
+
+
+/**
+ * Set the VCPU state using guest agent.
+ *
+ * Attempts to set the guest agent state for all cpus or until a proper error is
+ * reported by the guest agent. This may require multiple calls.
+ *
+ * Returns -1 on error, 0 on success.
+ */
+int
+qemuAgentSetVCPUs(qemuAgentPtr mon,
+ qemuAgentCPUInfoPtr info,
+ size_t ninfo)
+{
+ int rv;
+ int nmodified;
+ size_t i;
+
+ do {
+ if ((rv = qemuAgentSetVCPUsCommand(mon, info, ninfo, &nmodified)) < 0)
+ return -1;
+
+ /* all vcpus were set successfully */
+ if (rv == nmodified)
+ return 0;
+
+ /* un-mark vcpus that were already set */
+ for (i = 0; i < ninfo && rv > 0; i++) {
+ if (!info[i].modified)
+ continue;
+
+ info[i].modified = false;
+ rv--;
+ }
+ } while (1);
+
+ return 0;
+}
+
+
+/* modify the cpu info structure to set the correct amount of cpus */
+int
+qemuAgentUpdateCPUInfo(unsigned int nvcpus,
+ qemuAgentCPUInfoPtr cpuinfo,
+ int ncpuinfo)
+{
+ size_t i;
+ int nonline = 0;
+ int nofflinable = 0;
+ ssize_t cpu0 = -1;
+
+ /* count the active and offlinable cpus */
+ for (i = 0; i < ncpuinfo; i++) {
+ if (cpuinfo[i].id == 0)
+ cpu0 = i;
+
+ if (cpuinfo[i].online)
+ nonline++;
+
+ if (cpuinfo[i].offlinable && cpuinfo[i].online)
+ nofflinable++;
+
+ /* This shouldn't happen, but we can't trust the guest agent */
+ if (!cpuinfo[i].online && !cpuinfo[i].offlinable) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Invalid data provided by guest agent"));
+ return -1;
+ }
+ }
+
+ /* CPU0 was made offlinable in linux a while ago, but certain parts (suspend
+ * to ram) of the kernel still don't cope well with that. Make sure that if
+ * all remaining vCPUs are offlinable, vCPU0 will not be selected to be
+ * offlined automatically */
+ if (nofflinable == nonline && cpu0 >= 0 && cpuinfo[cpu0].online)
{
+ cpuinfo[cpu0].offlinable = false;
+ nofflinable--;
+ }
+
+ /* the guest agent reported less cpus than requested */
+ if (nvcpus > ncpuinfo) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest agent reports less cpu than requested"));
+ return -1;
+ }
+
+ /* not enough offlinable CPUs to support the request */
+ if (nvcpus < nonline - nofflinable) {
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
+ _("Cannot offline enough CPUs"));
+ return -1;
+ }
+
+ for (i = 0; i < ncpuinfo; i++) {
+ if (nvcpus < nonline) {
+ /* unplug */
+ if (cpuinfo[i].offlinable && cpuinfo[i].online) {
+ cpuinfo[i].online = false;
+ cpuinfo[i].modified = true;
+ nonline--;
+ }
+ } else if (nvcpus > nonline) {
+ /* plug */
+ if (!cpuinfo[i].online) {
+ cpuinfo[i].online = true;
+ cpuinfo[i].modified = true;
+ nonline++;
+ }
+ } else {
+ /* done */
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+int
+qemuAgentGetTime(qemuAgentPtr mon,
+ long long *seconds,
+ unsigned int *nseconds)
+{
+ int ret = -1;
+ unsigned long long json_time;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuAgentMakeCommand("guest-get-time",
+ NULL);
+ if (!cmd)
+ return ret;
+
+ if (qemuAgentCommand(mon, cmd, &reply, true,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
+ goto cleanup;
+
+ if (virJSONValueObjectGetNumberUlong(reply, "return", &json_time) <
0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("malformed return value"));
+ goto cleanup;
+ }
+
+ /* guest agent returns time in nanoseconds,
+ * we need it in seconds here */
+ *seconds = json_time / 1000000000LL;
+ *nseconds = json_time % 1000000000LL;
+ ret = 0;
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+
+/**
+ * qemuAgentSetTime:
+ * @setTime: time to set
+ * @sync: let guest agent to read domain's RTC (@setTime is ignored)
+ */
+int
+qemuAgentSetTime(qemuAgentPtr mon,
+ long long seconds,
+ unsigned int nseconds,
+ bool rtcSync)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ if (rtcSync) {
+ cmd = qemuAgentMakeCommand("guest-set-time", NULL);
+ } else {
+ /* guest agent expect time with nanosecond granularity.
+ * Impressing. */
+ long long json_time;
+
+ /* Check if we overflow. For some reason qemu doesn't handle unsigned
+ * long long on the monitor well as it silently truncates numbers to
+ * signed long long. Therefore we must check overflow against LLONG_MAX
+ * not ULLONG_MAX. */
+ if (seconds > LLONG_MAX / 1000000000LL) {
+ virReportError(VIR_ERR_INVALID_ARG,
+ _("Time '%lld' is too big for guest
agent"),
+ seconds);
+ return ret;
+ }
+
+ json_time = seconds * 1000000000LL;
+ json_time += nseconds;
+ cmd = qemuAgentMakeCommand("guest-set-time",
+ "I:time", json_time,
+ NULL);
+ }
+
+ if (!cmd)
+ return ret;
+
+ if (qemuAgentCommand(mon, cmd, &reply, true,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+
+int
+qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info,
+ virDomainDefPtr vmdef)
+{
+ size_t i, j, k;
+ int ret = -1;
+ ssize_t ndata = 0, ndisk;
+ char **alias;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+ virJSONValuePtr data;
+ virDomainFSInfoPtr *info_ret = NULL;
+ virPCIDeviceAddress pci_address;
+
+ cmd = qemuAgentMakeCommand("guest-get-fsinfo", NULL);
+ if (!cmd)
+ return ret;
+
+ if (qemuAgentCommand(mon, cmd, &reply, true,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
+ goto cleanup;
+
+ if (!(data = virJSONValueObjectGet(reply, "return"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest-get-fsinfo reply was missing return data"));
+ goto cleanup;
+ }
+
+ if (data->type != VIR_JSON_TYPE_ARRAY) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest-get-fsinfo return information was not "
+ "an array"));
+ goto cleanup;
+ }
+
+ ndata = virJSONValueArraySize(data);
+ if (!ndata) {
+ ret = 0;
+ *info = NULL;
+ goto cleanup;
+ }
+ if (VIR_ALLOC_N(info_ret, ndata) < 0)
+ goto cleanup;
+
+ for (i = 0; i < ndata; i++) {
+ /* Reverse the order to arrange in mount order */
+ virJSONValuePtr entry = virJSONValueArrayGet(data, ndata - 1 - i);
+
+ if (!entry) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("array element '%zd' of '%zd' missing
in "
+ "guest-get-fsinfo return data"),
+ i, ndata);
+ goto cleanup;
+ }
+
+ if (VIR_ALLOC(info_ret[i]) < 0)
+ goto cleanup;
+
+ if (VIR_STRDUP(info_ret[i]->mountpoint,
+ virJSONValueObjectGetString(entry, "mountpoint")) <
0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'mountpoint' missing in reply of "
+ "guest-get-fsinfo"));
+ goto cleanup;
+ }
+
+ if (VIR_STRDUP(info_ret[i]->name,
+ virJSONValueObjectGetString(entry, "name")) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'name' missing in reply of
guest-get-fsinfo"));
+ goto cleanup;
+ }
+
+ if (VIR_STRDUP(info_ret[i]->fstype,
+ virJSONValueObjectGetString(entry, "type")) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'type' missing in reply of
guest-get-fsinfo"));
+ goto cleanup;
+ }
+
+ if (!(entry = virJSONValueObjectGet(entry, "disk"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'disk' missing in reply of
guest-get-fsinfo"));
+ goto cleanup;
+ }
+
+ if (entry->type != VIR_JSON_TYPE_ARRAY) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest-get-fsinfo 'disk' data was not an
array"));
+ goto cleanup;
+ }
+
+ ndisk = virJSONValueArraySize(entry);
+ if (!ndisk)
+ continue;
+ if (VIR_ALLOC_N(info_ret[i]->devAlias, ndisk) < 0)
+ goto cleanup;
+
+ alias = info_ret[i]->devAlias;
+ info_ret[i]->ndevAlias = 0;
+ for (j = 0; j < ndisk; j++) {
+ virJSONValuePtr disk = virJSONValueArrayGet(entry, j);
+ virJSONValuePtr pci;
+ int diskaddr[3], pciaddr[4];
+ const char *diskaddr_comp[] = {"bus", "target",
"unit"};
+ const char *pciaddr_comp[] = {"domain", "bus",
"slot", "function"};
+ virDomainDiskDefPtr diskDef;
+
+ if (!disk) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("array element '%zd' of '%zd'
missing in "
+ "guest-get-fsinfo 'disk' data"),
+ j, ndisk);
+ goto cleanup;
+ }
+
+ if (!(pci = virJSONValueObjectGet(disk, "pci-controller"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'pci-controller' missing in
guest-get-fsinfo "
+ "'disk' data"));
+ goto cleanup;
+ }
+
+ for (k = 0; k < 3; k++) {
+ if (virJSONValueObjectGetNumberInt(
+ disk, diskaddr_comp[k], &diskaddr[k]) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("'%s' missing in guest-get-fsinfo
"
+ "'disk' data"),
diskaddr_comp[k]);
+ goto cleanup;
+ }
+ }
+ for (k = 0; k < 4; k++) {
+ if (virJSONValueObjectGetNumberInt(
+ pci, pciaddr_comp[k], &pciaddr[k]) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("'%s' missing in guest-get-fsinfo
"
+ "'pci-address' data"),
pciaddr_comp[k]);
+ goto cleanup;
+ }
+ }
+
+ pci_address.domain = pciaddr[0];
+ pci_address.bus = pciaddr[1];
+ pci_address.slot = pciaddr[2];
+ pci_address.function = pciaddr[3];
+ if (!(diskDef = virDomainDiskByAddress(
+ vmdef, &pci_address,
+ diskaddr[0], diskaddr[1], diskaddr[2])))
+ continue;
+
+ if (VIR_STRDUP(*alias, diskDef->dst) < 0)
+ goto cleanup;
+
+ if (*alias) {
+ alias++;
+ info_ret[i]->ndevAlias++;
+ }
+ }
+ }
+
+ *info = info_ret;
+ info_ret = NULL;
+ ret = ndata;
+
+ cleanup:
+ if (info_ret) {
+ for (i = 0; i < ndata; i++)
+ virDomainFSInfoFree(info_ret[i]);
+ VIR_FREE(info_ret);
+ }
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+/*
+ * qemuAgentGetInterfaces:
+ * @mon: Agent monitor
+ * @ifaces: pointer to an array of pointers pointing to interface objects
+ *
+ * Issue guest-network-get-interfaces to guest agent, which returns a
+ * list of interfaces of a running domain along with their IP and MAC
+ * addresses.
+ *
+ * Returns: number of interfaces on success, -1 on error.
+ */
+int
+qemuAgentGetInterfaces(qemuAgentPtr mon,
+ virDomainInterfacePtr **ifaces)
+{
+ int ret = -1;
+ size_t i, j;
+ ssize_t size = -1;
+ virJSONValuePtr cmd = NULL;
+ virJSONValuePtr reply = NULL;
+ virJSONValuePtr ret_array = NULL;
+ size_t ifaces_count = 0;
+ size_t addrs_count = 0;
+ virDomainInterfacePtr *ifaces_ret = NULL;
+ virHashTablePtr ifaces_store = NULL;
+ char **ifname = NULL;
+
+ /* Hash table to handle the interface alias */
+ if (!(ifaces_store = virHashCreate(ifaces_count, NULL))) {
+ virHashFree(ifaces_store);
+ return -1;
+ }
+
+ if (!(cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL)))
+ goto cleanup;
+
+ if (qemuAgentCommand(mon, cmd, &reply, false,
VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0 ||
+ qemuAgentCheckError(cmd, reply) < 0) {
+ goto cleanup;
+ }
+
+ if (!(ret_array = virJSONValueObjectGet(reply, "return"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("qemu agent didn't provide 'return'
field"));
+ goto cleanup;
+ }
+
+ if ((size = virJSONValueArraySize(ret_array)) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("qemu agent didn't return an array of
interfaces"));
+ goto cleanup;
+ }
+
+ for (i = 0; i < size; i++) {
+ virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i);
+ virJSONValuePtr ip_addr_arr = NULL;
+ const char *hwaddr, *ifname_s, *name = NULL;
+ ssize_t ip_addr_arr_size;
+ virDomainInterfacePtr iface = NULL;
+
+ /* Shouldn't happen but doesn't hurt to check neither */
+ if (!tmp_iface) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("qemu agent reply missing interface entry in
array"));
+ goto error;
+ }
+
+ /* interface name is required to be presented */
+ name = virJSONValueObjectGetString(tmp_iface, "name");
+ if (!name) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("qemu agent didn't provide 'name'
field"));
+ goto error;
+ }
+
+ /* Handle interface alias (<ifname>:<alias>) */
+ ifname = virStringSplit(name, ":", 2);
+ ifname_s = ifname[0];
+
+ iface = virHashLookup(ifaces_store, ifname_s);
+
+ /* If the hash table doesn't contain this iface, add it */
+ if (!iface) {
+ if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0)
+ goto error;
+
+ if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0)
+ goto error;
+
+ if (virHashAddEntry(ifaces_store, ifname_s,
+ ifaces_ret[ifaces_count - 1]) < 0)
+ goto error;
+
+ iface = ifaces_ret[ifaces_count - 1];
+ iface->naddrs = 0;
+
+ if (VIR_STRDUP(iface->name, ifname_s) < 0)
+ goto error;
+
+ hwaddr = virJSONValueObjectGetString(tmp_iface,
"hardware-address");
+ if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0)
+ goto error;
+ }
+
+ /* Has to be freed for each interface. */
+ virStringListFree(ifname);
+
+ /* as well as IP address which - moreover -
+ * can be presented multiple times */
+ ip_addr_arr = virJSONValueObjectGet(tmp_iface, "ip-addresses");
+ if (!ip_addr_arr)
+ continue;
+
+ if ((ip_addr_arr_size = virJSONValueArraySize(ip_addr_arr)) < 0)
+ /* Mmm, empty 'ip-address'? */
+ goto error;
+
+ /* If current iface already exists, continue with the count */
+ addrs_count = iface->naddrs;
+
+ for (j = 0; j < ip_addr_arr_size; j++) {
+ const char *type, *addr;
+ virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j);
+ virDomainIPAddressPtr ip_addr;
+
+ if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0)
+ goto error;
+
+ ip_addr = &iface->addrs[addrs_count - 1];
+
+ /* Shouldn't happen but doesn't hurt to check neither */
+ if (!ip_addr_obj) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("qemu agent reply missing IP addr in
array"));
+ goto error;
+ }
+
+ type = virJSONValueObjectGetString(ip_addr_obj,
"ip-address-type");
+ if (!type) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("qemu agent didn't provide
'ip-address-type'"
+ " field for interface '%s'"), name);
+ goto error;
+ } else if (STREQ(type, "ipv4")) {
+ ip_addr->type = VIR_IP_ADDR_TYPE_IPV4;
+ } else if (STREQ(type, "ipv6")) {
+ ip_addr->type = VIR_IP_ADDR_TYPE_IPV6;
+ } else {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unknown ip address type '%s'"),
+ type);
+ goto error;
+ }
+
+ addr = virJSONValueObjectGetString(ip_addr_obj, "ip-address");
+ if (!addr) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("qemu agent didn't provide
'ip-address'"
+ " field for interface '%s'"), name);
+ goto error;
+ }
+ if (VIR_STRDUP(ip_addr->addr, addr) < 0)
+ goto error;
+
+ if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix",
+ &ip_addr->prefix) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("malformed 'prefix' field"));
+ goto error;
+ }
+ }
+
+ iface->naddrs = addrs_count;
+ }
+
+ *ifaces = ifaces_ret;
+ ifaces_ret = NULL;
+ ret = ifaces_count;
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ virHashFree(ifaces_store);
+ return ret;
+
+ error:
+ if (ifaces_ret) {
+ for (i = 0; i < ifaces_count; i++)
+ virDomainInterfaceFree(ifaces_ret[i]);
+ }
+ VIR_FREE(ifaces_ret);
+ virStringListFree(ifname);
+
+ goto cleanup;
+}
+
+
+int
+qemuAgentSetUserPassword(qemuAgentPtr mon,
+ const char *user,
+ const char *password,
+ bool crypted)
+{
+ int ret = -1;
+ virJSONValuePtr cmd = NULL;
+ virJSONValuePtr reply = NULL;
+ char *password64 = NULL;
+
+ if (!(password64 = virStringEncodeBase64((unsigned char *) password,
+ strlen(password))))
+ goto cleanup;
+
+ if (!(cmd = qemuAgentMakeCommand("guest-set-user-password",
+ "b:crypted", crypted,
+ "s:username", user,
+ "s:password", password64,
+ NULL)))
+ goto cleanup;
+
+ if (qemuAgentCommand(mon, cmd, &reply, true,
+ VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0)
+ goto cleanup;
+
+ ret = 0;
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ VIR_FREE(password64);
+ return ret;
+}
diff --git a/src/util/virqemuagent.h b/src/util/virqemuagent.h
new file mode 100644
index 0000000..2e81020
--- /dev/null
+++ b/src/util/virqemuagent.h
@@ -0,0 +1,123 @@
+/*
+ * virqemuagent.h: interaction with QEMU guest agent
+ *
+ * Copyright (C) 2006-2012 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/>.
+ *
+ * Author: Daniel P. Berrange <berrange(a)redhat.com>
+ */
+
+
+#ifndef __QEMU_AGENT_H__
+# define __QEMU_AGENT_H__
+
+# include "internal.h"
+# include "domain_conf.h"
+
+typedef struct _qemuAgent qemuAgent;
+typedef qemuAgent *qemuAgentPtr;
+
+typedef struct _qemuAgentCallbacks qemuAgentCallbacks;
+typedef qemuAgentCallbacks *qemuAgentCallbacksPtr;
+struct _qemuAgentCallbacks {
+ void (*destroy)(qemuAgentPtr mon,
+ virDomainObjPtr vm);
+ void (*eofNotify)(qemuAgentPtr mon,
+ virDomainObjPtr vm);
+ void (*errorNotify)(qemuAgentPtr mon,
+ virDomainObjPtr vm);
+};
+
+
+qemuAgentPtr qemuAgentOpen(virDomainObjPtr vm,
+ const virDomainChrSourceDef *config,
+ qemuAgentCallbacksPtr cb);
+
+void qemuAgentClose(qemuAgentPtr mon);
+
+void qemuAgentNotifyClose(qemuAgentPtr mon);
+
+typedef enum {
+ QEMU_AGENT_EVENT_NONE = 0,
+ QEMU_AGENT_EVENT_SHUTDOWN,
+ QEMU_AGENT_EVENT_SUSPEND,
+ QEMU_AGENT_EVENT_RESET,
+} qemuAgentEvent;
+
+void qemuAgentNotifyEvent(qemuAgentPtr mon,
+ qemuAgentEvent event);
+
+typedef enum {
+ QEMU_AGENT_SHUTDOWN_POWERDOWN,
+ QEMU_AGENT_SHUTDOWN_REBOOT,
+ QEMU_AGENT_SHUTDOWN_HALT,
+
+ QEMU_AGENT_SHUTDOWN_LAST,
+} qemuAgentShutdownMode;
+
+int qemuAgentShutdown(qemuAgentPtr mon,
+ qemuAgentShutdownMode mode);
+
+int qemuAgentFSFreeze(qemuAgentPtr mon,
+ const char **mountpoints, unsigned int nmountpoints);
+int qemuAgentFSThaw(qemuAgentPtr mon);
+int qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info,
+ virDomainDefPtr vmdef);
+
+int qemuAgentSuspend(qemuAgentPtr mon,
+ unsigned int target);
+
+int qemuAgentArbitraryCommand(qemuAgentPtr mon,
+ const char *cmd,
+ char **result,
+ int timeout);
+int qemuAgentFSTrim(qemuAgentPtr mon,
+ unsigned long long minimum);
+
+
+typedef struct _qemuAgentCPUInfo qemuAgentCPUInfo;
+typedef qemuAgentCPUInfo *qemuAgentCPUInfoPtr;
+struct _qemuAgentCPUInfo {
+ unsigned int id; /* logical cpu ID */
+ bool online; /* true if the CPU is activated */
+ bool offlinable; /* true if the CPU can be offlined */
+
+ bool modified; /* set to true if the vcpu state needs to be changed */
+};
+
+int qemuAgentGetVCPUs(qemuAgentPtr mon, qemuAgentCPUInfoPtr *info);
+int qemuAgentSetVCPUs(qemuAgentPtr mon, qemuAgentCPUInfoPtr cpus, size_t ncpus);
+int qemuAgentUpdateCPUInfo(unsigned int nvcpus,
+ qemuAgentCPUInfoPtr cpuinfo,
+ int ncpuinfo);
+
+int qemuAgentGetTime(qemuAgentPtr mon,
+ long long *seconds,
+ unsigned int *nseconds);
+int qemuAgentSetTime(qemuAgentPtr mon,
+ long long seconds,
+ unsigned int nseconds,
+ bool sync);
+
+int qemuAgentGetInterfaces(qemuAgentPtr mon,
+ virDomainInterfacePtr **ifaces);
+
+int qemuAgentSetUserPassword(qemuAgentPtr mon,
+ const char *user,
+ const char *password,
+ bool crypted);
+#endif /* __QEMU_AGENT_H__ */
diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c
index 3be745e..a011838 100644
--- a/tests/qemuagenttest.c
+++ b/tests/qemuagenttest.c
@@ -23,7 +23,7 @@
#include "testutilsqemu.h"
#include "qemumonitortestutils.h"
#include "qemu/qemu_conf.h"
-#include "qemu/qemu_agent.h"
+#include "virqemuagent.h"
#include "virthread.h"
#include "virerror.h"
#include "virstring.h"
diff --git a/tests/qemumonitortestutils.c b/tests/qemumonitortestutils.c
index cfd0a38..f509011 100644
--- a/tests/qemumonitortestutils.c
+++ b/tests/qemumonitortestutils.c
@@ -30,7 +30,7 @@
#include "virthread.h"
#include "qemu/qemu_processpriv.h"
#include "qemu/qemu_monitor.h"
-#include "qemu/qemu_agent.h"
+#include "virqemuagent.h"
#include "rpc/virnetsocket.h"
#include "viralloc.h"
#include "virlog.h"
diff --git a/tests/qemumonitortestutils.h b/tests/qemumonitortestutils.h
index 8b19b37..b9e219d 100644
--- a/tests/qemumonitortestutils.h
+++ b/tests/qemumonitortestutils.h
@@ -23,7 +23,7 @@
# include "domain_conf.h"
# include "qemu/qemu_conf.h"
# include "qemu/qemu_monitor.h"
-# include "qemu/qemu_agent.h"
+# include "virqemuagent.h"
typedef struct _qemuMonitorTest qemuMonitorTest;
typedef qemuMonitorTest *qemuMonitorTestPtr;
--
2.1.4