[libvirt] [PATCH 1/4] QEMU guest agent support

Michal Privoznik mprivozn at redhat.com
Tue Jan 17 11:44:24 UTC 2012


There is now a standard QEMU guest agent that can be installed
and given a virtio serial channel

    <channel type='unix'>
      <source mode='bind' path='/var/lib/libvirt/qemu/f16x86_64.agent'/>
      <target type='virtio' name='org.qemu.guest_agent.0'/>
    </channel>

The protocol that runs over the guest agent is JSON based and
very similar to the JSON monitor. We can't use exactly the same
code because there are some odd differences in the way messages
and errors are structured. The qemu_agent.c file is based on
a combination and simplification of qemu_monitor.c and
qemu_monitor_json.c

* src/qemu/qemu_agent.c, src/qemu/qemu_agent.h: Support for
  talking to the agent for shutdown
* src/qemu/qemu_domain.c, src/qemu/qemu_domain.h: Add thread
  helpers for talking to the agent
* src/qemu/qemu_process.c: Connect to agent whenever starting
  a guest
* src/qemu/qemu_monitor_json.c: Make variable static
---
 po/POTFILES.in               |    1 +
 src/Makefile.am              |    1 +
 src/qemu/qemu_agent.c        | 1151 ++++++++++++++++++++++++++++++++++++++++++
 src/qemu/qemu_agent.h        |   69 +++
 src/qemu/qemu_domain.c       |   97 ++++
 src/qemu/qemu_domain.h       |   22 +
 src/qemu/qemu_monitor_json.c |    2 +-
 src/qemu/qemu_process.c      |  188 +++++++
 8 files changed, 1530 insertions(+), 1 deletions(-)
 create mode 100644 src/qemu/qemu_agent.c
 create mode 100644 src/qemu/qemu_agent.h

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3e8359a..418d48a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -57,6 +57,7 @@ src/nwfilter/nwfilter_learnipaddr.c
 src/openvz/openvz_conf.c
 src/openvz/openvz_driver.c
 src/phyp/phyp_driver.c
+src/qemu/qemu_agent.c
 src/qemu/qemu_bridge_filter.c
 src/qemu/qemu_capabilities.c
 src/qemu/qemu_cgroup.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 0a1221a..a18bcde 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -363,6 +363,7 @@ 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_capabilities.c qemu/qemu_capabilities.h\
 		qemu/qemu_command.c qemu/qemu_command.h		\
 		qemu/qemu_domain.c qemu/qemu_domain.h		\
diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c
new file mode 100644
index 0000000..9f31367
--- /dev/null
+++ b/src/qemu/qemu_agent.c
@@ -0,0 +1,1151 @@
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Daniel P. Berrange <berrange at 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 "qemu_command.h"
+#include "memory.h"
+#include "logging.h"
+#include "virterror_internal.h"
+#include "json.h"
+#include "virfile.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+#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 text monitor reply / error */
+    char *rxBuffer;
+    int rxLength;
+    /* Used by the JSON monitor to hold reply / error */
+    void *rxObject;
+
+    /* True if rxBuffer / rxObject are ready, or a
+     * fatal error occurred on the monitor channel
+     */
+    bool finished;
+};
+
+
+struct _qemuAgent {
+    virMutex lock; /* also used to protect fd */
+    virCond notify;
+
+    int refs;
+
+    int fd;
+    int watch;
+
+    bool connectPending;
+
+    virDomainObjPtr vm;
+
+    qemuAgentCallbacksPtr cb;
+
+    /* If there's a command being processed this will be
+     * non-NULL */
+    qemuAgentMessagePtr msg;
+
+    /* Buffer incoming data ready for Text/QMP 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;
+};
+
+#if DEBUG_RAW_IO
+# include <c-ctype.h>
+static char * qemuAgentEscapeNonPrintable(const char *text)
+{
+    int i;
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    for (i = 0 ; text[i] != '\0' ; i++) {
+        if (c_isprint(text[i]) ||
+            text[i] == '\n' ||
+            (text[i] == '\r' && text[i+1] == '\n'))
+            virBufferAsprintf(&buf,"%c", text[i]);
+        else
+            virBufferAsprintf(&buf, "0x%02x", text[i]);
+    }
+    return virBufferContentAndReset(&buf);
+}
+#endif
+
+void qemuAgentLock(qemuAgentPtr mon)
+{
+    virMutexLock(&mon->lock);
+}
+
+
+void qemuAgentUnlock(qemuAgentPtr mon)
+{
+    virMutexUnlock(&mon->lock);
+}
+
+
+static void qemuAgentFree(qemuAgentPtr mon)
+{
+    VIR_DEBUG("mon=%p", mon);
+    if (mon->cb && mon->cb->destroy)
+        (mon->cb->destroy)(mon, mon->vm);
+    ignore_value(virCondDestroy(&mon->notify));
+    virMutexDestroy(&mon->lock);
+    VIR_FREE(mon->buffer);
+    VIR_FREE(mon);
+}
+
+int qemuAgentRef(qemuAgentPtr mon)
+{
+    mon->refs++;
+    return mon->refs;
+}
+
+int qemuAgentUnref(qemuAgentPtr mon)
+{
+    mon->refs--;
+
+    if (mon->refs == 0) {
+        qemuAgentUnlock(mon);
+        qemuAgentFree(mon);
+        return 0;
+    }
+
+    return mon->refs;
+}
+
+static void
+qemuAgentUnwatch(void *monitor)
+{
+    qemuAgentPtr mon = monitor;
+
+    qemuAgentLock(mon);
+    if (qemuAgentUnref(mon) > 0)
+        qemuAgentUnlock(mon);
+}
+
+static int
+qemuAgentOpenUnix(const char *monitor, pid_t cpid, bool *inProgress)
+{
+    struct sockaddr_un addr;
+    int monfd;
+    int timeout = 3; /* In seconds */
+    int ret, i = 0;
+
+    *inProgress = false;
+
+    if ((monfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+        virReportSystemError(errno,
+                             "%s", _("failed to create socket"));
+        return -1;
+    }
+
+    if (virSetNonBlock(monfd) < 0) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        "%s", _("Unable to put monitor into non-blocking mode"));
+        goto error;
+    }
+
+    if (virSetCloseExec(monfd) < 0) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        "%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) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        _("Agent path %s too big for destination"), monitor);
+        goto error;
+    }
+
+    do {
+        ret = connect(monfd, (struct sockaddr *) &addr, sizeof(addr));
+
+        if (ret == 0)
+            break;
+
+        if ((errno == ENOENT || errno == ECONNREFUSED) &&
+            virKillProcess(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;
+
+    } while ((++i <= timeout*5) && (usleep(.2 * 1000000) <= 0));
+
+    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)) < 0) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        _("Unable to open monitor path %s"), monitor);
+        return -1;
+    }
+
+    if (virSetNonBlock(monfd) < 0) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        "%s", _("Unable to put monitor into non-blocking mode"));
+        goto error;
+    }
+
+    if (virSetCloseExec(monfd) < 0) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        "%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)))
+        goto cleanup;
+
+    if (obj->type != VIR_JSON_TYPE_OBJECT) {
+        qemuReportError(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) {
+            msg->rxObject = obj;
+            msg->finished = 1;
+            obj = NULL;
+            ret = 0;
+        } else {
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("Unexpected JSON reply '%s'"), line);
+        }
+    } else {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        _("Unknown JSON reply '%s'"), line);
+    }
+
+cleanup:
+    virJSONValueFree(obj);
+    return ret;
+}
+
+static int qemuAgentIOProcessData(qemuAgentPtr mon,
+                                  const char *data,
+                                  size_t len,
+                                  qemuAgentMessagePtr msg)
+{
+    int used = 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);
+            char *line = strndup(data + used, got);
+            if (!line) {
+                virReportOOMError();
+                return -1;
+            }
+            used += got + strlen(LINE_ENDING);
+            line[got] = '\0'; /* kill \n */
+            if (qemuAgentIOProcessLine(mon, line, msg) < 0) {
+                VIR_FREE(line);
+                return -1;
+            }
+
+            VIR_FREE(line);
+        } 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 %d %p %p [[[%s]]][[[%s]]]"),
+              (int)mon->bufferOffset, mon->msg, msg, str1, str2);
+    VIR_FREE(str1);
+    VIR_FREE(str2);
+# else
+    VIR_DEBUG("Process %d", (int)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 %d used %d", (int)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) {
+            virReportOOMError();
+            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 %d bytes of data", (int)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;
+
+    /* lock access to the monitor and protect fd */
+    qemuAgentLock(mon);
+    qemuAgentRef(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;
+        qemuReportError(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) {
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("End of file from monitor"));
+            eof = 1;
+            events &= ~VIR_EVENT_HANDLE_HANGUP;
+        }
+
+        if (!error && !eof &&
+            events & VIR_EVENT_HANDLE_ERROR) {
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("Invalid file descriptor while waiting for monitor"));
+            eof = 1;
+            events &= ~VIR_EVENT_HANDLE_ERROR;
+        }
+        if (!error && events) {
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("Unhandled event %d for monitor fd %d"),
+                            events, mon->fd);
+            error = 1;
+        }
+    }
+
+    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)
+                qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                                _("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);
+        if (qemuAgentUnref(mon) > 0)
+            qemuAgentUnlock(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);
+        if (qemuAgentUnref(mon) > 0)
+            qemuAgentUnlock(mon);
+        VIR_DEBUG("Triggering error callback");
+        (errorNotify)(mon, vm);
+    } else {
+        if (qemuAgentUnref(mon) > 0)
+            qemuAgentUnlock(mon);
+    }
+}
+
+
+qemuAgentPtr
+qemuAgentOpen(virDomainObjPtr vm,
+              virDomainChrSourceDefPtr config,
+              qemuAgentCallbacksPtr cb)
+{
+    qemuAgentPtr mon;
+
+    if (!cb || !cb->eofNotify) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("EOF notify callback must be supplied"));
+        return NULL;
+    }
+
+    if (VIR_ALLOC(mon) < 0) {
+        virReportOOMError();
+        return NULL;
+    }
+
+    if (virMutexInit(&mon->lock) < 0) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("cannot initialize monitor mutex"));
+        VIR_FREE(mon);
+        return NULL;
+    }
+    if (virCondInit(&mon->notify) < 0) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("cannot initialize monitor condition"));
+        virMutexDestroy(&mon->lock);
+        VIR_FREE(mon);
+        return NULL;
+    }
+    mon->fd = -1;
+    mon->refs = 1;
+    mon->vm = vm;
+    mon->cb = cb;
+    qemuAgentLock(mon);
+
+    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:
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        _("unable to handle monitor type: %s"),
+                        virDomainChrTypeToString(config->type));
+        goto cleanup;
+    }
+
+    if (mon->fd == -1) goto cleanup;
+
+    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, qemuAgentUnwatch)) < 0) {
+        qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                        _("unable to register monitor events"));
+        goto cleanup;
+    }
+    qemuAgentRef(mon);
+
+    VIR_DEBUG("New mon %p fd =%d watch=%d", mon, mon->fd, mon->watch);
+    qemuAgentUnlock(mon);
+
+    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;
+    qemuAgentUnlock(mon);
+    qemuAgentClose(mon);
+    return NULL;
+}
+
+
+void qemuAgentClose(qemuAgentPtr mon)
+{
+    if (!mon)
+        return;
+
+    VIR_DEBUG("mon=%p", mon);
+
+    qemuAgentLock(mon);
+
+    if (mon->fd >= 0) {
+        if (mon->watch)
+            virEventRemoveHandle(mon->watch);
+        VIR_FORCE_CLOSE(mon->fd);
+    }
+
+    if (qemuAgentUnref(mon) > 0)
+        qemuAgentUnlock(mon);
+}
+
+
+static int qemuAgentSend(qemuAgentPtr mon,
+                         qemuAgentMessagePtr msg)
+{
+    int ret = -1;
+
+    /* 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;
+    }
+
+    mon->msg = msg;
+    qemuAgentUpdateWatch(mon);
+
+    while (!mon->msg->finished) {
+        if (virCondWait(&mon->notify, &mon->lock) < 0) {
+            qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                            _("Unable to wait on 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;
+}
+
+
+static int
+qemuAgentCommand(qemuAgentPtr mon,
+                 virJSONValuePtr cmd,
+                 virJSONValuePtr *reply)
+{
+    int ret = -1;
+    qemuAgentMessage msg;
+    char *cmdstr = NULL;
+
+    *reply = NULL;
+
+    memset(&msg, 0, sizeof msg);
+
+    if (!(cmdstr = virJSONValueToString(cmd))) {
+        virReportOOMError();
+        goto cleanup;
+    }
+    if (virAsprintf(&msg.txBuffer, "%s" LINE_ENDING, cmdstr) < 0) {
+        virReportOOMError();
+        goto cleanup;
+    }
+    msg.txLength = strlen(msg.txBuffer);
+
+    VIR_DEBUG("Send command '%s' for write", cmdstr);
+
+    ret = qemuAgentSend(mon, &msg);
+
+    VIR_DEBUG("Receive command reply ret=%d rxObject=%p",
+              ret, msg.rxObject);
+
+
+    if (ret == 0) {
+        if (!msg.rxObject) {
+            qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                            _("Missing monitor reply object"));
+            ret = -1;
+        } else {
+            *reply = msg.rxObject;
+        }
+    }
+
+cleanup:
+    VIR_FREE(cmdstr);
+    VIR_FREE(msg.txBuffer);
+
+    return ret;
+}
+
+
+/* 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 = NULL;
+
+    /* The QMP 'desc' field is usually sufficient for our generic
+     * error reporting needs.
+     */
+    if (klass)
+        detail = virJSONValueObjectGetString(error, "desc");
+
+
+    if (!detail)
+        detail = "unknown QEMU command error";
+
+    return detail;
+}
+
+static const char *
+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);
+        char *replystr = virJSONValueToString(reply);
+
+        /* Log the full JSON formatted command & error */
+        VIR_DEBUG("unable to execute QEMU command %s: %s",
+                  cmdstr, replystr);
+
+        /* Only send the user the command name + friendly error */
+        if (!error)
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("unable to execute QEMU command '%s'"),
+                            qemuAgentCommandName(cmd));
+        else
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("unable to execute QEMU command '%s': %s"),
+                            qemuAgentCommandName(cmd),
+                            qemuAgentStringifyError(error));
+
+        VIR_FREE(cmdstr);
+        VIR_FREE(replystr);
+        return -1;
+    } else if (!virJSONValueObjectHasKey(reply, "return")) {
+        char *cmdstr = virJSONValueToString(cmd);
+        char *replystr = virJSONValueToString(reply);
+
+        VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %s: %s",
+                  cmdstr, replystr);
+        qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                        _("unable to execute QEMU command '%s'"),
+                        qemuAgentCommandName(cmd));
+        VIR_FREE(cmdstr);
+        VIR_FREE(replystr);
+        return -1;
+    }
+    return 0;
+}
+
+
+#if 0
+static int
+qemuAgentHasError(virJSONValuePtr reply,
+                        const char *klass)
+{
+    virJSONValuePtr error;
+    const char *thisklass;
+
+    if (!virJSONValueObjectHasKey(reply, "error"))
+        return 0;
+
+    error = virJSONValueObjectGet(reply, "error");
+    if (!error)
+        return 0;
+
+    if (!virJSONValueObjectHasKey(error, "class"))
+        return 0;
+
+    thisklass = virJSONValueObjectGetString(error, "class");
+
+    if (!thisklass)
+        return 0;
+
+    return STREQ(klass, thisklass);
+}
+#endif
+
+
+static virJSONValuePtr ATTRIBUTE_SENTINEL
+qemuAgentMakeCommand(const char *cmdname,
+                           ...)
+{
+    virJSONValuePtr obj;
+    virJSONValuePtr jargs = NULL;
+    va_list args;
+    char *key;
+
+    va_start(args, cmdname);
+
+    if (!(obj = virJSONValueNewObject()))
+        goto no_memory;
+
+    if (virJSONValueObjectAppendString(obj, "execute", cmdname) < 0)
+        goto no_memory;
+
+    while ((key = va_arg(args, char *)) != NULL) {
+        int ret;
+        char type;
+
+        if (strlen(key) < 3) {
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("argument key '%s' is too short, missing type prefix"),
+                            key);
+            goto error;
+        }
+
+        /* Keys look like   s:name  the first letter is a type code */
+        type = key[0];
+        key += 2;
+
+        if (!jargs &&
+            !(jargs = virJSONValueNewObject()))
+            goto no_memory;
+
+        /* This doesn't support maps/arrays.  This hasn't
+         * proved to be a problem..... yet :-)  */
+        switch (type) {
+        case 's': {
+            char *val = va_arg(args, char *);
+            ret = virJSONValueObjectAppendString(jargs, key, val);
+        }   break;
+        case 'i': {
+            int val = va_arg(args, int);
+            ret = virJSONValueObjectAppendNumberInt(jargs, key, val);
+        }   break;
+        case 'u': {
+            unsigned int val = va_arg(args, unsigned int);
+            ret = virJSONValueObjectAppendNumberUint(jargs, key, val);
+        }   break;
+        case 'I': {
+            long long val = va_arg(args, long long);
+            ret = virJSONValueObjectAppendNumberLong(jargs, key, val);
+        }   break;
+        case 'U': {
+            /* qemu silently truncates numbers larger than LLONG_MAX,
+             * so passing the full range of unsigned 64 bit integers
+             * is not safe here.  Pass them as signed 64 bit integers
+             * instead.
+             */
+            long long val = va_arg(args, long long);
+            ret = virJSONValueObjectAppendNumberLong(jargs, key, val);
+        }   break;
+        case 'd': {
+            double val = va_arg(args, double);
+            ret = virJSONValueObjectAppendNumberDouble(jargs, key, val);
+        }   break;
+        case 'b': {
+            int val = va_arg(args, int);
+            ret = virJSONValueObjectAppendBoolean(jargs, key, val);
+        }   break;
+        case 'n': {
+            ret = virJSONValueObjectAppendNull(jargs, key);
+        }   break;
+        default:
+            qemuReportError(VIR_ERR_INTERNAL_ERROR,
+                            _("unsupported data type '%c' for arg '%s'"), type, key - 2);
+            goto error;
+        }
+        if (ret < 0)
+            goto no_memory;
+    }
+
+    if (jargs &&
+        virJSONValueObjectAppend(obj, "arguments", jargs) < 0)
+        goto no_memory;
+
+    va_end(args);
+
+    return obj;
+
+no_memory:
+    virReportOOMError();
+error:
+    virJSONValueFree(obj);
+    virJSONValueFree(jargs);
+    va_end(args);
+    return NULL;
+}
+
+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;
+
+    ret = qemuAgentCommand(mon, cmd, &reply);
+
+    if (ret == 0)
+        ret = qemuAgentCheckError(cmd, reply);
+
+    virJSONValueFree(cmd);
+    virJSONValueFree(reply);
+    return ret;
+}
diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h
new file mode 100644
index 0000000..93c2ae7
--- /dev/null
+++ b/src/qemu/qemu_agent.h
@@ -0,0 +1,69 @@
+/*
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Daniel P. Berrange <berrange at 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,
+                           virDomainChrSourceDefPtr config,
+                           qemuAgentCallbacksPtr cb);
+
+void qemuAgentLock(qemuAgentPtr mon);
+void qemuAgentUnlock(qemuAgentPtr mon);
+
+int qemuAgentRef(qemuAgentPtr mon);
+int qemuAgentUnref(qemuAgentPtr mon) ATTRIBUTE_RETURN_CHECK;
+
+void qemuAgentClose(qemuAgentPtr mon);
+
+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);
+
+#endif /* __QEMU_AGENT_H__ */
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 6b03c62..fac5fe8 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -219,6 +219,10 @@ static void qemuDomainObjPrivateFree(void *data)
         VIR_ERROR(_("Unexpected QEMU monitor still active during domain deletion"));
         qemuMonitorClose(priv->mon);
     }
