Implement QEMU Guest Agent support for bhyve. In bhyve it can configured
using the virtio-console device.

This change covers only two APIs using the agent:

 - DomainQemuAgentCommand -- the most generic one.
 - DomainGetHostname -- extended to support not only DHCP lease source,
   but an agent as well.

There are two things that I'm not sure about this patch.

 - The protocol files were updated to make DomainQemuAgentCommand generic
   instead of Qemu specific. QEMU_PROC_DOMAIN_AGENT_COMMAND was removed in
   favor of REMOTE_PROC_DOMAIN_QEMU_AGENT_COMMAND.

   Even though the protocol is documented as private, I'm not sure how
   desired this change is.

 - src/qemu/bhyve_qemu_agent.c and src/qemu/qemu_agent.c should
   share the same implementation. Ideally this should live
   somewhere in src/util/virqemuagent.c, but as it is using things
   from conf/, such as virDomainObj, it cannot be moved as is.

   I considered extracting a simpler data structure that will not
   use the conf/ types, but it didn't work very well for me and
   I didn't like the way it looks in the driver code.

   Maybe I'm missing some better approach?

Signed-off-by: Roman Bogorodskiy <[email protected]>
---
 po/POTFILES                  |    1 +
 src/bhyve/bhyve_domain.c     |   32 +
 src/bhyve/bhyve_domain.h     |   14 +
 src/bhyve/bhyve_driver.c     |  179 ++++-
 src/bhyve/bhyve_process.c    |  117 ++++
 src/bhyve/bhyve_process.h    |    4 +
 src/bhyve/bhyve_qemu_agent.c | 1259 ++++++++++++++++++++++++++++++++++
 src/bhyve/bhyve_qemu_agent.h |  197 ++++++
 src/bhyve/meson.build        |    5 +-
 src/remote/qemu_protocol.x   |   18 -
 src/remote/remote_protocol.x |   20 +-
 11 files changed, 1822 insertions(+), 24 deletions(-)
 create mode 100644 src/bhyve/bhyve_qemu_agent.c
 create mode 100644 src/bhyve/bhyve_qemu_agent.h

diff --git a/po/POTFILES b/po/POTFILES
index a5f8705eb8..1d80d9a57a 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -19,6 +19,7 @@ src/bhyve/bhyve_firmware.c
 src/bhyve/bhyve_monitor.c
 src/bhyve/bhyve_parse_command.c
 src/bhyve/bhyve_process.c
+src/bhyve/bhyve_qemu_agent.c
 src/ch/ch_alias.c
 src/ch/ch_conf.c
 src/ch/ch_domain.c
