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
