From nobody Mon May 6 08:43:58 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.37 as permitted sender) client-ip=209.132.183.37; envelope-from=libvir-list-bounces@redhat.com; helo=mx5-phx2.redhat.com; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.37 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; Return-Path: Received: from mx5-phx2.redhat.com (mx5-phx2.redhat.com [209.132.183.37]) by mx.zohomail.com with SMTPS id 1486572424747920.895978409593; Wed, 8 Feb 2017 08:47:04 -0800 (PST) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by mx5-phx2.redhat.com (8.14.4/8.14.4) with ESMTP id v18GgXJA027452; Wed, 8 Feb 2017 11:42:33 -0500 Received: from smtp.corp.redhat.com (int-mx16.intmail.prod.int.phx2.redhat.com [10.5.11.28]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id v18GgFNc019769 for ; Wed, 8 Feb 2017 11:42:15 -0500 Received: by smtp.corp.redhat.com (Postfix) id 0350F6E90C; Wed, 8 Feb 2017 16:42:15 +0000 (UTC) Received: from mx1.redhat.com (ext-mx03.extmail.prod.ext.phx2.redhat.com [10.5.110.27]) by smtp.corp.redhat.com (Postfix) with ESMTPS id EC9646D15D for ; Wed, 8 Feb 2017 16:42:14 +0000 (UTC) Received: from userp1040.oracle.com (userp1040.oracle.com [156.151.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 3844C804EF for ; Wed, 8 Feb 2017 16:42:09 +0000 (UTC) Received: from aserv0021.oracle.com (aserv0021.oracle.com [141.146.126.233]) by userp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with ESMTP id v18Gg6Wp003760 (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Wed, 8 Feb 2017 16:42:07 GMT Received: from userv0122.oracle.com (userv0122.oracle.com [156.151.31.75]) by aserv0021.oracle.com (8.13.8/8.14.4) with ESMTP id v18Gg6C0018339 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Wed, 8 Feb 2017 16:42:06 GMT Received: from abhmp0018.oracle.com (abhmp0018.oracle.com [141.146.116.24]) by userv0122.oracle.com (8.14.4/8.14.4) with ESMTP id v18Gg5no005032; Wed, 8 Feb 2017 16:42:05 GMT Received: from paddy.lan (/89.114.92.174) by default (Oracle Beehive Gateway v4.0) with ESMTP ; Wed, 08 Feb 2017 08:42:04 -0800 From: Joao Martins To: Libvirt Development List Date: Wed, 8 Feb 2017 16:44:37 +0000 Message-Id: <1486572280-2212-2-git-send-email-joao.m.martins@oracle.com> In-Reply-To: <1486572280-2212-1-git-send-email-joao.m.martins@oracle.com> References: <1486572280-2212-1-git-send-email-joao.m.martins@oracle.com> X-Source-IP: aserv0021.oracle.com [141.146.126.233] X-Greylist: Sender passed SPF test, Sender IP whitelisted by DNSRBL, ACL 200 matched, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.27]); Wed, 08 Feb 2017 16:42:09 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.27]); Wed, 08 Feb 2017 16:42:09 +0000 (UTC) for IP:'156.151.31.81' DOMAIN:'userp1040.oracle.com' HELO:'userp1040.oracle.com' FROM:'joao.m.martins@oracle.com' RCPT:'' X-RedHat-Spam-Score: -103.788 (BAYES_50, DCC_REPUT_00_12, RCVD_IN_DNSWL_MED, RCVD_IN_MSPIKE_H2, RP_MATCHES_RCVD, SPF_PASS, UNPARSEABLE_RELAY, USER_IN_WHITELIST) 156.151.31.81 userp1040.oracle.com 156.151.31.81 userp1040.oracle.com X-Scanned-By: MIMEDefang 2.78 on 10.5.110.27 X-Scanned-By: MIMEDefang 2.74 on 10.5.11.28 X-loop: libvir-list@redhat.com Cc: Joao Martins Subject: [libvirt] [PATCH RFC 1/4] qemu_agent: move agent into util X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Type: text/plain; charset="utf-8" As it could be shared with libxl which now allows channels to be created. Also changed filename to match others in the same directory namely to virqemuagent.{h,c} Signed-off-by: Joao Martins --- po/POTFILES.in | 2 +- src/Makefile.am | 2 +- src/libvirt_private.syms | 21 + src/qemu/qemu_agent.c | 2248 --------------------------------------= ---- src/qemu/qemu_agent.h | 123 --- src/qemu/qemu_domain.h | 2 +- src/qemu/qemu_driver.c | 2 +- src/util/virqemuagent.c | 2248 ++++++++++++++++++++++++++++++++++++++= ++++ src/util/virqemuagent.h | 123 +++ tests/qemuagenttest.c | 2 +- tests/qemumonitortestutils.c | 2 +- tests/qemumonitortestutils.h | 2 +- 12 files changed, 2399 insertions(+), 2378 deletions(-) delete mode 100644 src/qemu/qemu_agent.c delete mode 100644 src/qemu/qemu_agent.h create mode 100644 src/util/virqemuagent.c create mode 100644 src/util/virqemuagent.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 365ea66..ebb247b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -122,7 +122,6 @@ src/openvz/openvz_conf.c src/openvz/openvz_driver.c src/openvz/openvz_util.c src/phyp/phyp_driver.c -src/qemu/qemu_agent.c src/qemu/qemu_alias.c src/qemu/qemu_capabilities.c src/qemu/qemu_cgroup.c @@ -239,6 +238,7 @@ src/util/virpolkit.c src/util/virportallocator.c src/util/virprocess.c src/util/virqemu.c +src/util/virqemuagent.c src/util/virrandom.c src/util/virrotatingfile.c src/util/virscsi.c diff --git a/src/Makefile.am b/src/Makefile.am index 2f32d41..62c8733 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -161,6 +161,7 @@ UTIL_SOURCES =3D \ util/virprobe.h \ util/virprocess.c util/virprocess.h \ util/virqemu.c util/virqemu.h \ + util/virqemuagent.c util/virqemuagent.h \ util/virrandom.h util/virrandom.c \ util/virrotatingfile.h util/virrotatingfile.c \ util/virscsi.c util/virscsi.h \ @@ -815,7 +816,6 @@ VBOX_DRIVER_EXTRA_DIST =3D \ vbox/vbox_XPCOMCGlue.c vbox/vbox_XPCOMCGlue.h =20 QEMU_DRIVER_SOURCES =3D \ - qemu/qemu_agent.c qemu/qemu_agent.h \ qemu/qemu_alias.c qemu/qemu_alias.h \ qemu/qemu_blockjob.c qemu/qemu_blockjob.h \ qemu/qemu_capabilities.c qemu/qemu_capabilities.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index d556c7d..a5a1313 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2306,6 +2306,27 @@ virQEMUBuildLuksOpts; virQEMUBuildObjectCommandlineFromJSON; =20 =20 +# util/virqemuagent.h +qemuAgentArbitraryCommand; +qemuAgentClose; +qemuAgentFSFreeze; +qemuAgentFSThaw; +qemuAgentFSTrim; +qemuAgentGetFSInfo; +qemuAgentGetInterfaces; +qemuAgentGetTime; +qemuAgentGetVCPUs; +qemuAgentNotifyClose; +qemuAgentNotifyEvent; +qemuAgentOpen; +qemuAgentSetVCPUs; +qemuAgentSetUserPassword; +qemuAgentSetTime; +qemuAgentShutdown; +qemuAgentSuspend; +qemuAgentUpdateCPUInfo; + + # util/virrandom.h virRandom; virRandomBits; diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c deleted file mode 100644 index 46cad53..0000000 --- a/src/qemu/qemu_agent.c +++ /dev/null @@ -1,2248 +0,0 @@ -/* - * qemu_agent.c: interaction with QEMU guest agent - * - * Copyright (C) 2006-2014 Red Hat, Inc. - * Copyright (C) 2006 Daniel P. Berrange - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - * Author: Daniel P. Berrange - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "qemu_agent.h" -#include "viralloc.h" -#include "virlog.h" -#include "virerror.h" -#include "virjson.h" -#include "virfile.h" -#include "virprocess.h" -#include "virtime.h" -#include "virobject.h" -#include "virstring.h" -#include "base64.h" - -#define VIR_FROM_THIS VIR_FROM_QEMU - -VIR_LOG_INIT("qemu.qemu_agent"); - -#define LINE_ENDING "\n" - -#define DEBUG_IO 0 -#define DEBUG_RAW_IO 0 - -/* When you are the first to uncomment this, - * don't forget to uncomment the corresponding - * part in qemuAgentIOProcessEvent as well. - * -static struct { - const char *type; - void (*handler)(qemuAgentPtr mon, virJSONValuePtr data); -} eventHandlers[] =3D { -}; -*/ - -typedef struct _qemuAgentMessage qemuAgentMessage; -typedef qemuAgentMessage *qemuAgentMessagePtr; - -struct _qemuAgentMessage { - char *txBuffer; - int txOffset; - int txLength; - - /* Used by the JSON monitor to hold reply / error */ - char *rxBuffer; - int rxLength; - void *rxObject; - - /* True if rxBuffer / rxObject are ready, or a - * fatal error occurred on the monitor channel - */ - bool finished; - /* true for sync command */ - bool sync; - /* id of the issued sync comand */ - unsigned long long id; - bool first; -}; - - -struct _qemuAgent { - virObjectLockable parent; - - virCond notify; - - int fd; - int watch; - - bool connectPending; - bool running; - - virDomainObjPtr vm; - - qemuAgentCallbacksPtr cb; - - /* If there's a command being processed this will be - * non-NULL */ - qemuAgentMessagePtr msg; - - /* Buffer incoming data ready for Agent monitor - * code to process & find message boundaries */ - size_t bufferOffset; - size_t bufferLength; - char *buffer; - - /* If anything went wrong, this will be fed back - * the next monitor msg */ - virError lastError; - - /* Some guest agent commands don't return anything - * but fire up an event on qemu monitor instead. - * Take that as indication of successful completion */ - qemuAgentEvent await_event; -}; - -static virClassPtr qemuAgentClass; -static void qemuAgentDispose(void *obj); - -static int qemuAgentOnceInit(void) -{ - if (!(qemuAgentClass =3D virClassNew(virClassForObjectLockable(), - "qemuAgent", - sizeof(qemuAgent), - qemuAgentDispose))) - return -1; - - return 0; -} - -VIR_ONCE_GLOBAL_INIT(qemuAgent) - - -#if DEBUG_RAW_IO -# include -static char * -qemuAgentEscapeNonPrintable(const char *text) -{ - size_t i; - virBuffer buf =3D VIR_BUFFER_INITIALIZER; - for (i =3D 0; text[i] !=3D '\0'; i++) { - if (text[i] =3D=3D '\\') - virBufferAddLit(&buf, "\\\\"); - else if (c_isprint(text[i]) || text[i] =3D=3D '\n' || - (text[i] =3D=3D '\r' && text[i+1] =3D=3D '\n')) - virBufferAddChar(&buf, text[i]); - else - virBufferAsprintf(&buf, "\\x%02x", text[i]); - } - return virBufferContentAndReset(&buf); -} -#endif - - -static void qemuAgentDispose(void *obj) -{ - qemuAgentPtr mon =3D obj; - VIR_DEBUG("mon=3D%p", mon); - if (mon->cb && mon->cb->destroy) - (mon->cb->destroy)(mon, mon->vm); - virCondDestroy(&mon->notify); - VIR_FREE(mon->buffer); - virResetError(&mon->lastError); -} - -static int -qemuAgentOpenUnix(const char *monitor, pid_t cpid, bool *inProgress) -{ - struct sockaddr_un addr; - int monfd; - virTimeBackOffVar timeout; - int ret =3D -1; - - *inProgress =3D false; - - if ((monfd =3D socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - virReportSystemError(errno, - "%s", _("failed to create socket")); - return -1; - } - - if (virSetNonBlock(monfd) < 0) { - virReportSystemError(errno, "%s", - _("Unable to put monitor " - "into non-blocking mode")); - goto error; - } - - if (virSetCloseExec(monfd) < 0) { - virReportSystemError(errno, "%s", - _("Unable to set monitor " - "close-on-exec flag")); - goto error; - } - - memset(&addr, 0, sizeof(addr)); - addr.sun_family =3D AF_UNIX; - if (virStrcpyStatic(addr.sun_path, monitor) =3D=3D NULL) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Agent path %s too big for destination"), monitor= ); - goto error; - } - - if (virTimeBackOffStart(&timeout, 1, 3*1000 /* ms */) < 0) - goto error; - while (virTimeBackOffWait(&timeout)) { - ret =3D connect(monfd, (struct sockaddr *) &addr, sizeof(addr)); - - if (ret =3D=3D 0) - break; - - if ((errno =3D=3D ENOENT || errno =3D=3D ECONNREFUSED) && - virProcessKill(cpid, 0) =3D=3D 0) { - /* ENOENT : Socket may not have shown up yet - * ECONNREFUSED : Leftover socket hasn't been removed yet */ - continue; - } - - if ((errno =3D=3D EINPROGRESS) || - (errno =3D=3D EAGAIN)) { - VIR_DEBUG("Connection attempt continuing in background"); - *inProgress =3D true; - ret =3D 0; - break; - } - - virReportSystemError(errno, "%s", - _("failed to connect to monitor socket")); - goto error; - - } - - if (ret !=3D 0) { - virReportSystemError(errno, "%s", - _("monitor socket did not show up")); - goto error; - } - - return monfd; - - error: - VIR_FORCE_CLOSE(monfd); - return -1; -} - -static int -qemuAgentOpenPty(const char *monitor) -{ - int monfd; - - if ((monfd =3D open(monitor, O_RDWR | O_NONBLOCK)) < 0) { - virReportSystemError(errno, - _("Unable to open monitor path %s"), monitor); - return -1; - } - - if (virSetCloseExec(monfd) < 0) { - virReportSystemError(errno, "%s", - _("Unable to set monitor close-on-exec flag")= ); - goto error; - } - - return monfd; - - error: - VIR_FORCE_CLOSE(monfd); - return -1; -} - - -static int -qemuAgentIOProcessEvent(qemuAgentPtr mon, - virJSONValuePtr obj) -{ - const char *type; - VIR_DEBUG("mon=3D%p obj=3D%p", mon, obj); - - type =3D virJSONValueObjectGetString(obj, "event"); - if (!type) { - VIR_WARN("missing event type in message"); - errno =3D EINVAL; - return -1; - } - -/* - for (i =3D 0; i < ARRAY_CARDINALITY(eventHandlers); i++) { - if (STREQ(eventHandlers[i].type, type)) { - virJSONValuePtr data =3D virJSONValueObjectGet(obj, "data"); - VIR_DEBUG("handle %s handler=3D%p data=3D%p", type, - eventHandlers[i].handler, data); - (eventHandlers[i].handler)(mon, data); - break; - } - } -*/ - return 0; -} - -static int -qemuAgentIOProcessLine(qemuAgentPtr mon, - const char *line, - qemuAgentMessagePtr msg) -{ - virJSONValuePtr obj =3D NULL; - int ret =3D -1; - - VIR_DEBUG("Line [%s]", line); - - if (!(obj =3D virJSONValueFromString(line))) { - /* receiving garbage on first sync is regular situation */ - if (msg && msg->sync && msg->first) { - VIR_DEBUG("Received garbage on sync"); - msg->finished =3D 1; - return 0; - } - - goto cleanup; - } - - if (obj->type !=3D VIR_JSON_TYPE_OBJECT) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Parsed JSON reply '%s' isn't an object"), line); - goto cleanup; - } - - if (virJSONValueObjectHasKey(obj, "QMP") =3D=3D 1) { - ret =3D 0; - } else if (virJSONValueObjectHasKey(obj, "event") =3D=3D 1) { - ret =3D qemuAgentIOProcessEvent(mon, obj); - } else if (virJSONValueObjectHasKey(obj, "error") =3D=3D 1 || - virJSONValueObjectHasKey(obj, "return") =3D=3D 1) { - if (msg) { - if (msg->sync) { - unsigned long long id; - - if (virJSONValueObjectGetNumberUlong(obj, "return", &id) <= 0) { - VIR_DEBUG("Ignoring delayed reply on sync"); - ret =3D 0; - goto cleanup; - } - - VIR_DEBUG("Guest returned ID: %llu", id); - - if (msg->id !=3D id) { - VIR_DEBUG("Guest agent returned ID: %llu instead of %l= lu", - id, msg->id); - ret =3D 0; - goto cleanup; - } - } - msg->rxObject =3D obj; - msg->finished =3D 1; - obj =3D NULL; - } else { - /* we are out of sync */ - VIR_DEBUG("Ignoring delayed reply"); - } - ret =3D 0; - } else { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Unknown JSON reply '%s'"), line); - } - - cleanup: - virJSONValueFree(obj); - return ret; -} - -static int qemuAgentIOProcessData(qemuAgentPtr mon, - char *data, - size_t len, - qemuAgentMessagePtr msg) -{ - int used =3D 0; - size_t i =3D 0; -#if DEBUG_IO -# if DEBUG_RAW_IO - char *str1 =3D qemuAgentEscapeNonPrintable(data); - VIR_ERROR("[%s]", str1); - VIR_FREE(str1); -# else - VIR_DEBUG("Data %zu bytes [%s]", len, data); -# endif -#endif - - while (used < len) { - char *nl =3D strstr(data + used, LINE_ENDING); - - if (nl) { - int got =3D nl - (data + used); - for (i =3D 0; i < strlen(LINE_ENDING); i++) - data[used + got + i] =3D '\0'; - if (qemuAgentIOProcessLine(mon, data + used, msg) < 0) - return -1; - used +=3D got + strlen(LINE_ENDING); - } else { - break; - } - } - - VIR_DEBUG("Total used %d bytes out of %zd available in buffer", used, = len); - return used; -} - -/* This method processes data that has been received - * from the monitor. Looking for async events and - * replies/errors. - */ -static int -qemuAgentIOProcess(qemuAgentPtr mon) -{ - int len; - qemuAgentMessagePtr msg =3D NULL; - - /* See if there's a message ready for reply; that is, - * one that has completed writing all its data. - */ - if (mon->msg && mon->msg->txOffset =3D=3D mon->msg->txLength) - msg =3D mon->msg; - -#if DEBUG_IO -# if DEBUG_RAW_IO - char *str1 =3D qemuAgentEscapeNonPrintable(msg ? msg->txBuffer : ""); - char *str2 =3D qemuAgentEscapeNonPrintable(mon->buffer); - VIR_ERROR(_("Process %zu %p %p [[[%s]]][[[%s]]]"), - mon->bufferOffset, mon->msg, msg, str1, str2); - VIR_FREE(str1); - VIR_FREE(str2); -# else - VIR_DEBUG("Process %zu", mon->bufferOffset); -# endif -#endif - - len =3D qemuAgentIOProcessData(mon, - mon->buffer, mon->bufferOffset, - msg); - - if (len < 0) - return -1; - - if (len < mon->bufferOffset) { - memmove(mon->buffer, mon->buffer + len, mon->bufferOffset - len); - mon->bufferOffset -=3D len; - } else { - VIR_FREE(mon->buffer); - mon->bufferOffset =3D mon->bufferLength =3D 0; - } -#if DEBUG_IO - VIR_DEBUG("Process done %zu used %d", mon->bufferOffset, len); -#endif - if (msg && msg->finished) - virCondBroadcast(&mon->notify); - return len; -} - - -static int -qemuAgentIOConnect(qemuAgentPtr mon) -{ - int optval; - socklen_t optlen; - - VIR_DEBUG("Checking on background connection status"); - - mon->connectPending =3D false; - - optlen =3D sizeof(optval); - - if (getsockopt(mon->fd, SOL_SOCKET, SO_ERROR, - &optval, &optlen) < 0) { - virReportSystemError(errno, "%s", - _("Cannot check socket connection status")); - return -1; - } - - if (optval !=3D 0) { - virReportSystemError(optval, "%s", - _("Cannot connect to agent socket")); - return -1; - } - - VIR_DEBUG("Agent is now connected"); - return 0; -} - -/* - * Called when the monitor is able to write data - * Call this function while holding the monitor lock. - */ -static int -qemuAgentIOWrite(qemuAgentPtr mon) -{ - int done; - - /* If no active message, or fully transmitted, then no-op */ - if (!mon->msg || mon->msg->txOffset =3D=3D mon->msg->txLength) - return 0; - - done =3D safewrite(mon->fd, - mon->msg->txBuffer + mon->msg->txOffset, - mon->msg->txLength - mon->msg->txOffset); - - if (done < 0) { - if (errno =3D=3D EAGAIN) - return 0; - - virReportSystemError(errno, "%s", - _("Unable to write to monitor")); - return -1; - } - mon->msg->txOffset +=3D done; - return done; -} - -/* - * Called when the monitor has incoming data to read - * Call this function while holding the monitor lock. - * - * Returns -1 on error, or number of bytes read - */ -static int -qemuAgentIORead(qemuAgentPtr mon) -{ - size_t avail =3D mon->bufferLength - mon->bufferOffset; - int ret =3D 0; - - if (avail < 1024) { - if (VIR_REALLOC_N(mon->buffer, - mon->bufferLength + 1024) < 0) - return -1; - mon->bufferLength +=3D 1024; - avail +=3D 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 =3D read(mon->fd, - mon->buffer + mon->bufferOffset, - avail - 1); - if (got < 0) { - if (errno =3D=3D EAGAIN) - break; - virReportSystemError(errno, "%s", - _("Unable to read from monitor")); - ret =3D -1; - break; - } - if (got =3D=3D 0) - break; - - ret +=3D got; - avail -=3D got; - mon->bufferOffset +=3D got; - mon->buffer[mon->bufferOffset] =3D '\0'; - } - -#if DEBUG_IO - VIR_DEBUG("Now read %zu bytes of data", mon->bufferOffset); -#endif - - return ret; -} - - -static void qemuAgentUpdateWatch(qemuAgentPtr mon) -{ - int events =3D - VIR_EVENT_HANDLE_HANGUP | - VIR_EVENT_HANDLE_ERROR; - - if (mon->lastError.code =3D=3D VIR_ERR_OK) { - events |=3D VIR_EVENT_HANDLE_READABLE; - - if (mon->msg && mon->msg->txOffset < mon->msg->txLength) - events |=3D VIR_EVENT_HANDLE_WRITABLE; - } - - virEventUpdateHandle(mon->watch, events); -} - - -static void -qemuAgentIO(int watch, int fd, int events, void *opaque) -{ - qemuAgentPtr mon =3D opaque; - bool error =3D false; - bool eof =3D false; - - virObjectRef(mon); - /* lock access to the monitor and protect fd */ - virObjectLock(mon); -#if DEBUG_IO - VIR_DEBUG("Agent %p I/O on watch %d fd %d events %d", mon, watch, fd, = events); -#endif - - if (mon->fd !=3D fd || mon->watch !=3D watch) { - if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR)) - eof =3D true; - virReportError(VIR_ERR_INTERNAL_ERROR, - _("event from unexpected fd %d!=3D%d / watch %d!=3D= %d"), - mon->fd, fd, mon->watch, watch); - error =3D true; - } else if (mon->lastError.code !=3D VIR_ERR_OK) { - if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR)) - eof =3D true; - error =3D true; - } else { - if (events & VIR_EVENT_HANDLE_WRITABLE) { - if (mon->connectPending) { - if (qemuAgentIOConnect(mon) < 0) - error =3D true; - } else { - if (qemuAgentIOWrite(mon) < 0) - error =3D true; - } - events &=3D ~VIR_EVENT_HANDLE_WRITABLE; - } - - if (!error && - events & VIR_EVENT_HANDLE_READABLE) { - int got =3D qemuAgentIORead(mon); - events &=3D ~VIR_EVENT_HANDLE_READABLE; - if (got < 0) { - error =3D true; - } else if (got =3D=3D 0) { - eof =3D true; - } else { - /* Ignore hangup/error events if we read some data, to - * give time for that data to be consumed */ - events =3D 0; - - if (qemuAgentIOProcess(mon) < 0) - error =3D true; - } - } - - if (!error && - events & VIR_EVENT_HANDLE_HANGUP) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("End of file from agent monitor")); - eof =3D true; - events &=3D ~VIR_EVENT_HANDLE_HANGUP; - } - - if (!error && !eof && - events & VIR_EVENT_HANDLE_ERROR) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Invalid file descriptor while waiting for mo= nitor")); - eof =3D true; - events &=3D ~VIR_EVENT_HANDLE_ERROR; - } - if (!error && events) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Unhandled event %d for monitor fd %d"), - events, mon->fd); - error =3D true; - } - } - - if (error || eof) { - if (mon->lastError.code !=3D VIR_ERR_OK) { - /* Already have an error, so clear any new error */ - virResetLastError(); - } else { - virErrorPtr err =3D virGetLastError(); - if (!err) - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Error while processing monitor IO")); - virCopyLastError(&mon->lastError); - virResetLastError(); - } - - VIR_DEBUG("Error on monitor %s", NULLSTR(mon->lastError.message)); - /* If IO process resulted in an error & we have a message, - * then wakeup that waiter */ - if (mon->msg && !mon->msg->finished) { - mon->msg->finished =3D 1; - virCondSignal(&mon->notify); - } - } - - qemuAgentUpdateWatch(mon); - - /* We have to unlock to avoid deadlock against command thread, - * but is this safe ? I think it is, because the callback - * will try to acquire the virDomainObjPtr mutex next */ - if (eof) { - void (*eofNotify)(qemuAgentPtr, virDomainObjPtr) - =3D mon->cb->eofNotify; - virDomainObjPtr vm =3D mon->vm; - - /* Make sure anyone waiting wakes up now */ - virCondSignal(&mon->notify); - virObjectUnlock(mon); - virObjectUnref(mon); - VIR_DEBUG("Triggering EOF callback"); - (eofNotify)(mon, vm); - } else if (error) { - void (*errorNotify)(qemuAgentPtr, virDomainObjPtr) - =3D mon->cb->errorNotify; - virDomainObjPtr vm =3D mon->vm; - - /* Make sure anyone waiting wakes up now */ - virCondSignal(&mon->notify); - virObjectUnlock(mon); - virObjectUnref(mon); - VIR_DEBUG("Triggering error callback"); - (errorNotify)(mon, vm); - } else { - virObjectUnlock(mon); - virObjectUnref(mon); - } -} - - -qemuAgentPtr -qemuAgentOpen(virDomainObjPtr vm, - const virDomainChrSourceDef *config, - qemuAgentCallbacksPtr cb) -{ - qemuAgentPtr mon; - - if (!cb || !cb->eofNotify) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("EOF notify callback must be supplied")); - return NULL; - } - - if (qemuAgentInitialize() < 0) - return NULL; - - if (!(mon =3D virObjectLockableNew(qemuAgentClass))) - return NULL; - - mon->fd =3D -1; - if (virCondInit(&mon->notify) < 0) { - virReportSystemError(errno, "%s", - _("cannot initialize monitor condition")); - virObjectUnref(mon); - return NULL; - } - mon->vm =3D vm; - mon->cb =3D cb; - - switch (config->type) { - case VIR_DOMAIN_CHR_TYPE_UNIX: - mon->fd =3D qemuAgentOpenUnix(config->data.nix.path, vm->pid, - &mon->connectPending); - break; - - case VIR_DOMAIN_CHR_TYPE_PTY: - mon->fd =3D qemuAgentOpenPty(config->data.file.path); - break; - - default: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unable to handle monitor type: %s"), - virDomainChrTypeToString(config->type)); - goto cleanup; - } - - if (mon->fd =3D=3D -1) - goto cleanup; - - virObjectRef(mon); - if ((mon->watch =3D virEventAddHandle(mon->fd, - VIR_EVENT_HANDLE_HANGUP | - VIR_EVENT_HANDLE_ERROR | - VIR_EVENT_HANDLE_READABLE | - (mon->connectPending ? - VIR_EVENT_HANDLE_WRITABLE : - 0), - qemuAgentIO, - mon, - virObjectFreeCallback)) < 0) { - virObjectUnref(mon); - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to register monitor events")); - goto cleanup; - } - - mon->running =3D true; - VIR_DEBUG("New mon %p fd =3D%d watch=3D%d", mon, mon->fd, mon->watch); - - return mon; - - cleanup: - /* We don't want the 'destroy' callback invoked during - * cleanup from construction failure, because that can - * give a double-unref on virDomainObjPtr in the caller, - * so kill the callbacks now. - */ - mon->cb =3D NULL; - qemuAgentClose(mon); - return NULL; -} - - -static void -qemuAgentNotifyCloseLocked(qemuAgentPtr mon) -{ - if (mon) { - mon->running =3D false; - - /* If there is somebody waiting for a message - * wake him up. No message will arrive anyway. */ - if (mon->msg && !mon->msg->finished) { - mon->msg->finished =3D 1; - virCondSignal(&mon->notify); - } - } -} - - -void -qemuAgentNotifyClose(qemuAgentPtr mon) -{ - if (!mon) - return; - - VIR_DEBUG("mon=3D%p", mon); - - virObjectLock(mon); - qemuAgentNotifyCloseLocked(mon); - virObjectUnlock(mon); -} - - -void qemuAgentClose(qemuAgentPtr mon) -{ - if (!mon) - return; - - VIR_DEBUG("mon=3D%p", mon); - - virObjectLock(mon); - - if (mon->fd >=3D 0) { - if (mon->watch) - virEventRemoveHandle(mon->watch); - VIR_FORCE_CLOSE(mon->fd); - } - - qemuAgentNotifyCloseLocked(mon); - virObjectUnlock(mon); - - virObjectUnref(mon); -} - -#define QEMU_AGENT_WAIT_TIME 5 - -/** - * qemuAgentSend: - * @mon: Monitor - * @msg: Message - * @seconds: number of seconds to wait for the result, it can be either - * -2, -1, 0 or positive. - * - * Send @msg to agent @mon. If @seconds is equal to - * VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK(-2), this function will block forev= er - * waiting for the result. The value of - * VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT(-1) means use default timeout val= ue - * and VIR_DOMAIN_QEMU_AGENT_COMMAND_NOWAIT(0) makes this this function re= turn - * immediately without waiting. Any positive value means the number of sec= onds - * to wait for the result. - * - * Returns: 0 on success, - * -2 on timeout, - * -1 otherwise - */ -static int qemuAgentSend(qemuAgentPtr mon, - qemuAgentMessagePtr msg, - int seconds) -{ - int ret =3D -1; - unsigned long long then =3D 0; - - /* Check whether qemu quit unexpectedly */ - if (mon->lastError.code !=3D VIR_ERR_OK) { - VIR_DEBUG("Attempt to send command while error is set %s", - NULLSTR(mon->lastError.message)); - virSetError(&mon->lastError); - return -1; - } - - if (seconds > VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) { - unsigned long long now; - if (virTimeMillisNow(&now) < 0) - return -1; - if (seconds =3D=3D VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT) - seconds =3D QEMU_AGENT_WAIT_TIME; - then =3D now + seconds * 1000ull; - } - - mon->msg =3D msg; - qemuAgentUpdateWatch(mon); - - while (!mon->msg->finished) { - if ((then && virCondWaitUntil(&mon->notify, &mon->parent.lock, the= n) < 0) || - (!then && virCondWait(&mon->notify, &mon->parent.lock) < 0)) { - if (errno =3D=3D ETIMEDOUT) { - virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", - _("Guest agent not available for now")); - ret =3D -2; - } else { - virReportSystemError(errno, "%s", - _("Unable to wait on agent monitor " - "condition")); - } - goto cleanup; - } - } - - if (mon->lastError.code !=3D VIR_ERR_OK) { - VIR_DEBUG("Send command resulted in error %s", - NULLSTR(mon->lastError.message)); - virSetError(&mon->lastError); - goto cleanup; - } - - ret =3D 0; - - cleanup: - mon->msg =3D NULL; - qemuAgentUpdateWatch(mon); - - return ret; -} - - -/** - * qemuAgentGuestSync: - * @mon: Monitor - * - * Send guest-sync with unique ID - * and wait for reply. If we get one, check if - * received ID is equal to given. - * - * Returns: 0 on success, - * -1 otherwise - */ -static int -qemuAgentGuestSync(qemuAgentPtr mon) -{ - int ret =3D -1; - int send_ret; - unsigned long long id; - qemuAgentMessage sync_msg; - - memset(&sync_msg, 0, sizeof(sync_msg)); - /* set only on first sync */ - sync_msg.first =3D true; - - retry: - if (virTimeMillisNow(&id) < 0) - return -1; - - if (virAsprintf(&sync_msg.txBuffer, - "{\"execute\":\"guest-sync\", " - "\"arguments\":{\"id\":%llu}}\n", id) < 0) - return -1; - - sync_msg.txLength =3D strlen(sync_msg.txBuffer); - sync_msg.sync =3D true; - sync_msg.id =3D id; - - VIR_DEBUG("Sending guest-sync command with ID: %llu", id); - - send_ret =3D qemuAgentSend(mon, &sync_msg, - VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT); - - VIR_DEBUG("qemuAgentSend returned: %d", send_ret); - - if (send_ret < 0) - goto cleanup; - - if (!sync_msg.rxObject) { - if (sync_msg.first) { - VIR_FREE(sync_msg.txBuffer); - memset(&sync_msg, 0, sizeof(sync_msg)); - goto retry; - } else { - if (mon->running) - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Missing monitor reply object")); - else - virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", - _("Guest agent disappeared while executing = command")); - goto cleanup; - } - } - - ret =3D 0; - - cleanup: - virJSONValueFree(sync_msg.rxObject); - VIR_FREE(sync_msg.txBuffer); - return ret; -} - -static const char * -qemuAgentStringifyErrorClass(const char *klass) -{ - if (STREQ_NULLABLE(klass, "BufferOverrun")) - return "Buffer overrun"; - else if (STREQ_NULLABLE(klass, "CommandDisabled")) - return "The command has been disabled for this instance"; - else if (STREQ_NULLABLE(klass, "CommandNotFound")) - return "The command has not been found"; - else if (STREQ_NULLABLE(klass, "FdNotFound")) - return "File descriptor not found"; - else if (STREQ_NULLABLE(klass, "InvalidParameter")) - return "Invalid parameter"; - else if (STREQ_NULLABLE(klass, "InvalidParameterType")) - return "Invalid parameter type"; - else if (STREQ_NULLABLE(klass, "InvalidParameterValue")) - return "Invalid parameter value"; - else if (STREQ_NULLABLE(klass, "OpenFileFailed")) - return "Cannot open file"; - else if (STREQ_NULLABLE(klass, "QgaCommandFailed")) - return "Guest agent command failed"; - else if (STREQ_NULLABLE(klass, "QMPBadInputObjectMember")) - return "Bad QMP input object member"; - else if (STREQ_NULLABLE(klass, "QMPExtraInputObjectMember")) - return "Unexpected extra object member"; - else if (STREQ_NULLABLE(klass, "UndefinedError")) - return "An undefined error has occurred"; - else if (STREQ_NULLABLE(klass, "Unsupported")) - return "this feature or command is not currently supported"; - else if (klass) - return klass; - else - return "unknown QEMU command error"; -} - -/* Ignoring OOM in this method, since we're already reporting - * a more important error - * - * XXX see qerror.h for different klasses & fill out useful params - */ -static const char * -qemuAgentStringifyError(virJSONValuePtr error) -{ - const char *klass =3D virJSONValueObjectGetString(error, "class"); - const char *detail =3D 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 =3D qemuAgentStringifyErrorClass(klass); - - return detail; -} - -static const char * -qemuAgentCommandName(virJSONValuePtr cmd) -{ - const char *name =3D virJSONValueObjectGetString(cmd, "execute"); - if (name) - return name; - else - return ""; -} - -static int -qemuAgentCheckError(virJSONValuePtr cmd, - virJSONValuePtr reply) -{ - if (virJSONValueObjectHasKey(reply, "error")) { - virJSONValuePtr error =3D virJSONValueObjectGet(reply, "error"); - char *cmdstr =3D virJSONValueToString(cmd, false); - char *replystr =3D virJSONValueToString(reply, false); - - /* Log the full JSON formatted command & error */ - VIR_DEBUG("unable to execute QEMU agent command %s: %s", - NULLSTR(cmdstr), NULLSTR(replystr)); - - /* Only send the user the command name + friendly error */ - if (!error) - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unable to execute QEMU agent command '%s'"), - qemuAgentCommandName(cmd)); - else - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unable to execute QEMU agent command '%s': %= s"), - qemuAgentCommandName(cmd), - qemuAgentStringifyError(error)); - - VIR_FREE(cmdstr); - VIR_FREE(replystr); - return -1; - } else if (!virJSONValueObjectHasKey(reply, "return")) { - char *cmdstr =3D virJSONValueToString(cmd, false); - char *replystr =3D virJSONValueToString(reply, false); - - VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %= s: %s", - NULLSTR(cmdstr), NULLSTR(replystr)); - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unable to execute QEMU agent command '%s'"), - qemuAgentCommandName(cmd)); - VIR_FREE(cmdstr); - VIR_FREE(replystr); - return -1; - } - return 0; -} - -static int -qemuAgentCommand(qemuAgentPtr mon, - virJSONValuePtr cmd, - virJSONValuePtr *reply, - bool needReply, - int seconds) -{ - int ret =3D -1; - qemuAgentMessage msg; - char *cmdstr =3D NULL; - int await_event =3D mon->await_event; - - *reply =3D NULL; - - if (!mon->running) { - virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", - _("Guest agent disappeared while executing command"= )); - return -1; - } - - if (qemuAgentGuestSync(mon) < 0) - return -1; - - memset(&msg, 0, sizeof(msg)); - - if (!(cmdstr =3D virJSONValueToString(cmd, false))) - goto cleanup; - if (virAsprintf(&msg.txBuffer, "%s" LINE_ENDING, cmdstr) < 0) - goto cleanup; - msg.txLength =3D strlen(msg.txBuffer); - - VIR_DEBUG("Send command '%s' for write, seconds =3D %d", cmdstr, secon= ds); - - ret =3D qemuAgentSend(mon, &msg, seconds); - - VIR_DEBUG("Receive command reply ret=3D%d rxObject=3D%p", - ret, msg.rxObject); - - if (ret =3D=3D 0) { - /* If we haven't obtained any reply but we wait for an - * event, then don't report this as error */ - if (!msg.rxObject) { - if (await_event && !needReply) { - VIR_DEBUG("Woken up by event %d", await_event); - } else { - if (mon->running) - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Missing monitor reply object")); - else - virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", - _("Guest agent disappeared while execut= ing command")); - ret =3D -1; - } - } else { - *reply =3D msg.rxObject; - ret =3D qemuAgentCheckError(cmd, *reply); - } - } - - cleanup: - VIR_FREE(cmdstr); - VIR_FREE(msg.txBuffer); - - return ret; -} - -static virJSONValuePtr ATTRIBUTE_SENTINEL -qemuAgentMakeCommand(const char *cmdname, - ...) -{ - virJSONValuePtr obj; - virJSONValuePtr jargs =3D NULL; - va_list args; - - va_start(args, cmdname); - - if (!(obj =3D virJSONValueNewObject())) - goto error; - - if (virJSONValueObjectAppendString(obj, "execute", cmdname) < 0) - goto error; - - if (virJSONValueObjectCreateVArgs(&jargs, args) < 0) - goto error; - - if (jargs && - virJSONValueObjectAppend(obj, "arguments", jargs) < 0) - goto error; - - va_end(args); - - return obj; - - error: - virJSONValueFree(obj); - virJSONValueFree(jargs); - va_end(args); - return NULL; -} - -static virJSONValuePtr -qemuAgentMakeStringsArray(const char **strings, unsigned int len) -{ - size_t i; - virJSONValuePtr ret =3D virJSONValueNewArray(), str; - - if (!ret) - return NULL; - - for (i =3D 0; i < len; i++) { - str =3D virJSONValueNewString(strings[i]); - if (!str) - goto error; - - if (virJSONValueArrayAppend(ret, str) < 0) { - virJSONValueFree(str); - goto error; - } - } - return ret; - - error: - virJSONValueFree(ret); - return NULL; -} - -void qemuAgentNotifyEvent(qemuAgentPtr mon, - qemuAgentEvent event) -{ - virObjectLock(mon); - - VIR_DEBUG("mon=3D%p event=3D%d await_event=3D%d", mon, event, mon->awa= it_event); - if (mon->await_event =3D=3D event) { - mon->await_event =3D QEMU_AGENT_EVENT_NONE; - /* somebody waiting for this event, wake him up. */ - if (mon->msg && !mon->msg->finished) { - mon->msg->finished =3D 1; - virCondSignal(&mon->notify); - } - } - - virObjectUnlock(mon); -} - -VIR_ENUM_DECL(qemuAgentShutdownMode); - -VIR_ENUM_IMPL(qemuAgentShutdownMode, - QEMU_AGENT_SHUTDOWN_LAST, - "powerdown", "reboot", "halt"); - -int qemuAgentShutdown(qemuAgentPtr mon, - qemuAgentShutdownMode mode) -{ - int ret =3D -1; - virJSONValuePtr cmd; - virJSONValuePtr reply =3D NULL; - - cmd =3D qemuAgentMakeCommand("guest-shutdown", - "s:mode", qemuAgentShutdownModeTypeToString= (mode), - NULL); - if (!cmd) - return -1; - - if (mode =3D=3D QEMU_AGENT_SHUTDOWN_REBOOT) - mon->await_event =3D QEMU_AGENT_EVENT_RESET; - else - mon->await_event =3D QEMU_AGENT_EVENT_SHUTDOWN; - ret =3D qemuAgentCommand(mon, cmd, &reply, false, - VIR_DOMAIN_QEMU_AGENT_COMMAND_SHUTDOWN); - - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -/* - * qemuAgentFSFreeze: - * @mon: Agent - * @mountpoints: Array of mountpoint paths to be frozen, or NULL for all - * @nmountpoints: Number of mountpoints to be frozen, or 0 for all - * - * Issue guest-fsfreeze-freeze command to guest agent, - * which freezes file systems mounted on specified mountpoints - * (or all file systems when @mountpoints is NULL), and returns - * number of frozen file systems on success. - * - * Returns: number of file system frozen on success, - * -1 on error. - */ -int qemuAgentFSFreeze(qemuAgentPtr mon, const char **mountpoints, - unsigned int nmountpoints) -{ - int ret =3D -1; - virJSONValuePtr cmd, arg =3D NULL; - virJSONValuePtr reply =3D NULL; - - if (mountpoints && nmountpoints) { - arg =3D qemuAgentMakeStringsArray(mountpoints, nmountpoints); - if (!arg) - return -1; - - cmd =3D qemuAgentMakeCommand("guest-fsfreeze-freeze-list", - "a:mountpoints", arg, NULL); - } else { - cmd =3D qemuAgentMakeCommand("guest-fsfreeze-freeze", NULL); - } - - if (!cmd) - goto cleanup; - arg =3D NULL; - - if (qemuAgentCommand(mon, cmd, &reply, true, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) - goto cleanup; - - if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("malformed return value")); - } - - cleanup: - virJSONValueFree(arg); - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -/* - * qemuAgentFSThaw: - * @mon: Agent - * - * Issue guest-fsfreeze-thaw command to guest agent, - * which unfreezes all mounted file systems and returns - * number of thawed file systems on success. - * - * Returns: number of file system thawed on success, - * -1 on error. - */ -int qemuAgentFSThaw(qemuAgentPtr mon) -{ - int ret =3D -1; - virJSONValuePtr cmd; - virJSONValuePtr reply =3D NULL; - - cmd =3D qemuAgentMakeCommand("guest-fsfreeze-thaw", NULL); - - if (!cmd) - return -1; - - if (qemuAgentCommand(mon, cmd, &reply, true, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) - goto cleanup; - - if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("malformed return value")); - } - - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -VIR_ENUM_DECL(qemuAgentSuspendMode); - -VIR_ENUM_IMPL(qemuAgentSuspendMode, - VIR_NODE_SUSPEND_TARGET_LAST, - "guest-suspend-ram", - "guest-suspend-disk", - "guest-suspend-hybrid"); - -int -qemuAgentSuspend(qemuAgentPtr mon, - unsigned int target) -{ - int ret =3D -1; - virJSONValuePtr cmd; - virJSONValuePtr reply =3D NULL; - - cmd =3D qemuAgentMakeCommand(qemuAgentSuspendModeTypeToString(target), - NULL); - if (!cmd) - return -1; - - mon->await_event =3D QEMU_AGENT_EVENT_SUSPEND; - ret =3D qemuAgentCommand(mon, cmd, &reply, false, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK); - - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -int -qemuAgentArbitraryCommand(qemuAgentPtr mon, - const char *cmd_str, - char **result, - int timeout) -{ - int ret =3D -1; - virJSONValuePtr cmd =3D NULL; - virJSONValuePtr reply =3D NULL; - - *result =3D NULL; - if (timeout < VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN) { - virReportError(VIR_ERR_INVALID_ARG, - _("guest agent timeout '%d' is " - "less than the minimum '%d'"), - timeout, VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN); - goto cleanup; - } - - if (!(cmd =3D virJSONValueFromString(cmd_str))) - goto cleanup; - - if ((ret =3D qemuAgentCommand(mon, cmd, &reply, true, timeout)) < 0) - goto cleanup; - - if (!(*result =3D virJSONValueToString(reply, false))) - ret =3D -1; - - - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -int -qemuAgentFSTrim(qemuAgentPtr mon, - unsigned long long minimum) -{ - int ret =3D -1; - virJSONValuePtr cmd; - virJSONValuePtr reply =3D NULL; - - cmd =3D qemuAgentMakeCommand("guest-fstrim", - "U:minimum", minimum, - NULL); - if (!cmd) - return ret; - - ret =3D qemuAgentCommand(mon, cmd, &reply, false, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK); - - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -int -qemuAgentGetVCPUs(qemuAgentPtr mon, - qemuAgentCPUInfoPtr *info) -{ - int ret =3D -1; - size_t i; - virJSONValuePtr cmd; - virJSONValuePtr reply =3D NULL; - virJSONValuePtr data =3D NULL; - ssize_t ndata; - - if (!(cmd =3D qemuAgentMakeCommand("guest-get-vcpus", NULL))) - return -1; - - if (qemuAgentCommand(mon, cmd, &reply, true, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) - goto cleanup; - - if (!(data =3D virJSONValueObjectGet(reply, "return"))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest-get-vcpus reply was missing return data")); - goto cleanup; - } - - if (data->type !=3D VIR_JSON_TYPE_ARRAY) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest-get-vcpus return information was not an ar= ray")); - goto cleanup; - } - - ndata =3D virJSONValueArraySize(data); - - if (VIR_ALLOC_N(*info, ndata) < 0) - goto cleanup; - - for (i =3D 0; i < ndata; i++) { - virJSONValuePtr entry =3D virJSONValueArrayGet(data, i); - qemuAgentCPUInfoPtr in =3D *info + i; - - if (!entry) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("array element missing in guest-get-vcpus ret= urn " - "value")); - goto cleanup; - } - - if (virJSONValueObjectGetNumberUint(entry, "logical-id", &in->id) = < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'logical-id' missing in reply of guest-get-v= cpus")); - goto cleanup; - } - - if (virJSONValueObjectGetBoolean(entry, "online", &in->online) < 0= ) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'online' missing in reply of guest-get-vcpus= ")); - goto cleanup; - } - - if (virJSONValueObjectGetBoolean(entry, "can-offline", - &in->offlinable) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'can-offline' missing in reply of guest-get-= vcpus")); - goto cleanup; - } - } - - ret =3D ndata; - - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - - -/* returns the value provided by the guest agent or -1 on internal error */ -static int -qemuAgentSetVCPUsCommand(qemuAgentPtr mon, - qemuAgentCPUInfoPtr info, - size_t ninfo, - int *nmodified) -{ - int ret =3D -1; - virJSONValuePtr cmd =3D NULL; - virJSONValuePtr reply =3D NULL; - virJSONValuePtr cpus =3D NULL; - virJSONValuePtr cpu =3D NULL; - size_t i; - - *nmodified =3D 0; - - /* create the key data array */ - if (!(cpus =3D virJSONValueNewArray())) - goto cleanup; - - for (i =3D 0; i < ninfo; i++) { - qemuAgentCPUInfoPtr in =3D &info[i]; - - /* don't set state for cpus that were not touched */ - if (!in->modified) - continue; - - (*nmodified)++; - - /* create single cpu object */ - if (!(cpu =3D virJSONValueNewObject())) - goto cleanup; - - if (virJSONValueObjectAppendNumberInt(cpu, "logical-id", in->id) <= 0) - goto cleanup; - - if (virJSONValueObjectAppendBoolean(cpu, "online", in->online) < 0) - goto cleanup; - - if (virJSONValueArrayAppend(cpus, cpu) < 0) - goto cleanup; - - cpu =3D NULL; - } - - if (*nmodified =3D=3D 0) { - ret =3D 0; - goto cleanup; - } - - if (!(cmd =3D qemuAgentMakeCommand("guest-set-vcpus", - "a:vcpus", cpus, - NULL))) - goto cleanup; - - cpus =3D NULL; - - if (qemuAgentCommand(mon, cmd, &reply, true, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) - goto cleanup; - - if (qemuAgentCheckError(cmd, reply) < 0) - goto cleanup; - - /* All negative values are invalid. Return of 0 is bogus since we woul= dn't - * call the guest agent so that 0 cpus would be set successfully. Repo= rting - * more successfully set vcpus that we've asked for is invalid. */ - if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0 || - ret <=3D 0 || ret > *nmodified) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest agent returned malformed or invalid return= value")); - ret =3D -1; - } - - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - virJSONValueFree(cpu); - virJSONValueFree(cpus); - return ret; -} - - -/** - * Set the VCPU state using guest agent. - * - * Attempts to set the guest agent state for all cpus or until a proper er= ror is - * reported by the guest agent. This may require multiple calls. - * - * Returns -1 on error, 0 on success. - */ -int -qemuAgentSetVCPUs(qemuAgentPtr mon, - qemuAgentCPUInfoPtr info, - size_t ninfo) -{ - int rv; - int nmodified; - size_t i; - - do { - if ((rv =3D qemuAgentSetVCPUsCommand(mon, info, ninfo, &nmodified)= ) < 0) - return -1; - - /* all vcpus were set successfully */ - if (rv =3D=3D nmodified) - return 0; - - /* un-mark vcpus that were already set */ - for (i =3D 0; i < ninfo && rv > 0; i++) { - if (!info[i].modified) - continue; - - info[i].modified =3D false; - rv--; - } - } while (1); - - return 0; -} - - -/* modify the cpu info structure to set the correct amount of cpus */ -int -qemuAgentUpdateCPUInfo(unsigned int nvcpus, - qemuAgentCPUInfoPtr cpuinfo, - int ncpuinfo) -{ - size_t i; - int nonline =3D 0; - int nofflinable =3D 0; - ssize_t cpu0 =3D -1; - - /* count the active and offlinable cpus */ - for (i =3D 0; i < ncpuinfo; i++) { - if (cpuinfo[i].id =3D=3D 0) - cpu0 =3D i; - - if (cpuinfo[i].online) - nonline++; - - if (cpuinfo[i].offlinable && cpuinfo[i].online) - nofflinable++; - - /* This shouldn't happen, but we can't trust the guest agent */ - if (!cpuinfo[i].online && !cpuinfo[i].offlinable) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Invalid data provided by guest agent")); - return -1; - } - } - - /* CPU0 was made offlinable in linux a while ago, but certain parts (s= uspend - * to ram) of the kernel still don't cope well with that. Make sure th= at if - * all remaining vCPUs are offlinable, vCPU0 will not be selected to be - * offlined automatically */ - if (nofflinable =3D=3D nonline && cpu0 >=3D 0 && cpuinfo[cpu0].online)= { - cpuinfo[cpu0].offlinable =3D false; - nofflinable--; - } - - /* the guest agent reported less cpus than requested */ - if (nvcpus > ncpuinfo) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest agent reports less cpu than requested")); - return -1; - } - - /* not enough offlinable CPUs to support the request */ - if (nvcpus < nonline - nofflinable) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("Cannot offline enough CPUs")); - return -1; - } - - for (i =3D 0; i < ncpuinfo; i++) { - if (nvcpus < nonline) { - /* unplug */ - if (cpuinfo[i].offlinable && cpuinfo[i].online) { - cpuinfo[i].online =3D false; - cpuinfo[i].modified =3D true; - nonline--; - } - } else if (nvcpus > nonline) { - /* plug */ - if (!cpuinfo[i].online) { - cpuinfo[i].online =3D true; - cpuinfo[i].modified =3D true; - nonline++; - } - } else { - /* done */ - break; - } - } - - return 0; -} - - -int -qemuAgentGetTime(qemuAgentPtr mon, - long long *seconds, - unsigned int *nseconds) -{ - int ret =3D -1; - unsigned long long json_time; - virJSONValuePtr cmd; - virJSONValuePtr reply =3D NULL; - - cmd =3D qemuAgentMakeCommand("guest-get-time", - NULL); - if (!cmd) - return ret; - - if (qemuAgentCommand(mon, cmd, &reply, true, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) - goto cleanup; - - if (virJSONValueObjectGetNumberUlong(reply, "return", &json_time) < 0)= { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("malformed return value")); - goto cleanup; - } - - /* guest agent returns time in nanoseconds, - * we need it in seconds here */ - *seconds =3D json_time / 1000000000LL; - *nseconds =3D json_time % 1000000000LL; - ret =3D 0; - - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - - -/** - * qemuAgentSetTime: - * @setTime: time to set - * @sync: let guest agent to read domain's RTC (@setTime is ignored) - */ -int -qemuAgentSetTime(qemuAgentPtr mon, - long long seconds, - unsigned int nseconds, - bool rtcSync) -{ - int ret =3D -1; - virJSONValuePtr cmd; - virJSONValuePtr reply =3D NULL; - - if (rtcSync) { - cmd =3D qemuAgentMakeCommand("guest-set-time", NULL); - } else { - /* guest agent expect time with nanosecond granularity. - * Impressing. */ - long long json_time; - - /* Check if we overflow. For some reason qemu doesn't handle unsig= ned - * long long on the monitor well as it silently truncates numbers = to - * signed long long. Therefore we must check overflow against LLON= G_MAX - * not ULLONG_MAX. */ - if (seconds > LLONG_MAX / 1000000000LL) { - virReportError(VIR_ERR_INVALID_ARG, - _("Time '%lld' is too big for guest agent"), - seconds); - return ret; - } - - json_time =3D seconds * 1000000000LL; - json_time +=3D nseconds; - cmd =3D qemuAgentMakeCommand("guest-set-time", - "I:time", json_time, - NULL); - } - - if (!cmd) - return ret; - - if (qemuAgentCommand(mon, cmd, &reply, true, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) - goto cleanup; - - ret =3D 0; - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - - -int -qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info, - virDomainDefPtr vmdef) -{ - size_t i, j, k; - int ret =3D -1; - ssize_t ndata =3D 0, ndisk; - char **alias; - virJSONValuePtr cmd; - virJSONValuePtr reply =3D NULL; - virJSONValuePtr data; - virDomainFSInfoPtr *info_ret =3D NULL; - virPCIDeviceAddress pci_address; - - cmd =3D qemuAgentMakeCommand("guest-get-fsinfo", NULL); - if (!cmd) - return ret; - - if (qemuAgentCommand(mon, cmd, &reply, true, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) - goto cleanup; - - if (!(data =3D virJSONValueObjectGet(reply, "return"))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest-get-fsinfo reply was missing return data")= ); - goto cleanup; - } - - if (data->type !=3D VIR_JSON_TYPE_ARRAY) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest-get-fsinfo return information was not " - "an array")); - goto cleanup; - } - - ndata =3D virJSONValueArraySize(data); - if (!ndata) { - ret =3D 0; - *info =3D NULL; - goto cleanup; - } - if (VIR_ALLOC_N(info_ret, ndata) < 0) - goto cleanup; - - for (i =3D 0; i < ndata; i++) { - /* Reverse the order to arrange in mount order */ - virJSONValuePtr entry =3D virJSONValueArrayGet(data, ndata - 1 - i= ); - - if (!entry) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("array element '%zd' of '%zd' missing in " - "guest-get-fsinfo return data"), - i, ndata); - goto cleanup; - } - - if (VIR_ALLOC(info_ret[i]) < 0) - goto cleanup; - - if (VIR_STRDUP(info_ret[i]->mountpoint, - virJSONValueObjectGetString(entry, "mountpoint")) <= 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'mountpoint' missing in reply of " - "guest-get-fsinfo")); - goto cleanup; - } - - if (VIR_STRDUP(info_ret[i]->name, - virJSONValueObjectGetString(entry, "name")) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'name' missing in reply of guest-get-fsinfo"= )); - goto cleanup; - } - - if (VIR_STRDUP(info_ret[i]->fstype, - virJSONValueObjectGetString(entry, "type")) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'type' missing in reply of guest-get-fsinfo"= )); - goto cleanup; - } - - if (!(entry =3D virJSONValueObjectGet(entry, "disk"))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'disk' missing in reply of guest-get-fsinfo"= )); - goto cleanup; - } - - if (entry->type !=3D VIR_JSON_TYPE_ARRAY) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest-get-fsinfo 'disk' data was not an arra= y")); - goto cleanup; - } - - ndisk =3D virJSONValueArraySize(entry); - if (!ndisk) - continue; - if (VIR_ALLOC_N(info_ret[i]->devAlias, ndisk) < 0) - goto cleanup; - - alias =3D info_ret[i]->devAlias; - info_ret[i]->ndevAlias =3D 0; - for (j =3D 0; j < ndisk; j++) { - virJSONValuePtr disk =3D virJSONValueArrayGet(entry, j); - virJSONValuePtr pci; - int diskaddr[3], pciaddr[4]; - const char *diskaddr_comp[] =3D {"bus", "target", "unit"}; - const char *pciaddr_comp[] =3D {"domain", "bus", "slot", "func= tion"}; - virDomainDiskDefPtr diskDef; - - if (!disk) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("array element '%zd' of '%zd' missing in " - "guest-get-fsinfo 'disk' data"), - j, ndisk); - goto cleanup; - } - - if (!(pci =3D virJSONValueObjectGet(disk, "pci-controller"))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'pci-controller' missing in guest-get-fs= info " - "'disk' data")); - goto cleanup; - } - - for (k =3D 0; k < 3; k++) { - if (virJSONValueObjectGetNumberInt( - disk, diskaddr_comp[k], &diskaddr[k]) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("'%s' missing in guest-get-fsinfo " - "'disk' data"), diskaddr_comp[k]); - goto cleanup; - } - } - for (k =3D 0; k < 4; k++) { - if (virJSONValueObjectGetNumberInt( - pci, pciaddr_comp[k], &pciaddr[k]) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("'%s' missing in guest-get-fsinfo " - "'pci-address' data"), pciaddr_comp[k= ]); - goto cleanup; - } - } - - pci_address.domain =3D pciaddr[0]; - pci_address.bus =3D pciaddr[1]; - pci_address.slot =3D pciaddr[2]; - pci_address.function =3D pciaddr[3]; - if (!(diskDef =3D virDomainDiskByAddress( - vmdef, &pci_address, - diskaddr[0], diskaddr[1], diskaddr[2]))) - continue; - - if (VIR_STRDUP(*alias, diskDef->dst) < 0) - goto cleanup; - - if (*alias) { - alias++; - info_ret[i]->ndevAlias++; - } - } - } - - *info =3D info_ret; - info_ret =3D NULL; - ret =3D ndata; - - cleanup: - if (info_ret) { - for (i =3D 0; i < ndata; i++) - virDomainFSInfoFree(info_ret[i]); - VIR_FREE(info_ret); - } - virJSONValueFree(cmd); - virJSONValueFree(reply); - return ret; -} - -/* - * qemuAgentGetInterfaces: - * @mon: Agent monitor - * @ifaces: pointer to an array of pointers pointing to interface objects - * - * Issue guest-network-get-interfaces to guest agent, which returns a - * list of interfaces of a running domain along with their IP and MAC - * addresses. - * - * Returns: number of interfaces on success, -1 on error. - */ -int -qemuAgentGetInterfaces(qemuAgentPtr mon, - virDomainInterfacePtr **ifaces) -{ - int ret =3D -1; - size_t i, j; - ssize_t size =3D -1; - virJSONValuePtr cmd =3D NULL; - virJSONValuePtr reply =3D NULL; - virJSONValuePtr ret_array =3D NULL; - size_t ifaces_count =3D 0; - size_t addrs_count =3D 0; - virDomainInterfacePtr *ifaces_ret =3D NULL; - virHashTablePtr ifaces_store =3D NULL; - char **ifname =3D NULL; - - /* Hash table to handle the interface alias */ - if (!(ifaces_store =3D virHashCreate(ifaces_count, NULL))) { - virHashFree(ifaces_store); - return -1; - } - - if (!(cmd =3D qemuAgentMakeCommand("guest-network-get-interfaces", NUL= L))) - goto cleanup; - - if (qemuAgentCommand(mon, cmd, &reply, false, VIR_DOMAIN_QEMU_AGENT_CO= MMAND_BLOCK) < 0 || - qemuAgentCheckError(cmd, reply) < 0) { - goto cleanup; - } - - if (!(ret_array =3D virJSONValueObjectGet(reply, "return"))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("qemu agent didn't provide 'return' field")); - goto cleanup; - } - - if ((size =3D virJSONValueArraySize(ret_array)) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("qemu agent didn't return an array of interfaces"= )); - goto cleanup; - } - - for (i =3D 0; i < size; i++) { - virJSONValuePtr tmp_iface =3D virJSONValueArrayGet(ret_array, i); - virJSONValuePtr ip_addr_arr =3D NULL; - const char *hwaddr, *ifname_s, *name =3D NULL; - ssize_t ip_addr_arr_size; - virDomainInterfacePtr iface =3D NULL; - - /* Shouldn't happen but doesn't hurt to check neither */ - if (!tmp_iface) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("qemu agent reply missing interface entry in = array")); - goto error; - } - - /* interface name is required to be presented */ - name =3D virJSONValueObjectGetString(tmp_iface, "name"); - if (!name) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("qemu agent didn't provide 'name' field")); - goto error; - } - - /* Handle interface alias (:) */ - ifname =3D virStringSplit(name, ":", 2); - ifname_s =3D ifname[0]; - - iface =3D virHashLookup(ifaces_store, ifname_s); - - /* If the hash table doesn't contain this iface, add it */ - if (!iface) { - if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) - goto error; - - if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) - goto error; - - if (virHashAddEntry(ifaces_store, ifname_s, - ifaces_ret[ifaces_count - 1]) < 0) - goto error; - - iface =3D ifaces_ret[ifaces_count - 1]; - iface->naddrs =3D 0; - - if (VIR_STRDUP(iface->name, ifname_s) < 0) - goto error; - - hwaddr =3D virJSONValueObjectGetString(tmp_iface, "hardware-ad= dress"); - if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0) - goto error; - } - - /* Has to be freed for each interface. */ - virStringListFree(ifname); - - /* as well as IP address which - moreover - - * can be presented multiple times */ - ip_addr_arr =3D virJSONValueObjectGet(tmp_iface, "ip-addresses"); - if (!ip_addr_arr) - continue; - - if ((ip_addr_arr_size =3D virJSONValueArraySize(ip_addr_arr)) < 0) - /* Mmm, empty 'ip-address'? */ - goto error; - - /* If current iface already exists, continue with the count */ - addrs_count =3D iface->naddrs; - - for (j =3D 0; j < ip_addr_arr_size; j++) { - const char *type, *addr; - virJSONValuePtr ip_addr_obj =3D virJSONValueArrayGet(ip_addr_a= rr, j); - virDomainIPAddressPtr ip_addr; - - if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0) - goto error; - - ip_addr =3D &iface->addrs[addrs_count - 1]; - - /* Shouldn't happen but doesn't hurt to check neither */ - if (!ip_addr_obj) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("qemu agent reply missing IP addr in arra= y")); - goto error; - } - - type =3D virJSONValueObjectGetString(ip_addr_obj, "ip-address-= type"); - if (!type) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("qemu agent didn't provide 'ip-address-ty= pe'" - " field for interface '%s'"), name); - goto error; - } else if (STREQ(type, "ipv4")) { - ip_addr->type =3D VIR_IP_ADDR_TYPE_IPV4; - } else if (STREQ(type, "ipv6")) { - ip_addr->type =3D VIR_IP_ADDR_TYPE_IPV6; - } else { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unknown ip address type '%s'"), - type); - goto error; - } - - addr =3D virJSONValueObjectGetString(ip_addr_obj, "ip-address"= ); - if (!addr) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("qemu agent didn't provide 'ip-address'" - " field for interface '%s'"), name); - goto error; - } - if (VIR_STRDUP(ip_addr->addr, addr) < 0) - goto error; - - if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix", - &ip_addr->prefix) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("malformed 'prefix' field")); - goto error; - } - } - - iface->naddrs =3D addrs_count; - } - - *ifaces =3D ifaces_ret; - ifaces_ret =3D NULL; - ret =3D ifaces_count; - - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - virHashFree(ifaces_store); - return ret; - - error: - if (ifaces_ret) { - for (i =3D 0; i < ifaces_count; i++) - virDomainInterfaceFree(ifaces_ret[i]); - } - VIR_FREE(ifaces_ret); - virStringListFree(ifname); - - goto cleanup; -} - - -int -qemuAgentSetUserPassword(qemuAgentPtr mon, - const char *user, - const char *password, - bool crypted) -{ - int ret =3D -1; - virJSONValuePtr cmd =3D NULL; - virJSONValuePtr reply =3D NULL; - char *password64 =3D NULL; - - if (!(password64 =3D virStringEncodeBase64((unsigned char *) password, - strlen(password)))) - goto cleanup; - - if (!(cmd =3D qemuAgentMakeCommand("guest-set-user-password", - "b:crypted", crypted, - "s:username", user, - "s:password", password64, - NULL))) - goto cleanup; - - if (qemuAgentCommand(mon, cmd, &reply, true, - VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) - goto cleanup; - - ret =3D 0; - - cleanup: - virJSONValueFree(cmd); - virJSONValueFree(reply); - VIR_FREE(password64); - return ret; -} diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h deleted file mode 100644 index 6dd9c70..0000000 --- a/src/qemu/qemu_agent.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * qemu_agent.h: interaction with QEMU guest agent - * - * Copyright (C) 2006-2012 Red Hat, Inc. - * Copyright (C) 2006 Daniel P. Berrange - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see - * . - * - * Author: Daniel P. Berrange - */ - - -#ifndef __QEMU_AGENT_H__ -# define __QEMU_AGENT_H__ - -# include "internal.h" -# include "domain_conf.h" - -typedef struct _qemuAgent qemuAgent; -typedef qemuAgent *qemuAgentPtr; - -typedef struct _qemuAgentCallbacks qemuAgentCallbacks; -typedef qemuAgentCallbacks *qemuAgentCallbacksPtr; -struct _qemuAgentCallbacks { - void (*destroy)(qemuAgentPtr mon, - virDomainObjPtr vm); - void (*eofNotify)(qemuAgentPtr mon, - virDomainObjPtr vm); - void (*errorNotify)(qemuAgentPtr mon, - virDomainObjPtr vm); -}; - - -qemuAgentPtr qemuAgentOpen(virDomainObjPtr vm, - const virDomainChrSourceDef *config, - qemuAgentCallbacksPtr cb); - -void qemuAgentClose(qemuAgentPtr mon); - -void qemuAgentNotifyClose(qemuAgentPtr mon); - -typedef enum { - QEMU_AGENT_EVENT_NONE =3D 0, - QEMU_AGENT_EVENT_SHUTDOWN, - QEMU_AGENT_EVENT_SUSPEND, - QEMU_AGENT_EVENT_RESET, -} qemuAgentEvent; - -void qemuAgentNotifyEvent(qemuAgentPtr mon, - qemuAgentEvent event); - -typedef enum { - QEMU_AGENT_SHUTDOWN_POWERDOWN, - QEMU_AGENT_SHUTDOWN_REBOOT, - QEMU_AGENT_SHUTDOWN_HALT, - - QEMU_AGENT_SHUTDOWN_LAST, -} qemuAgentShutdownMode; - -int qemuAgentShutdown(qemuAgentPtr mon, - qemuAgentShutdownMode mode); - -int qemuAgentFSFreeze(qemuAgentPtr mon, - const char **mountpoints, unsigned int nmountpoints); -int qemuAgentFSThaw(qemuAgentPtr mon); -int qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info, - virDomainDefPtr vmdef); - -int qemuAgentSuspend(qemuAgentPtr mon, - unsigned int target); - -int qemuAgentArbitraryCommand(qemuAgentPtr mon, - const char *cmd, - char **result, - int timeout); -int qemuAgentFSTrim(qemuAgentPtr mon, - unsigned long long minimum); - - -typedef struct _qemuAgentCPUInfo qemuAgentCPUInfo; -typedef qemuAgentCPUInfo *qemuAgentCPUInfoPtr; -struct _qemuAgentCPUInfo { - unsigned int id; /* logical cpu ID */ - bool online; /* true if the CPU is activated */ - bool offlinable; /* true if the CPU can be offlined */ - - bool modified; /* set to true if the vcpu state needs to be changed */ -}; - -int qemuAgentGetVCPUs(qemuAgentPtr mon, qemuAgentCPUInfoPtr *info); -int qemuAgentSetVCPUs(qemuAgentPtr mon, qemuAgentCPUInfoPtr cpus, size_t n= cpus); -int qemuAgentUpdateCPUInfo(unsigned int nvcpus, - qemuAgentCPUInfoPtr cpuinfo, - int ncpuinfo); - -int qemuAgentGetTime(qemuAgentPtr mon, - long long *seconds, - unsigned int *nseconds); -int qemuAgentSetTime(qemuAgentPtr mon, - long long seconds, - unsigned int nseconds, - bool sync); - -int qemuAgentGetInterfaces(qemuAgentPtr mon, - virDomainInterfacePtr **ifaces); - -int qemuAgentSetUserPassword(qemuAgentPtr mon, - const char *user, - const char *password, - bool crypted); -#endif /* __QEMU_AGENT_H__ */ diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 3973182..8d8fd95 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -31,7 +31,7 @@ # include "domain_conf.h" # include "snapshot_conf.h" # include "qemu_monitor.h" -# include "qemu_agent.h" +# include "virqemuagent.h" # include "qemu_conf.h" # include "qemu_capabilities.h" # include "virchrdev.h" diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 37ccfdf..177ca51 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -44,7 +44,6 @@ =20 =20 #include "qemu_driver.h" -#include "qemu_agent.h" #include "qemu_alias.h" #include "qemu_conf.h" #include "qemu_capabilities.h" @@ -59,6 +58,7 @@ #include "qemu_blockjob.h" #include "qemu_security.h" =20 +#include "virqemuagent.h" #include "virerror.h" #include "virlog.h" #include "datatypes.h" diff --git a/src/util/virqemuagent.c b/src/util/virqemuagent.c new file mode 100644 index 0000000..caabae0 --- /dev/null +++ b/src/util/virqemuagent.c @@ -0,0 +1,2248 @@ +/* + * virqemuagent.c: interaction with QEMU guest agent + * + * Copyright (C) 2006-2014 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Author: Daniel P. Berrange + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virqemuagent.h" +#include "viralloc.h" +#include "virlog.h" +#include "virerror.h" +#include "virjson.h" +#include "virfile.h" +#include "virprocess.h" +#include "virtime.h" +#include "virobject.h" +#include "virstring.h" +#include "base64.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +VIR_LOG_INIT("qemu.qemu_agent"); + +#define LINE_ENDING "\n" + +#define DEBUG_IO 0 +#define DEBUG_RAW_IO 0 + +/* When you are the first to uncomment this, + * don't forget to uncomment the corresponding + * part in qemuAgentIOProcessEvent as well. + * +static struct { + const char *type; + void (*handler)(qemuAgentPtr mon, virJSONValuePtr data); +} eventHandlers[] =3D { +}; +*/ + +typedef struct _qemuAgentMessage qemuAgentMessage; +typedef qemuAgentMessage *qemuAgentMessagePtr; + +struct _qemuAgentMessage { + char *txBuffer; + int txOffset; + int txLength; + + /* Used by the JSON monitor to hold reply / error */ + char *rxBuffer; + int rxLength; + void *rxObject; + + /* True if rxBuffer / rxObject are ready, or a + * fatal error occurred on the monitor channel + */ + bool finished; + /* true for sync command */ + bool sync; + /* id of the issued sync comand */ + unsigned long long id; + bool first; +}; + + +struct _qemuAgent { + virObjectLockable parent; + + virCond notify; + + int fd; + int watch; + + bool connectPending; + bool running; + + virDomainObjPtr vm; + + qemuAgentCallbacksPtr cb; + + /* If there's a command being processed this will be + * non-NULL */ + qemuAgentMessagePtr msg; + + /* Buffer incoming data ready for Agent monitor + * code to process & find message boundaries */ + size_t bufferOffset; + size_t bufferLength; + char *buffer; + + /* If anything went wrong, this will be fed back + * the next monitor msg */ + virError lastError; + + /* Some guest agent commands don't return anything + * but fire up an event on qemu monitor instead. + * Take that as indication of successful completion */ + qemuAgentEvent await_event; +}; + +static virClassPtr qemuAgentClass; +static void qemuAgentDispose(void *obj); + +static int qemuAgentOnceInit(void) +{ + if (!(qemuAgentClass =3D virClassNew(virClassForObjectLockable(), + "qemuAgent", + sizeof(qemuAgent), + qemuAgentDispose))) + return -1; + + return 0; +} + +VIR_ONCE_GLOBAL_INIT(qemuAgent) + + +#if DEBUG_RAW_IO +# include +static char * +qemuAgentEscapeNonPrintable(const char *text) +{ + size_t i; + virBuffer buf =3D VIR_BUFFER_INITIALIZER; + for (i =3D 0; text[i] !=3D '\0'; i++) { + if (text[i] =3D=3D '\\') + virBufferAddLit(&buf, "\\\\"); + else if (c_isprint(text[i]) || text[i] =3D=3D '\n' || + (text[i] =3D=3D '\r' && text[i+1] =3D=3D '\n')) + virBufferAddChar(&buf, text[i]); + else + virBufferAsprintf(&buf, "\\x%02x", text[i]); + } + return virBufferContentAndReset(&buf); +} +#endif + + +static void qemuAgentDispose(void *obj) +{ + qemuAgentPtr mon =3D obj; + VIR_DEBUG("mon=3D%p", mon); + if (mon->cb && mon->cb->destroy) + (mon->cb->destroy)(mon, mon->vm); + virCondDestroy(&mon->notify); + VIR_FREE(mon->buffer); + virResetError(&mon->lastError); +} + +static int +qemuAgentOpenUnix(const char *monitor, pid_t cpid, bool *inProgress) +{ + struct sockaddr_un addr; + int monfd; + virTimeBackOffVar timeout; + int ret =3D -1; + + *inProgress =3D false; + + if ((monfd =3D socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + virReportSystemError(errno, + "%s", _("failed to create socket")); + return -1; + } + + if (virSetNonBlock(monfd) < 0) { + virReportSystemError(errno, "%s", + _("Unable to put monitor " + "into non-blocking mode")); + goto error; + } + + if (virSetCloseExec(monfd) < 0) { + virReportSystemError(errno, "%s", + _("Unable to set monitor " + "close-on-exec flag")); + goto error; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family =3D AF_UNIX; + if (virStrcpyStatic(addr.sun_path, monitor) =3D=3D NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Agent path %s too big for destination"), monitor= ); + goto error; + } + + if (virTimeBackOffStart(&timeout, 1, 3*1000 /* ms */) < 0) + goto error; + while (virTimeBackOffWait(&timeout)) { + ret =3D connect(monfd, (struct sockaddr *) &addr, sizeof(addr)); + + if (ret =3D=3D 0) + break; + + if ((errno =3D=3D ENOENT || errno =3D=3D ECONNREFUSED) && + virProcessKill(cpid, 0) =3D=3D 0) { + /* ENOENT : Socket may not have shown up yet + * ECONNREFUSED : Leftover socket hasn't been removed yet */ + continue; + } + + if ((errno =3D=3D EINPROGRESS) || + (errno =3D=3D EAGAIN)) { + VIR_DEBUG("Connection attempt continuing in background"); + *inProgress =3D true; + ret =3D 0; + break; + } + + virReportSystemError(errno, "%s", + _("failed to connect to monitor socket")); + goto error; + + } + + if (ret !=3D 0) { + virReportSystemError(errno, "%s", + _("monitor socket did not show up")); + goto error; + } + + return monfd; + + error: + VIR_FORCE_CLOSE(monfd); + return -1; +} + +static int +qemuAgentOpenPty(const char *monitor) +{ + int monfd; + + if ((monfd =3D open(monitor, O_RDWR | O_NONBLOCK)) < 0) { + virReportSystemError(errno, + _("Unable to open monitor path %s"), monitor); + return -1; + } + + if (virSetCloseExec(monfd) < 0) { + virReportSystemError(errno, "%s", + _("Unable to set monitor close-on-exec flag")= ); + goto error; + } + + return monfd; + + error: + VIR_FORCE_CLOSE(monfd); + return -1; +} + + +static int +qemuAgentIOProcessEvent(qemuAgentPtr mon, + virJSONValuePtr obj) +{ + const char *type; + VIR_DEBUG("mon=3D%p obj=3D%p", mon, obj); + + type =3D virJSONValueObjectGetString(obj, "event"); + if (!type) { + VIR_WARN("missing event type in message"); + errno =3D EINVAL; + return -1; + } + +/* + for (i =3D 0; i < ARRAY_CARDINALITY(eventHandlers); i++) { + if (STREQ(eventHandlers[i].type, type)) { + virJSONValuePtr data =3D virJSONValueObjectGet(obj, "data"); + VIR_DEBUG("handle %s handler=3D%p data=3D%p", type, + eventHandlers[i].handler, data); + (eventHandlers[i].handler)(mon, data); + break; + } + } +*/ + return 0; +} + +static int +qemuAgentIOProcessLine(qemuAgentPtr mon, + const char *line, + qemuAgentMessagePtr msg) +{ + virJSONValuePtr obj =3D NULL; + int ret =3D -1; + + VIR_DEBUG("Line [%s]", line); + + if (!(obj =3D virJSONValueFromString(line))) { + /* receiving garbage on first sync is regular situation */ + if (msg && msg->sync && msg->first) { + VIR_DEBUG("Received garbage on sync"); + msg->finished =3D 1; + return 0; + } + + goto cleanup; + } + + if (obj->type !=3D VIR_JSON_TYPE_OBJECT) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Parsed JSON reply '%s' isn't an object"), line); + goto cleanup; + } + + if (virJSONValueObjectHasKey(obj, "QMP") =3D=3D 1) { + ret =3D 0; + } else if (virJSONValueObjectHasKey(obj, "event") =3D=3D 1) { + ret =3D qemuAgentIOProcessEvent(mon, obj); + } else if (virJSONValueObjectHasKey(obj, "error") =3D=3D 1 || + virJSONValueObjectHasKey(obj, "return") =3D=3D 1) { + if (msg) { + if (msg->sync) { + unsigned long long id; + + if (virJSONValueObjectGetNumberUlong(obj, "return", &id) <= 0) { + VIR_DEBUG("Ignoring delayed reply on sync"); + ret =3D 0; + goto cleanup; + } + + VIR_DEBUG("Guest returned ID: %llu", id); + + if (msg->id !=3D id) { + VIR_DEBUG("Guest agent returned ID: %llu instead of %l= lu", + id, msg->id); + ret =3D 0; + goto cleanup; + } + } + msg->rxObject =3D obj; + msg->finished =3D 1; + obj =3D NULL; + } else { + /* we are out of sync */ + VIR_DEBUG("Ignoring delayed reply"); + } + ret =3D 0; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown JSON reply '%s'"), line); + } + + cleanup: + virJSONValueFree(obj); + return ret; +} + +static int qemuAgentIOProcessData(qemuAgentPtr mon, + char *data, + size_t len, + qemuAgentMessagePtr msg) +{ + int used =3D 0; + size_t i =3D 0; +#if DEBUG_IO +# if DEBUG_RAW_IO + char *str1 =3D qemuAgentEscapeNonPrintable(data); + VIR_ERROR("[%s]", str1); + VIR_FREE(str1); +# else + VIR_DEBUG("Data %zu bytes [%s]", len, data); +# endif +#endif + + while (used < len) { + char *nl =3D strstr(data + used, LINE_ENDING); + + if (nl) { + int got =3D nl - (data + used); + for (i =3D 0; i < strlen(LINE_ENDING); i++) + data[used + got + i] =3D '\0'; + if (qemuAgentIOProcessLine(mon, data + used, msg) < 0) + return -1; + used +=3D got + strlen(LINE_ENDING); + } else { + break; + } + } + + VIR_DEBUG("Total used %d bytes out of %zd available in buffer", used, = len); + return used; +} + +/* This method processes data that has been received + * from the monitor. Looking for async events and + * replies/errors. + */ +static int +qemuAgentIOProcess(qemuAgentPtr mon) +{ + int len; + qemuAgentMessagePtr msg =3D NULL; + + /* See if there's a message ready for reply; that is, + * one that has completed writing all its data. + */ + if (mon->msg && mon->msg->txOffset =3D=3D mon->msg->txLength) + msg =3D mon->msg; + +#if DEBUG_IO +# if DEBUG_RAW_IO + char *str1 =3D qemuAgentEscapeNonPrintable(msg ? msg->txBuffer : ""); + char *str2 =3D qemuAgentEscapeNonPrintable(mon->buffer); + VIR_ERROR(_("Process %zu %p %p [[[%s]]][[[%s]]]"), + mon->bufferOffset, mon->msg, msg, str1, str2); + VIR_FREE(str1); + VIR_FREE(str2); +# else + VIR_DEBUG("Process %zu", mon->bufferOffset); +# endif +#endif + + len =3D qemuAgentIOProcessData(mon, + mon->buffer, mon->bufferOffset, + msg); + + if (len < 0) + return -1; + + if (len < mon->bufferOffset) { + memmove(mon->buffer, mon->buffer + len, mon->bufferOffset - len); + mon->bufferOffset -=3D len; + } else { + VIR_FREE(mon->buffer); + mon->bufferOffset =3D mon->bufferLength =3D 0; + } +#if DEBUG_IO + VIR_DEBUG("Process done %zu used %d", mon->bufferOffset, len); +#endif + if (msg && msg->finished) + virCondBroadcast(&mon->notify); + return len; +} + + +static int +qemuAgentIOConnect(qemuAgentPtr mon) +{ + int optval; + socklen_t optlen; + + VIR_DEBUG("Checking on background connection status"); + + mon->connectPending =3D false; + + optlen =3D sizeof(optval); + + if (getsockopt(mon->fd, SOL_SOCKET, SO_ERROR, + &optval, &optlen) < 0) { + virReportSystemError(errno, "%s", + _("Cannot check socket connection status")); + return -1; + } + + if (optval !=3D 0) { + virReportSystemError(optval, "%s", + _("Cannot connect to agent socket")); + return -1; + } + + VIR_DEBUG("Agent is now connected"); + return 0; +} + +/* + * Called when the monitor is able to write data + * Call this function while holding the monitor lock. + */ +static int +qemuAgentIOWrite(qemuAgentPtr mon) +{ + int done; + + /* If no active message, or fully transmitted, then no-op */ + if (!mon->msg || mon->msg->txOffset =3D=3D mon->msg->txLength) + return 0; + + done =3D safewrite(mon->fd, + mon->msg->txBuffer + mon->msg->txOffset, + mon->msg->txLength - mon->msg->txOffset); + + if (done < 0) { + if (errno =3D=3D EAGAIN) + return 0; + + virReportSystemError(errno, "%s", + _("Unable to write to monitor")); + return -1; + } + mon->msg->txOffset +=3D done; + return done; +} + +/* + * Called when the monitor has incoming data to read + * Call this function while holding the monitor lock. + * + * Returns -1 on error, or number of bytes read + */ +static int +qemuAgentIORead(qemuAgentPtr mon) +{ + size_t avail =3D mon->bufferLength - mon->bufferOffset; + int ret =3D 0; + + if (avail < 1024) { + if (VIR_REALLOC_N(mon->buffer, + mon->bufferLength + 1024) < 0) + return -1; + mon->bufferLength +=3D 1024; + avail +=3D 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 =3D read(mon->fd, + mon->buffer + mon->bufferOffset, + avail - 1); + if (got < 0) { + if (errno =3D=3D EAGAIN) + break; + virReportSystemError(errno, "%s", + _("Unable to read from monitor")); + ret =3D -1; + break; + } + if (got =3D=3D 0) + break; + + ret +=3D got; + avail -=3D got; + mon->bufferOffset +=3D got; + mon->buffer[mon->bufferOffset] =3D '\0'; + } + +#if DEBUG_IO + VIR_DEBUG("Now read %zu bytes of data", mon->bufferOffset); +#endif + + return ret; +} + + +static void qemuAgentUpdateWatch(qemuAgentPtr mon) +{ + int events =3D + VIR_EVENT_HANDLE_HANGUP | + VIR_EVENT_HANDLE_ERROR; + + if (mon->lastError.code =3D=3D VIR_ERR_OK) { + events |=3D VIR_EVENT_HANDLE_READABLE; + + if (mon->msg && mon->msg->txOffset < mon->msg->txLength) + events |=3D VIR_EVENT_HANDLE_WRITABLE; + } + + virEventUpdateHandle(mon->watch, events); +} + + +static void +qemuAgentIO(int watch, int fd, int events, void *opaque) +{ + qemuAgentPtr mon =3D opaque; + bool error =3D false; + bool eof =3D false; + + virObjectRef(mon); + /* lock access to the monitor and protect fd */ + virObjectLock(mon); +#if DEBUG_IO + VIR_DEBUG("Agent %p I/O on watch %d fd %d events %d", mon, watch, fd, = events); +#endif + + if (mon->fd !=3D fd || mon->watch !=3D watch) { + if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR)) + eof =3D true; + virReportError(VIR_ERR_INTERNAL_ERROR, + _("event from unexpected fd %d!=3D%d / watch %d!=3D= %d"), + mon->fd, fd, mon->watch, watch); + error =3D true; + } else if (mon->lastError.code !=3D VIR_ERR_OK) { + if (events & (VIR_EVENT_HANDLE_HANGUP | VIR_EVENT_HANDLE_ERROR)) + eof =3D true; + error =3D true; + } else { + if (events & VIR_EVENT_HANDLE_WRITABLE) { + if (mon->connectPending) { + if (qemuAgentIOConnect(mon) < 0) + error =3D true; + } else { + if (qemuAgentIOWrite(mon) < 0) + error =3D true; + } + events &=3D ~VIR_EVENT_HANDLE_WRITABLE; + } + + if (!error && + events & VIR_EVENT_HANDLE_READABLE) { + int got =3D qemuAgentIORead(mon); + events &=3D ~VIR_EVENT_HANDLE_READABLE; + if (got < 0) { + error =3D true; + } else if (got =3D=3D 0) { + eof =3D true; + } else { + /* Ignore hangup/error events if we read some data, to + * give time for that data to be consumed */ + events =3D 0; + + if (qemuAgentIOProcess(mon) < 0) + error =3D true; + } + } + + if (!error && + events & VIR_EVENT_HANDLE_HANGUP) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("End of file from agent monitor")); + eof =3D true; + events &=3D ~VIR_EVENT_HANDLE_HANGUP; + } + + if (!error && !eof && + events & VIR_EVENT_HANDLE_ERROR) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Invalid file descriptor while waiting for mo= nitor")); + eof =3D true; + events &=3D ~VIR_EVENT_HANDLE_ERROR; + } + if (!error && events) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unhandled event %d for monitor fd %d"), + events, mon->fd); + error =3D true; + } + } + + if (error || eof) { + if (mon->lastError.code !=3D VIR_ERR_OK) { + /* Already have an error, so clear any new error */ + virResetLastError(); + } else { + virErrorPtr err =3D virGetLastError(); + if (!err) + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Error while processing monitor IO")); + virCopyLastError(&mon->lastError); + virResetLastError(); + } + + VIR_DEBUG("Error on monitor %s", NULLSTR(mon->lastError.message)); + /* If IO process resulted in an error & we have a message, + * then wakeup that waiter */ + if (mon->msg && !mon->msg->finished) { + mon->msg->finished =3D 1; + virCondSignal(&mon->notify); + } + } + + qemuAgentUpdateWatch(mon); + + /* We have to unlock to avoid deadlock against command thread, + * but is this safe ? I think it is, because the callback + * will try to acquire the virDomainObjPtr mutex next */ + if (eof) { + void (*eofNotify)(qemuAgentPtr, virDomainObjPtr) + =3D mon->cb->eofNotify; + virDomainObjPtr vm =3D mon->vm; + + /* Make sure anyone waiting wakes up now */ + virCondSignal(&mon->notify); + virObjectUnlock(mon); + virObjectUnref(mon); + VIR_DEBUG("Triggering EOF callback"); + (eofNotify)(mon, vm); + } else if (error) { + void (*errorNotify)(qemuAgentPtr, virDomainObjPtr) + =3D mon->cb->errorNotify; + virDomainObjPtr vm =3D mon->vm; + + /* Make sure anyone waiting wakes up now */ + virCondSignal(&mon->notify); + virObjectUnlock(mon); + virObjectUnref(mon); + VIR_DEBUG("Triggering error callback"); + (errorNotify)(mon, vm); + } else { + virObjectUnlock(mon); + virObjectUnref(mon); + } +} + + +qemuAgentPtr +qemuAgentOpen(virDomainObjPtr vm, + const virDomainChrSourceDef *config, + qemuAgentCallbacksPtr cb) +{ + qemuAgentPtr mon; + + if (!cb || !cb->eofNotify) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("EOF notify callback must be supplied")); + return NULL; + } + + if (qemuAgentInitialize() < 0) + return NULL; + + if (!(mon =3D virObjectLockableNew(qemuAgentClass))) + return NULL; + + mon->fd =3D -1; + if (virCondInit(&mon->notify) < 0) { + virReportSystemError(errno, "%s", + _("cannot initialize monitor condition")); + virObjectUnref(mon); + return NULL; + } + mon->vm =3D vm; + mon->cb =3D cb; + + switch (config->type) { + case VIR_DOMAIN_CHR_TYPE_UNIX: + mon->fd =3D qemuAgentOpenUnix(config->data.nix.path, vm->pid, + &mon->connectPending); + break; + + case VIR_DOMAIN_CHR_TYPE_PTY: + mon->fd =3D qemuAgentOpenPty(config->data.file.path); + break; + + default: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to handle monitor type: %s"), + virDomainChrTypeToString(config->type)); + goto cleanup; + } + + if (mon->fd =3D=3D -1) + goto cleanup; + + virObjectRef(mon); + if ((mon->watch =3D virEventAddHandle(mon->fd, + VIR_EVENT_HANDLE_HANGUP | + VIR_EVENT_HANDLE_ERROR | + VIR_EVENT_HANDLE_READABLE | + (mon->connectPending ? + VIR_EVENT_HANDLE_WRITABLE : + 0), + qemuAgentIO, + mon, + virObjectFreeCallback)) < 0) { + virObjectUnref(mon); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to register monitor events")); + goto cleanup; + } + + mon->running =3D true; + VIR_DEBUG("New mon %p fd =3D%d watch=3D%d", mon, mon->fd, mon->watch); + + return mon; + + cleanup: + /* We don't want the 'destroy' callback invoked during + * cleanup from construction failure, because that can + * give a double-unref on virDomainObjPtr in the caller, + * so kill the callbacks now. + */ + mon->cb =3D NULL; + qemuAgentClose(mon); + return NULL; +} + + +static void +qemuAgentNotifyCloseLocked(qemuAgentPtr mon) +{ + if (mon) { + mon->running =3D false; + + /* If there is somebody waiting for a message + * wake him up. No message will arrive anyway. */ + if (mon->msg && !mon->msg->finished) { + mon->msg->finished =3D 1; + virCondSignal(&mon->notify); + } + } +} + + +void +qemuAgentNotifyClose(qemuAgentPtr mon) +{ + if (!mon) + return; + + VIR_DEBUG("mon=3D%p", mon); + + virObjectLock(mon); + qemuAgentNotifyCloseLocked(mon); + virObjectUnlock(mon); +} + + +void qemuAgentClose(qemuAgentPtr mon) +{ + if (!mon) + return; + + VIR_DEBUG("mon=3D%p", mon); + + virObjectLock(mon); + + if (mon->fd >=3D 0) { + if (mon->watch) + virEventRemoveHandle(mon->watch); + VIR_FORCE_CLOSE(mon->fd); + } + + qemuAgentNotifyCloseLocked(mon); + virObjectUnlock(mon); + + virObjectUnref(mon); +} + +#define QEMU_AGENT_WAIT_TIME 5 + +/** + * qemuAgentSend: + * @mon: Monitor + * @msg: Message + * @seconds: number of seconds to wait for the result, it can be either + * -2, -1, 0 or positive. + * + * Send @msg to agent @mon. If @seconds is equal to + * VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK(-2), this function will block forev= er + * waiting for the result. The value of + * VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT(-1) means use default timeout val= ue + * and VIR_DOMAIN_QEMU_AGENT_COMMAND_NOWAIT(0) makes this this function re= turn + * immediately without waiting. Any positive value means the number of sec= onds + * to wait for the result. + * + * Returns: 0 on success, + * -2 on timeout, + * -1 otherwise + */ +static int qemuAgentSend(qemuAgentPtr mon, + qemuAgentMessagePtr msg, + int seconds) +{ + int ret =3D -1; + unsigned long long then =3D 0; + + /* Check whether qemu quit unexpectedly */ + if (mon->lastError.code !=3D VIR_ERR_OK) { + VIR_DEBUG("Attempt to send command while error is set %s", + NULLSTR(mon->lastError.message)); + virSetError(&mon->lastError); + return -1; + } + + if (seconds > VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) { + unsigned long long now; + if (virTimeMillisNow(&now) < 0) + return -1; + if (seconds =3D=3D VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT) + seconds =3D QEMU_AGENT_WAIT_TIME; + then =3D now + seconds * 1000ull; + } + + mon->msg =3D msg; + qemuAgentUpdateWatch(mon); + + while (!mon->msg->finished) { + if ((then && virCondWaitUntil(&mon->notify, &mon->parent.lock, the= n) < 0) || + (!then && virCondWait(&mon->notify, &mon->parent.lock) < 0)) { + if (errno =3D=3D ETIMEDOUT) { + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("Guest agent not available for now")); + ret =3D -2; + } else { + virReportSystemError(errno, "%s", + _("Unable to wait on agent monitor " + "condition")); + } + goto cleanup; + } + } + + if (mon->lastError.code !=3D VIR_ERR_OK) { + VIR_DEBUG("Send command resulted in error %s", + NULLSTR(mon->lastError.message)); + virSetError(&mon->lastError); + goto cleanup; + } + + ret =3D 0; + + cleanup: + mon->msg =3D NULL; + qemuAgentUpdateWatch(mon); + + return ret; +} + + +/** + * qemuAgentGuestSync: + * @mon: Monitor + * + * Send guest-sync with unique ID + * and wait for reply. If we get one, check if + * received ID is equal to given. + * + * Returns: 0 on success, + * -1 otherwise + */ +static int +qemuAgentGuestSync(qemuAgentPtr mon) +{ + int ret =3D -1; + int send_ret; + unsigned long long id; + qemuAgentMessage sync_msg; + + memset(&sync_msg, 0, sizeof(sync_msg)); + /* set only on first sync */ + sync_msg.first =3D true; + + retry: + if (virTimeMillisNow(&id) < 0) + return -1; + + if (virAsprintf(&sync_msg.txBuffer, + "{\"execute\":\"guest-sync\", " + "\"arguments\":{\"id\":%llu}}\n", id) < 0) + return -1; + + sync_msg.txLength =3D strlen(sync_msg.txBuffer); + sync_msg.sync =3D true; + sync_msg.id =3D id; + + VIR_DEBUG("Sending guest-sync command with ID: %llu", id); + + send_ret =3D qemuAgentSend(mon, &sync_msg, + VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT); + + VIR_DEBUG("qemuAgentSend returned: %d", send_ret); + + if (send_ret < 0) + goto cleanup; + + if (!sync_msg.rxObject) { + if (sync_msg.first) { + VIR_FREE(sync_msg.txBuffer); + memset(&sync_msg, 0, sizeof(sync_msg)); + goto retry; + } else { + if (mon->running) + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing monitor reply object")); + else + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("Guest agent disappeared while executing = command")); + goto cleanup; + } + } + + ret =3D 0; + + cleanup: + virJSONValueFree(sync_msg.rxObject); + VIR_FREE(sync_msg.txBuffer); + return ret; +} + +static const char * +qemuAgentStringifyErrorClass(const char *klass) +{ + if (STREQ_NULLABLE(klass, "BufferOverrun")) + return "Buffer overrun"; + else if (STREQ_NULLABLE(klass, "CommandDisabled")) + return "The command has been disabled for this instance"; + else if (STREQ_NULLABLE(klass, "CommandNotFound")) + return "The command has not been found"; + else if (STREQ_NULLABLE(klass, "FdNotFound")) + return "File descriptor not found"; + else if (STREQ_NULLABLE(klass, "InvalidParameter")) + return "Invalid parameter"; + else if (STREQ_NULLABLE(klass, "InvalidParameterType")) + return "Invalid parameter type"; + else if (STREQ_NULLABLE(klass, "InvalidParameterValue")) + return "Invalid parameter value"; + else if (STREQ_NULLABLE(klass, "OpenFileFailed")) + return "Cannot open file"; + else if (STREQ_NULLABLE(klass, "QgaCommandFailed")) + return "Guest agent command failed"; + else if (STREQ_NULLABLE(klass, "QMPBadInputObjectMember")) + return "Bad QMP input object member"; + else if (STREQ_NULLABLE(klass, "QMPExtraInputObjectMember")) + return "Unexpected extra object member"; + else if (STREQ_NULLABLE(klass, "UndefinedError")) + return "An undefined error has occurred"; + else if (STREQ_NULLABLE(klass, "Unsupported")) + return "this feature or command is not currently supported"; + else if (klass) + return klass; + else + return "unknown QEMU command error"; +} + +/* Ignoring OOM in this method, since we're already reporting + * a more important error + * + * XXX see qerror.h for different klasses & fill out useful params + */ +static const char * +qemuAgentStringifyError(virJSONValuePtr error) +{ + const char *klass =3D virJSONValueObjectGetString(error, "class"); + const char *detail =3D 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 =3D qemuAgentStringifyErrorClass(klass); + + return detail; +} + +static const char * +qemuAgentCommandName(virJSONValuePtr cmd) +{ + const char *name =3D virJSONValueObjectGetString(cmd, "execute"); + if (name) + return name; + else + return ""; +} + +static int +qemuAgentCheckError(virJSONValuePtr cmd, + virJSONValuePtr reply) +{ + if (virJSONValueObjectHasKey(reply, "error")) { + virJSONValuePtr error =3D virJSONValueObjectGet(reply, "error"); + char *cmdstr =3D virJSONValueToString(cmd, false); + char *replystr =3D virJSONValueToString(reply, false); + + /* Log the full JSON formatted command & error */ + VIR_DEBUG("unable to execute QEMU agent command %s: %s", + NULLSTR(cmdstr), NULLSTR(replystr)); + + /* Only send the user the command name + friendly error */ + if (!error) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to execute QEMU agent command '%s'"), + qemuAgentCommandName(cmd)); + else + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to execute QEMU agent command '%s': %= s"), + qemuAgentCommandName(cmd), + qemuAgentStringifyError(error)); + + VIR_FREE(cmdstr); + VIR_FREE(replystr); + return -1; + } else if (!virJSONValueObjectHasKey(reply, "return")) { + char *cmdstr =3D virJSONValueToString(cmd, false); + char *replystr =3D virJSONValueToString(reply, false); + + VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %= s: %s", + NULLSTR(cmdstr), NULLSTR(replystr)); + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to execute QEMU agent command '%s'"), + qemuAgentCommandName(cmd)); + VIR_FREE(cmdstr); + VIR_FREE(replystr); + return -1; + } + return 0; +} + +static int +qemuAgentCommand(qemuAgentPtr mon, + virJSONValuePtr cmd, + virJSONValuePtr *reply, + bool needReply, + int seconds) +{ + int ret =3D -1; + qemuAgentMessage msg; + char *cmdstr =3D NULL; + int await_event =3D mon->await_event; + + *reply =3D NULL; + + if (!mon->running) { + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("Guest agent disappeared while executing command"= )); + return -1; + } + + if (qemuAgentGuestSync(mon) < 0) + return -1; + + memset(&msg, 0, sizeof(msg)); + + if (!(cmdstr =3D virJSONValueToString(cmd, false))) + goto cleanup; + if (virAsprintf(&msg.txBuffer, "%s" LINE_ENDING, cmdstr) < 0) + goto cleanup; + msg.txLength =3D strlen(msg.txBuffer); + + VIR_DEBUG("Send command '%s' for write, seconds =3D %d", cmdstr, secon= ds); + + ret =3D qemuAgentSend(mon, &msg, seconds); + + VIR_DEBUG("Receive command reply ret=3D%d rxObject=3D%p", + ret, msg.rxObject); + + if (ret =3D=3D 0) { + /* If we haven't obtained any reply but we wait for an + * event, then don't report this as error */ + if (!msg.rxObject) { + if (await_event && !needReply) { + VIR_DEBUG("Woken up by event %d", await_event); + } else { + if (mon->running) + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Missing monitor reply object")); + else + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("Guest agent disappeared while execut= ing command")); + ret =3D -1; + } + } else { + *reply =3D msg.rxObject; + ret =3D qemuAgentCheckError(cmd, *reply); + } + } + + cleanup: + VIR_FREE(cmdstr); + VIR_FREE(msg.txBuffer); + + return ret; +} + +static virJSONValuePtr ATTRIBUTE_SENTINEL +qemuAgentMakeCommand(const char *cmdname, + ...) +{ + virJSONValuePtr obj; + virJSONValuePtr jargs =3D NULL; + va_list args; + + va_start(args, cmdname); + + if (!(obj =3D virJSONValueNewObject())) + goto error; + + if (virJSONValueObjectAppendString(obj, "execute", cmdname) < 0) + goto error; + + if (virJSONValueObjectCreateVArgs(&jargs, args) < 0) + goto error; + + if (jargs && + virJSONValueObjectAppend(obj, "arguments", jargs) < 0) + goto error; + + va_end(args); + + return obj; + + error: + virJSONValueFree(obj); + virJSONValueFree(jargs); + va_end(args); + return NULL; +} + +static virJSONValuePtr +qemuAgentMakeStringsArray(const char **strings, unsigned int len) +{ + size_t i; + virJSONValuePtr ret =3D virJSONValueNewArray(), str; + + if (!ret) + return NULL; + + for (i =3D 0; i < len; i++) { + str =3D virJSONValueNewString(strings[i]); + if (!str) + goto error; + + if (virJSONValueArrayAppend(ret, str) < 0) { + virJSONValueFree(str); + goto error; + } + } + return ret; + + error: + virJSONValueFree(ret); + return NULL; +} + +void qemuAgentNotifyEvent(qemuAgentPtr mon, + qemuAgentEvent event) +{ + virObjectLock(mon); + + VIR_DEBUG("mon=3D%p event=3D%d await_event=3D%d", mon, event, mon->awa= it_event); + if (mon->await_event =3D=3D event) { + mon->await_event =3D QEMU_AGENT_EVENT_NONE; + /* somebody waiting for this event, wake him up. */ + if (mon->msg && !mon->msg->finished) { + mon->msg->finished =3D 1; + virCondSignal(&mon->notify); + } + } + + virObjectUnlock(mon); +} + +VIR_ENUM_DECL(qemuAgentShutdownMode); + +VIR_ENUM_IMPL(qemuAgentShutdownMode, + QEMU_AGENT_SHUTDOWN_LAST, + "powerdown", "reboot", "halt"); + +int qemuAgentShutdown(qemuAgentPtr mon, + qemuAgentShutdownMode mode) +{ + int ret =3D -1; + virJSONValuePtr cmd; + virJSONValuePtr reply =3D NULL; + + cmd =3D qemuAgentMakeCommand("guest-shutdown", + "s:mode", qemuAgentShutdownModeTypeToString= (mode), + NULL); + if (!cmd) + return -1; + + if (mode =3D=3D QEMU_AGENT_SHUTDOWN_REBOOT) + mon->await_event =3D QEMU_AGENT_EVENT_RESET; + else + mon->await_event =3D QEMU_AGENT_EVENT_SHUTDOWN; + ret =3D qemuAgentCommand(mon, cmd, &reply, false, + VIR_DOMAIN_QEMU_AGENT_COMMAND_SHUTDOWN); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +/* + * qemuAgentFSFreeze: + * @mon: Agent + * @mountpoints: Array of mountpoint paths to be frozen, or NULL for all + * @nmountpoints: Number of mountpoints to be frozen, or 0 for all + * + * Issue guest-fsfreeze-freeze command to guest agent, + * which freezes file systems mounted on specified mountpoints + * (or all file systems when @mountpoints is NULL), and returns + * number of frozen file systems on success. + * + * Returns: number of file system frozen on success, + * -1 on error. + */ +int qemuAgentFSFreeze(qemuAgentPtr mon, const char **mountpoints, + unsigned int nmountpoints) +{ + int ret =3D -1; + virJSONValuePtr cmd, arg =3D NULL; + virJSONValuePtr reply =3D NULL; + + if (mountpoints && nmountpoints) { + arg =3D qemuAgentMakeStringsArray(mountpoints, nmountpoints); + if (!arg) + return -1; + + cmd =3D qemuAgentMakeCommand("guest-fsfreeze-freeze-list", + "a:mountpoints", arg, NULL); + } else { + cmd =3D qemuAgentMakeCommand("guest-fsfreeze-freeze", NULL); + } + + if (!cmd) + goto cleanup; + arg =3D NULL; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed return value")); + } + + cleanup: + virJSONValueFree(arg); + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +/* + * qemuAgentFSThaw: + * @mon: Agent + * + * Issue guest-fsfreeze-thaw command to guest agent, + * which unfreezes all mounted file systems and returns + * number of thawed file systems on success. + * + * Returns: number of file system thawed on success, + * -1 on error. + */ +int qemuAgentFSThaw(qemuAgentPtr mon) +{ + int ret =3D -1; + virJSONValuePtr cmd; + virJSONValuePtr reply =3D NULL; + + cmd =3D qemuAgentMakeCommand("guest-fsfreeze-thaw", NULL); + + if (!cmd) + return -1; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed return value")); + } + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +VIR_ENUM_DECL(qemuAgentSuspendMode); + +VIR_ENUM_IMPL(qemuAgentSuspendMode, + VIR_NODE_SUSPEND_TARGET_LAST, + "guest-suspend-ram", + "guest-suspend-disk", + "guest-suspend-hybrid"); + +int +qemuAgentSuspend(qemuAgentPtr mon, + unsigned int target) +{ + int ret =3D -1; + virJSONValuePtr cmd; + virJSONValuePtr reply =3D NULL; + + cmd =3D qemuAgentMakeCommand(qemuAgentSuspendModeTypeToString(target), + NULL); + if (!cmd) + return -1; + + mon->await_event =3D QEMU_AGENT_EVENT_SUSPEND; + ret =3D qemuAgentCommand(mon, cmd, &reply, false, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +int +qemuAgentArbitraryCommand(qemuAgentPtr mon, + const char *cmd_str, + char **result, + int timeout) +{ + int ret =3D -1; + virJSONValuePtr cmd =3D NULL; + virJSONValuePtr reply =3D NULL; + + *result =3D NULL; + if (timeout < VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN) { + virReportError(VIR_ERR_INVALID_ARG, + _("guest agent timeout '%d' is " + "less than the minimum '%d'"), + timeout, VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN); + goto cleanup; + } + + if (!(cmd =3D virJSONValueFromString(cmd_str))) + goto cleanup; + + if ((ret =3D qemuAgentCommand(mon, cmd, &reply, true, timeout)) < 0) + goto cleanup; + + if (!(*result =3D virJSONValueToString(reply, false))) + ret =3D -1; + + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +int +qemuAgentFSTrim(qemuAgentPtr mon, + unsigned long long minimum) +{ + int ret =3D -1; + virJSONValuePtr cmd; + virJSONValuePtr reply =3D NULL; + + cmd =3D qemuAgentMakeCommand("guest-fstrim", + "U:minimum", minimum, + NULL); + if (!cmd) + return ret; + + ret =3D qemuAgentCommand(mon, cmd, &reply, false, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK); + + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +int +qemuAgentGetVCPUs(qemuAgentPtr mon, + qemuAgentCPUInfoPtr *info) +{ + int ret =3D -1; + size_t i; + virJSONValuePtr cmd; + virJSONValuePtr reply =3D NULL; + virJSONValuePtr data =3D NULL; + ssize_t ndata; + + if (!(cmd =3D qemuAgentMakeCommand("guest-get-vcpus", NULL))) + return -1; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + if (!(data =3D virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest-get-vcpus reply was missing return data")); + goto cleanup; + } + + if (data->type !=3D VIR_JSON_TYPE_ARRAY) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest-get-vcpus return information was not an ar= ray")); + goto cleanup; + } + + ndata =3D virJSONValueArraySize(data); + + if (VIR_ALLOC_N(*info, ndata) < 0) + goto cleanup; + + for (i =3D 0; i < ndata; i++) { + virJSONValuePtr entry =3D virJSONValueArrayGet(data, i); + qemuAgentCPUInfoPtr in =3D *info + i; + + if (!entry) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("array element missing in guest-get-vcpus ret= urn " + "value")); + goto cleanup; + } + + if (virJSONValueObjectGetNumberUint(entry, "logical-id", &in->id) = < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'logical-id' missing in reply of guest-get-v= cpus")); + goto cleanup; + } + + if (virJSONValueObjectGetBoolean(entry, "online", &in->online) < 0= ) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'online' missing in reply of guest-get-vcpus= ")); + goto cleanup; + } + + if (virJSONValueObjectGetBoolean(entry, "can-offline", + &in->offlinable) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'can-offline' missing in reply of guest-get-= vcpus")); + goto cleanup; + } + } + + ret =3D ndata; + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + + +/* returns the value provided by the guest agent or -1 on internal error */ +static int +qemuAgentSetVCPUsCommand(qemuAgentPtr mon, + qemuAgentCPUInfoPtr info, + size_t ninfo, + int *nmodified) +{ + int ret =3D -1; + virJSONValuePtr cmd =3D NULL; + virJSONValuePtr reply =3D NULL; + virJSONValuePtr cpus =3D NULL; + virJSONValuePtr cpu =3D NULL; + size_t i; + + *nmodified =3D 0; + + /* create the key data array */ + if (!(cpus =3D virJSONValueNewArray())) + goto cleanup; + + for (i =3D 0; i < ninfo; i++) { + qemuAgentCPUInfoPtr in =3D &info[i]; + + /* don't set state for cpus that were not touched */ + if (!in->modified) + continue; + + (*nmodified)++; + + /* create single cpu object */ + if (!(cpu =3D virJSONValueNewObject())) + goto cleanup; + + if (virJSONValueObjectAppendNumberInt(cpu, "logical-id", in->id) <= 0) + goto cleanup; + + if (virJSONValueObjectAppendBoolean(cpu, "online", in->online) < 0) + goto cleanup; + + if (virJSONValueArrayAppend(cpus, cpu) < 0) + goto cleanup; + + cpu =3D NULL; + } + + if (*nmodified =3D=3D 0) { + ret =3D 0; + goto cleanup; + } + + if (!(cmd =3D qemuAgentMakeCommand("guest-set-vcpus", + "a:vcpus", cpus, + NULL))) + goto cleanup; + + cpus =3D NULL; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + if (qemuAgentCheckError(cmd, reply) < 0) + goto cleanup; + + /* All negative values are invalid. Return of 0 is bogus since we woul= dn't + * call the guest agent so that 0 cpus would be set successfully. Repo= rting + * more successfully set vcpus that we've asked for is invalid. */ + if (virJSONValueObjectGetNumberInt(reply, "return", &ret) < 0 || + ret <=3D 0 || ret > *nmodified) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest agent returned malformed or invalid return= value")); + ret =3D -1; + } + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + virJSONValueFree(cpu); + virJSONValueFree(cpus); + return ret; +} + + +/** + * Set the VCPU state using guest agent. + * + * Attempts to set the guest agent state for all cpus or until a proper er= ror is + * reported by the guest agent. This may require multiple calls. + * + * Returns -1 on error, 0 on success. + */ +int +qemuAgentSetVCPUs(qemuAgentPtr mon, + qemuAgentCPUInfoPtr info, + size_t ninfo) +{ + int rv; + int nmodified; + size_t i; + + do { + if ((rv =3D qemuAgentSetVCPUsCommand(mon, info, ninfo, &nmodified)= ) < 0) + return -1; + + /* all vcpus were set successfully */ + if (rv =3D=3D nmodified) + return 0; + + /* un-mark vcpus that were already set */ + for (i =3D 0; i < ninfo && rv > 0; i++) { + if (!info[i].modified) + continue; + + info[i].modified =3D false; + rv--; + } + } while (1); + + return 0; +} + + +/* modify the cpu info structure to set the correct amount of cpus */ +int +qemuAgentUpdateCPUInfo(unsigned int nvcpus, + qemuAgentCPUInfoPtr cpuinfo, + int ncpuinfo) +{ + size_t i; + int nonline =3D 0; + int nofflinable =3D 0; + ssize_t cpu0 =3D -1; + + /* count the active and offlinable cpus */ + for (i =3D 0; i < ncpuinfo; i++) { + if (cpuinfo[i].id =3D=3D 0) + cpu0 =3D i; + + if (cpuinfo[i].online) + nonline++; + + if (cpuinfo[i].offlinable && cpuinfo[i].online) + nofflinable++; + + /* This shouldn't happen, but we can't trust the guest agent */ + if (!cpuinfo[i].online && !cpuinfo[i].offlinable) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Invalid data provided by guest agent")); + return -1; + } + } + + /* CPU0 was made offlinable in linux a while ago, but certain parts (s= uspend + * to ram) of the kernel still don't cope well with that. Make sure th= at if + * all remaining vCPUs are offlinable, vCPU0 will not be selected to be + * offlined automatically */ + if (nofflinable =3D=3D nonline && cpu0 >=3D 0 && cpuinfo[cpu0].online)= { + cpuinfo[cpu0].offlinable =3D false; + nofflinable--; + } + + /* the guest agent reported less cpus than requested */ + if (nvcpus > ncpuinfo) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest agent reports less cpu than requested")); + return -1; + } + + /* not enough offlinable CPUs to support the request */ + if (nvcpus < nonline - nofflinable) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("Cannot offline enough CPUs")); + return -1; + } + + for (i =3D 0; i < ncpuinfo; i++) { + if (nvcpus < nonline) { + /* unplug */ + if (cpuinfo[i].offlinable && cpuinfo[i].online) { + cpuinfo[i].online =3D false; + cpuinfo[i].modified =3D true; + nonline--; + } + } else if (nvcpus > nonline) { + /* plug */ + if (!cpuinfo[i].online) { + cpuinfo[i].online =3D true; + cpuinfo[i].modified =3D true; + nonline++; + } + } else { + /* done */ + break; + } + } + + return 0; +} + + +int +qemuAgentGetTime(qemuAgentPtr mon, + long long *seconds, + unsigned int *nseconds) +{ + int ret =3D -1; + unsigned long long json_time; + virJSONValuePtr cmd; + virJSONValuePtr reply =3D NULL; + + cmd =3D qemuAgentMakeCommand("guest-get-time", + NULL); + if (!cmd) + return ret; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + if (virJSONValueObjectGetNumberUlong(reply, "return", &json_time) < 0)= { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed return value")); + goto cleanup; + } + + /* guest agent returns time in nanoseconds, + * we need it in seconds here */ + *seconds =3D json_time / 1000000000LL; + *nseconds =3D json_time % 1000000000LL; + ret =3D 0; + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + + +/** + * qemuAgentSetTime: + * @setTime: time to set + * @sync: let guest agent to read domain's RTC (@setTime is ignored) + */ +int +qemuAgentSetTime(qemuAgentPtr mon, + long long seconds, + unsigned int nseconds, + bool rtcSync) +{ + int ret =3D -1; + virJSONValuePtr cmd; + virJSONValuePtr reply =3D NULL; + + if (rtcSync) { + cmd =3D qemuAgentMakeCommand("guest-set-time", NULL); + } else { + /* guest agent expect time with nanosecond granularity. + * Impressing. */ + long long json_time; + + /* Check if we overflow. For some reason qemu doesn't handle unsig= ned + * long long on the monitor well as it silently truncates numbers = to + * signed long long. Therefore we must check overflow against LLON= G_MAX + * not ULLONG_MAX. */ + if (seconds > LLONG_MAX / 1000000000LL) { + virReportError(VIR_ERR_INVALID_ARG, + _("Time '%lld' is too big for guest agent"), + seconds); + return ret; + } + + json_time =3D seconds * 1000000000LL; + json_time +=3D nseconds; + cmd =3D qemuAgentMakeCommand("guest-set-time", + "I:time", json_time, + NULL); + } + + if (!cmd) + return ret; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + ret =3D 0; + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + + +int +qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info, + virDomainDefPtr vmdef) +{ + size_t i, j, k; + int ret =3D -1; + ssize_t ndata =3D 0, ndisk; + char **alias; + virJSONValuePtr cmd; + virJSONValuePtr reply =3D NULL; + virJSONValuePtr data; + virDomainFSInfoPtr *info_ret =3D NULL; + virPCIDeviceAddress pci_address; + + cmd =3D qemuAgentMakeCommand("guest-get-fsinfo", NULL); + if (!cmd) + return ret; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + if (!(data =3D virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest-get-fsinfo reply was missing return data")= ); + goto cleanup; + } + + if (data->type !=3D VIR_JSON_TYPE_ARRAY) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest-get-fsinfo return information was not " + "an array")); + goto cleanup; + } + + ndata =3D virJSONValueArraySize(data); + if (!ndata) { + ret =3D 0; + *info =3D NULL; + goto cleanup; + } + if (VIR_ALLOC_N(info_ret, ndata) < 0) + goto cleanup; + + for (i =3D 0; i < ndata; i++) { + /* Reverse the order to arrange in mount order */ + virJSONValuePtr entry =3D virJSONValueArrayGet(data, ndata - 1 - i= ); + + if (!entry) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("array element '%zd' of '%zd' missing in " + "guest-get-fsinfo return data"), + i, ndata); + goto cleanup; + } + + if (VIR_ALLOC(info_ret[i]) < 0) + goto cleanup; + + if (VIR_STRDUP(info_ret[i]->mountpoint, + virJSONValueObjectGetString(entry, "mountpoint")) <= 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'mountpoint' missing in reply of " + "guest-get-fsinfo")); + goto cleanup; + } + + if (VIR_STRDUP(info_ret[i]->name, + virJSONValueObjectGetString(entry, "name")) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'name' missing in reply of guest-get-fsinfo"= )); + goto cleanup; + } + + if (VIR_STRDUP(info_ret[i]->fstype, + virJSONValueObjectGetString(entry, "type")) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'type' missing in reply of guest-get-fsinfo"= )); + goto cleanup; + } + + if (!(entry =3D virJSONValueObjectGet(entry, "disk"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'disk' missing in reply of guest-get-fsinfo"= )); + goto cleanup; + } + + if (entry->type !=3D VIR_JSON_TYPE_ARRAY) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest-get-fsinfo 'disk' data was not an arra= y")); + goto cleanup; + } + + ndisk =3D virJSONValueArraySize(entry); + if (!ndisk) + continue; + if (VIR_ALLOC_N(info_ret[i]->devAlias, ndisk) < 0) + goto cleanup; + + alias =3D info_ret[i]->devAlias; + info_ret[i]->ndevAlias =3D 0; + for (j =3D 0; j < ndisk; j++) { + virJSONValuePtr disk =3D virJSONValueArrayGet(entry, j); + virJSONValuePtr pci; + int diskaddr[3], pciaddr[4]; + const char *diskaddr_comp[] =3D {"bus", "target", "unit"}; + const char *pciaddr_comp[] =3D {"domain", "bus", "slot", "func= tion"}; + virDomainDiskDefPtr diskDef; + + if (!disk) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("array element '%zd' of '%zd' missing in " + "guest-get-fsinfo 'disk' data"), + j, ndisk); + goto cleanup; + } + + if (!(pci =3D virJSONValueObjectGet(disk, "pci-controller"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'pci-controller' missing in guest-get-fs= info " + "'disk' data")); + goto cleanup; + } + + for (k =3D 0; k < 3; k++) { + if (virJSONValueObjectGetNumberInt( + disk, diskaddr_comp[k], &diskaddr[k]) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("'%s' missing in guest-get-fsinfo " + "'disk' data"), diskaddr_comp[k]); + goto cleanup; + } + } + for (k =3D 0; k < 4; k++) { + if (virJSONValueObjectGetNumberInt( + pci, pciaddr_comp[k], &pciaddr[k]) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("'%s' missing in guest-get-fsinfo " + "'pci-address' data"), pciaddr_comp[k= ]); + goto cleanup; + } + } + + pci_address.domain =3D pciaddr[0]; + pci_address.bus =3D pciaddr[1]; + pci_address.slot =3D pciaddr[2]; + pci_address.function =3D pciaddr[3]; + if (!(diskDef =3D virDomainDiskByAddress( + vmdef, &pci_address, + diskaddr[0], diskaddr[1], diskaddr[2]))) + continue; + + if (VIR_STRDUP(*alias, diskDef->dst) < 0) + goto cleanup; + + if (*alias) { + alias++; + info_ret[i]->ndevAlias++; + } + } + } + + *info =3D info_ret; + info_ret =3D NULL; + ret =3D ndata; + + cleanup: + if (info_ret) { + for (i =3D 0; i < ndata; i++) + virDomainFSInfoFree(info_ret[i]); + VIR_FREE(info_ret); + } + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + +/* + * qemuAgentGetInterfaces: + * @mon: Agent monitor + * @ifaces: pointer to an array of pointers pointing to interface objects + * + * Issue guest-network-get-interfaces to guest agent, which returns a + * list of interfaces of a running domain along with their IP and MAC + * addresses. + * + * Returns: number of interfaces on success, -1 on error. + */ +int +qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces) +{ + int ret =3D -1; + size_t i, j; + ssize_t size =3D -1; + virJSONValuePtr cmd =3D NULL; + virJSONValuePtr reply =3D NULL; + virJSONValuePtr ret_array =3D NULL; + size_t ifaces_count =3D 0; + size_t addrs_count =3D 0; + virDomainInterfacePtr *ifaces_ret =3D NULL; + virHashTablePtr ifaces_store =3D NULL; + char **ifname =3D NULL; + + /* Hash table to handle the interface alias */ + if (!(ifaces_store =3D virHashCreate(ifaces_count, NULL))) { + virHashFree(ifaces_store); + return -1; + } + + if (!(cmd =3D qemuAgentMakeCommand("guest-network-get-interfaces", NUL= L))) + goto cleanup; + + if (qemuAgentCommand(mon, cmd, &reply, false, VIR_DOMAIN_QEMU_AGENT_CO= MMAND_BLOCK) < 0 || + qemuAgentCheckError(cmd, reply) < 0) { + goto cleanup; + } + + if (!(ret_array =3D virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'return' field")); + goto cleanup; + } + + if ((size =3D virJSONValueArraySize(ret_array)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of interfaces"= )); + goto cleanup; + } + + for (i =3D 0; i < size; i++) { + virJSONValuePtr tmp_iface =3D virJSONValueArrayGet(ret_array, i); + virJSONValuePtr ip_addr_arr =3D NULL; + const char *hwaddr, *ifname_s, *name =3D NULL; + ssize_t ip_addr_arr_size; + virDomainInterfacePtr iface =3D NULL; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!tmp_iface) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent reply missing interface entry in = array")); + goto error; + } + + /* interface name is required to be presented */ + name =3D virJSONValueObjectGetString(tmp_iface, "name"); + if (!name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'name' field")); + goto error; + } + + /* Handle interface alias (:) */ + ifname =3D virStringSplit(name, ":", 2); + ifname_s =3D ifname[0]; + + iface =3D virHashLookup(ifaces_store, ifname_s); + + /* If the hash table doesn't contain this iface, add it */ + if (!iface) { + if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) + goto error; + + if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + if (virHashAddEntry(ifaces_store, ifname_s, + ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + iface =3D ifaces_ret[ifaces_count - 1]; + iface->naddrs =3D 0; + + if (VIR_STRDUP(iface->name, ifname_s) < 0) + goto error; + + hwaddr =3D virJSONValueObjectGetString(tmp_iface, "hardware-ad= dress"); + if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0) + goto error; + } + + /* Has to be freed for each interface. */ + virStringListFree(ifname); + + /* as well as IP address which - moreover - + * can be presented multiple times */ + ip_addr_arr =3D virJSONValueObjectGet(tmp_iface, "ip-addresses"); + if (!ip_addr_arr) + continue; + + if ((ip_addr_arr_size =3D virJSONValueArraySize(ip_addr_arr)) < 0) + /* Mmm, empty 'ip-address'? */ + goto error; + + /* If current iface already exists, continue with the count */ + addrs_count =3D iface->naddrs; + + for (j =3D 0; j < ip_addr_arr_size; j++) { + const char *type, *addr; + virJSONValuePtr ip_addr_obj =3D virJSONValueArrayGet(ip_addr_a= rr, j); + virDomainIPAddressPtr ip_addr; + + if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0) + goto error; + + ip_addr =3D &iface->addrs[addrs_count - 1]; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!ip_addr_obj) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent reply missing IP addr in arra= y")); + goto error; + } + + type =3D virJSONValueObjectGetString(ip_addr_obj, "ip-address-= type"); + if (!type) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address-ty= pe'" + " field for interface '%s'"), name); + goto error; + } else if (STREQ(type, "ipv4")) { + ip_addr->type =3D VIR_IP_ADDR_TYPE_IPV4; + } else if (STREQ(type, "ipv6")) { + ip_addr->type =3D VIR_IP_ADDR_TYPE_IPV6; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown ip address type '%s'"), + type); + goto error; + } + + addr =3D virJSONValueObjectGetString(ip_addr_obj, "ip-address"= ); + if (!addr) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address'" + " field for interface '%s'"), name); + goto error; + } + if (VIR_STRDUP(ip_addr->addr, addr) < 0) + goto error; + + if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix", + &ip_addr->prefix) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed 'prefix' field")); + goto error; + } + } + + iface->naddrs =3D addrs_count; + } + + *ifaces =3D ifaces_ret; + ifaces_ret =3D NULL; + ret =3D ifaces_count; + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + virHashFree(ifaces_store); + return ret; + + error: + if (ifaces_ret) { + for (i =3D 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces_ret[i]); + } + VIR_FREE(ifaces_ret); + virStringListFree(ifname); + + goto cleanup; +} + + +int +qemuAgentSetUserPassword(qemuAgentPtr mon, + const char *user, + const char *password, + bool crypted) +{ + int ret =3D -1; + virJSONValuePtr cmd =3D NULL; + virJSONValuePtr reply =3D NULL; + char *password64 =3D NULL; + + if (!(password64 =3D virStringEncodeBase64((unsigned char *) password, + strlen(password)))) + goto cleanup; + + if (!(cmd =3D qemuAgentMakeCommand("guest-set-user-password", + "b:crypted", crypted, + "s:username", user, + "s:password", password64, + NULL))) + goto cleanup; + + if (qemuAgentCommand(mon, cmd, &reply, true, + VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0) + goto cleanup; + + ret =3D 0; + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + VIR_FREE(password64); + return ret; +} diff --git a/src/util/virqemuagent.h b/src/util/virqemuagent.h new file mode 100644 index 0000000..2e81020 --- /dev/null +++ b/src/util/virqemuagent.h @@ -0,0 +1,123 @@ +/* + * virqemuagent.h: interaction with QEMU guest agent + * + * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Author: Daniel P. Berrange + */ + + +#ifndef __QEMU_AGENT_H__ +# define __QEMU_AGENT_H__ + +# include "internal.h" +# include "domain_conf.h" + +typedef struct _qemuAgent qemuAgent; +typedef qemuAgent *qemuAgentPtr; + +typedef struct _qemuAgentCallbacks qemuAgentCallbacks; +typedef qemuAgentCallbacks *qemuAgentCallbacksPtr; +struct _qemuAgentCallbacks { + void (*destroy)(qemuAgentPtr mon, + virDomainObjPtr vm); + void (*eofNotify)(qemuAgentPtr mon, + virDomainObjPtr vm); + void (*errorNotify)(qemuAgentPtr mon, + virDomainObjPtr vm); +}; + + +qemuAgentPtr qemuAgentOpen(virDomainObjPtr vm, + const virDomainChrSourceDef *config, + qemuAgentCallbacksPtr cb); + +void qemuAgentClose(qemuAgentPtr mon); + +void qemuAgentNotifyClose(qemuAgentPtr mon); + +typedef enum { + QEMU_AGENT_EVENT_NONE =3D 0, + QEMU_AGENT_EVENT_SHUTDOWN, + QEMU_AGENT_EVENT_SUSPEND, + QEMU_AGENT_EVENT_RESET, +} qemuAgentEvent; + +void qemuAgentNotifyEvent(qemuAgentPtr mon, + qemuAgentEvent event); + +typedef enum { + QEMU_AGENT_SHUTDOWN_POWERDOWN, + QEMU_AGENT_SHUTDOWN_REBOOT, + QEMU_AGENT_SHUTDOWN_HALT, + + QEMU_AGENT_SHUTDOWN_LAST, +} qemuAgentShutdownMode; + +int qemuAgentShutdown(qemuAgentPtr mon, + qemuAgentShutdownMode mode); + +int qemuAgentFSFreeze(qemuAgentPtr mon, + const char **mountpoints, unsigned int nmountpoints); +int qemuAgentFSThaw(qemuAgentPtr mon); +int qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info, + virDomainDefPtr vmdef); + +int qemuAgentSuspend(qemuAgentPtr mon, + unsigned int target); + +int qemuAgentArbitraryCommand(qemuAgentPtr mon, + const char *cmd, + char **result, + int timeout); +int qemuAgentFSTrim(qemuAgentPtr mon, + unsigned long long minimum); + + +typedef struct _qemuAgentCPUInfo qemuAgentCPUInfo; +typedef qemuAgentCPUInfo *qemuAgentCPUInfoPtr; +struct _qemuAgentCPUInfo { + unsigned int id; /* logical cpu ID */ + bool online; /* true if the CPU is activated */ + bool offlinable; /* true if the CPU can be offlined */ + + bool modified; /* set to true if the vcpu state needs to be changed */ +}; + +int qemuAgentGetVCPUs(qemuAgentPtr mon, qemuAgentCPUInfoPtr *info); +int qemuAgentSetVCPUs(qemuAgentPtr mon, qemuAgentCPUInfoPtr cpus, size_t n= cpus); +int qemuAgentUpdateCPUInfo(unsigned int nvcpus, + qemuAgentCPUInfoPtr cpuinfo, + int ncpuinfo); + +int qemuAgentGetTime(qemuAgentPtr mon, + long long *seconds, + unsigned int *nseconds); +int qemuAgentSetTime(qemuAgentPtr mon, + long long seconds, + unsigned int nseconds, + bool sync); + +int qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces); + +int qemuAgentSetUserPassword(qemuAgentPtr mon, + const char *user, + const char *password, + bool crypted); +#endif /* __QEMU_AGENT_H__ */ diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c index 3be745e..a011838 100644 --- a/tests/qemuagenttest.c +++ b/tests/qemuagenttest.c @@ -23,7 +23,7 @@ #include "testutilsqemu.h" #include "qemumonitortestutils.h" #include "qemu/qemu_conf.h" -#include "qemu/qemu_agent.h" +#include "virqemuagent.h" #include "virthread.h" #include "virerror.h" #include "virstring.h" diff --git a/tests/qemumonitortestutils.c b/tests/qemumonitortestutils.c index cfd0a38..f509011 100644 --- a/tests/qemumonitortestutils.c +++ b/tests/qemumonitortestutils.c @@ -30,7 +30,7 @@ #include "virthread.h" #include "qemu/qemu_processpriv.h" #include "qemu/qemu_monitor.h" -#include "qemu/qemu_agent.h" +#include "virqemuagent.h" #include "rpc/virnetsocket.h" #include "viralloc.h" #include "virlog.h" diff --git a/tests/qemumonitortestutils.h b/tests/qemumonitortestutils.h index 8b19b37..b9e219d 100644 --- a/tests/qemumonitortestutils.h +++ b/tests/qemumonitortestutils.h @@ -23,7 +23,7 @@ # include "domain_conf.h" # include "qemu/qemu_conf.h" # include "qemu/qemu_monitor.h" -# include "qemu/qemu_agent.h" +# include "virqemuagent.h" =20 typedef struct _qemuMonitorTest qemuMonitorTest; typedef qemuMonitorTest *qemuMonitorTestPtr; --=20 2.1.4 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list From nobody Mon May 6 08:43:58 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.39 as permitted sender) client-ip=209.132.183.39; envelope-from=libvir-list-bounces@redhat.com; helo=mx6-phx2.redhat.com; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.39 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; Return-Path: Received: from mx6-phx2.redhat.com (mx6-phx2.redhat.com [209.132.183.39]) by mx.zohomail.com with SMTPS id 1486572349673720.4676672467548; Wed, 8 Feb 2017 08:45:49 -0800 (PST) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by mx6-phx2.redhat.com (8.14.4/8.14.4) with ESMTP id v18GgUU0042121; Wed, 8 Feb 2017 11:42:30 -0500 Received: from int-mx14.intmail.prod.int.phx2.redhat.com (int-mx14.intmail.prod.int.phx2.redhat.com [10.5.11.27]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id v18GgC6I019749 for ; Wed, 8 Feb 2017 11:42:12 -0500 Received: from mx1.redhat.com (ext-mx10.extmail.prod.ext.phx2.redhat.com [10.5.110.39]) by int-mx14.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id v18GgBRb030209 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO) for ; Wed, 8 Feb 2017 11:42:11 -0500 Received: from aserp1040.oracle.com (aserp1040.oracle.com [141.146.126.69]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id D2F2051442 for ; Wed, 8 Feb 2017 16:42:10 +0000 (UTC) Received: from userv0021.oracle.com (userv0021.oracle.com [156.151.31.71]) by aserp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with ESMTP id v18Gg83b011703 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Wed, 8 Feb 2017 16:42:09 GMT Received: from userv0122.oracle.com (userv0122.oracle.com [156.151.31.75]) by userv0021.oracle.com (8.14.4/8.14.4) with ESMTP id v18Gg8ne010310 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Wed, 8 Feb 2017 16:42:08 GMT Received: from abhmp0018.oracle.com (abhmp0018.oracle.com [141.146.116.24]) by userv0122.oracle.com (8.14.4/8.14.4) with ESMTP id v18Gg81f005043; Wed, 8 Feb 2017 16:42:08 GMT Received: from paddy.lan (/89.114.92.174) by default (Oracle Beehive Gateway v4.0) with ESMTP ; Wed, 08 Feb 2017 08:42:07 -0800 From: Joao Martins To: Libvirt Development List Date: Wed, 8 Feb 2017 16:44:38 +0000 Message-Id: <1486572280-2212-3-git-send-email-joao.m.martins@oracle.com> In-Reply-To: <1486572280-2212-1-git-send-email-joao.m.martins@oracle.com> References: <1486572280-2212-1-git-send-email-joao.m.martins@oracle.com> X-Source-IP: userv0021.oracle.com [156.151.31.71] X-Greylist: Sender passed SPF test, Sender IP whitelisted by DNSRBL, ACL 200 matched, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.39]); Wed, 08 Feb 2017 16:42:11 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.39]); Wed, 08 Feb 2017 16:42:11 +0000 (UTC) for IP:'141.146.126.69' DOMAIN:'aserp1040.oracle.com' HELO:'aserp1040.oracle.com' FROM:'joao.m.martins@oracle.com' RCPT:'' X-RedHat-Spam-Score: -101.621 (BAYES_50, DCC_REPUT_13_19, RCVD_IN_DNSWL_MED, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RP_MATCHES_RCVD, SPF_PASS, UNPARSEABLE_RELAY, USER_IN_WHITELIST) 141.146.126.69 aserp1040.oracle.com 141.146.126.69 aserp1040.oracle.com X-Scanned-By: MIMEDefang 2.68 on 10.5.11.27 X-Scanned-By: MIMEDefang 2.78 on 10.5.110.39 X-loop: libvir-list@redhat.com Cc: Joao Martins Subject: [libvirt] [PATCH RFC 2/4] qemu_agent: ignore requests echoed back by guest X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Type: text/plain; charset="utf-8" Signed-off-by: Joao Martins --- src/util/virqemuagent.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/virqemuagent.c b/src/util/virqemuagent.c index caabae0..ffb3489 100644 --- a/src/util/virqemuagent.c +++ b/src/util/virqemuagent.c @@ -333,7 +333,8 @@ qemuAgentIOProcessLine(qemuAgentPtr mon, goto cleanup; } =20 - if (virJSONValueObjectHasKey(obj, "QMP") =3D=3D 1) { + if (virJSONValueObjectHasKey(obj, "QMP") =3D=3D 1 || + virJSONValueObjectHasKey(obj, "execute") =3D=3D 1) { ret =3D 0; } else if (virJSONValueObjectHasKey(obj, "event") =3D=3D 1) { ret =3D qemuAgentIOProcessEvent(mon, obj); --=20 2.1.4 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list From nobody Mon May 6 08:43:58 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.37 as permitted sender) client-ip=209.132.183.37; envelope-from=libvir-list-bounces@redhat.com; helo=mx5-phx2.redhat.com; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.37 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; Return-Path: Received: from mx5-phx2.redhat.com (mx5-phx2.redhat.com [209.132.183.37]) by mx.zohomail.com with SMTPS id 1486572358770513.5696689367079; Wed, 8 Feb 2017 08:45:58 -0800 (PST) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by mx5-phx2.redhat.com (8.14.4/8.14.4) with ESMTP id v18GgWtf027441; Wed, 8 Feb 2017 11:42:32 -0500 Received: from int-mx14.intmail.prod.int.phx2.redhat.com (int-mx14.intmail.prod.int.phx2.redhat.com [10.5.11.27]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id v18GgEEG019761 for ; Wed, 8 Feb 2017 11:42:14 -0500 Received: from mx1.redhat.com (ext-mx06.extmail.prod.ext.phx2.redhat.com [10.5.110.30]) by int-mx14.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id v18GgE5G030225 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO) for ; Wed, 8 Feb 2017 11:42:14 -0500 Received: from aserp1040.oracle.com (aserp1040.oracle.com [141.146.126.69]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 52DB33B709 for ; Wed, 8 Feb 2017 16:42:13 +0000 (UTC) Received: from aserv0021.oracle.com (aserv0021.oracle.com [141.146.126.233]) by aserp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with ESMTP id v18GgBRs011754 (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Wed, 8 Feb 2017 16:42:11 GMT Received: from userv0122.oracle.com (userv0122.oracle.com [156.151.31.75]) by aserv0021.oracle.com (8.13.8/8.14.4) with ESMTP id v18GgAxp018746 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK); Wed, 8 Feb 2017 16:42:11 GMT Received: from abhmp0018.oracle.com (abhmp0018.oracle.com [141.146.116.24]) by userv0122.oracle.com (8.14.4/8.14.4) with ESMTP id v18GgAFG005053; Wed, 8 Feb 2017 16:42:10 GMT Received: from paddy.lan (/89.114.92.174) by default (Oracle Beehive Gateway v4.0) with ESMTP ; Wed, 08 Feb 2017 08:42:10 -0800 From: Joao Martins To: Libvirt Development List Date: Wed, 8 Feb 2017 16:44:39 +0000 Message-Id: <1486572280-2212-4-git-send-email-joao.m.martins@oracle.com> In-Reply-To: <1486572280-2212-1-git-send-email-joao.m.martins@oracle.com> References: <1486572280-2212-1-git-send-email-joao.m.martins@oracle.com> X-Source-IP: aserv0021.oracle.com [141.146.126.233] X-Greylist: Sender passed SPF test, Sender IP whitelisted by DNSRBL, ACL 200 matched, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.30]); Wed, 08 Feb 2017 16:42:13 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.30]); Wed, 08 Feb 2017 16:42:13 +0000 (UTC) for IP:'141.146.126.69' DOMAIN:'aserp1040.oracle.com' HELO:'aserp1040.oracle.com' FROM:'joao.m.martins@oracle.com' RCPT:'' X-RedHat-Spam-Score: -101.621 (BAYES_50, DCC_REPUT_13_19, RCVD_IN_DNSWL_MED, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RP_MATCHES_RCVD, SPF_PASS, UNPARSEABLE_RELAY, USER_IN_WHITELIST) 141.146.126.69 aserp1040.oracle.com 141.146.126.69 aserp1040.oracle.com X-Scanned-By: MIMEDefang 2.68 on 10.5.11.27 X-Scanned-By: MIMEDefang 2.78 on 10.5.110.30 X-loop: libvir-list@redhat.com Cc: Joao Martins Subject: [libvirt] [PATCH RFC 3/4] libxl: implement qemu-agent-command X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Type: text/plain; charset="utf-8" Signed-off-by: Joao Martins --- src/libxl/libxl_domain.c | 239 +++++++++++++++++++++++++++++++++++++++++++= +++- src/libxl/libxl_domain.h | 16 ++++ src/libxl/libxl_driver.c | 51 ++++++++++ 3 files changed, 305 insertions(+), 1 deletion(-) diff --git a/src/libxl/libxl_domain.c b/src/libxl/libxl_domain.c index ed73cd2..6bdd0ec 100644 --- a/src/libxl/libxl_domain.c +++ b/src/libxl/libxl_domain.c @@ -782,6 +782,12 @@ libxlDomainCleanup(libxlDriverPrivatePtr driver, } } =20 + if (priv->agent) { + qemuAgentClose(priv->agent); + priv->agent =3D NULL; + priv->agentError =3D false; + } + if ((vm->def->nnets)) { size_t i; =20 @@ -940,6 +946,228 @@ libxlDomainFreeMem(libxl_ctx *ctx, libxl_domain_confi= g *d_config) return -1; } =20 +/* + * This is a callback registered with a qemuAgentPtr instance, + * and to be invoked when the agent console hits an end of file + * condition, or error, thus indicating VM shutdown should be + * performed + */ +static void +libxlHandleAgentEOF(qemuAgentPtr agent, + virDomainObjPtr vm) +{ + libxlDomainObjPrivatePtr priv; + + VIR_DEBUG("Received EOF from agent on %p '%s'", vm, vm->def->name); + + virObjectLock(vm); + + priv =3D vm->privateData; + + if (!priv->agent) { + VIR_DEBUG("Agent freed already"); + goto unlock; + } + + qemuAgentClose(agent); + priv->agent =3D NULL; + + 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 +libxlHandleAgentError(qemuAgentPtr agent ATTRIBUTE_UNUSED, + virDomainObjPtr vm) +{ + libxlDomainObjPrivatePtr priv; + + VIR_DEBUG("Received error from agent on %p '%s'", vm, vm->def->name); + + virObjectLock(vm); + + priv =3D vm->privateData; + + priv->agentError =3D true; + + virObjectUnlock(vm); +} + +static void libxlHandleAgentDestroy(qemuAgentPtr agent, + virDomainObjPtr vm) +{ + VIR_DEBUG("Received destroy agent=3D%p vm=3D%p", agent, vm); + + virObjectUnref(vm); +} + +static qemuAgentCallbacks agentCallbacks =3D { + .destroy =3D libxlHandleAgentDestroy, + .eofNotify =3D libxlHandleAgentEOF, + .errorNotify =3D libxlHandleAgentError, +}; + +static virDomainChrDefPtr +libxlFindAgentConfig(virDomainDefPtr def) +{ + size_t i; + + for (i =3D 0; i < def->nchannels; i++) { + virDomainChrDefPtr channel =3D def->channels[i]; + + if (channel->targetType !=3D VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XE= N) + continue; + + if (STREQ_NULLABLE(channel->target.name, "org.qemu.guest_agent.0")) + return channel; + } + + return NULL; +} + +bool +libxlDomainAgentAvailable(virDomainObjPtr vm, bool reportError) +{ + libxlDomainObjPrivatePtr priv =3D vm->privateData; + + if (virDomainObjGetState(vm, NULL) !=3D VIR_DOMAIN_RUNNING) { + if (reportError) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + } + return false; + } + if (priv->agentError) { + if (reportError) { + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("QEMU guest agent is not " + "available due to an error")); + } + return false; + } + if (!priv->agent) { + if (libxlFindAgentConfig(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 +libxlConnectAgent(virDomainObjPtr vm) +{ + virDomainChrDefPtr config =3D libxlFindAgentConfig(vm->def); + libxlDomainObjPrivatePtr priv =3D vm->privateData; + qemuAgentPtr agent =3D NULL; + int ret =3D -1; + + if (!config) + return 0; + + if (priv->agent) + return 0; + + /* Hold an extra reference because we can't allow 'vm' to be + * deleted while the agent is active */ + virObjectRef(vm); + + ignore_value(virTimeMillisNow(&priv->agentStart)); + virObjectUnlock(vm); + + agent =3D qemuAgentOpen(vm, config->source, &agentCallbacks); + + virObjectLock(vm); + priv->agentStart =3D 0; + + if (agent =3D=3D NULL) + virObjectUnref(vm); + + if (!virDomainObjIsActive(vm)) { + qemuAgentClose(agent); + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest crashed while connecting to the guest agen= t")); + ret =3D -2; + goto cleanup; + } + + priv->agent =3D agent; + + if (priv->agent =3D=3D NULL) { + VIR_INFO("Failed to connect agent for %s", vm->def->name); + goto cleanup; + } + + ret =3D 0; + + cleanup: + return ret; +} + +/* + * obj must be locked before calling + * + * To be called immediately before any QEMU agent API call. + * Must have already called libxlDomainObjBeginJob() and checked + * that the VM is still active. + * + * To be followed with libxlDomainObjExitAgent() once complete + */ +void +libxlDomainObjEnterAgent(virDomainObjPtr obj) +{ + libxlDomainObjPrivatePtr priv =3D obj->privateData; + + VIR_DEBUG("Entering agent (agent=3D%p vm=3D%p name=3D%s)", + priv->agent, obj, obj->def->name); + virObjectLock(priv->agent); + virObjectRef(priv->agent); + ignore_value(virTimeMillisNow(&priv->agentStart)); + virObjectUnlock(obj); +} + + +/* obj must NOT be locked before calling + * + * Should be paired with an earlier qemuDomainObjEnterAgent() call + */ +void +libxlDomainObjExitAgent(virDomainObjPtr obj) +{ + libxlDomainObjPrivatePtr priv =3D obj->privateData; + bool hasRefs; + + hasRefs =3D virObjectUnref(priv->agent); + + if (hasRefs) + virObjectUnlock(priv->agent); + + virObjectLock(obj); + VIR_DEBUG("Exited agent (agent=3D%p vm=3D%p name=3D%s)", + priv->agent, obj, obj->def->name); + + priv->agentStart =3D 0; + if (!hasRefs) + priv->agent =3D NULL; +} + static int libxlNetworkPrepareDevices(virDomainDefPtr def) { @@ -1312,8 +1540,17 @@ libxlDomainStart(libxlDriverPrivatePtr driver, libxlDomainCreateIfaceNames(vm->def, &d_config); =20 #ifdef LIBXL_HAVE_DEVICE_CHANNEL - if (vm->def->nchannels > 0) + if (vm->def->nchannels > 0) { libxlDomainCreateChannelPTY(vm->def, cfg->ctx); + + /* Failure to connect to agent shouldn't be fatal */ + if (libxlConnectAgent(vm) < 0) { + VIR_WARN("Cannot connect to QEMU guest agent for %s", + vm->def->name); + virResetLastError(); + priv->agentError =3D true; + } + } #endif =20 if ((dom_xml =3D virDomainDefFormat(vm->def, cfg->caps, 0)) =3D=3D NUL= L) diff --git a/src/libxl/libxl_domain.h b/src/libxl/libxl_domain.h index 3a3890b..59f9f8d 100644 --- a/src/libxl/libxl_domain.h +++ b/src/libxl/libxl_domain.h @@ -29,6 +29,7 @@ # include "domain_conf.h" # include "libxl_conf.h" # include "virchrdev.h" +# include "virqemuagent.h" =20 # define JOB_MASK(job) (1 << (job - 1)) # define DEFAULT_JOB_MASK \ @@ -62,6 +63,11 @@ typedef libxlDomainObjPrivate *libxlDomainObjPrivatePtr; struct _libxlDomainObjPrivate { virObjectLockable parent; =20 + /* agent */ + qemuAgentPtr agent; + bool agentError; + unsigned long long agentStart; + /* console */ virChrdevsPtr devs; libxl_evgen_domain_death *deathW; @@ -91,6 +97,16 @@ void libxlDomainObjEndJob(libxlDriverPrivatePtr driver, virDomainObjPtr obj); =20 +bool +libxlDomainAgentAvailable(virDomainObjPtr vm, + bool reportError); + +void +libxlDomainObjEnterAgent(virDomainObjPtr obj); + +void +libxlDomainObjExitAgent(virDomainObjPtr obj); + int libxlDomainJobUpdateTime(struct libxlDomainJobObj *job) ATTRIBUTE_RETURN_CHECK; diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index 3a69720..cf5e702 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -57,6 +57,7 @@ #include "virstring.h" #include "virsysinfo.h" #include "viraccessapicheck.h" +#include "viraccessapicheckqemu.h" #include "viratomic.h" #include "virhostdev.h" #include "network/bridge_driver.h" @@ -6415,6 +6416,55 @@ libxlConnectBaselineCPU(virConnectPtr conn, return cpu; } =20 +static char * +libxlDomainQemuAgentCommand(virDomainPtr domain, + const char *cmd, + int timeout, + unsigned int flags) +{ + libxlDriverPrivatePtr driver =3D domain->conn->privateData; + virDomainObjPtr vm; + int ret =3D -1; + char *result =3D NULL; + libxlDomainObjPrivatePtr priv; + + virCheckFlags(0, NULL); + + if (!(vm =3D libxlDomObjFromDomain(domain))) + goto cleanup; + + priv =3D vm->privateData; + + if (virDomainQemuAgentCommandEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + if (libxlDomainObjBeginJob(driver, vm, LIBXL_JOB_MODIFY) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto endjob; + } + + if (!libxlDomainAgentAvailable(vm, true)) + goto endjob; + + libxlDomainObjEnterAgent(vm); + ret =3D qemuAgentArbitraryCommand(priv->agent, cmd, &result, timeout); + libxlDomainObjExitAgent(vm); + if (ret < 0) + VIR_FREE(result); + + endjob: + libxlDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + return result; +} + + static virHypervisorDriver libxlHypervisorDriver =3D { .name =3D LIBXL_DRIVER_NAME, .connectOpen =3D libxlConnectOpen, /* 0.9.0 */ @@ -6522,6 +6572,7 @@ static virHypervisorDriver libxlHypervisorDriver =3D { .connectGetDomainCapabilities =3D libxlConnectGetDomainCapabilities, /= * 2.0.0 */ .connectCompareCPU =3D libxlConnectCompareCPU, /* 2.3.0 */ .connectBaselineCPU =3D libxlConnectBaselineCPU, /* 2.3.0 */ + .domainQemuAgentCommand =3D libxlDomainQemuAgentCommand, /* 3.1.0 */ }; =20 static virConnectDriver libxlConnectDriver =3D { --=20 2.1.4 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list From nobody Mon May 6 08:43:58 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.39 as permitted sender) client-ip=209.132.183.39; envelope-from=libvir-list-bounces@redhat.com; helo=mx6-phx2.redhat.com; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.39 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; Return-Path: Received: from mx6-phx2.redhat.com (mx6-phx2.redhat.com [209.132.183.39]) by mx.zohomail.com with SMTPS id 1486572388070731.042678762702; Wed, 8 Feb 2017 08:46:28 -0800 (PST) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by mx6-phx2.redhat.com (8.14.4/8.14.4) with ESMTP id v18GgJ7Z042103; Wed, 8 Feb 2017 11:42:19 -0500 Received: from smtp.corp.redhat.com (int-mx16.intmail.prod.int.phx2.redhat.com [10.5.11.28]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id v18GgGBs019776 for ; Wed, 8 Feb 2017 11:42:16 -0500 Received: by smtp.corp.redhat.com (Postfix) id 4AA126E90C; Wed, 8 Feb 2017 16:42:16 +0000 (UTC) Received: from mx1.redhat.com (ext-mx06.extmail.prod.ext.phx2.redhat.com [10.5.110.30]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 44E966D15D for ; Wed, 8 Feb 2017 16:42:16 +0000 (UTC) Received: from userp1040.oracle.com (userp1040.oracle.com [156.151.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id A2C0FC7051 for ; Wed, 8 Feb 2017 16:42:15 +0000 (UTC) Received: from aserv0022.oracle.com (aserv0022.oracle.com [141.146.126.234]) by userp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2) with ESMTP id v18GgDMu003847 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Wed, 8 Feb 2017 16:42:14 GMT Received: from aserv0122.oracle.com (aserv0122.oracle.com [141.146.126.236]) by aserv0022.oracle.com (8.14.4/8.14.4) with ESMTP id v18GgDPt002148 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Wed, 8 Feb 2017 16:42:13 GMT Received: from abhmp0018.oracle.com (abhmp0018.oracle.com [141.146.116.24]) by aserv0122.oracle.com (8.14.4/8.14.4) with ESMTP id v18GgCqg011722; Wed, 8 Feb 2017 16:42:13 GMT Received: from paddy.lan (/89.114.92.174) by default (Oracle Beehive Gateway v4.0) with ESMTP ; Wed, 08 Feb 2017 08:42:12 -0800 From: Joao Martins To: Libvirt Development List Date: Wed, 8 Feb 2017 16:44:40 +0000 Message-Id: <1486572280-2212-5-git-send-email-joao.m.martins@oracle.com> In-Reply-To: <1486572280-2212-1-git-send-email-joao.m.martins@oracle.com> References: <1486572280-2212-1-git-send-email-joao.m.martins@oracle.com> X-Source-IP: aserv0022.oracle.com [141.146.126.234] X-Greylist: Sender passed SPF test, Sender IP whitelisted by DNSRBL, ACL 200 matched, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.30]); Wed, 08 Feb 2017 16:42:15 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.30]); Wed, 08 Feb 2017 16:42:15 +0000 (UTC) for IP:'156.151.31.81' DOMAIN:'userp1040.oracle.com' HELO:'userp1040.oracle.com' FROM:'joao.m.martins@oracle.com' RCPT:'' X-RedHat-Spam-Score: -103.788 (BAYES_50, DCC_REPUT_00_12, RCVD_IN_DNSWL_MED, RCVD_IN_MSPIKE_H2, RP_MATCHES_RCVD, SPF_PASS, UNPARSEABLE_RELAY, USER_IN_WHITELIST) 156.151.31.81 userp1040.oracle.com 156.151.31.81 userp1040.oracle.com X-Scanned-By: MIMEDefang 2.78 on 10.5.110.30 X-Scanned-By: MIMEDefang 2.74 on 10.5.11.28 X-loop: libvir-list@redhat.com Cc: Joao Martins Subject: [libvirt] [PATCH RFC 4/4] libxl: domainInterfaceAddresses agent support X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Type: text/plain; charset="utf-8" Allow querying of guest interface address through agent through command `virsh domifaddr test --source agent` Signed-off-by: Joao Martins --- src/libxl/libxl_driver.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index cf5e702..8f8fbec 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -6261,6 +6261,8 @@ libxlDomainInterfaceAddresses(virDomainPtr dom, unsigned int source, unsigned int flags) { + libxlDriverPrivatePtr driver =3D dom->conn->privateData; + libxlDomainObjPrivatePtr priv; virDomainObjPtr vm =3D NULL; int ret =3D -1; =20 @@ -6282,6 +6284,22 @@ libxlDomainInterfaceAddresses(virDomainPtr dom, case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE: ret =3D libxlGetDHCPInterfaces(dom, vm, ifaces); break; + case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT: + priv =3D vm->privateData; + if (libxlDomainObjBeginJob(driver, vm, LIBXL_JOB_QUERY) < 0) + goto cleanup; + + if (!libxlDomainAgentAvailable(vm, true)) + goto endjob; + + libxlDomainObjEnterAgent(vm); + ret =3D qemuAgentGetInterfaces(priv->agent, ifaces); + libxlDomainObjExitAgent(vm); + + endjob: + libxlDomainObjEndJob(driver, vm); + + break; =20 default: virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, --=20 2.1.4 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list