diff --git a/src/bhyve/bhyve_domain.c b/src/bhyve/bhyve_domain.c
index 832a9b58d1..6367985efc 100644
--- a/src/bhyve/bhyve_domain.c
+++ b/src/bhyve/bhyve_domain.c
@@ -41,6 +41,7 @@ bhyveDomainObjPrivateAlloc(void *opaque)
 {
     bhyveDomainObjPrivate *priv = g_new0(bhyveDomainObjPrivate, 1);
 
+    priv->agentTimeout = 30;
     priv->driver = opaque;
 
     return priv;
@@ -663,3 +664,34 @@ virXMLNamespace virBhyveDriverDomainXMLNamespace = {
     .uri = "http://libvirt.org/schemas/domain/bhyve/1.0";,
 
 };
+
+
+int
+virBhyveDomainObjStartWorker(virDomainObj *dom)
+{
+    bhyveDomainObjPrivate *priv = dom->privateData;
+
+    if (!priv->eventThread) {
+        g_autofree char *threadName = g_strdup_printf("vm-%s", dom->def->name);
+        if (!(priv->eventThread = virEventThreadNew(threadName)))
+            return -1;
+    }
+
+    return 0;
+}
+
+
+void
+virBhyveDomainObjStopWorker(virDomainObj *dom)
+{
+    bhyveDomainObjPrivate *priv = dom->privateData;
+    virEventThread *eventThread;
+
+    if (!priv->eventThread)
+        return;
+
+    eventThread = g_steal_pointer(&priv->eventThread);
+    virObjectUnlock(dom);
+    g_object_unref(eventThread);
+    virObjectLock(dom);
+}
diff --git a/src/bhyve/bhyve_domain.h b/src/bhyve/bhyve_domain.h
index 5a539bc4c0..8e3663f4c0 100644
--- a/src/bhyve/bhyve_domain.h
+++ b/src/bhyve/bhyve_domain.h
@@ -22,8 +22,10 @@
 
 #include "domain_addr.h"
 #include "domain_conf.h"
+#include "vireventthread.h"
 
 #include "bhyve_monitor.h"
+#include "bhyve_qemu_agent.h"
 
 typedef struct _bhyveDomainObjPrivate bhyveDomainObjPrivate;
 struct _bhyveDomainObjPrivate {
@@ -33,10 +35,22 @@ struct _bhyveDomainObjPrivate {
     bool persistentAddrs;
 
     bhyveMonitor *mon;
+
+    qemuAgent *agent;
+    bool agentError;
+    int agentTimeout;
+
+    virEventThread *eventThread;
 };
 
+#define BHYVE_DOMAIN_PRIVATE(vm) \
+    ((bhyveDomainObjPrivate *) (vm)->privateData)
+
 virDomainXMLOption *virBhyveDriverCreateXMLConf(struct _bhyveConn *);
 
 extern virDomainXMLPrivateDataCallbacks virBhyveDriverPrivateDataCallbacks;
 extern virDomainDefParserConfig virBhyveDriverDomainDefParserConfig;
 extern virXMLNamespace virBhyveDriverDomainXMLNamespace;
+
+int virBhyveDomainObjStartWorker(virDomainObj *dom);
+void virBhyveDomainObjStopWorker(virDomainObj *dom);
diff --git a/src/bhyve/bhyve_driver.c b/src/bhyve/bhyve_driver.c
index c8dd1a728a..8bffbbec43 100644
--- a/src/bhyve/bhyve_driver.c
+++ b/src/bhyve/bhyve_driver.c
@@ -1895,6 +1895,165 @@ bhyveDomainInterfaceAddresses(virDomainPtr domain,
 }
 
 
+static qemuAgent *
+bhyveDomainObjEnterAgent(virDomainObj *obj)
+{
+    bhyveDomainObjPrivate *priv = obj->privateData;
+    qemuAgent *agent = priv->agent;
+
+    VIR_DEBUG("Entering agent (agent=%p vm=%p name=%s)",
+              priv->agent, obj, obj->def->name);
+
+    virObjectLock(agent);
+    virObjectRef(agent);
+    virObjectUnlock(obj);
+
+    return agent;
+}
+
+
+static void
+bhyveDomainObjExitAgent(virDomainObj *obj, qemuAgent *agent)
+{
+    virObjectUnlock(agent);
+    virObjectUnref(agent);
+    virObjectLock(obj);
+
+    VIR_DEBUG("Exited agent (agent=%p vm=%p name=%s)",
+              agent, obj, obj->def->name);
+}
+
+
+static bool
+bhyveDomainAgentAvailable(virDomainObj *vm,
+                          bool reportError)
+{
+    bhyveDomainObjPrivate *priv = vm->privateData;
+
+    if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_RUNNING) {
+        if (reportError) {
+            virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                           _("domain is not running"));
+        }
+        return false;
+    }
+
+    if (!priv->agent) {
+        if (bhyveFindAgentConfig(vm->def)) {
+            if (reportError) {
+                virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+                               _("QEMU guest agent is not connected"));
+            }
+            return false;
+        } else {
+            if (reportError) {
+                virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+                               _("QEMU guest agent is not configured"));
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
+
+static int
+bhyveDomainEnsureAgent(virDomainObj *vm,
+                       bool reportError)
+{
+    bhyveDomainObjPrivate *priv = vm->privateData;
+
+    if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_RUNNING) {
+        if (reportError) {
+            virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                           _("domain is not running"));
+        }
+        return -1;
+    }
+
+    if (priv->agent)
+        return 0;
+
+    if (!priv->eventThread &&
+        virBhyveDomainObjStartWorker(vm) < 0)
+        return -1;
+
+    if (bhyveConnectAgent(NULL, vm) < 0)
+        return -1;
+
+    return 0;
+}
+
+
+static int
+bhyveDomainGetHostnameAgent(virDomainObj *vm,
+                            char **hostname)
+{
+    qemuAgent *agent;
+    int ret = -1;
+
+    if (virDomainObjBeginAgentJob(vm, VIR_AGENT_JOB_QUERY) < 0)
+        return -1;
+
+    if (virDomainObjCheckActive(vm) < 0)
+        goto endjob;
+
+    if (bhyveDomainEnsureAgent(vm, true) < 0)
+        goto endjob;
+
+    agent = bhyveDomainObjEnterAgent(vm);
+    ret = qemuAgentGetHostname(agent, hostname, true);
+    bhyveDomainObjExitAgent(vm, agent);
+
+ endjob:
+    virDomainObjEndAgentJob(vm);
+    return ret;
+}
+
+
+static char *
+bhyveDomainQemuAgentCommand(virDomainPtr domain,
+                            const char *cmd,
+                            int timeout,
+                            unsigned int flags)
+{
+    virDomainObj *vm;
+    int ret = -1;
+    char *result = NULL;
+    qemuAgent *agent;
+
+    virCheckFlags(0, NULL);
+
+    if (!(vm = bhyveDomObjFromDomain(domain)))
+        goto cleanup;
+
+    if (virDomainQemuAgentCommandEnsureACL(domain->conn, vm->def) < 0)
+        goto cleanup;
+
+    if (virDomainObjBeginAgentJob(vm, VIR_AGENT_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    if (virDomainObjCheckActive(vm) < 0)
+        goto endjob;
+
+    if (!bhyveDomainAgentAvailable(vm, true))
+        goto endjob;
+
+    agent = bhyveDomainObjEnterAgent(vm);
+    ret = qemuAgentArbitraryCommand(agent, cmd, &result, timeout);
+    bhyveDomainObjExitAgent(vm, agent);
+    if (ret < 0)
+        VIR_FREE(result);
+
+ endjob:
+    virDomainObjEndAgentJob(vm);
+
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return result;
+}
+
+
 static int
 bhyveDomainGetHostnameLease(virDomainObj *vm,
                             char **hostname)
@@ -1961,7 +2120,15 @@ bhyveDomainGetHostname(virDomainPtr domain,
     virDomainObj *vm = NULL;
     char *hostname = NULL;
 
-    virCheckFlags(VIR_DOMAIN_GET_HOSTNAME_LEASE, NULL);
+    virCheckFlags(VIR_DOMAIN_GET_HOSTNAME_LEASE |
+                  VIR_DOMAIN_GET_HOSTNAME_AGENT, NULL);
+
+    VIR_EXCLUSIVE_FLAGS_RET(VIR_DOMAIN_GET_HOSTNAME_LEASE,
+                            VIR_DOMAIN_GET_HOSTNAME_AGENT,
+                            NULL);
+
+    if (!(flags & VIR_DOMAIN_GET_HOSTNAME_AGENT))
+        flags |= VIR_DOMAIN_GET_HOSTNAME_LEASE;
 
     if (!(vm = bhyveDomObjFromDomain(domain)))
         return NULL;
@@ -1969,8 +2136,13 @@ bhyveDomainGetHostname(virDomainPtr domain,
     if (virDomainGetHostnameEnsureACL(domain->conn, vm->def) < 0)
         goto cleanup;
 
-    if (bhyveDomainGetHostnameLease(vm, &hostname) < 0)
-        goto cleanup;
+    if (flags & VIR_DOMAIN_GET_HOSTNAME_LEASE) {
+        if (bhyveDomainGetHostnameLease(vm, &hostname) < 0)
+            goto cleanup;
+    } else if (flags & VIR_DOMAIN_GET_HOSTNAME_AGENT) {
+        if (bhyveDomainGetHostnameAgent(vm, &hostname) < 0)
+            goto cleanup;
+    }
 
     if (!hostname) {
         virReportError(VIR_ERR_NO_HOSTNAME,
@@ -2052,6 +2224,7 @@ static virHypervisorDriver bhyveHypervisorDriver = {
     .domainGetVcpuPinInfo = bhyveDomainGetVcpuPinInfo, /* 12.1.0 */
     .domainInterfaceAddresses = bhyveDomainInterfaceAddresses, /* 12.3.0 */
     .domainGetHostname = bhyveDomainGetHostname, /* 12.3.0 */
+    .domainQemuAgentCommand = bhyveDomainQemuAgentCommand, /* 12.4.0 */
 };
 
 
diff --git a/src/bhyve/bhyve_process.c b/src/bhyve/bhyve_process.c
index 6078d995cd..fc97731510 100644
--- a/src/bhyve/bhyve_process.c
+++ b/src/bhyve/bhyve_process.c
@@ -171,6 +171,117 @@ bhyveSetResourceLimits(struct _bhyveConn *driver, 
virDomainObj *vm)
     return 0;
 }
 
+virDomainChrDef *
+bhyveFindAgentConfig(virDomainDef *def)
+{
+    size_t i;
+
+    for (i = 0; i < def->nchannels; i++) {
+        virDomainChrDef *channel = def->channels[i];
+
+        if (channel->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO)
+            continue;
+
+
+        if (STREQ_NULLABLE(channel->target.name, "org.qemu.guest_agent.0")) {
+            return channel;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+qemuProcessHandleAgentEOF(qemuAgent *agent,
+                          virDomainObj *vm)
+{
+    bhyveDomainObjPrivate *priv;
+
+    virObjectLock(vm);
+    VIR_INFO("Received EOF from agent on %p '%s'", vm, vm->def->name);
+
+    priv = vm->privateData;
+
+    if (!priv->agent) {
+        VIR_DEBUG("Agent freed already");
+        goto unlock;
+    }
+
+    qemuAgentClose(agent);
+    priv->agent = NULL;
+    priv->agentError = false;
+
+    virObjectUnlock(vm);
+    return;
+
+ unlock:
+    virObjectUnlock(vm);
+    return;
+}
+
+/*
+ * 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(qemuAgent *agent G_GNUC_UNUSED,
+                            virDomainObj *vm)
+{
+    bhyveDomainObjPrivate *priv;
+
+    virObjectLock(vm);
+    VIR_INFO("Received error from agent on %p '%s'", vm, vm->def->name);
+
+    priv = vm->privateData;
+
+    priv->agentError = true;
+
+    virObjectUnlock(vm);
+}
+
+static qemuAgentCallbacks agentCallbacks = {
+    .eofNotify = qemuProcessHandleAgentEOF,
+    .errorNotify = qemuProcessHandleAgentError,
+};
+
+int
+bhyveConnectAgent(struct _bhyveConn *driver G_GNUC_UNUSED, virDomainObj *vm)
+{
+    bhyveDomainObjPrivate *priv = vm->privateData;
+    qemuAgent *agent = NULL;
+    virDomainChrDef *config = bhyveFindAgentConfig(vm->def);
+
+    if (!config)
+        return 0;
+
+    if (priv->agent)
+        return 0;
+
+    agent = qemuAgentOpen(vm,
+                          config->source,
+                          virEventThreadGetContext(priv->eventThread),
+                          &agentCallbacks);
+
+    if (!virDomainObjIsActive(vm)) {
+        qemuAgentClose(agent);
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("guest crashed while connecting to the guest agent"));
+        return -1;
+    }
+
+    priv->agent = agent;
+    if (!priv->agent) {
+        VIR_WARN("Cannot connect to QEMU guest agent for %s", vm->def->name);
+        priv->agentError = true;
+        virResetLastError();
+    }
+
+    return 0;
+}
+
+
 static int
 virBhyveProcessStartImpl(struct _bhyveConn *driver,
                          virDomainObj *vm,
@@ -293,6 +404,9 @@ virBhyveProcessStartImpl(struct _bhyveConn *driver,
     virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, reason);
     priv->mon = bhyveMonitorOpen(vm, driver);
 
+    if (virBhyveDomainObjStartWorker(vm) < 0)
+        goto cleanup;
+
     if (virDomainObjSave(vm, driver->xmlopt,
                          BHYVE_STATE_DIR) < 0)
         goto cleanup;
@@ -714,6 +828,9 @@ virBhyveProcessReconnect(virDomainObj *vm,
         virDomainNetNotifyActualDevice(conn, vm->def, net);
     }
 
+    if (virBhyveDomainObjStartWorker(vm) < 0)
+        goto cleanup;
+
  cleanup:
     if (ret < 0) {
         /* If VM is reported to be in active state, but we cannot find
diff --git a/src/bhyve/bhyve_process.h b/src/bhyve/bhyve_process.h
index 5e0acc810c..bf82f748a6 100644
--- a/src/bhyve/bhyve_process.h
+++ b/src/bhyve/bhyve_process.h
@@ -56,6 +56,10 @@ int virBhyveGetDomainTotalCpuStats(virDomainObj *vm,
 
 void virBhyveProcessReconnectAll(struct _bhyveConn *driver);
 
+int bhyveConnectAgent(struct _bhyveConn *driver, virDomainObj *vm);
+
+virDomainChrDef *bhyveFindAgentConfig(virDomainDef *def);
+
 typedef enum {
     VIR_BHYVE_PROCESS_START_AUTODESTROY = 1 << 0,
 } bhyveProcessStartFlags;
diff --git a/src/bhyve/bhyve_qemu_agent.c b/src/bhyve/bhyve_qemu_agent.c
new file mode 100644
index 0000000000..619d8baf57
--- /dev/null
+++ b/src/bhyve/bhyve_qemu_agent.c
@@ -0,0 +1,1259 @@
+/*
+ * bhyve_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/>.
+ */
+
+#include <config.h>
+
+#include <poll.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <gio/gio.h>
+
+#include "bhyve_qemu_agent.h"
+#include "bhyve_domain.h"
+#include "viralloc.h"
+#include "virlog.h"
+#include "virerror.h"
+#include "virjson.h"
+#include "virfile.h"
+#include "virtime.h"
+#include "virobject.h"
+#include "virstring.h"
+#include "virenum.h"
+#include "virutil.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("bhyve.bhyve_qemu_agent");
+
+#define LINE_ENDING "\n"
+
+/* We read from QEMU until seeing a \r\n pair to indicate a
+ * completed reply or event. To avoid memory denial-of-service
+ * though, we must have a size limit on amount of data we
+ * buffer. 10 MB is large enough that it ought to cope with
+ * normal QEMU replies, and small enough that we're not
+ * consuming unreasonable mem.
+ */
+#define QEMU_AGENT_MAX_RESPONSE (10 * 1024 * 1024)
+
+typedef struct _qemuAgentMessage qemuAgentMessage;
+struct _qemuAgentMessage {
+    char *txBuffer;
+    int txOffset;
+    int txLength;
+
+    /* Used by the JSON agent to hold reply / error */
+    char *rxBuffer;
+    int rxLength;
+    void *rxObject;
+
+    /* True if rxBuffer / rxObject are ready, or a
+     * fatal error occurred on the agent channel
+     */
+    bool finished;
+    /* true for sync command */
+    bool sync;
+    /* id of the issued sync command */
+    unsigned long long id;
+    bool first;
+};
+
+
+struct _qemuAgent {
+    virObjectLockable parent;
+
+    virCond notify;
+
+    int fd;
+
+    GMainContext *context;
+    GSocket *socket;
+    GSource *watch;
+
+    bool running;
+    bool inSync;
+
+    virDomainObj *vm;
+
+    qemuAgentCallbacks *cb;
+
+    /* If there's a command being processed this will be
+     * non-NULL */
+    qemuAgentMessage *msg;
+
+    /* Buffer incoming data ready for agent
+     * 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 agent msg */
+    virError lastError;
+
+    /* Some guest agent commands don't return anything
+     * but fire up an event on qemu agent instead.
+     * Take that as indication of successful completion */
+    qemuAgentEvent await_event;
+    int timeout;
+};
+
+static virClass *qemuAgentClass;
+static void qemuAgentDispose(void *obj);
+
+static int qemuAgentOnceInit(void)
+{
+    if (!VIR_CLASS_NEW(qemuAgent, virClassForObjectLockable()))
+        return -1;
+
+    return 0;
+}
+
+VIR_ONCE_GLOBAL_INIT(qemuAgent);
+
+
+
+static void qemuAgentDispose(void *obj)
+{
+    qemuAgent *agent = obj;
+
+    VIR_DEBUG("agent=%p", agent);
+
+    if (agent->vm)
+        virObjectUnref(agent->vm);
+    virCondDestroy(&agent->notify);
+    g_free(agent->buffer);
+    g_main_context_unref(agent->context);
+    virResetError(&agent->lastError);
+}
+
+static int
+qemuAgentOpenUnix(const char *socketpath)
+{
+    struct sockaddr_un addr = { 0 };
+    int agentfd;
+
+    if ((agentfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+        virReportSystemError(errno,
+                             "%s", _("failed to create socket"));
+        return -1;
+    }
+
+    if (virSetCloseExec(agentfd) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("Unable to set agent close-on-exec flag"));
+        goto error;
+    }
+
+    addr.sun_family = AF_UNIX;
+    if (virStrcpyStatic(addr.sun_path, socketpath) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Socket path %1$s too big for destination"), 
socketpath);
+        goto error;
+    }
+
+    if (connect(agentfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("failed to connect to agent socket"));
+        goto error;
+    }
+
+    return agentfd;
+
+ error:
+    VIR_FORCE_CLOSE(agentfd);
+    return -1;
+}
+
+
+static int
+qemuAgentIOProcessEvent(qemuAgent *agent,
+                        virJSONValue *obj)
+{
+    const char *type;
+    VIR_DEBUG("agent=%p obj=%p", agent, obj);
+
+    type = virJSONValueObjectGetString(obj, "event");
+    if (!type) {
+        VIR_WARN("missing event type in message");
+        errno = EINVAL;
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+qemuAgentIOProcessLine(qemuAgent *agent,
+                       const char *line,
+                       qemuAgentMessage *msg)
+{
+    g_autoptr(virJSONValue) obj = NULL;
+
+    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 = true;
+            return 0;
+        }
+
+        return -1;
+    }
+
+    if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Parsed JSON reply '%1$s' isn't an object"), line);
+        return -1;
+    }
+
+    if (virJSONValueObjectHasKey(obj, "QMP")) {
+        return 0;
+    } else if (virJSONValueObjectHasKey(obj, "event")) {
+        return qemuAgentIOProcessEvent(agent, obj);
+    } else if (virJSONValueObjectHasKey(obj, "error") ||
+               virJSONValueObjectHasKey(obj, "return")) {
+        if (msg) {
+            if (msg->sync) {
+                unsigned long long id;
+
+                if (virJSONValueObjectGetNumberUlong(obj, "return", &id) < 0) {
+                    VIR_DEBUG("Ignoring delayed reply on sync");
+                    return 0;
+                }
+
+                VIR_DEBUG("Guest returned ID: %llu", id);
+
+                if (msg->id != id) {
+                    VIR_DEBUG("Guest agent returned ID: %llu instead of %llu",
+                              id, msg->id);
+                    return 0;
+                }
+            }
+            msg->rxObject = g_steal_pointer(&obj);
+            msg->finished = true;
+        } else {
+            /* we are out of sync */
+            VIR_DEBUG("Ignoring delayed reply");
+        }
+
+        return 0;
+    }
+
+    virReportError(VIR_ERR_INTERNAL_ERROR,
+                   _("Unknown JSON reply '%1$s'"), line);
+    return -1;
+}
+
+static int qemuAgentIOProcessData(qemuAgent *agent,
+                                  char *data,
+                                  size_t len,
+                                  qemuAgentMessage *msg)
+{
+    int used = 0;
+    size_t i = 0;
+
+    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(agent, 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 agent. Looking for async events and
+ * replies/errors.
+ */
+static int
+qemuAgentIOProcess(qemuAgent *agent)
+{
+    int len;
+    qemuAgentMessage *msg = NULL;
+
+    /* See if there's a message ready for reply; that is,
+     * one that has completed writing all its data.
+     */
+    if (agent->msg && agent->msg->txOffset == agent->msg->txLength)
+        msg = agent->msg;
+
+    len = qemuAgentIOProcessData(agent,
+                                 agent->buffer, agent->bufferOffset,
+                                 msg);
+
+    if (len < 0)
+        return -1;
+
+    if (len < agent->bufferOffset) {
+        memmove(agent->buffer, agent->buffer + len, agent->bufferOffset - len);
+        agent->bufferOffset -= len;
+    } else {
+        VIR_FREE(agent->buffer);
+        agent->bufferOffset = agent->bufferLength = 0;
+    }
+    if (msg && msg->finished)
+        virCondBroadcast(&agent->notify);
+    return len;
+}
+
+
+/*
+ * Called when the agent is able to write data
+ * Call this function while holding the agent lock.
+ */
+static int
+qemuAgentIOWrite(qemuAgent *agent)
+{
+    int done;
+
+    /* If no active message, or fully transmitted, then no-op */
+    if (!agent->msg || agent->msg->txOffset == agent->msg->txLength)
+        return 0;
+
+    done = safewrite(agent->fd,
+                     agent->msg->txBuffer + agent->msg->txOffset,
+                     agent->msg->txLength - agent->msg->txOffset);
+
+    if (done < 0) {
+        if (errno == EAGAIN)
+            return 0;
+
+        virReportSystemError(errno, "%s",
+                             _("Unable to write to agent"));
+        return -1;
+    }
+    agent->msg->txOffset += done;
+    return done;
+}
+
+/*
+ * Called when the agent has incoming data to read
+ * Call this function while holding the agent lock.
+ *
+ * Returns -1 on error, or number of bytes read
+ */
+static int
+qemuAgentIORead(qemuAgent *agent)
+{
+    size_t avail = agent->bufferLength - agent->bufferOffset;
+    int ret = 0;
+
+    if (avail < 1024) {
+        if (agent->bufferLength >= QEMU_AGENT_MAX_RESPONSE) {
+            virReportSystemError(ERANGE,
+                                 _("No complete agent response found in %1$d 
bytes"),
+                                 QEMU_AGENT_MAX_RESPONSE);
+            return -1;
+        }
+        VIR_REALLOC_N(agent->buffer, agent->bufferLength + 1024);
+        agent->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(agent->fd,
+                   agent->buffer + agent->bufferOffset,
+                   avail - 1);
+        if (got < 0) {
+            if (errno == EAGAIN)
+                break;
+            virReportSystemError(errno, "%s",
+                                 _("Unable to read from agent"));
+            ret = -1;
+            break;
+        }
+        if (got == 0)
+            break;
+
+        ret += got;
+        avail -= got;
+        agent->bufferOffset += got;
+        agent->buffer[agent->bufferOffset] = '\0';
+    }
+
+    return ret;
+}
+
+
+static gboolean
+qemuAgentIO(GSocket *socket,
+            GIOCondition cond,
+            gpointer opaque);
+
+
+static void
+qemuAgentRegister(qemuAgent *agent)
+{
+    GIOCondition cond = 0;
+
+    if (agent->lastError.code == VIR_ERR_OK) {
+        cond |= G_IO_IN;
+
+        if (agent->msg && agent->msg->txOffset < agent->msg->txLength)
+            cond |= G_IO_OUT;
+    }
+
+    agent->watch = g_socket_create_source(agent->socket,
+                                        cond,
+                                        NULL);
+
+    virObjectRef(agent);
+    g_source_set_callback(agent->watch,
+                          (GSourceFunc)qemuAgentIO,
+                          agent,
+                          (GDestroyNotify)virObjectUnref);
+
+    g_source_attach(agent->watch,
+                    agent->context);
+}
+
+
+static void
+qemuAgentUnregister(qemuAgent *agent)
+{
+    if (agent->watch) {
+        g_source_destroy(agent->watch);
+        g_source_unref(agent->watch);
+        agent->watch = NULL;
+    }
+}
+
+
+static void qemuAgentUpdateWatch(qemuAgent *agent)
+{
+    qemuAgentUnregister(agent);
+    if (agent->socket)
+        qemuAgentRegister(agent);
+}
+
+
+static gboolean
+qemuAgentIO(GSocket *socket G_GNUC_UNUSED,
+            GIOCondition cond,
+            gpointer opaque)
+{
+    qemuAgent *agent = opaque;
+    bool error = false;
+    bool eof = false;
+
+    virObjectRef(agent);
+    /* lock access to the agent and protect fd */
+    virObjectLock(agent);
+
+    if (agent->fd == -1 || !agent->watch) {
+        virObjectUnlock(agent);
+        virObjectUnref(agent);
+        return G_SOURCE_REMOVE;
+    }
+
+    if (agent->lastError.code != VIR_ERR_OK) {
+        if (cond & (G_IO_HUP | G_IO_ERR))
+            eof = true;
+        error = true;
+    } else {
+        if (cond & G_IO_OUT) {
+            if (qemuAgentIOWrite(agent) < 0)
+                error = true;
+        }
+
+        if (!error &&
+            cond & G_IO_IN) {
+            int got = qemuAgentIORead(agent);
+            if (got < 0) {
+                error = true;
+            } else if (got == 0) {
+                eof = true;
+            } else {
+                /* Ignore hangup/error cond if we read some data, to
+                 * give time for that data to be consumed */
+                cond = 0;
+
+                if (qemuAgentIOProcess(agent) < 0)
+                    error = true;
+            }
+        }
+
+        if (!error &&
+            cond & G_IO_HUP) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("End of file from agent socket"));
+            eof = true;
+        }
+
+        if (!error && !eof &&
+            cond & G_IO_ERR) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("Invalid file descriptor while waiting for 
agent"));
+            eof = true;
+        }
+    }
+
+    if (error || eof) {
+        if (agent->lastError.code != VIR_ERR_OK) {
+            /* Already have an error, so clear any new error */
+            virResetLastError();
+        } else {
+            if (virGetLastErrorCode() == VIR_ERR_OK)
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("Error while processing agent IO"));
+            virCopyLastError(&agent->lastError);
+            virResetLastError();
+        }
+
+        VIR_DEBUG("Error on agent %s", NULLSTR(agent->lastError.message));
+        /* If IO process resulted in an error & we have a message,
+         * then wakeup that waiter */
+        if (agent->msg && !agent->msg->finished) {
+            agent->msg->finished = true;
+            virCondSignal(&agent->notify);
+        }
+    }
+
+    qemuAgentUpdateWatch(agent);
+
+    /* 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 virDomainObj *mutex next */
+    if (eof) {
+        void (*eofNotify)(qemuAgent *, virDomainObj *)
+            = agent->cb->eofNotify;
+        virDomainObj *vm = agent->vm;
+
+        /* Make sure anyone waiting wakes up now */
+        virCondSignal(&agent->notify);
+        virObjectUnlock(agent);
+        virObjectUnref(agent);
+        VIR_DEBUG("Triggering EOF callback");
+        (eofNotify)(agent, vm);
+    } else if (error) {
+        void (*errorNotify)(qemuAgent *, virDomainObj *)
+            = agent->cb->errorNotify;
+        virDomainObj *vm = agent->vm;
+
+        /* Make sure anyone waiting wakes up now */
+        virCondSignal(&agent->notify);
+        virObjectUnlock(agent);
+        virObjectUnref(agent);
+        VIR_DEBUG("Triggering error callback");
+        (errorNotify)(agent, vm);
+    } else {
+        virObjectUnlock(agent);
+        virObjectUnref(agent);
+    }
+
+    return G_SOURCE_REMOVE;
+}
+
+
+qemuAgent *
+qemuAgentOpen(virDomainObj *vm,
+              const virDomainChrSourceDef *config,
+              GMainContext *context,
+              qemuAgentCallbacks *cb)
+{
+    qemuAgent *agent;
+    g_autoptr(GError) gerr = NULL;
+
+    if (!cb || !cb->eofNotify) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("EOF notify callback must be supplied"));
+        return NULL;
+    }
+
+    if (qemuAgentInitialize() < 0)
+        return NULL;
+
+    if (!(agent = virObjectLockableNew(qemuAgentClass)))
+        return NULL;
+
+    agent->timeout = BHYVE_DOMAIN_PRIVATE(vm)->agentTimeout;
+    agent->fd = -1;
+    if (virCondInit(&agent->notify) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("cannot initialize agent condition"));
+        virObjectUnref(agent);
+        return NULL;
+    }
+    agent->vm = virObjectRef(vm);
+    agent->cb = cb;
+
+    if (config->type != VIR_DOMAIN_CHR_TYPE_UNIX) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("unable to handle agent type: %1$s"),
+                       virDomainChrTypeToString(config->type));
+        goto cleanup;
+    }
+
+    virObjectUnlock(vm);
+    agent->fd = qemuAgentOpenUnix(config->data.nix.path);
+    virObjectLock(vm);
+
+    if (agent->fd == -1)
+        goto cleanup;
+
+    agent->context = g_main_context_ref(context);
+
+    agent->socket = g_socket_new_from_fd(agent->fd, &gerr);
+    if (!agent->socket) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("Unable to create socket object: %1$s"),
+                       gerr->message);
+        goto cleanup;
+    }
+
+    qemuAgentRegister(agent);
+
+    agent->running = true;
+    VIR_DEBUG("New agent %p fd=%d", agent, agent->fd);
+
+    return agent;
+
+ cleanup:
+    qemuAgentClose(agent);
+    return NULL;
+}
+
+
+static void
+qemuAgentNotifyCloseLocked(qemuAgent *agent)
+{
+    if (agent) {
+        agent->running = false;
+
+        /* If there is somebody waiting for a message
+         * wake him up. No message will arrive anyway. */
+        if (agent->msg && !agent->msg->finished) {
+            agent->msg->finished = true;
+            virCondSignal(&agent->notify);
+        }
+    }
+}
+
+
+void
+qemuAgentNotifyClose(qemuAgent *agent)
+{
+    if (!agent)
+        return;
+
+    VIR_DEBUG("agent=%p", agent);
+
+    VIR_WITH_OBJECT_LOCK_GUARD(agent) {
+        qemuAgentNotifyCloseLocked(agent);
+    }
+}
+
+
+void qemuAgentClose(qemuAgent *agent)
+{
+    if (!agent)
+        return;
+
+    VIR_DEBUG("agent=%p", agent);
+
+    VIR_WITH_OBJECT_LOCK_GUARD(agent) {
+        if (agent->socket) {
+            qemuAgentUnregister(agent);
+            g_clear_pointer(&agent->socket, g_object_unref);
+            agent->fd = -1;
+        }
+
+        qemuAgentNotifyCloseLocked(agent);
+    }
+
+    virObjectUnref(agent);
+}
+
+#define QEMU_AGENT_WAIT_TIME 5
+
+/**
+ * qemuAgentSend:
+ * @agent: agent object
+ * @msg: Message
+ * @seconds: number of seconds to wait for the result, it can be either
+ *           -2, -1, 0 or positive.
+ * @report_sync: On timeout; report synchronization error instead of the 
normal error
+ *
+ * Send @msg to agent @agent. 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 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(qemuAgent *agent,
+              qemuAgentMessage *msg,
+              int seconds,
+              bool report_sync)
+{
+    int ret = -1;
+    unsigned long long then = 0;
+
+    VIR_INFO("qemuAgentSend: seconds=%d", seconds);
+
+    /* Check whether qemu quit unexpectedly */
+    if (agent->lastError.code != VIR_ERR_OK) {
+        VIR_DEBUG("Attempt to send command while error is set %s",
+                  NULLSTR(agent->lastError.message));
+        virSetError(&agent->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;
+    }
+
+    agent->msg = msg;
+    qemuAgentUpdateWatch(agent);
+
+    while (!agent->msg->finished) {
+        if ((then && virCondWaitUntil(&agent->notify, &agent->parent.lock, 
then) < 0) ||
+            (!then && virCondWait(&agent->notify, &agent->parent.lock) < 0)) {
+            if (errno == ETIMEDOUT) {
+                if (report_sync) {
+                    virReportError(VIR_ERR_AGENT_UNRESPONSIVE,
+                                   _("guest agent didn't respond to 
synchronization within '%1$d' seconds"),
+                                   seconds);
+                } else {
+                    virReportError(VIR_ERR_AGENT_COMMAND_TIMEOUT,
+                                   _("guest agent didn't respond to command 
within '%1$d' seconds"),
+                                   seconds);
+                }
+                ret = -2;
+            } else {
+                virReportSystemError(errno, "%s",
+                                     _("Unable to wait on agent socket 
condition"));
+            }
+            agent->inSync = false;
+            goto cleanup;
+        }
+    }
+
+    if (agent->lastError.code != VIR_ERR_OK) {
+        VIR_DEBUG("Send command resulted in error %s",
+                  NULLSTR(agent->lastError.message));
+        virSetError(&agent->lastError);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    agent->msg = NULL;
+    qemuAgentUpdateWatch(agent);
+
+    return ret;
+}
+
+
+/**
+ * qemuAgentGuestSyncSend:
+ * @agent: agent object
+ * @timeout: timeout for the command
+ * @first: true when this is the first invocation to drain possible leftovers
+ *         from the pipe
+ *
+ * Sends a sync request to the guest agent.
+ * Returns: -1 on error
+ *           0 on successful send, but when no reply was received
+ *           1 when a reply was received
+ */
+static int
+qemuAgentGuestSyncSend(qemuAgent *agent,
+                       int timeout,
+                       bool first)
+{
+    g_autofree char *txMsg = NULL;
+    g_autoptr(virJSONValue) rxObj = NULL;
+    unsigned long long id;
+    qemuAgentMessage sync_msg = { 0 };
+    int rc;
+
+    if (virTimeMillisNow(&id) < 0)
+        return -1;
+
+    txMsg = g_strdup_printf("{\"execute\":\"guest-sync\", "
+                             "\"arguments\":{\"id\":%llu}}\n", id);
+
+    sync_msg.txBuffer = txMsg;
+    sync_msg.txLength = strlen(txMsg);
+    sync_msg.sync = true;
+    sync_msg.id = id;
+    sync_msg.first = first;
+
+    VIR_DEBUG("Sending guest-sync command with ID: %llu", id);
+
+    rc = qemuAgentSend(agent, &sync_msg, timeout, true);
+    rxObj = g_steal_pointer(&sync_msg.rxObject);
+
+    VIR_DEBUG("qemuAgentSend returned: %d", rc);
+
+    if (rc < 0)
+        return -1;
+
+    if (rxObj)
+        return 1;
+
+    return 0;
+}
+
+
+/**
+ * qemuAgentGuestSync:
+ * @agent: agent object
+ * @seconds: qemu agent command timeout value
+ *
+ * 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(qemuAgent *agent,
+                   int seconds)
+{
+    int timeout = QEMU_AGENT_WAIT_TIME;
+    int rc;
+
+    if (agent->inSync)
+        return 0;
+
+    /* if user specified a custom agent timeout that is lower than the
+     * default timeout, use the shorter timeout instead */
+    if ((agent->timeout >= 0) && (agent->timeout < timeout))
+        timeout = agent->timeout;
+
+    /* If user specified a timeout parameter smaller than both default
+     * value and agent->timeout in qga APIs(such as qemu-agent-command),
+     * use the parameter timeout value */
+    if ((seconds >= 0) && (seconds < timeout))
+        timeout = seconds;
+
+    if ((rc = qemuAgentGuestSyncSend(agent, timeout, true)) < 0)
+        return -1;
+
+    /* successfully sync'd */
+    if (rc == 1) {
+        agent->inSync = true;
+        return 0;
+    }
+
+    /* send another sync */
+    if ((rc = qemuAgentGuestSyncSend(agent, timeout, false)) < 0)
+        return -1;
+
+    /* successfully sync'd */
+    if (rc == 1) {
+        agent->inSync = true;
+        return 0;
+    }
+
+    if (agent->running)
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("Missing agent reply object"));
+    else
+        virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+                       _("Guest agent disappeared while executing command"));
+
+    return -1;
+}
+
+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(virJSONValue *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(virJSONValue *cmd)
+{
+    const char *name = virJSONValueObjectGetString(cmd, "execute");
+    if (name)
+        return name;
+    return "<unknown>";
+}
+
+static int
+qemuAgentCheckError(virJSONValue *cmd,
+                    virJSONValue *reply,
+                    bool report_unsupported)
+{
+    if (virJSONValueObjectHasKey(reply, "error")) {
+        virJSONValue *error = virJSONValueObjectGet(reply, "error");
+        g_autofree char *cmdstr = virJSONValueToString(cmd, false);
+        g_autofree char *replystr = virJSONValueToString(reply, false);
+
+        /* Log the full JSON formatted command & error */
+        VIR_DEBUG("unable to execute QEMU agent command %s: %s",
+                  NULLSTR(cmdstr), NULLSTR(replystr));
+
+        /* Only send the user the command name + friendly error */
+        if (!error) {
+            virReportError(VIR_ERR_AGENT_COMMAND_FAILED,
+                           _("unable to execute QEMU agent command '%1$s'"),
+                           qemuAgentCommandName(cmd));
+            return -1;
+        }
+
+        if (!report_unsupported) {
+            const char *klass = virJSONValueObjectGetString(error, "class");
+
+            if (STREQ_NULLABLE(klass, "CommandNotFound") ||
+                STREQ_NULLABLE(klass, "CommandDisabled"))
+                return -2;
+        }
+
+        virReportError(VIR_ERR_AGENT_COMMAND_FAILED,
+                       _("unable to execute QEMU agent command '%1$s': %2$s"),
+                       qemuAgentCommandName(cmd),
+                       qemuAgentStringifyError(error));
+
+        return -1;
+    }
+    if (!virJSONValueObjectHasKey(reply, "return")) {
+        g_autofree char *cmdstr = virJSONValueToString(cmd, false);
+        g_autofree char *replystr = virJSONValueToString(reply, false);
+
+        VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %s: 
%s",
+                  NULLSTR(cmdstr), NULLSTR(replystr));
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("QEMU agent command '%1$s' returned neither error nor 
success"),
+                       qemuAgentCommandName(cmd));
+        return -1;
+    }
+    return 0;
+}
+
+static int
+qemuAgentCommandFull(qemuAgent *agent,
+                     virJSONValue *cmd,
+                     virJSONValue **reply,
+                     int seconds,
+                     bool report_unsupported)
+{
+    int ret = -1;
+    qemuAgentMessage msg = { 0 };
+    g_autofree char *cmdstr = NULL;
+    int await_event = agent->await_event;
+
+    *reply = NULL;
+
+    if (!agent->running) {
+        virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+                       _("Guest agent disappeared while executing command"));
+        goto cleanup;
+    }
+
+    if (qemuAgentGuestSync(agent, seconds) < 0)
+        goto cleanup;
+
+    if (!(cmdstr = virJSONValueToString(cmd, false)))
+        goto cleanup;
+    msg.txBuffer = g_strdup_printf("%s" LINE_ENDING, cmdstr);
+    msg.txLength = strlen(msg.txBuffer);
+
+    VIR_DEBUG("Send command '%s' for write, seconds = %d", cmdstr, seconds);
+
+    ret = qemuAgentSend(agent, &msg, seconds, false);
+
+    VIR_DEBUG("Receive command reply ret=%d rxObject=%p",
+              ret, msg.rxObject);
+
+    if (ret < 0)
+        goto cleanup;
+
+    /* 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) {
+            if (agent->running) {
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("Missing agent reply object"));
+            } else {
+                virReportError(VIR_ERR_AGENT_COMMAND_TIMEOUT, "%s",
+                               _("Guest agent disappeared while executing 
command"));
+            }
+            ret = -1;
+        }
+        goto cleanup;
+    }
+
+    *reply = msg.rxObject;
+    ret = qemuAgentCheckError(cmd, *reply, report_unsupported);
+
+ cleanup:
+    VIR_FREE(msg.txBuffer);
+    agent->await_event = QEMU_AGENT_EVENT_NONE;
+
+    return ret;
+}
+
+static int
+qemuAgentCommand(qemuAgent *agent,
+                 virJSONValue *cmd,
+                 virJSONValue **reply,
+                 int seconds)
+{
+    return qemuAgentCommandFull(agent, cmd, reply, seconds, true);
+}
+
+static virJSONValue *G_GNUC_NULL_TERMINATED
+qemuAgentMakeCommand(const char *cmdname,
+                     ...)
+{
+    g_autoptr(virJSONValue) obj = NULL;
+    g_autoptr(virJSONValue) jargs = NULL;
+    va_list args;
+
+    va_start(args, cmdname);
+
+    if (virJSONValueObjectAddVArgs(&jargs, args) < 0) {
+        va_end(args);
+        return NULL;
+    }
+
+    va_end(args);
+
+    if (virJSONValueObjectAdd(&obj,
+                              "s:execute", cmdname,
+                              "A:arguments", &jargs,
+                              NULL) < 0)
+        return NULL;
+
+    return g_steal_pointer(&obj);
+}
+
+static virJSONValue *
+qemuAgentMakeStringsArray(const char **strings, unsigned int len)
+{
+    size_t i;
+    g_autoptr(virJSONValue) ret = virJSONValueNewArray();
+
+    for (i = 0; i < len; i++) {
+        if (virJSONValueArrayAppendString(ret, strings[i]) < 0)
+            return NULL;
+    }
+
+    return g_steal_pointer(&ret);
+}
+
+void qemuAgentNotifyEvent(qemuAgent *agent,
+                          qemuAgentEvent event)
+{
+    VIR_LOCK_GUARD lock = virObjectLockGuard(agent);
+
+    VIR_DEBUG("agent=%p event=%d await_event=%d", agent, event, 
agent->await_event);
+    if (agent->await_event == event) {
+        agent->await_event = QEMU_AGENT_EVENT_NONE;
+        /* somebody waiting for this event, wake him up. */
+        if (agent->msg && !agent->msg->finished) {
+            agent->msg->finished = true;
+            virCondSignal(&agent->notify);
+        }
+    }
+}
+
+
+int
+qemuAgentArbitraryCommand(qemuAgent *agent,
+                          const char *cmd_str,
+                          char **result,
+                          int timeout)
+{
+    int rc;
+    g_autoptr(virJSONValue) cmd = NULL;
+    g_autoptr(virJSONValue) reply = NULL;
+
+    *result = NULL;
+    if (timeout < VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN) {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("guest agent timeout '%1$d' is less than the minimum 
'%2$d'"),
+                       timeout, VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN);
+        return -1;
+    }
+
+    if (!(cmd = virJSONValueFromString(cmd_str)))
+        return -1;
+
+    if ((rc = qemuAgentCommand(agent, cmd, &reply, timeout)) < 0)
+        return rc;
+
+    if (!(*result = virJSONValueToString(reply, false)))
+        return -1;
+
+    return rc;
+}
+
+
+/**
+ * qemuAgentGetHostname:
+ *
+ * Gets the guest hostname using the guest agent.
+ *
+ * Returns 0 on success and fills @hostname. On error -1 is returned with an
+ * error reported and if '@report_unsupported' is false -2 is returned if the
+ * guest agent does not support the command without reporting an error
+ */
+int
+qemuAgentGetHostname(qemuAgent *agent,
+                     char **hostname,
+                     bool report_unsupported)
+{
+    g_autoptr(virJSONValue) cmd = qemuAgentMakeCommand("guest-get-host-name", 
NULL);
+    g_autoptr(virJSONValue) reply = NULL;
+    virJSONValue *data = NULL;
+    const char *result = NULL;
+    int rc;
+
+    if (!cmd)
+        return -1;
+
+    if ((rc = qemuAgentCommandFull(agent, cmd, &reply, agent->timeout,
+                                   report_unsupported)) < 0)
+        return rc;
+
+    if (!(data = virJSONValueObjectGet(reply, "return"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("malformed return value"));
+        return -1;
+    }
+
+    if (!(result = virJSONValueObjectGetString(data, "host-name"))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("'host-name' missing in guest-get-host-name reply"));
+        return -1;
+    }
+
+    *hostname = g_strdup(result);
+
+    return 0;
+}
+
+
+int
+qemuAgentGetTime(qemuAgent *agent,
+                 long long *seconds,
+                 unsigned int *nseconds)
+{
+    unsigned long long json_time;
+    g_autoptr(virJSONValue) cmd = NULL;
+    g_autoptr(virJSONValue) reply = NULL;
+
+    cmd = qemuAgentMakeCommand("guest-get-time",
+                               NULL);
+    if (!cmd)
+        return -1;
+
+    if (qemuAgentCommand(agent, cmd, &reply, agent->timeout) < 0)
+        return -1;
+
+    if (virJSONValueObjectGetNumberUlong(reply, "return", &json_time) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("malformed return value"));
+        return -1;
+    }
+
+    /* guest agent returns time in nanoseconds,
+     * we need it in seconds here */
+    *seconds = json_time / 1000000000LL;
+    *nseconds = json_time % 1000000000LL;
+    return 0;
+}
diff --git a/src/bhyve/bhyve_qemu_agent.h b/src/bhyve/bhyve_qemu_agent.h
new file mode 100644
index 0000000000..0e1752a2cb
--- /dev/null
+++ b/src/bhyve/bhyve_qemu_agent.h
@@ -0,0 +1,197 @@
+/*
+ * bhyve_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/>.
+ */
+
+#pragma once
+
+#include "internal.h"
+#include "domain_conf.h"
+
+typedef struct _qemuAgent qemuAgent;
+
+typedef struct _qemuAgentCallbacks qemuAgentCallbacks;
+struct _qemuAgentCallbacks {
+    void (*eofNotify)(qemuAgent *mon,
+                      virDomainObj *vm);
+    void (*errorNotify)(qemuAgent *mon,
+                        virDomainObj *vm);
+};
+
+
+qemuAgent *qemuAgentOpen(virDomainObj *vm,
+                           const virDomainChrSourceDef *config,
+                           GMainContext *context,
+                           qemuAgentCallbacks *cb);
+
+void qemuAgentClose(qemuAgent *mon);
+
+void qemuAgentNotifyClose(qemuAgent *mon);
+
+typedef enum {
+    QEMU_AGENT_EVENT_NONE = 0,
+    QEMU_AGENT_EVENT_SHUTDOWN,
+    QEMU_AGENT_EVENT_SUSPEND,
+    QEMU_AGENT_EVENT_RESET,
+} qemuAgentEvent;
+
+void qemuAgentNotifyEvent(qemuAgent *mon,
+                          qemuAgentEvent event);
+
+typedef enum {
+    QEMU_AGENT_SHUTDOWN_POWERDOWN,
+    QEMU_AGENT_SHUTDOWN_REBOOT,
+    QEMU_AGENT_SHUTDOWN_HALT,
+
+    QEMU_AGENT_SHUTDOWN_LAST,
+} qemuAgentShutdownMode;
+
+typedef struct _qemuAgentDiskAddress qemuAgentDiskAddress;
+struct _qemuAgentDiskAddress {
+    char *serial;
+    virPCIDeviceAddress pci_controller;
+    char *bus_type;
+    unsigned int bus;
+    unsigned int target;
+    unsigned int unit;
+    char *devnode;
+    virCCWDeviceAddress *ccw_addr;
+};
+void qemuAgentDiskAddressFree(qemuAgentDiskAddress *addr);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuAgentDiskAddress, qemuAgentDiskAddressFree);
+
+typedef struct _qemuAgentDiskInfo qemuAgentDiskInfo;
+struct _qemuAgentDiskInfo {
+    char *name;
+    bool partition;
+    char **dependencies;
+    qemuAgentDiskAddress *address;
+    char *alias;
+};
+void qemuAgentDiskInfoFree(qemuAgentDiskInfo *info);
+
+typedef struct _qemuAgentFSInfo qemuAgentFSInfo;
+struct _qemuAgentFSInfo {
+    char *mountpoint; /* path to mount point */
+    char *name;       /* device name in the guest (e.g. "sda1") */
+    char *fstype;     /* filesystem type */
+    long long total_bytes;
+    long long used_bytes;
+    size_t ndisks;
+    qemuAgentDiskAddress **disks;
+};
+void qemuAgentFSInfoFree(qemuAgentFSInfo *info);
+
+int qemuAgentShutdown(qemuAgent *mon,
+                      qemuAgentShutdownMode mode);
+
+int qemuAgentFSFreeze(qemuAgent *mon,
+                      const char **mountpoints, unsigned int nmountpoints);
+int qemuAgentFSThaw(qemuAgent *mon);
+int qemuAgentGetFSInfo(qemuAgent *mon,
+                       qemuAgentFSInfo ***info,
+                       bool report_unsupported);
+
+int qemuAgentSuspend(qemuAgent *mon,
+                     unsigned int target);
+
+int qemuAgentArbitraryCommand(qemuAgent *mon,
+                              const char *cmd,
+                              char **result,
+                              int timeout);
+int qemuAgentFSTrim(qemuAgent *mon,
+                    unsigned long long minimum);
+
+
+typedef struct _qemuAgentCPUInfo qemuAgentCPUInfo;
+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(qemuAgent *mon, qemuAgentCPUInfo **info);
+int qemuAgentSetVCPUs(qemuAgent *mon, qemuAgentCPUInfo *cpus, size_t ncpus);
+int qemuAgentUpdateCPUInfo(unsigned int nvcpus,
+                           qemuAgentCPUInfo *cpuinfo,
+                           int ncpuinfo);
+
+int
+qemuAgentGetHostname(qemuAgent *mon,
+                     char **hostname,
+                     bool report_unsupported);
+
+int qemuAgentGetTime(qemuAgent *mon,
+                     long long *seconds,
+                     unsigned int *nseconds);
+int qemuAgentSetTime(qemuAgent *mon,
+                     long long seconds,
+                     unsigned int nseconds,
+                     bool sync);
+
+int qemuAgentGetInterfaces(qemuAgent *mon,
+                           virDomainInterfacePtr **ifaces,
+                           bool report_unsupported);
+
+int qemuAgentSetUserPassword(qemuAgent *mon,
+                             const char *user,
+                             const char *password,
+                             bool crypted);
+
+int qemuAgentGetUsers(qemuAgent *mon,
+                      virTypedParamList *list,
+                      bool report_unsupported);
+
+int qemuAgentGetOSInfo(qemuAgent *mon,
+                       virTypedParamList *list,
+                       bool report_unsupported);
+
+int qemuAgentGetTimezone(qemuAgent *mon,
+                         virTypedParamList *list,
+                         bool report_unsupported);
+
+void qemuAgentSetResponseTimeout(qemuAgent *mon,
+                                 int timeout);
+
+int qemuAgentSSHGetAuthorizedKeys(qemuAgent *agent,
+                                  const char *user,
+                                  char ***keys);
+
+int qemuAgentSSHAddAuthorizedKeys(qemuAgent *agent,
+                                  const char *user,
+                                  const char **keys,
+                                  size_t nkeys,
+                                  bool reset);
+
+int qemuAgentSSHRemoveAuthorizedKeys(qemuAgent *agent,
+                                     const char *user,
+                                     const char **keys,
+                                     size_t nkeys);
+
+int qemuAgentGetDisks(qemuAgent *mon,
+                      qemuAgentDiskInfo ***disks,
+                      bool report_unsupported);
+
+int qemuAgentGetLoadAvg(qemuAgent *agent,
+                        double *load1m,
+                        double *load5m,
+                        double *load15m,
+                        bool report_unsupported);
diff --git a/src/bhyve/meson.build b/src/bhyve/meson.build
index 11920d9c3e..a1f0aa63b2 100644
--- a/src/bhyve/meson.build
+++ b/src/bhyve/meson.build
@@ -2,13 +2,14 @@ bhyve_sources = files(
   'bhyve_capabilities.c',
   'bhyve_command.c',
   'bhyve_conf.c',
-  'bhyve_firmware.c',
-  'bhyve_parse_command.c',
   'bhyve_device.c',
   'bhyve_domain.c',
   'bhyve_driver.c',
+  'bhyve_firmware.c',
   'bhyve_monitor.c',
+  'bhyve_parse_command.c',
   'bhyve_process.c',
+  'bhyve_qemu_agent.c',
 )
 
 driver_source_files += bhyve_sources