+    if (priv->agent) {
+        VIR_ERROR(_("Unexpected QEMU agent still active during domain deletion"));
+        qemuAgentClose(priv->agent);
+    }
     VIR_FREE(priv);
 }
 
@@ -1025,6 +1029,99 @@ void qemuDomainObjExitMonitorWithDriver(struct qemud_driver *driver,
     qemuDomainObjExitMonitorInternal(driver, true, obj);
 }
 
+
+
+static int
+qemuDomainObjEnterAgentInternal(struct qemud_driver *driver,
+                                bool driver_locked,
+                                virDomainObjPtr obj)
+{
+    qemuDomainObjPrivatePtr priv = obj->privateData;
+
+    qemuAgentLock(priv->agent);
+    qemuAgentRef(priv->agent);
+    ignore_value(virTimeMillisNow(&priv->agentStart));
+    virDomainObjUnlock(obj);
+    if (driver_locked)
+        qemuDriverUnlock(driver);
+
+    return 0;
+}
+
+static void ATTRIBUTE_NONNULL(1)
+qemuDomainObjExitAgentInternal(struct qemud_driver *driver,
+                               bool driver_locked,
+                               virDomainObjPtr obj)
+{
+    qemuDomainObjPrivatePtr priv = obj->privateData;
+    int refs;
+
+    refs = qemuAgentUnref(priv->agent);
+
+    if (refs > 0)
+        qemuAgentUnlock(priv->agent);
+
+    if (driver_locked)
+        qemuDriverLock(driver);
+    virDomainObjLock(obj);
+
+    priv->agentStart = 0;
+    if (refs == 0) {
+        priv->agent = NULL;
+    }
+}
+
+/*
+ * obj must be locked before calling, qemud_driver must be unlocked
+ *
+ * To be called immediately before any QEMU agent API call
+ * Must have already either called qemuDomainObjBeginJob() and checked
+ * that the VM is still active;
+ *
+ * To be followed with qemuDomainObjExitAgent() once complete
+ */
+void qemuDomainObjEnterAgent(struct qemud_driver *driver,
+                             virDomainObjPtr obj)
+{
+    ignore_value(qemuDomainObjEnterAgentInternal(driver, false, obj));
+}
+
+/* obj must NOT be locked before calling, qemud_driver must be unlocked
+ *
+ * Should be paired with an earlier qemuDomainObjEnterAgent() call
+ */
+void qemuDomainObjExitAgent(struct qemud_driver *driver,
+                            virDomainObjPtr obj)
+{
+    qemuDomainObjExitAgentInternal(driver, false, obj);
+}
+
+/*
+ * obj must be locked before calling, qemud_driver must be locked
+ *
+ * To be called immediately before any QEMU agent API call
+ * Must have already either called qemuDomainObjBeginJobWithDriver() and
+ * checked that the VM is still active; may not be used for nested async jobs.
+ *
+ * To be followed with qemuDomainObjExitAgentWithDriver() once complete
+ */
+void qemuDomainObjEnterAgentWithDriver(struct qemud_driver *driver,
+                                       virDomainObjPtr obj)
+{
+    ignore_value(qemuDomainObjEnterAgentInternal(driver, true, obj));
+}
+
+/* obj must NOT be locked before calling, qemud_driver must be unlocked,
+ * and will be locked after returning
+ *
+ * Should be paired with an earlier qemuDomainObjEnterAgentWithDriver() call
+ */
+void qemuDomainObjExitAgentWithDriver(struct qemud_driver *driver,
+                                      virDomainObjPtr obj)
+{
+    qemuDomainObjExitAgentInternal(driver, true, obj);
+}
+
 void qemuDomainObjEnterRemoteWithDriver(struct qemud_driver *driver,
                                         virDomainObjPtr obj)
 {
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
index f40fa09..d76d717 100644
--- a/src/qemu/qemu_domain.h
+++ b/src/qemu/qemu_domain.h
@@ -27,6 +27,7 @@
 # include "threads.h"
 # include "domain_conf.h"
 # include "qemu_monitor.h"
+# include "qemu_agent.h"
 # include "qemu_conf.h"
 # include "bitmap.h"
 
@@ -102,6 +103,11 @@ struct _qemuDomainObjPrivate {
     int monJSON;
     bool monError;
     unsigned long long monStart;
+
+    qemuAgentPtr agent;
+    bool agentError;
+    unsigned long long agentStart;
+
     bool gotShutdown;
     bool beingDestroyed;
     char *pidfile;
@@ -192,6 +198,22 @@ int qemuDomainObjEnterMonitorAsync(struct qemud_driver *driver,
 void qemuDomainObjExitMonitorWithDriver(struct qemud_driver *driver,
                                         virDomainObjPtr obj)
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+
+void qemuDomainObjEnterAgent(struct qemud_driver *driver,
+                               virDomainObjPtr obj)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+void qemuDomainObjExitAgent(struct qemud_driver *driver,
+                              virDomainObjPtr obj)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+void qemuDomainObjEnterAgentWithDriver(struct qemud_driver *driver,
+                                         virDomainObjPtr obj)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+void qemuDomainObjExitAgentWithDriver(struct qemud_driver *driver,
+                                      virDomainObjPtr obj)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+
 void qemuDomainObjEnterRemoteWithDriver(struct qemud_driver *driver,
                                         virDomainObjPtr obj)
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 7eb2a92..9e01cae 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -59,7 +59,7 @@ static void qemuMonitorJSONHandleVNCInitialize(qemuMonitorPtr mon, virJSONValueP
 static void qemuMonitorJSONHandleVNCDisconnect(qemuMonitorPtr mon, virJSONValuePtr data);
 static void qemuMonitorJSONHandleBlockJob(qemuMonitorPtr mon, virJSONValuePtr data);
 
-struct {
+static struct {
     const char *type;
     void (*handler)(qemuMonitorPtr mon, virJSONValuePtr data);
 } eventHandlers[] = {
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index e16ca07..d22020b 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -107,6 +107,164 @@ qemuProcessRemoveDomainStatus(struct qemud_driver *driver,
 extern struct qemud_driver *qemu_driver;
 
 /*
+ * This is a callback registered with a qemuAgentPtr instance,
+ * and to be invoked when the agent console hits an end of file
+ * condition, or error, thus indicating VM shutdown should be
+ * performed
+ */
+static void
+qemuProcessHandleAgentEOF(qemuAgentPtr agent ATTRIBUTE_UNUSED,
+                          virDomainObjPtr vm)
+{
+    struct qemud_driver *driver = qemu_driver;
+    qemuDomainObjPrivatePtr priv;
+
+    VIR_DEBUG("Received EOF from agent on %p '%s'", vm, vm->def->name);
+
+    qemuDriverLock(driver);
+    virDomainObjLock(vm);
+
+    priv = vm->privateData;
+
+    qemuAgentClose(agent);
+    priv->agent = NULL;
+
+    virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+}
+
+
+/*
+ * This is invoked when there is some kind of error
+ * parsing data to/from the agent. The VM can continue
+ * to run, but no further agent commands will be
+ * allowed
+ */
+static void
+qemuProcessHandleAgentError(qemuAgentPtr agent ATTRIBUTE_UNUSED,
+                            virDomainObjPtr vm)
+{
+    struct qemud_driver *driver = qemu_driver;
+    qemuDomainObjPrivatePtr priv;
+
+    VIR_DEBUG("Received error from agent on %p '%s'", vm, vm->def->name);
+
+    qemuDriverLock(driver);
+    virDomainObjLock(vm);
+
+    priv = vm->privateData;
+
+    priv->agentError = true;
+
+    virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+}
+
+static void qemuProcessHandleAgentDestroy(qemuAgentPtr agent,
+                                          virDomainObjPtr vm)
+{
+    qemuDomainObjPrivatePtr priv;
+
+    virDomainObjLock(vm);
+    priv = vm->privateData;
+    if (priv->agent == agent)
+        priv->agent = NULL;
+    if (virDomainObjUnref(vm) > 0)
+        virDomainObjUnlock(vm);
+}
+
+
+static qemuAgentCallbacks agentCallbacks = {
+    .destroy = qemuProcessHandleAgentDestroy,
+    .eofNotify = qemuProcessHandleAgentEOF,
+    .errorNotify = qemuProcessHandleAgentError,
+};
+
+static virDomainChrSourceDefPtr
+qemuFindAgentConfig(virDomainDefPtr def)
+{
+    virDomainChrSourceDefPtr config = NULL;
+    int i;
+
+    for (i = 0 ; i < def->nchannels ; i++) {
+        virDomainChrDefPtr channel = def->channels[i];
+
+        if (channel->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO)
+            continue;
+
+        if (STREQ(channel->target.name, "org.qemu.guest_agent.0")) {
+            config = &channel->source;
+            break;
+        }
+    }
+
+    return config;
+}
+
+static int
+qemuConnectAgent(struct qemud_driver *driver, virDomainObjPtr vm)
+{
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    int ret = -1;
+    qemuAgentPtr agent = NULL;
+    virDomainChrSourceDefPtr config = qemuFindAgentConfig(vm->def);
+
+    if (!config)
+        return 0;
+
+    if (virSecurityManagerSetDaemonSocketLabel(driver->securityManager,
+                                               vm->def) < 0) {
+        VIR_ERROR(_("Failed to set security context for agent for %s"),
+                  vm->def->name);
+        goto cleanup;
+    }
+
+    /* Hold an extra reference because we can't allow 'vm' to be
+     * deleted while the agent is active */
+    virDomainObjRef(vm);
+
+    ignore_value(virTimeMillisNow(&priv->agentStart));
+    virDomainObjUnlock(vm);
+    qemuDriverUnlock(driver);
+
+    agent = qemuAgentOpen(vm,
+                          config,
+                          &agentCallbacks);
+
+    qemuDriverLock(driver);
+    virDomainObjLock(vm);
+    priv->agentStart = 0;
+
+    if (virSecurityManagerClearSocketLabel(driver->securityManager,
+                                           vm->def) < 0) {
+        VIR_ERROR(_("Failed to clear security context for agent for %s"),
+                  vm->def->name);
+        goto cleanup;
+    }
+
+    /* Safe to ignore value since ref count was incremented above */
+    if (agent == NULL)
+        ignore_value(virDomainObjUnref(vm));
+
+    if (!virDomainObjIsActive(vm)) {
+        qemuAgentClose(agent);
+        goto cleanup;
+    }
+    priv->agent = agent;
+
+    if (priv->agent == NULL) {
+        VIR_INFO("Failed to connect agent for %s", vm->def->name);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    return ret;
+}
+
+
+/*
  * This is a callback registered with a qemuMonitorPtr instance,
  * and to be invoked when the monitor console hits an end of file
  * condition, or error, thus indicating VM shutdown should be
@@ -2691,6 +2849,14 @@ qemuProcessReconnect(void *opaque)
     if (qemuConnectMonitor(driver, obj) < 0)
         goto error;
 
+    /* Failure to connect to agent shouldn't be fatal */
+    if (qemuConnectAgent(driver, obj) < 0) {
+        VIR_WARN("Cannot connect to QEMU guest agent for %s",
+                 obj->def->name);
+        virResetLastError();
+        priv->agentError = true;
+    }
+
     if (qemuUpdateActivePciHostdevs(driver, obj->def) < 0) {
         goto error;
     }
@@ -3258,6 +3424,14 @@ int qemuProcessStart(virConnectPtr conn,
     if (qemuProcessWaitForMonitor(driver, vm, priv->qemuCaps, pos) < 0)
         goto cleanup;
 
+    /* Failure to connect to agent shouldn't be fatal */
+    if (qemuConnectAgent(driver, vm) < 0) {
+        VIR_WARN("Cannot connect to QEMU guest agent for %s",
+                 vm->def->name);
+        virResetLastError();
+        priv->agentError = true;
+    }
+
     VIR_DEBUG("Detecting VCPU PIDs");
     if (qemuProcessDetectVcpuPIDs(driver, vm) < 0)
         goto cleanup;
@@ -3460,6 +3634,12 @@ void qemuProcessStop(struct qemud_driver *driver,
         }
     }
 
+    if (priv->agent) {
+        qemuAgentClose(priv->agent);
+        priv->agent = NULL;
+        priv->agentError = false;
+    }
+
     if (priv->mon)
         qemuMonitorClose(priv->mon);
 
@@ -3713,6 +3893,14 @@ int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED,
     if (qemuProcessWaitForMonitor(driver, vm, priv->qemuCaps, -1) < 0)
         goto cleanup;
 
+    /* Failure to connect to agent shouldn't be fatal */
+    if (qemuConnectAgent(driver, vm) < 0) {
+        VIR_WARN("Cannot connect to QEMU guest agent for %s",
+                 vm->def->name);
+        virResetLastError();
+        priv->agentError = true;
+    }
+
     VIR_DEBUG("Detecting VCPU PIDs");
     if (qemuProcessDetectVcpuPIDs(driver, vm) < 0)
         goto cleanup;
-- 
1.7.3.4




More information about the libvir-list mailing list