diff --git a/src/remote/qemu_protocol.x b/src/remote/qemu_protocol.x
index c7f3abfcbf..757e54efcc 100644
--- a/src/remote/qemu_protocol.x
+++ b/src/remote/qemu_protocol.x
@@ -44,17 +44,6 @@ struct qemu_domain_attach_ret {
     remote_nonnull_domain dom;
 };
 
-struct qemu_domain_agent_command_args {
-    remote_nonnull_domain dom;
-    remote_nonnull_string cmd;
-    int timeout;
-    unsigned int flags;
-};
-
-struct qemu_domain_agent_command_ret {
-    remote_string result;
-};
-
 
 struct qemu_connect_domain_monitor_event_register_args {
     remote_domain dom;
@@ -135,13 +124,6 @@ enum qemu_procedure {
      */
     QEMU_PROC_DOMAIN_ATTACH = 2,
 
-    /**
-     * @generate: both
-     * @priority: low
-     * @acl: domain:write
-     */
-    QEMU_PROC_DOMAIN_AGENT_COMMAND = 3,
-
     /**
      * @generate: none
      * @priority: high
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 38a83c64ea..a3a0920061 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -4009,6 +4009,17 @@ struct remote_domain_event_nic_mac_change_msg {
     remote_nonnull_string newMAC;
 };
 
+struct remote_domain_qemu_agent_command_args {
+    remote_nonnull_domain dom;
+    remote_nonnull_string cmd;
+    int timeout;
+    unsigned int flags;
+};
+
+struct remote_domain_qemu_agent_command_ret {
+    remote_string result;
+};
+
 /*----- Protocol. -----*/
 
 /* Define the program number, protocol version and procedure numbers here. */
@@ -7120,5 +7131,12 @@ enum remote_procedure {
      * @generate: both
      * @acl: none
      */
-    REMOTE_PROC_DOMAIN_EVENT_NIC_MAC_CHANGE = 453
+    REMOTE_PROC_DOMAIN_EVENT_NIC_MAC_CHANGE = 453,
+
+    /**
+     * @generate: both
+     * @priority: low
+     * @acl: domain:write
+     */
+    REMOTE_PROC_DOMAIN_QEMU_AGENT_COMMAND = 454
 };
-- 
2.52.0

Reply via email to