Devel
Threads by month
- ----- 2026 -----
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
May 2026
- 23 participants
- 31 discussions
[RFC PATCH] resctrl: add energy monitoring via resctrl's PERF_PKG_MON
by Jedrzej Wasiukiewicz 12 May '26
by Jedrzej Wasiukiewicz 12 May '26
12 May '26
Linux kernel 7.0 introduced PERF_PKG_MON support in resctrl filesystem
which exposes per-workload energy and performance monitoring.
This patch enables per-VM energy monitoring via core_energy (Joules) and
activity (Farads) counters. Energy monitors can be configured through new
<energytune> element under <cputune> following earlier cachetune and
memorytune patterns.
Design notes:
Energy values from resctrl are floating-point. I added separate dvals/ndvals
pair to virResctrlMonitorStats to handle them. I kept them in a single
struct for easier integration with performance counters (integers and floats
within same monitor) that might be integrated in another patch.
The new XML element is <energytune> under <cputune> following earlier pattern for
resctrl features (cachetune, memorytune). Energytune doesn't currently support
the "tuning" part, only monitoring. I added it as energytune for consistency with
cache and memory features, keeping all resctrl handling under cputune. This also makes
sense with current resctrl architecture - all monitoring groups are part of an
allocation group. This approach allows for easy opt-in/out on the monitoring features.
Signed-off-by: Jedrzej Wasiukiewicz <jedrzej.wasiukiewicz(a)intel.com>
Signed-off-by: Christopher M. Cantalupo <christopher.m.cantalupo(a)intel.com>
---
Please let me know if there's a better approach for this. I'm happy to rework
the design based on feedback. I can also split this patch if necessary.
NEWS.rst | 6 +
docs/formatdomain.rst | 20 ++
include/libvirt/libvirt-domain.h | 65 +++++++
src/conf/capabilities.c | 42 ++++
src/conf/capabilities.h | 6 +
src/conf/domain_conf.c | 99 ++++++++++
src/conf/schemas/capability.rng | 26 +++
src/conf/schemas/domaincommon.rng | 19 ++
src/conf/virconftypes.h | 2 +
src/qemu/qemu_driver.c | 70 ++++++-
src/util/virresctrl.c | 180 +++++++++++++++---
src/util/virresctrl.h | 12 +-
.../genericxml2xmlindata/energytune-basic.xml | 32 ++++
tests/genericxml2xmltest.c | 1 +
14 files changed, 543 insertions(+), 37 deletions(-)
create mode 100644 tests/genericxml2xmlindata/energytune-basic.xml
diff --git a/NEWS.rst b/NEWS.rst
index 105a398ca8..02dda023ed 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -17,6 +17,12 @@ v12.4.0 (unreleased)
* **New features**
+ * resctrl: Add energy monitoring via resctrl's PERF_PKG_MON
+
+ Add support for Linux kernel 7.0 feature - energy monitoring via resctrl.
+ This allows to monitor per-VM energy consumption on supported platforms.
+ Implemented via ``energytune`` element in ``cputune`` .
+
* **Improvements**
* **Bug fixes**
diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index 078cd7aa84..db1ca5637a 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -900,6 +900,9 @@ CPU Tuning
<memorytune vcpus='0-3'>
<node id='0' bandwidth='60'/>
</memorytune>
+ <energytune vcpus='0-3'>
+ <monitor vcpus='0-3'/>
+ </energytune>
</cputune>
...
@@ -1084,6 +1087,23 @@ CPU Tuning
responsible for making sure the value makes sense on their system and
configuration.
+``energytune`` :since:`Since 12.4.0`
+ Optional ``energytune`` element allows to monitor energy consumption using the
+ resctrl filesystem on the host. Whether or not is this supported can be
+ gathered from capabilities where number of monitors and available features are
+ reported. The required attribute ``vcpus`` specifies to which allocation group
+ this monitor belongs. A vCPU can only be member of one allocation group and monitor
+ group. The ``vcpus`` specified by ``energytune`` can be identical to those
+ specified by ``cachetune`` or ``memorytune``. However they are not allowed to
+ overlap each other. Supported subelements are:
+
+ ``monitor``
+ The optional element ``monitor`` creates the energy monitor for
+ this allocation group and has the following required attribute:
+
+ ``vcpus``
+ vCPU list the monitor applies to.
+
Memory Allocation
-----------------
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h
index 4a8e3114b3..3ba1b27743 100644
--- a/include/libvirt/libvirt-domain.h
+++ b/include/libvirt/libvirt-domain.h
@@ -4401,6 +4401,71 @@ struct _virDomainStatsRecord {
# define VIR_DOMAIN_STATS_MEMORY_BANDWIDTH_MONITOR_SUFFIX_NODE_SUFFIX_BYTES_TOTAL ".bytes.total"
+/**
+ * VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_COUNT:
+ *
+ * The number of energy monitors for this domain, as an unsigned int.
+ *
+ * Since: 12.4.0
+ */
+# define VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_COUNT "cpu.energy.monitor.count"
+
+/**
+ * VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_PREFIX:
+ *
+ * Prefix for an individual energy monitor group. Concatenate
+ * with the monitor index and one of the "cpu.energy.monitor.<i>." suffix
+ * macros below to form a full parameter name.
+ *
+ * Since: 12.4.0
+ */
+# define VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_PREFIX "cpu.energy.monitor."
+
+/**
+ * VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_NAME:
+ *
+ * Name of the monitor group as a string.
+ *
+ * Since: 12.4.0
+ */
+# define VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_NAME ".name"
+
+/**
+ * VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_VCPUS:
+ *
+ * vCPU set covered by the monitor group as a string.
+ *
+ * Since: 12.4.0
+ */
+# define VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_VCPUS ".vcpus"
+
+/**
+ * VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_COUNT:
+ *
+ * Number of PERF_PKG nodes the monitor group exposes, as an unsigned int.
+ *
+ * Since: 12.4.0
+ */
+# define VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_COUNT ".pkg.count"
+
+/**
+ * VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_PREFIX:
+ *
+ * Prefix for a single mon_PERF_PKG node inside a monitor group.
+ *
+ * Since: 12.4.0
+ */
+# define VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_PREFIX ".pkg."
+
+/**
+ * VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_SUFFIX_ID:
+ *
+ * Kernel-assigned mon_PERF_PKG node id, as an unsigned int.
+ *
+ * Since: 12.4.0
+ */
+# define VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_SUFFIX_ID ".id"
+
/**
* VIR_DOMAIN_STATS_DIRTYRATE_CALC_STATUS:
*
diff --git a/src/conf/capabilities.c b/src/conf/capabilities.c
index 1821e36e61..e83de6d1bc 100644
--- a/src/conf/capabilities.c
+++ b/src/conf/capabilities.c
@@ -263,6 +263,8 @@ virCapsDispose(void *object)
virResctrlInfoMonFree(caps->host.memBW.monitor);
g_free(caps->host.memBW.nodes);
+ virResctrlInfoMonFree(caps->host.energy.monitor);
+
g_free(caps->host.netprefix);
g_free(caps->host.pagesSize);
virCPUDefFree(caps->host.cpu);
@@ -1057,6 +1059,26 @@ virCapabilitiesFormatMemoryBandwidth(virBuffer *buf,
}
+static int
+virCapabilitiesFormatEnergy(virBuffer *buf,
+ virCapsHostEnergy *energy)
+{
+ if (!energy->monitor)
+ return 0;
+
+ virBufferAddLit(buf, "<energy>\n");
+ virBufferAdjustIndent(buf, 2);
+
+ if (virCapabilitiesFormatResctrlMonitor(buf, energy->monitor) < 0)
+ return -1;
+
+ virBufferAdjustIndent(buf, -2);
+ virBufferAddLit(buf, "</energy>\n");
+
+ return 0;
+}
+
+
static int
virCapabilitiesFormatHostXML(virCapsHost *host,
virBuffer *buf)
@@ -1156,6 +1178,9 @@ virCapabilitiesFormatHostXML(virCapsHost *host,
if (virCapabilitiesFormatMemoryBandwidth(buf, &host->memBW) < 0)
return -1;
+ if (virCapabilitiesFormatEnergy(buf, &host->energy) < 0)
+ return -1;
+
for (i = 0; i < host->nsecModels; i++) {
virBufferAddLit(buf, "<secmodel>\n");
virBufferAdjustIndent(buf, 2);
@@ -2143,6 +2168,20 @@ virCapabilitiesInitResctrlMemory(virCaps *caps)
}
+static int
+virCapabilitiesInitEnergy(virCaps *caps)
+{
+ const char *prefix = virResctrlMonitorPrefixTypeToString(
+ VIR_RESCTRL_MONITOR_TYPE_ENERGY);
+
+ if (virResctrlInfoGetMonitorPrefix(caps->host.resctrl, prefix,
+ &caps->host.energy.monitor) < 0)
+ return -1;
+
+ return 0;
+}
+
+
int
virCapabilitiesInitCaches(virCaps *caps)
{
@@ -2294,6 +2333,9 @@ virCapabilitiesInitCaches(virCaps *caps)
&caps->host.cache.monitor) < 0)
return -1;
+ if (virCapabilitiesInitEnergy(caps) < 0)
+ return -1;
+
return 0;
}
diff --git a/src/conf/capabilities.h b/src/conf/capabilities.h
index daea835817..0482e4297a 100644
--- a/src/conf/capabilities.h
+++ b/src/conf/capabilities.h
@@ -162,6 +162,10 @@ struct _virCapsHostMemBW {
virResctrlInfoMon *monitor;
};
+struct _virCapsHostEnergy {
+ virResctrlInfoMon *monitor;
+};
+
struct _virCapsHost {
virArch arch;
size_t nfeatures;
@@ -184,6 +188,8 @@ struct _virCapsHost {
virCapsHostMemBW memBW;
+ virCapsHostEnergy energy;
+
size_t nsecModels;
virCapsHostSecModel *secModels;
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 3497e84bf5..946b34abf3 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -19467,6 +19467,57 @@ virDomainMemorytuneDefParse(virDomainDef *def,
}
+static int
+virDomainEnergytuneDefParse(virDomainDef *def,
+ xmlXPathContextPtr ctxt,
+ xmlNodePtr node,
+ unsigned int flags)
+{
+ VIR_XPATH_NODE_AUTORESTORE(ctxt)
+ virDomainResctrlDef *resctrl = NULL;
+ virDomainResctrlDef *newresctrl = NULL;
+ g_autoptr(virBitmap) vcpus = NULL;
+ g_autoptr(virResctrlAlloc) alloc = NULL;
+ size_t nmons;
+ int ret = -1;
+
+ ctxt->node = node;
+
+ if (virDomainResctrlParseVcpus(def, node, &vcpus) < 0)
+ return -1;
+
+ if (virBitmapIsAllClear(vcpus))
+ return 0;
+
+ if (virDomainResctrlVcpuMatch(def, vcpus, &resctrl) < 0)
+ return -1;
+
+ if (resctrl) {
+ alloc = virObjectRef(resctrl->alloc);
+ } else {
+ if (!(alloc = virResctrlAllocNew()))
+ return -1;
+ if (!(newresctrl = virDomainResctrlNew(node, alloc, vcpus, flags)))
+ return -1;
+ resctrl = newresctrl;
+ }
+
+ nmons = resctrl->nmonitors;
+ if (virDomainResctrlMonDefParse(def, ctxt, node,
+ VIR_RESCTRL_MONITOR_TYPE_ENERGY,
+ resctrl) < 0)
+ goto cleanup;
+
+ if (newresctrl && resctrl->nmonitors > nmons)
+ VIR_APPEND_ELEMENT(def->resctrls, def->nresctrls, newresctrl);
+
+ ret = 0;
+ cleanup:
+ virDomainResctrlDefFree(newresctrl);
+ return ret;
+}
+
+
static int
virDomainDefTunablesParse(virDomainDef *def,
xmlXPathContextPtr ctxt,
@@ -19671,6 +19722,15 @@ virDomainDefTunablesParse(virDomainDef *def,
}
VIR_FREE(nodes);
+ if ((n = virXPathNodeSet("./cputune/energytune", ctxt, &nodes)) < 0)
+ return -1;
+
+ for (i = 0; i < n; i++) {
+ if (virDomainEnergytuneDefParse(def, ctxt, nodes[i], flags) < 0)
+ return -1;
+ }
+ VIR_FREE(nodes);
+
return 0;
}
@@ -28721,6 +28781,42 @@ virDomainMemorytuneDefFormat(virBuffer *buf,
return 0;
}
+
+static int
+virDomainEnergytuneDefFormat(virBuffer *buf,
+ virDomainResctrlDef *resctrl,
+ unsigned int flags)
+{
+ g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
+ g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
+ g_autofree char *vcpus = NULL;
+ size_t i;
+
+ for (i = 0; i < resctrl->nmonitors; i++) {
+ if (virDomainResctrlMonDefFormatHelper(resctrl->monitors[i],
+ VIR_RESCTRL_MONITOR_TYPE_ENERGY,
+ &childrenBuf) < 0)
+ return -1;
+ }
+
+ if (!virBufferUse(&childrenBuf))
+ return 0;
+
+ vcpus = virBitmapFormat(resctrl->vcpus);
+ virBufferAsprintf(&attrBuf, " vcpus='%s'", vcpus);
+
+ if (!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE)) {
+ const char *alloc_id = virResctrlAllocGetID(resctrl->alloc);
+ if (!alloc_id)
+ return -1;
+
+ virBufferAsprintf(&attrBuf, " id='%s'", alloc_id);
+ }
+
+ virXMLFormatElement(buf, "energytune", &attrBuf, &childrenBuf);
+ return 0;
+}
+
static int
virDomainCputuneDefFormat(virBuffer *buf,
virDomainDef *def,
@@ -28821,6 +28917,9 @@ virDomainCputuneDefFormat(virBuffer *buf,
for (i = 0; i < def->nresctrls; i++)
virDomainMemorytuneDefFormat(&childrenBuf, def->resctrls[i], flags);
+ for (i = 0; i < def->nresctrls; i++)
+ virDomainEnergytuneDefFormat(&childrenBuf, def->resctrls[i], flags);
+
virXMLFormatElement(buf, "cputune", NULL, &childrenBuf);
return 0;
diff --git a/src/conf/schemas/capability.rng b/src/conf/schemas/capability.rng
index 8ef6e9a282..2aa711178f 100644
--- a/src/conf/schemas/capability.rng
+++ b/src/conf/schemas/capability.rng
@@ -45,6 +45,9 @@
<optional>
<ref name="memory_bandwidth"/>
</optional>
+ <optional>
+ <ref name="energy"/>
+ </optional>
<zeroOrMore>
<ref name="secmodel"/>
</zeroOrMore>
@@ -333,6 +336,29 @@
</data>
</define>
+ <define name="energyMonitorFeature">
+ <data type="string">
+ <param name="pattern">[a-zA-Z0-9\-_]+</param>
+ </data>
+ </define>
+
+ <define name="energy">
+ <element name="energy">
+ <element name="monitor">
+ <attribute name="maxMonitors">
+ <ref name="unsignedInt"/>
+ </attribute>
+ <oneOrMore>
+ <element name="feature">
+ <attribute name="name">
+ <ref name="energyMonitorFeature"/>
+ </attribute>
+ </element>
+ </oneOrMore>
+ </element>
+ </element>
+ </define>
+
<define name="guestcaps">
<element name="guest">
<ref name="ostype"/>
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
index 8c03e14d37..eb365a83b5 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -1293,6 +1293,25 @@
</oneOrMore>
</element>
</zeroOrMore>
+ <zeroOrMore>
+ <element name="energytune">
+ <attribute name="vcpus">
+ <ref name="cpuset"/>
+ </attribute>
+ <optional>
+ <attribute name="id">
+ <data type="string"/>
+ </attribute>
+ </optional>
+ <oneOrMore>
+ <element name="monitor">
+ <attribute name="vcpus">
+ <ref name="cpuset"/>
+ </attribute>
+ </element>
+ </oneOrMore>
+ </element>
+ </zeroOrMore>
</interleave>
</element>
</define>
diff --git a/src/conf/virconftypes.h b/src/conf/virconftypes.h
index 0596791a4d..f1a200bfe2 100644
--- a/src/conf/virconftypes.h
+++ b/src/conf/virconftypes.h
@@ -52,6 +52,8 @@ typedef struct _virCapsHostMemBW virCapsHostMemBW;
typedef struct _virCapsHostMemBWNode virCapsHostMemBWNode;
+typedef struct _virCapsHostEnergy virCapsHostEnergy;
+
typedef struct _virCapsHostNUMA virCapsHostNUMA;
typedef struct _virCapsHostNUMACell virCapsHostNUMACell;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 529e9fe3be..3fafaaea19 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -16917,11 +16917,11 @@ qemuDomainFreeResctrlMonData(virQEMUResctrlMonData *resdata)
* returns an error, the caller is also required to call
* qemuDomainFreeResctrlMonData to free each element in the
* *@resdata array and then the array itself.
- * @tag: Could be VIR_RESCTRL_MONITOR_TYPE_CACHE for getting cache statistics
- * from @dom cache monitors. VIR_RESCTRL_MONITOR_TYPE_MEMBW for
- * getting memory bandwidth statistics from memory bandwidth monitors.
+ * @tag: VIR_RESCTRL_MONITOR_TYPE_CACHE for getting cache statistics.
+ * VIR_RESCTRL_MONITOR_TYPE_MEMBW for getting memory bandwidth statistics.
+ * VIR_RESCTRL_MONITOR_TYPE_ENERGY for getting energy statistics.
*
- * Get cache or memory bandwidth statistics from @dom monitors.
+ * Get cache, memory bandwidth or energy statistics from @dom monitors.
*
* Returns -1 on failure, or 0 on success.
*/
@@ -16951,6 +16951,10 @@ qemuDomainGetResctrlMonData(virQEMUDriver *driver,
if (caps->host.memBW.monitor)
features = caps->host.memBW.monitor->features;
break;
+ case VIR_RESCTRL_MONITOR_TYPE_ENERGY:
+ if (caps->host.energy.monitor)
+ features = caps->host.energy.monitor->features;
+ break;
case VIR_RESCTRL_MONITOR_TYPE_UNSUPPORT:
case VIR_RESCTRL_MONITOR_TYPE_LAST:
virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
@@ -17066,6 +17070,62 @@ qemuDomainGetStatsMemoryBandwidth(virQEMUDriver *driver,
}
+static void
+qemuDomainGetStatsEnergy(virQEMUDriver *driver,
+ virDomainObj *dom,
+ virTypedParamList *params)
+{
+ g_autofree virQEMUResctrlMonData **resdata = NULL;
+ size_t nresdata = 0;
+ size_t i = 0;
+ size_t j = 0;
+ size_t k = 0;
+
+ if (!virDomainObjIsActive(dom))
+ return;
+
+ if (qemuDomainGetResctrlMonData(driver, dom, &resdata, &nresdata,
+ VIR_RESCTRL_MONITOR_TYPE_ENERGY) < 0) {
+ virResetLastError();
+ return;
+ }
+
+ if (nresdata == 0)
+ return;
+
+ virTypedParamListAddUInt(params, nresdata,
+ VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_COUNT);
+
+ for (i = 0; i < nresdata; i++) {
+ virTypedParamListAddString(params, resdata[i]->name,
+ VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_PREFIX "%zu" VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_NAME, i);
+ virTypedParamListAddString(params, resdata[i]->vcpus,
+ VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_PREFIX "%zu" VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_VCPUS, i);
+ virTypedParamListAddUInt(params, resdata[i]->nstats,
+ VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_PREFIX "%zu" VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_COUNT, i);
+
+ for (j = 0; j < resdata[i]->nstats; j++) {
+ char **features = resdata[i]->stats[j]->features;
+
+ virTypedParamListAddUInt(params, resdata[i]->stats[j]->id,
+ VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_PREFIX "%zu" VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_PREFIX "%zu" VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_SUFFIX_ID, i, j);
+
+ for (k = 0; features[k]; k++) {
+ if (k >= resdata[i]->stats[j]->ndvals)
+ break;
+
+ virTypedParamListAddDouble(params, resdata[i]->stats[j]->dvals[k],
+ VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_PREFIX "%zu" VIR_DOMAIN_STATS_CPU_ENERGY_MONITOR_SUFFIX_PKG_PREFIX "%zu" ".%s", i, j,
+ features[k]);
+ }
+ }
+ }
+
+ for (i = 0; i < nresdata; i++)
+ qemuDomainFreeResctrlMonData(resdata[i]);
+}
+
+
static void
qemuDomainGetStatsCpuCache(virQEMUDriver *driver,
virDomainObj *dom,
@@ -17277,6 +17337,8 @@ qemuDomainGetStatsCpu(virQEMUDriver *driver,
qemuDomainGetStatsCpuCache(driver, dom, params);
+ qemuDomainGetStatsEnergy(driver, dom, params);
+
qemuDomainGetStatsCpuHaltPollTime(dom, params, privflags);
}
diff --git a/src/util/virresctrl.c b/src/util/virresctrl.c
index 8f33a85a56..32cbfb6c2e 100644
--- a/src/util/virresctrl.c
+++ b/src/util/virresctrl.c
@@ -79,15 +79,24 @@ VIR_ENUM_IMPL(virResctrl,
"DATA",
);
-/* Monitor feature name prefix mapping for monitor naming */
+/* Monitor feature prefix/type mapping for monitor naming */
VIR_ENUM_IMPL(virResctrlMonitorPrefix,
VIR_RESCTRL_MONITOR_TYPE_LAST,
"__unsupported__",
"llc_",
"mbm_",
+ "energy",
);
+/* PERF_PKG_MON features that report energy data (floating-point). */
+static const char *virResctrlEnergyFeatures[] = {
+ "core_energy",
+ "activity",
+ NULL,
+};
+
+
/* All private typedefs so that they exist for all later definitions. This way
* structs can be included in one or another without reorganizing the code every
* time. */
@@ -183,6 +192,8 @@ struct _virResctrlInfo {
virResctrlInfoMemBW *membw_info;
virResctrlInfoMongrp *monitor_info;
+
+ virResctrlInfoMongrp *perf_monitor_info;
};
static void
@@ -235,10 +246,14 @@ virResctrlInfoDispose(void *obj)
if (resctrl->monitor_info)
g_strfreev(resctrl->monitor_info->features);
+ if (resctrl->perf_monitor_info)
+ g_strfreev(resctrl->perf_monitor_info->features);
+
virResctrlInfoMemBWFree(resctrl->membw_info);
g_free(resctrl->levels);
g_free(resctrl->monitor_info);
+ g_free(resctrl->perf_monitor_info);
}
@@ -771,6 +786,52 @@ virResctrlGetMonitorInfo(virResctrlInfo *resctrl)
}
+static int
+virResctrlGetPerfMonitorInfo(virResctrlInfo *resctrl)
+{
+ int rv = -1;
+ g_autofree char *featurestr = NULL;
+ g_autofree virResctrlInfoMongrp *info_monitor = NULL;
+
+ info_monitor = g_new0(virResctrlInfoMongrp, 1);
+
+ rv = virFileReadValueUint(&info_monitor->max_monitor,
+ SYSFS_RESCTRL_PATH
+ "/info/PERF_PKG_MON/num_rmids");
+ if (rv == -2) {
+ VIR_INFO("The file '" SYSFS_RESCTRL_PATH "/info/PERF_PKG_MON/num_rmids' "
+ "does not exist");
+ return 0;
+ } else if (rv < 0) {
+ return -1;
+ }
+
+ rv = virFileReadValueString(&featurestr,
+ SYSFS_RESCTRL_PATH
+ "/info/PERF_PKG_MON/mon_features");
+ if (rv == -2)
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Cannot get mon_features from resctrl PERF_PKG_MON"));
+ if (rv < 0)
+ return -1;
+
+ if (!*featurestr) {
+ VIR_WARN("Got empty feature list from PERF_PKG_MON; "
+ "AET energy monitoring will not be available");
+ return 0;
+ }
+
+ info_monitor->features = g_strsplit(featurestr, "\n", 0);
+ info_monitor->nfeatures = g_strv_length(info_monitor->features);
+ VIR_DEBUG("Resctrl supported %zd AET monitoring features",
+ info_monitor->nfeatures);
+
+ resctrl->perf_monitor_info = g_steal_pointer(&info_monitor);
+
+ return 0;
+}
+
+
static int
virResctrlGetInfo(virResctrlInfo *resctrl)
{
@@ -790,6 +851,9 @@ virResctrlGetInfo(virResctrlInfo *resctrl)
if ((ret = virResctrlGetMonitorInfo(resctrl)) < 0)
return -1;
+ if ((ret = virResctrlGetPerfMonitorInfo(resctrl)) < 0)
+ return -1;
+
return 0;
}
@@ -830,6 +894,9 @@ virResctrlInfoIsEmpty(virResctrlInfo *resctrl)
if (resctrl->monitor_info)
return false;
+ if (resctrl->perf_monitor_info)
+ return false;
+
for (i = 0; i < resctrl->nlevels; i++) {
virResctrlInfoPerLevel *i_level = resctrl->levels[i];
@@ -986,13 +1053,6 @@ virResctrlInfoGetMonitorPrefix(virResctrlInfo *resctrl,
if (virResctrlInfoIsEmpty(resctrl))
return 0;
- mongrp_info = resctrl->monitor_info;
-
- if (!mongrp_info) {
- VIR_INFO("Monitor is not supported in host");
- return 0;
- }
-
for (i = 0; i < VIR_RESCTRL_MONITOR_TYPE_LAST; i++) {
if (STREQ(prefix, virResctrlMonitorPrefixTypeToString(i))) {
mon = g_new0(virResctrlInfoMon, 1);
@@ -1008,6 +1068,19 @@ virResctrlInfoGetMonitorPrefix(virResctrlInfo *resctrl,
return -1;
}
+ if (mon->type == VIR_RESCTRL_MONITOR_TYPE_ENERGY)
+ mongrp_info = resctrl->perf_monitor_info;
+ else
+ mongrp_info = resctrl->monitor_info;
+
+ if (!mongrp_info) {
+ VIR_INFO("Monitor prefix '%s' is not supported in host", prefix);
+ virResctrlInfoMonFree(*monitor);
+ *monitor = NULL;
+ ret = 0;
+ goto cleanup;
+ }
+
mon->max_monitor = mongrp_info->max_monitor;
if (mon->type == VIR_RESCTRL_MONITOR_TYPE_CACHE) {
@@ -1018,8 +1091,12 @@ virResctrlInfoGetMonitorPrefix(virResctrlInfo *resctrl,
mon->features = g_new0(char *, mongrp_info->nfeatures + 1);
for (i = 0; i < mongrp_info->nfeatures; i++) {
- if (STRPREFIX(mongrp_info->features[i], prefix))
+ if (mon->type == VIR_RESCTRL_MONITOR_TYPE_ENERGY) {
+ if (g_strv_contains(virResctrlEnergyFeatures, mongrp_info->features[i]))
+ mon->features[mon->nfeatures++] = g_strdup(mongrp_info->features[i]);
+ } else if (STRPREFIX(mongrp_info->features[i], prefix)) {
mon->features[mon->nfeatures++] = g_strdup(mongrp_info->features[i]);
+ }
}
mon->features = g_renew(char *, mon->features, mon->nfeatures + 1);
@@ -2558,7 +2635,7 @@ virResctrlMonitorStatsSorter(const void *a,
* memory bandwidth usage data.
* @nstats: A size_t pointer to hold the returned array length of @stats
*
- * Get cache or memory bandwidth utilization information.
+ * Get cache, memory bandwidth or energy utilization information.
*
* Returns 0 on success, -1 on error.
*/
@@ -2593,6 +2670,7 @@ virResctrlMonitorGetStats(virResctrlMonitor *monitor,
while (virDirRead(dirp, &ent, datapath) > 0) {
g_autofree char *filepath = NULL;
char *node_id = NULL;
+ bool is_energy = false;
/* Looking for directory that contains resource utilization
* information file. The directory name is arranged in format
@@ -2605,18 +2683,17 @@ virResctrlMonitorGetStats(virResctrlMonitor *monitor,
if (!virFileIsDir(filepath))
continue;
- /* Looking for directory has a prefix 'mon_L' */
- if (!(node_id = STRSKIP(ent->d_name, "mon_L")))
- continue;
-
- /* Looking for directory has another '_' */
- node_id = strchr(node_id, '_');
- if (!node_id)
- continue;
-
- /* Skip the character '_' */
- if (!(node_id = STRSKIP(node_id, "_")))
+ if ((node_id = STRSKIP(ent->d_name, "mon_PERF_PKG_"))) {
+ is_energy = true;
+ } else if ((node_id = STRSKIP(ent->d_name, "mon_L"))) {
+ node_id = strchr(node_id, '_');
+ if (!node_id)
+ continue;
+ if (!(node_id = STRSKIP(node_id, "_")))
+ continue;
+ } else {
continue;
+ }
stat = g_new0(virResctrlMonitorStats, 1);
stat->features = g_new0(char *, nresources + 1);
@@ -2626,21 +2703,61 @@ virResctrlMonitorGetStats(virResctrlMonitor *monitor,
goto cleanup;
for (i = 0; resources[i]; i++) {
- rv = virFileReadValueUllong(&val, "%s/%s/%s", datapath,
- ent->d_name, resources[i]);
- if (rv == -2) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("File '%1$s/%2$s/%3$s' does not exist."),
- datapath, ent->d_name, resources[i]);
+ if (is_energy) {
+ g_autofree char *valstr = NULL;
+ double dval = 0.0;
+ char *endp = NULL;
+
+ rv = virFileReadValueString(&valstr, "%s/%s/%s", datapath,
+ ent->d_name, resources[i]);
+ if (rv == -2) {
+ if (i == 0)
+ break;
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("File '%1$s/%2$s/%3$s' does not exist."),
+ datapath, ent->d_name, resources[i]);
+ goto cleanup;
+ }
+ if (rv < 0)
+ goto cleanup;
+
+ g_strstrip(valstr);
+ errno = 0;
+ dval = g_ascii_strtod(valstr, &endp);
+ if (endp == valstr || *endp != '\0' || errno != 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Cannot parse resctrl monitor value '%1$s' from '%2$s/%3$s/%4$s'"),
+ valstr, datapath, ent->d_name, resources[i]);
+ goto cleanup;
+ }
+
+ VIR_APPEND_ELEMENT(stat->dvals, stat->ndvals, dval);
+ } else {
+ rv = virFileReadValueUllong(&val, "%s/%s/%s", datapath,
+ ent->d_name, resources[i]);
+ if (rv == -2) {
+ if (i == 0)
+ break;
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("File '%1$s/%2$s/%3$s' does not exist."),
+ datapath, ent->d_name, resources[i]);
+ goto cleanup;
+ }
+ if (rv < 0)
+ goto cleanup;
+
+ VIR_APPEND_ELEMENT(stat->vals, stat->nvals, val);
}
- if (rv < 0)
- goto cleanup;
-
- VIR_APPEND_ELEMENT(stat->vals, stat->nvals, val);
stat->features[i] = g_strdup(resources[i]);
}
+ if (resources[i]) {
+ virResctrlMonitorStatsFree(stat);
+ stat = NULL;
+ continue;
+ }
+
VIR_APPEND_ELEMENT(*stats, *nstats, stat);
}
@@ -2665,5 +2782,6 @@ virResctrlMonitorStatsFree(virResctrlMonitorStats *stat)
g_strfreev(stat->features);
g_free(stat->vals);
+ g_free(stat->dvals);
g_free(stat);
}
diff --git a/src/util/virresctrl.h b/src/util/virresctrl.h
index c70b112864..0f38db47cf 100644
--- a/src/util/virresctrl.h
+++ b/src/util/virresctrl.h
@@ -38,6 +38,7 @@ typedef enum {
VIR_RESCTRL_MONITOR_TYPE_UNSUPPORT,
VIR_RESCTRL_MONITOR_TYPE_CACHE,
VIR_RESCTRL_MONITOR_TYPE_MEMBW,
+ VIR_RESCTRL_MONITOR_TYPE_ENERGY,
VIR_RESCTRL_MONITOR_TYPE_LAST
} virResctrlMonitorType;
@@ -196,11 +197,18 @@ struct _virResctrlMonitorStats {
/* @features is a NULL terminal string list tracking the statistical record
* name.*/
char **features;
- /* @vals store the statistical record values and @val[0] is the value for
- * @features[0], @val[1] for@features[1] ... respectively */
+ /* @vals store the statistical record values for integer-valued resources
+ * (cache occupancy, memory bandwidth). Entries correspond 1:1 with
+ * @features; empty when the resource reports floating-point data. */
unsigned long long *vals;
/* The length of @vals array */
size_t nvals;
+ /* @dvals store double-precision values for floating-point resources
+ * (energy in Joules). Entries correspond 1:1 with
+ * @features; empty when the resource reports integer data. */
+ double *dvals;
+ /* The length of @dvals array */
+ size_t ndvals;
};
virResctrlMonitor *
diff --git a/tests/genericxml2xmlindata/energytune-basic.xml b/tests/genericxml2xmlindata/energytune-basic.xml
new file mode 100644
index 0000000000..4ee4bedb68
--- /dev/null
+++ b/tests/genericxml2xmlindata/energytune-basic.xml
@@ -0,0 +1,32 @@
+<domain type='qemu'>
+ <name>QEMUGuest1</name>
+ <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+ <memory unit='KiB'>219136</memory>
+ <currentMemory unit='KiB'>219136</currentMemory>
+ <vcpu placement='static'>4</vcpu>
+ <cputune>
+ <energytune vcpus='0-1'>
+ <monitor vcpus='0-1'/>
+ </energytune>
+ <energytune vcpus='3'>
+ <monitor vcpus='3'/>
+ </energytune>
+ </cputune>
+ <os>
+ <type arch='i686' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-i386</emulator>
+ <controller type='usb' index='0'/>
+ <controller type='ide' index='0'/>
+ <controller type='pci' index='0' model='pci-root'/>
+ <input type='mouse' bus='ps2'/>
+ <input type='keyboard' bus='ps2'/>
+ <memballoon model='virtio'/>
+ </devices>
+</domain>
diff --git a/tests/genericxml2xmltest.c b/tests/genericxml2xmltest.c
index 6be694cac5..bcc9b07a13 100644
--- a/tests/genericxml2xmltest.c
+++ b/tests/genericxml2xmltest.c
@@ -210,6 +210,7 @@ mymain(void)
DO_TEST("cachetune-small");
DO_TEST("cachetune-cdp");
DO_TEST("cachetune");
+ DO_TEST("energytune-basic");
DO_TEST_DIFFERENT("cachetune-extra-tunes");
DO_TEST_FAIL_INACTIVE("cachetune-colliding-allocs");
DO_TEST_FAIL_INACTIVE("cachetune-colliding-tunes");
--
2.34.1
---------------------------------------------------------------------
Intel Technology Poland sp. z o.o.
ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
2
1
This series adds support for asynchronous live vCPU unplug in the QEMU
driver.
For live vCPU unplug libvirt currently waits only for a short
synchronous completion window. If the unplug completes later there is
no dedicated event reporting successful removal of the vCPU. This
series adds a new domain event for successful unplug completion and
extends the public APIs to allow requesting asynchronous unplug.
As recommended, this changeset is split into five parts-
Patch 1 adds the new event and the required public/internal plumbing.
Patch 2 wires the event into the existing QEMU unplug completion path.
Patch 3 adds the internal QEMU plumbing needed to skip waiting for
unplug completion, but leaves it unused for now.
Patches 4 and 5 then expose the new behaviour through virDomainSetVcpusFlags()
and virDomainSetVcpu() together with the corresponding virsh changes.
The new public flags are limited to live unplug. Rejected unplug
requests continue to be reported via device-removal-failed.
Most impactful change from the previous iteration is that event emission moves
from 1 vcpu-removed per qemu event model to one vcpu-removed per vcpu actually
removed. This change is effectively a no-op for x86 (because there is a 1:1
mapping), but is relevant for architectures where more than 1 vcpu might be
grouped under a single logical qemu vcpu object.
Changes from v2:
- s/virDomainSetVcpuBehaviour/virDomainSetVcpuFlags/g
- Reduce redundant phrasing
- emit `vcpu-removed` for every vcpu removed instead of per qemu event
- bump version ton 12.4.0
All test runs are still passing cleanly.
Akash Kulhalli (5):
API/qemu: add async unplug flag to virDomainSetVcpu
API/qemu: add async unplug flag to virDomainSetVcpusFlags
qemu: thread async vcpu unplug through internal helpers
qemu: emit vcpu-removed event on unplug completion
conf,remote: add vcpu-removed domain event
Related discussion:
RFC: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/2J53…
v2: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/YZID…
2
10
[PATCH 1/1] virStorageSource: note that capacity/allocation/physical are stale caches
by Denis V. Lunev 12 May '26
by Denis V. Lunev 12 May '26
12 May '26
These three fields are cached values that do not reflect reality unless
the caller refreshes them. 'allocation' is in addition ultra-unreliable:
any guest write into a previously unallocated part of a sparse image
invalidates it, even right after a refresh.
Document this on the struct so new callers do not trust the values.
Signed-off-by: Denis V. Lunev <den(a)openvz.org>
CC: Peter Krempa <pkrempa(a)redhat.com>
---
src/conf/storage_source_conf.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/conf/storage_source_conf.h b/src/conf/storage_source_conf.h
index 5ddcebb282..fa71a9ef39 100644
--- a/src/conf/storage_source_conf.h
+++ b/src/conf/storage_source_conf.h
@@ -326,6 +326,8 @@ struct _virStorageSource {
virStoragePerms *perms;
virStorageTimestamps *timestamps;
+
+ /* Cached, unreliable. Refresh before use. */
unsigned long long capacity; /* in bytes, 0 if unknown */
unsigned long long allocation; /* in bytes, 0 if unknown */
unsigned long long physical; /* in bytes, 0 if unknown */
--
2.51.0
1
0
From: Ján Tomko <jtomko(a)redhat.com>
https://redhat.atlassian.net/browse/RHEL-174491
Signed-off-by: Ján Tomko <jtomko(a)redhat.com>
---
src/util/virutil.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/util/virutil.c b/src/util/virutil.c
index 187b8202dd..2cc7edac50 100644
--- a/src/util/virutil.c
+++ b/src/util/virutil.c
@@ -1223,7 +1223,7 @@ virGetSubIDs(virSubID **retval, const char *file)
*retval = NULL;
- if (virFileReadAll(file, BUFSIZ, &buf) < 0)
+ if (virFileReadAll(file, SIZE_MAX, &buf) < 0)
return -1;
lines = g_strsplit(buf, "\n", 0);
--
2.54.0
2
1
From: Ján Tomko <jtomko(a)redhat.com>
https://redhat.atlassian.net/browse/RHEL-174491
Signed-off-by: Ján Tomko <jtomko(a)redhat.com>
---
v2: it's `int maxlen`, not size_t
src/util/virutil.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/util/virutil.c b/src/util/virutil.c
index 187b8202dd..24b0a10878 100644
--- a/src/util/virutil.c
+++ b/src/util/virutil.c
@@ -1223,7 +1223,7 @@ virGetSubIDs(virSubID **retval, const char *file)
*retval = NULL;
- if (virFileReadAll(file, BUFSIZ, &buf) < 0)
+ if (virFileReadAll(file, INT_MAX, &buf) < 0)
return -1;
lines = g_strsplit(buf, "\n", 0);
--
2.54.0
1
0
12 May '26
From: Peter Krempa <pkrempa(a)redhat.com>
The 'new->fds' array is not yet initialized at the point where the check
if the FD is occupied happens so the error would always report that FD
'0' is in use. Use 'new->testfds' instead.
Signed-off-by: Peter Krempa <pkrempa(a)redhat.com>
---
tests/testutilsqemu.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/testutilsqemu.c b/tests/testutilsqemu.c
index e9bdbdbbe7..e7a61d0c6f 100644
--- a/tests/testutilsqemu.c
+++ b/tests/testutilsqemu.c
@@ -727,7 +727,7 @@ testQemuInfoSetArgs(testQemuInfo *info,
new->testfds[i] = va_arg(argptr, unsigned int);
if (fcntl(new->testfds[i], F_GETFD) != -1) {
- fprintf(stderr, "fd '%d' is already in use\n", new->fds[i]);
+ fprintf(stderr, "fd '%d' is already in use\n", new->testfds[i]);
abort();
}
--
2.54.0
2
1
Make them the same as we do with error messages since they often end up
in logs.
Since they are not translated [1] the existing check didn't catch that.
[1] IMO in some cases they are in fact used as errors, and maybe would
be worth to be translated. But this is for another series
Peter Krempa (2):
Don't break up strings for VIR_WARN messages
syntax-check: Enforce no linebreaks in VIR_WARN messages
build-aux/syntax-check.mk | 8 +++++
src/bhyve/bhyve_command.c | 4 +--
src/conf/virdomainjob.c | 5 +--
src/cpu/cpu_x86.c | 5 ++-
src/esx/esx_vi.c | 3 +-
src/hypervisor/virhostdev.c | 12 +++----
src/interface/interface_backend_netcf.c | 12 +++----
src/libxl/libxl_conf.c | 4 +--
src/libxl/xen_xl.c | 4 +--
src/lxc/lxc_driver.c | 3 +-
src/lxc/lxc_process.c | 3 +-
src/network/bridge_driver.c | 15 +++------
src/nwfilter/nwfilter_dhcpsnoop.c | 3 +-
src/qemu/qemu_block.c | 3 +-
src/qemu/qemu_command.c | 3 +-
src/qemu/qemu_conf.c | 4 +--
src/qemu/qemu_domain.c | 44 +++++++++----------------
src/qemu/qemu_domain_address.c | 10 ++----
src/qemu/qemu_driver.c | 10 +++---
src/qemu/qemu_firmware.c | 6 ++--
src/qemu/qemu_hotplug.c | 3 +-
src/qemu/qemu_migration.c | 11 +++----
src/qemu/qemu_monitor_json.c | 4 +--
src/remote/remote_driver.c | 3 +-
src/rpc/virnetserverclient.c | 5 ++-
src/security/security_dac.c | 3 +-
src/security/security_selinux.c | 7 ++--
src/security/security_stack.c | 34 +++++++------------
src/storage/storage_backend_rbd.c | 4 +--
src/storage/storage_backend_zfs.c | 3 +-
src/storage/storage_util.c | 4 +--
src/storage_file/storage_file_probe.c | 3 +-
src/vbox/vbox_common.c | 4 +--
src/vmx/vmx.c | 6 ++--
src/vz/vz_sdk.c | 3 +-
35 files changed, 96 insertions(+), 162 deletions(-)
--
2.54.0
2
4
11 May '26
qemuDomainBlockResize() issues block_resize via QMP, which updates the
qcow2 header on disk and bs->total_sectors in qemu, but the cached
disk->src->capacity in libvirt is left at its pre-resize value.
All current readers re-read capacity from either QMP or the file itself
before returning to the user, so the staleness is not externally
observable today. Still, the cached field should reflect what we just
asked qemu to write; keeping the two in sync avoids surprises for any
future consumer that trusts the cached value.
Signed-off-by: Denis V. Lunev <den(a)openvz.org>
CC: Peter Krempa <pkrempa(a)redhat.com>
CC: Michal Privoznik <mprivozn(a)redhat.com>
---
src/qemu/qemu_driver.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 2d31d4aa31..a419c5cc95 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -9598,6 +9598,9 @@ qemuDomainBlockResize(virDomainPtr dom,
}
qemuDomainObjExitMonitor(vm);
+ /* Keep the cached capacity in sync with qemu representation. */
+ disk->src->capacity = size;
+
if (persistDisk) {
g_clear_pointer(&persistDisk->src->sliceStorage, virStorageSourceSliceFree);
qemuDomainSaveConfig(vm);
--
2.51.0
1
0
Implement QEMU Guest Agent support for bhyve. In bhyve it can configured
using the virtio-console device.
This change covers only two APIs using the agent:
- DomainQemuAgentCommand -- the most generic one.
- DomainGetHostname -- extended to support not only DHCP lease source,
but an agent as well.
There are two things that I'm not sure about this patch.
- The protocol files were updated to make DomainQemuAgentCommand generic
instead of Qemu specific. QEMU_PROC_DOMAIN_AGENT_COMMAND was removed in
favor of REMOTE_PROC_DOMAIN_QEMU_AGENT_COMMAND.
Even though the protocol is documented as private, I'm not sure how
desired this change is.
- src/qemu/bhyve_qemu_agent.c and src/qemu/qemu_agent.c should
share the same implementation. Ideally this should live
somewhere in src/util/virqemuagent.c, but as it is using things
from conf/, such as virDomainObj, it cannot be moved as is.
I considered extracting a simpler data structure that will not
use the conf/ types, but it didn't work very well for me and
I didn't like the way it looks in the driver code.
Maybe I'm missing some better approach?
Signed-off-by: Roman Bogorodskiy <bogorodskiy(a)gmail.com>
---
po/POTFILES | 1 +
src/bhyve/bhyve_domain.c | 32 +
src/bhyve/bhyve_domain.h | 14 +
src/bhyve/bhyve_driver.c | 179 ++++-
src/bhyve/bhyve_process.c | 117 ++++
src/bhyve/bhyve_process.h | 4 +
src/bhyve/bhyve_qemu_agent.c | 1259 ++++++++++++++++++++++++++++++++++
src/bhyve/bhyve_qemu_agent.h | 197 ++++++
src/bhyve/meson.build | 5 +-
src/remote/qemu_protocol.x | 18 -
src/remote/remote_protocol.x | 20 +-
11 files changed, 1822 insertions(+), 24 deletions(-)
create mode 100644 src/bhyve/bhyve_qemu_agent.c
create mode 100644 src/bhyve/bhyve_qemu_agent.h
diff --git a/po/POTFILES b/po/POTFILES
index a5f8705eb8..1d80d9a57a 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -19,6 +19,7 @@ src/bhyve/bhyve_firmware.c
src/bhyve/bhyve_monitor.c
src/bhyve/bhyve_parse_command.c
src/bhyve/bhyve_process.c
+src/bhyve/bhyve_qemu_agent.c
src/ch/ch_alias.c
src/ch/ch_conf.c
src/ch/ch_domain.c
diff --git a/src/bhyve/bhyve_domain.c b/src/bhyve/bhyve_domain.c
index 832a9b58d1..6367985efc 100644
--- a/src/bhyve/bhyve_domain.c
+++ b/src/bhyve/bhyve_domain.c
@@ -41,6 +41,7 @@ bhyveDomainObjPrivateAlloc(void *opaque)
{
bhyveDomainObjPrivate *priv = g_new0(bhyveDomainObjPrivate, 1);
+ priv->agentTimeout = 30;
priv->driver = opaque;
return priv;
@@ -663,3 +664,34 @@ virXMLNamespace virBhyveDriverDomainXMLNamespace = {
.uri = "http://libvirt.org/schemas/domain/bhyve/1.0",
};
+
+
+int
+virBhyveDomainObjStartWorker(virDomainObj *dom)
+{
+ bhyveDomainObjPrivate *priv = dom->privateData;
+
+ if (!priv->eventThread) {
+ g_autofree char *threadName = g_strdup_printf("vm-%s", dom->def->name);
+ if (!(priv->eventThread = virEventThreadNew(threadName)))
+ return -1;
+ }
+
+ return 0;
+}
+
+
+void
+virBhyveDomainObjStopWorker(virDomainObj *dom)
+{
+ bhyveDomainObjPrivate *priv = dom->privateData;
+ virEventThread *eventThread;
+
+ if (!priv->eventThread)
+ return;
+
+ eventThread = g_steal_pointer(&priv->eventThread);
+ virObjectUnlock(dom);
+ g_object_unref(eventThread);
+ virObjectLock(dom);
+}
diff --git a/src/bhyve/bhyve_domain.h b/src/bhyve/bhyve_domain.h
index 5a539bc4c0..8e3663f4c0 100644
--- a/src/bhyve/bhyve_domain.h
+++ b/src/bhyve/bhyve_domain.h
@@ -22,8 +22,10 @@
#include "domain_addr.h"
#include "domain_conf.h"
+#include "vireventthread.h"
#include "bhyve_monitor.h"
+#include "bhyve_qemu_agent.h"
typedef struct _bhyveDomainObjPrivate bhyveDomainObjPrivate;
struct _bhyveDomainObjPrivate {
@@ -33,10 +35,22 @@ struct _bhyveDomainObjPrivate {
bool persistentAddrs;
bhyveMonitor *mon;
+
+ qemuAgent *agent;
+ bool agentError;
+ int agentTimeout;
+
+ virEventThread *eventThread;
};
+#define BHYVE_DOMAIN_PRIVATE(vm) \
+ ((bhyveDomainObjPrivate *) (vm)->privateData)
+
virDomainXMLOption *virBhyveDriverCreateXMLConf(struct _bhyveConn *);
extern virDomainXMLPrivateDataCallbacks virBhyveDriverPrivateDataCallbacks;
extern virDomainDefParserConfig virBhyveDriverDomainDefParserConfig;
extern virXMLNamespace virBhyveDriverDomainXMLNamespace;
+
+int virBhyveDomainObjStartWorker(virDomainObj *dom);
+void virBhyveDomainObjStopWorker(virDomainObj *dom);
diff --git a/src/bhyve/bhyve_driver.c b/src/bhyve/bhyve_driver.c
index c8dd1a728a..8bffbbec43 100644
--- a/src/bhyve/bhyve_driver.c
+++ b/src/bhyve/bhyve_driver.c
@@ -1895,6 +1895,165 @@ bhyveDomainInterfaceAddresses(virDomainPtr domain,
}
+static qemuAgent *
+bhyveDomainObjEnterAgent(virDomainObj *obj)
+{
+ bhyveDomainObjPrivate *priv = obj->privateData;
+ qemuAgent *agent = priv->agent;
+
+ VIR_DEBUG("Entering agent (agent=%p vm=%p name=%s)",
+ priv->agent, obj, obj->def->name);
+
+ virObjectLock(agent);
+ virObjectRef(agent);
+ virObjectUnlock(obj);
+
+ return agent;
+}
+
+
+static void
+bhyveDomainObjExitAgent(virDomainObj *obj, qemuAgent *agent)
+{
+ virObjectUnlock(agent);
+ virObjectUnref(agent);
+ virObjectLock(obj);
+
+ VIR_DEBUG("Exited agent (agent=%p vm=%p name=%s)",
+ agent, obj, obj->def->name);
+}
+
+
+static bool
+bhyveDomainAgentAvailable(virDomainObj *vm,
+ bool reportError)
+{
+ bhyveDomainObjPrivate *priv = vm->privateData;
+
+ if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_RUNNING) {
+ if (reportError) {
+ virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+ _("domain is not running"));
+ }
+ return false;
+ }
+
+ if (!priv->agent) {
+ if (bhyveFindAgentConfig(vm->def)) {
+ if (reportError) {
+ virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+ _("QEMU guest agent is not connected"));
+ }
+ return false;
+ } else {
+ if (reportError) {
+ virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
+ _("QEMU guest agent is not configured"));
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+
+static int
+bhyveDomainEnsureAgent(virDomainObj *vm,
+ bool reportError)
+{
+ bhyveDomainObjPrivate *priv = vm->privateData;
+
+ if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_RUNNING) {
+ if (reportError) {
+ virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+ _("domain is not running"));
+ }
+ return -1;
+ }
+
+ if (priv->agent)
+ return 0;
+
+ if (!priv->eventThread &&
+ virBhyveDomainObjStartWorker(vm) < 0)
+ return -1;
+
+ if (bhyveConnectAgent(NULL, vm) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+static int
+bhyveDomainGetHostnameAgent(virDomainObj *vm,
+ char **hostname)
+{
+ qemuAgent *agent;
+ int ret = -1;
+
+ if (virDomainObjBeginAgentJob(vm, VIR_AGENT_JOB_QUERY) < 0)
+ return -1;
+
+ if (virDomainObjCheckActive(vm) < 0)
+ goto endjob;
+
+ if (bhyveDomainEnsureAgent(vm, true) < 0)
+ goto endjob;
+
+ agent = bhyveDomainObjEnterAgent(vm);
+ ret = qemuAgentGetHostname(agent, hostname, true);
+ bhyveDomainObjExitAgent(vm, agent);
+
+ endjob:
+ virDomainObjEndAgentJob(vm);
+ return ret;
+}
+
+
+static char *
+bhyveDomainQemuAgentCommand(virDomainPtr domain,
+ const char *cmd,
+ int timeout,
+ unsigned int flags)
+{
+ virDomainObj *vm;
+ int ret = -1;
+ char *result = NULL;
+ qemuAgent *agent;
+
+ virCheckFlags(0, NULL);
+
+ if (!(vm = bhyveDomObjFromDomain(domain)))
+ goto cleanup;
+
+ if (virDomainQemuAgentCommandEnsureACL(domain->conn, vm->def) < 0)
+ goto cleanup;
+
+ if (virDomainObjBeginAgentJob(vm, VIR_AGENT_JOB_MODIFY) < 0)
+ goto cleanup;
+
+ if (virDomainObjCheckActive(vm) < 0)
+ goto endjob;
+
+ if (!bhyveDomainAgentAvailable(vm, true))
+ goto endjob;
+
+ agent = bhyveDomainObjEnterAgent(vm);
+ ret = qemuAgentArbitraryCommand(agent, cmd, &result, timeout);
+ bhyveDomainObjExitAgent(vm, agent);
+ if (ret < 0)
+ VIR_FREE(result);
+
+ endjob:
+ virDomainObjEndAgentJob(vm);
+
+ cleanup:
+ virDomainObjEndAPI(&vm);
+ return result;
+}
+
+
static int
bhyveDomainGetHostnameLease(virDomainObj *vm,
char **hostname)
@@ -1961,7 +2120,15 @@ bhyveDomainGetHostname(virDomainPtr domain,
virDomainObj *vm = NULL;
char *hostname = NULL;
- virCheckFlags(VIR_DOMAIN_GET_HOSTNAME_LEASE, NULL);
+ virCheckFlags(VIR_DOMAIN_GET_HOSTNAME_LEASE |
+ VIR_DOMAIN_GET_HOSTNAME_AGENT, NULL);
+
+ VIR_EXCLUSIVE_FLAGS_RET(VIR_DOMAIN_GET_HOSTNAME_LEASE,
+ VIR_DOMAIN_GET_HOSTNAME_AGENT,
+ NULL);
+
+ if (!(flags & VIR_DOMAIN_GET_HOSTNAME_AGENT))
+ flags |= VIR_DOMAIN_GET_HOSTNAME_LEASE;
if (!(vm = bhyveDomObjFromDomain(domain)))
return NULL;
@@ -1969,8 +2136,13 @@ bhyveDomainGetHostname(virDomainPtr domain,
if (virDomainGetHostnameEnsureACL(domain->conn, vm->def) < 0)
goto cleanup;
- if (bhyveDomainGetHostnameLease(vm, &hostname) < 0)
- goto cleanup;
+ if (flags & VIR_DOMAIN_GET_HOSTNAME_LEASE) {
+ if (bhyveDomainGetHostnameLease(vm, &hostname) < 0)
+ goto cleanup;
+ } else if (flags & VIR_DOMAIN_GET_HOSTNAME_AGENT) {
+ if (bhyveDomainGetHostnameAgent(vm, &hostname) < 0)
+ goto cleanup;
+ }
if (!hostname) {
virReportError(VIR_ERR_NO_HOSTNAME,
@@ -2052,6 +2224,7 @@ static virHypervisorDriver bhyveHypervisorDriver = {
.domainGetVcpuPinInfo = bhyveDomainGetVcpuPinInfo, /* 12.1.0 */
.domainInterfaceAddresses = bhyveDomainInterfaceAddresses, /* 12.3.0 */
.domainGetHostname = bhyveDomainGetHostname, /* 12.3.0 */
+ .domainQemuAgentCommand = bhyveDomainQemuAgentCommand, /* 12.4.0 */
};
diff --git a/src/bhyve/bhyve_process.c b/src/bhyve/bhyve_process.c
index 6078d995cd..fc97731510 100644
--- a/src/bhyve/bhyve_process.c
+++ b/src/bhyve/bhyve_process.c
@@ -171,6 +171,117 @@ bhyveSetResourceLimits(struct _bhyveConn *driver, virDomainObj *vm)
return 0;
}
+virDomainChrDef *
+bhyveFindAgentConfig(virDomainDef *def)
+{
+ size_t i;
+
+ for (i = 0; i < def->nchannels; i++) {
+ virDomainChrDef *channel = def->channels[i];
+
+ if (channel->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO)
+ continue;
+
+
+ if (STREQ_NULLABLE(channel->target.name, "org.qemu.guest_agent.0")) {
+ return channel;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+qemuProcessHandleAgentEOF(qemuAgent *agent,
+ virDomainObj *vm)
+{
+ bhyveDomainObjPrivate *priv;
+
+ virObjectLock(vm);
+ VIR_INFO("Received EOF from agent on %p '%s'", vm, vm->def->name);
+
+ priv = vm->privateData;
+
+ if (!priv->agent) {
+ VIR_DEBUG("Agent freed already");
+ goto unlock;
+ }
+
+ qemuAgentClose(agent);
+ priv->agent = NULL;
+ priv->agentError = false;
+
+ virObjectUnlock(vm);
+ return;
+
+ unlock:
+ virObjectUnlock(vm);
+ return;
+}
+
+/*
+ * This is invoked when there is some kind of error
+ * parsing data to/from the agent. The VM can continue
+ * to run, but no further agent commands will be
+ * allowed
+ */
+static void
+qemuProcessHandleAgentError(qemuAgent *agent G_GNUC_UNUSED,
+ virDomainObj *vm)
+{
+ bhyveDomainObjPrivate *priv;
+
+ virObjectLock(vm);
+ VIR_INFO("Received error from agent on %p '%s'", vm, vm->def->name);
+
+ priv = vm->privateData;
+
+ priv->agentError = true;
+
+ virObjectUnlock(vm);
+}
+
+static qemuAgentCallbacks agentCallbacks = {
+ .eofNotify = qemuProcessHandleAgentEOF,
+ .errorNotify = qemuProcessHandleAgentError,
+};
+
+int
+bhyveConnectAgent(struct _bhyveConn *driver G_GNUC_UNUSED, virDomainObj *vm)
+{
+ bhyveDomainObjPrivate *priv = vm->privateData;
+ qemuAgent *agent = NULL;
+ virDomainChrDef *config = bhyveFindAgentConfig(vm->def);
+
+ if (!config)
+ return 0;
+
+ if (priv->agent)
+ return 0;
+
+ agent = qemuAgentOpen(vm,
+ config->source,
+ virEventThreadGetContext(priv->eventThread),
+ &agentCallbacks);
+
+ if (!virDomainObjIsActive(vm)) {
+ qemuAgentClose(agent);
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("guest crashed while connecting to the guest agent"));
+ return -1;
+ }
+
+ priv->agent = agent;
+ if (!priv->agent) {
+ VIR_WARN("Cannot connect to QEMU guest agent for %s", vm->def->name);
+ priv->agentError = true;
+ virResetLastError();
+ }
+
+ return 0;
+}
+
+
static int
virBhyveProcessStartImpl(struct _bhyveConn *driver,
virDomainObj *vm,
@@ -293,6 +404,9 @@ virBhyveProcessStartImpl(struct _bhyveConn *driver,
virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, reason);
priv->mon = bhyveMonitorOpen(vm, driver);
+ if (virBhyveDomainObjStartWorker(vm) < 0)
+ goto cleanup;
+
if (virDomainObjSave(vm, driver->xmlopt,
BHYVE_STATE_DIR) < 0)
goto cleanup;
@@ -714,6 +828,9 @@ virBhyveProcessReconnect(virDomainObj *vm,
virDomainNetNotifyActualDevice(conn, vm->def, net);
}
+ if (virBhyveDomainObjStartWorker(vm) < 0)
+ goto cleanup;
+
cleanup:
if (ret < 0) {
/* If VM is reported to be in active state, but we cannot find
diff --git a/src/bhyve/bhyve_process.h b/src/bhyve/bhyve_process.h
index 5e0acc810c..bf82f748a6 100644
--- a/src/bhyve/bhyve_process.h
+++ b/src/bhyve/bhyve_process.h
@@ -56,6 +56,10 @@ int virBhyveGetDomainTotalCpuStats(virDomainObj *vm,
void virBhyveProcessReconnectAll(struct _bhyveConn *driver);
+int bhyveConnectAgent(struct _bhyveConn *driver, virDomainObj *vm);
+
+virDomainChrDef *bhyveFindAgentConfig(virDomainDef *def);
+
typedef enum {
VIR_BHYVE_PROCESS_START_AUTODESTROY = 1 << 0,
} bhyveProcessStartFlags;
diff --git a/src/bhyve/bhyve_qemu_agent.c b/src/bhyve/bhyve_qemu_agent.c
new file mode 100644
index 0000000000..619d8baf57
--- /dev/null
+++ b/src/bhyve/bhyve_qemu_agent.c
@@ -0,0 +1,1259 @@
+/*
+ * bhyve_qemu_agent.c: interaction with QEMU guest agent
+ *
+ * Copyright (C) 2006-2014 Red Hat, Inc.
+ * Copyright (C) 2006 Daniel P. Berrange
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <poll.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <gio/gio.h>
+
+#include "bhyve_qemu_agent.h"
+#include "bhyve_domain.h"
+#include "viralloc.h"
+#include "virlog.h"
+#include "virerror.h"
+#include "virjson.h"
+#include "virfile.h"
+#include "virtime.h"
+#include "virobject.h"
+#include "virstring.h"
+#include "virenum.h"
+#include "virutil.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("bhyve.bhyve_qemu_agent");
+
+#define LINE_ENDING "\n"
+
+/* We read from QEMU until seeing a \r\n pair to indicate a
+ * completed reply or event. To avoid memory denial-of-service
+ * though, we must have a size limit on amount of data we
+ * buffer. 10 MB is large enough that it ought to cope with
+ * normal QEMU replies, and small enough that we're not
+ * consuming unreasonable mem.
+ */
+#define QEMU_AGENT_MAX_RESPONSE (10 * 1024 * 1024)
+
+typedef struct _qemuAgentMessage qemuAgentMessage;
+struct _qemuAgentMessage {
+ char *txBuffer;
+ int txOffset;
+ int txLength;
+
+ /* Used by the JSON agent to hold reply / error */
+ char *rxBuffer;
+ int rxLength;
+ void *rxObject;
+
+ /* True if rxBuffer / rxObject are ready, or a
+ * fatal error occurred on the agent channel
+ */
+ bool finished;
+ /* true for sync command */
+ bool sync;
+ /* id of the issued sync command */
+ unsigned long long id;
+ bool first;
+};
+
+
+struct _qemuAgent {
+ virObjectLockable parent;
+
+ virCond notify;
+
+ int fd;
+
+ GMainContext *context;
+ GSocket *socket;
+ GSource *watch;
+
+ bool running;
+ bool inSync;
+
+ virDomainObj *vm;
+
+ qemuAgentCallbacks *cb;
+
+ /* If there's a command being processed this will be
+ * non-NULL */
+ qemuAgentMessage *msg;
+
+ /* Buffer incoming data ready for agent
+ * code to process & find message boundaries */
+ size_t bufferOffset;
+ size_t bufferLength;
+ char *buffer;
+
+ /* If anything went wrong, this will be fed back
+ * the next agent msg */
+ virError lastError;
+
+ /* Some guest agent commands don't return anything
+ * but fire up an event on qemu agent instead.
+ * Take that as indication of successful completion */
+ qemuAgentEvent await_event;
+ int timeout;
+};
+
+static virClass *qemuAgentClass;
+static void qemuAgentDispose(void *obj);
+
+static int qemuAgentOnceInit(void)
+{
+ if (!VIR_CLASS_NEW(qemuAgent, virClassForObjectLockable()))
+ return -1;
+
+ return 0;
+}
+
+VIR_ONCE_GLOBAL_INIT(qemuAgent);
+
+
+
+static void qemuAgentDispose(void *obj)
+{
+ qemuAgent *agent = obj;
+
+ VIR_DEBUG("agent=%p", agent);
+
+ if (agent->vm)
+ virObjectUnref(agent->vm);
+ virCondDestroy(&agent->notify);
+ g_free(agent->buffer);
+ g_main_context_unref(agent->context);
+ virResetError(&agent->lastError);
+}
+
+static int
+qemuAgentOpenUnix(const char *socketpath)
+{
+ struct sockaddr_un addr = { 0 };
+ int agentfd;
+
+ if ((agentfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ virReportSystemError(errno,
+ "%s", _("failed to create socket"));
+ return -1;
+ }
+
+ if (virSetCloseExec(agentfd) < 0) {
+ virReportSystemError(errno, "%s",
+ _("Unable to set agent close-on-exec flag"));
+ goto error;
+ }
+
+ addr.sun_family = AF_UNIX;
+ if (virStrcpyStatic(addr.sun_path, socketpath) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Socket path %1$s too big for destination"), socketpath);
+ goto error;
+ }
+
+ if (connect(agentfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ virReportSystemError(errno, "%s",
+ _("failed to connect to agent socket"));
+ goto error;
+ }
+
+ return agentfd;
+
+ error:
+ VIR_FORCE_CLOSE(agentfd);
+ return -1;
+}
+
+
+static int
+qemuAgentIOProcessEvent(qemuAgent *agent,
+ virJSONValue *obj)
+{
+ const char *type;
+ VIR_DEBUG("agent=%p obj=%p", agent, obj);
+
+ type = virJSONValueObjectGetString(obj, "event");
+ if (!type) {
+ VIR_WARN("missing event type in message");
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+qemuAgentIOProcessLine(qemuAgent *agent,
+ const char *line,
+ qemuAgentMessage *msg)
+{
+ g_autoptr(virJSONValue) obj = NULL;
+
+ VIR_DEBUG("Line [%s]", line);
+
+ if (!(obj = virJSONValueFromString(line))) {
+ /* receiving garbage on first sync is regular situation */
+ if (msg && msg->sync && msg->first) {
+ VIR_DEBUG("Received garbage on sync");
+ msg->finished = true;
+ return 0;
+ }
+
+ return -1;
+ }
+
+ if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Parsed JSON reply '%1$s' isn't an object"), line);
+ return -1;
+ }
+
+ if (virJSONValueObjectHasKey(obj, "QMP")) {
+ return 0;
+ } else if (virJSONValueObjectHasKey(obj, "event")) {
+ return qemuAgentIOProcessEvent(agent, obj);
+ } else if (virJSONValueObjectHasKey(obj, "error") ||
+ virJSONValueObjectHasKey(obj, "return")) {
+ if (msg) {
+ if (msg->sync) {
+ unsigned long long id;
+
+ if (virJSONValueObjectGetNumberUlong(obj, "return", &id) < 0) {
+ VIR_DEBUG("Ignoring delayed reply on sync");
+ return 0;
+ }
+
+ VIR_DEBUG("Guest returned ID: %llu", id);
+
+ if (msg->id != id) {
+ VIR_DEBUG("Guest agent returned ID: %llu instead of %llu",
+ id, msg->id);
+ return 0;
+ }
+ }
+ msg->rxObject = g_steal_pointer(&obj);
+ msg->finished = true;
+ } else {
+ /* we are out of sync */
+ VIR_DEBUG("Ignoring delayed reply");
+ }
+
+ return 0;
+ }
+
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unknown JSON reply '%1$s'"), line);
+ return -1;
+}
+
+static int qemuAgentIOProcessData(qemuAgent *agent,
+ char *data,
+ size_t len,
+ qemuAgentMessage *msg)
+{
+ int used = 0;
+ size_t i = 0;
+
+ while (used < len) {
+ char *nl = strstr(data + used, LINE_ENDING);
+
+ if (nl) {
+ int got = nl - (data + used);
+ for (i = 0; i < strlen(LINE_ENDING); i++)
+ data[used + got + i] = '\0';
+ if (qemuAgentIOProcessLine(agent, data + used, msg) < 0)
+ return -1;
+ used += got + strlen(LINE_ENDING);
+ } else {
+ break;
+ }
+ }
+
+ VIR_DEBUG("Total used %d bytes out of %zd available in buffer", used, len);
+ return used;
+}
+
+/* This method processes data that has been received
+ * from the agent. Looking for async events and
+ * replies/errors.
+ */
+static int
+qemuAgentIOProcess(qemuAgent *agent)
+{
+ int len;
+ qemuAgentMessage *msg = NULL;
+
+ /* See if there's a message ready for reply; that is,
+ * one that has completed writing all its data.
+ */
+ if (agent->msg && agent->msg->txOffset == agent->msg->txLength)
+ msg = agent->msg;
+
+ len = qemuAgentIOProcessData(agent,
+ agent->buffer, agent->bufferOffset,
+ msg);
+
+ if (len < 0)
+ return -1;
+
+ if (len < agent->bufferOffset) {
+ memmove(agent->buffer, agent->buffer + len, agent->bufferOffset - len);
+ agent->bufferOffset -= len;
+ } else {
+ VIR_FREE(agent->buffer);
+ agent->bufferOffset = agent->bufferLength = 0;
+ }
+ if (msg && msg->finished)
+ virCondBroadcast(&agent->notify);
+ return len;
+}
+
+
+/*
+ * Called when the agent is able to write data
+ * Call this function while holding the agent lock.
+ */
+static int
+qemuAgentIOWrite(qemuAgent *agent)
+{
+ int done;
+
+ /* If no active message, or fully transmitted, then no-op */
+ if (!agent->msg || agent->msg->txOffset == agent->msg->txLength)
+ return 0;
+
+ done = safewrite(agent->fd,
+ agent->msg->txBuffer + agent->msg->txOffset,
+ agent->msg->txLength - agent->msg->txOffset);
+
+ if (done < 0) {
+ if (errno == EAGAIN)
+ return 0;
+
+ virReportSystemError(errno, "%s",
+ _("Unable to write to agent"));
+ return -1;
+ }
+ agent->msg->txOffset += done;
+ return done;
+}
+
+/*
+ * Called when the agent has incoming data to read
+ * Call this function while holding the agent lock.
+ *
+ * Returns -1 on error, or number of bytes read
+ */
+static int
+qemuAgentIORead(qemuAgent *agent)
+{
+ size_t avail = agent->bufferLength - agent->bufferOffset;
+ int ret = 0;
+
+ if (avail < 1024) {
+ if (agent->bufferLength >= QEMU_AGENT_MAX_RESPONSE) {
+ virReportSystemError(ERANGE,
+ _("No complete agent response found in %1$d bytes"),
+ QEMU_AGENT_MAX_RESPONSE);
+ return -1;
+ }
+ VIR_REALLOC_N(agent->buffer, agent->bufferLength + 1024);
+ agent->bufferLength += 1024;
+ avail += 1024;
+ }
+
+ /* Read as much as we can get into our buffer,
+ until we block on EAGAIN, or hit EOF */
+ while (avail > 1) {
+ int got;
+ got = read(agent->fd,
+ agent->buffer + agent->bufferOffset,
+ avail - 1);
+ if (got < 0) {
+ if (errno == EAGAIN)
+ break;
+ virReportSystemError(errno, "%s",
+ _("Unable to read from agent"));
+ ret = -1;
+ break;
+ }
+ if (got == 0)
+ break;
+
+ ret += got;
+ avail -= got;
+ agent->bufferOffset += got;
+ agent->buffer[agent->bufferOffset] = '\0';
+ }
+
+ return ret;
+}
+
+
+static gboolean
+qemuAgentIO(GSocket *socket,
+ GIOCondition cond,
+ gpointer opaque);
+
+
+static void
+qemuAgentRegister(qemuAgent *agent)
+{
+ GIOCondition cond = 0;
+
+ if (agent->lastError.code == VIR_ERR_OK) {
+ cond |= G_IO_IN;
+
+ if (agent->msg && agent->msg->txOffset < agent->msg->txLength)
+ cond |= G_IO_OUT;
+ }
+
+ agent->watch = g_socket_create_source(agent->socket,
+ cond,
+ NULL);
+
+ virObjectRef(agent);
+ g_source_set_callback(agent->watch,
+ (GSourceFunc)qemuAgentIO,
+ agent,
+ (GDestroyNotify)virObjectUnref);
+
+ g_source_attach(agent->watch,
+ agent->context);
+}
+
+
+static void
+qemuAgentUnregister(qemuAgent *agent)
+{
+ if (agent->watch) {
+ g_source_destroy(agent->watch);
+ g_source_unref(agent->watch);
+ agent->watch = NULL;
+ }
+}
+
+
+static void qemuAgentUpdateWatch(qemuAgent *agent)
+{
+ qemuAgentUnregister(agent);
+ if (agent->socket)
+ qemuAgentRegister(agent);
+}
+
+
+static gboolean
+qemuAgentIO(GSocket *socket G_GNUC_UNUSED,
+ GIOCondition cond,
+ gpointer opaque)
+{
+ qemuAgent *agent = opaque;
+ bool error = false;
+ bool eof = false;
+
+ virObjectRef(agent);
+ /* lock access to the agent and protect fd */
+ virObjectLock(agent);
+
+ if (agent->fd == -1 || !agent->watch) {
+ virObjectUnlock(agent);
+ virObjectUnref(agent);
+ return G_SOURCE_REMOVE;
+ }
+
+ if (agent->lastError.code != VIR_ERR_OK) {
+ if (cond & (G_IO_HUP | G_IO_ERR))
+ eof = true;
+ error = true;
+ } else {
+ if (cond & G_IO_OUT) {
+ if (qemuAgentIOWrite(agent) < 0)
+ error = true;
+ }
+
+ if (!error &&
+ cond & G_IO_IN) {
+ int got = qemuAgentIORead(agent);
+ if (got < 0) {
+ error = true;
+ } else if (got == 0) {
+ eof = true;
+ } else {
+ /* Ignore hangup/error cond if we read some data, to
+ * give time for that data to be consumed */
+ cond = 0;
+
+ if (qemuAgentIOProcess(agent) < 0)
+ error = true;
+ }
+ }
+
+ if (!error &&
+ cond & G_IO_HUP) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("End of file from agent socket"));
+ eof = true;
+ }
+
+ if (!error && !eof &&
+ cond & G_IO_ERR) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Invalid file descriptor while waiting for agent"));
+ eof = true;
+ }
+ }
+
+ if (error || eof) {
+ if (agent->lastError.code != VIR_ERR_OK) {
+ /* Already have an error, so clear any new error */
+ virResetLastError();
+ } else {
+ if (virGetLastErrorCode() == VIR_ERR_OK)
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Error while processing agent IO"));
+ virCopyLastError(&agent->lastError);
+ virResetLastError();
+ }
+
+ VIR_DEBUG("Error on agent %s", NULLSTR(agent->lastError.message));
+ /* If IO process resulted in an error & we have a message,
+ * then wakeup that waiter */
+ if (agent->msg && !agent->msg->finished) {
+ agent->msg->finished = true;
+ virCondSignal(&agent->notify);
+ }
+ }
+
+ qemuAgentUpdateWatch(agent);
+
+ /* We have to unlock to avoid deadlock against command thread,
+ * but is this safe ? I think it is, because the callback
+ * will try to acquire the virDomainObj *mutex next */
+ if (eof) {
+ void (*eofNotify)(qemuAgent *, virDomainObj *)
+ = agent->cb->eofNotify;
+ virDomainObj *vm = agent->vm;
+
+ /* Make sure anyone waiting wakes up now */
+ virCondSignal(&agent->notify);
+ virObjectUnlock(agent);
+ virObjectUnref(agent);
+ VIR_DEBUG("Triggering EOF callback");
+ (eofNotify)(agent, vm);
+ } else if (error) {
+ void (*errorNotify)(qemuAgent *, virDomainObj *)
+ = agent->cb->errorNotify;
+ virDomainObj *vm = agent->vm;
+
+ /* Make sure anyone waiting wakes up now */
+ virCondSignal(&agent->notify);
+ virObjectUnlock(agent);
+ virObjectUnref(agent);
+ VIR_DEBUG("Triggering error callback");
+ (errorNotify)(agent, vm);
+ } else {
+ virObjectUnlock(agent);
+ virObjectUnref(agent);
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+
+qemuAgent *
+qemuAgentOpen(virDomainObj *vm,
+ const virDomainChrSourceDef *config,
+ GMainContext *context,
+ qemuAgentCallbacks *cb)
+{
+ qemuAgent *agent;
+ g_autoptr(GError) gerr = NULL;
+
+ if (!cb || !cb->eofNotify) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("EOF notify callback must be supplied"));
+ return NULL;
+ }
+
+ if (qemuAgentInitialize() < 0)
+ return NULL;
+
+ if (!(agent = virObjectLockableNew(qemuAgentClass)))
+ return NULL;
+
+ agent->timeout = BHYVE_DOMAIN_PRIVATE(vm)->agentTimeout;
+ agent->fd = -1;
+ if (virCondInit(&agent->notify) < 0) {
+ virReportSystemError(errno, "%s",
+ _("cannot initialize agent condition"));
+ virObjectUnref(agent);
+ return NULL;
+ }
+ agent->vm = virObjectRef(vm);
+ agent->cb = cb;
+
+ if (config->type != VIR_DOMAIN_CHR_TYPE_UNIX) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to handle agent type: %1$s"),
+ virDomainChrTypeToString(config->type));
+ goto cleanup;
+ }
+
+ virObjectUnlock(vm);
+ agent->fd = qemuAgentOpenUnix(config->data.nix.path);
+ virObjectLock(vm);
+
+ if (agent->fd == -1)
+ goto cleanup;
+
+ agent->context = g_main_context_ref(context);
+
+ agent->socket = g_socket_new_from_fd(agent->fd, &gerr);
+ if (!agent->socket) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to create socket object: %1$s"),
+ gerr->message);
+ goto cleanup;
+ }
+
+ qemuAgentRegister(agent);
+
+ agent->running = true;
+ VIR_DEBUG("New agent %p fd=%d", agent, agent->fd);
+
+ return agent;
+
+ cleanup:
+ qemuAgentClose(agent);
+ return NULL;
+}
+
+
+static void
+qemuAgentNotifyCloseLocked(qemuAgent *agent)
+{
+ if (agent) {
+ agent->running = false;
+
+ /* If there is somebody waiting for a message
+ * wake him up. No message will arrive anyway. */
+ if (agent->msg && !agent->msg->finished) {
+ agent->msg->finished = true;
+ virCondSignal(&agent->notify);
+ }
+ }
+}
+
+
+void
+qemuAgentNotifyClose(qemuAgent *agent)
+{
+ if (!agent)
+ return;
+
+ VIR_DEBUG("agent=%p", agent);
+
+ VIR_WITH_OBJECT_LOCK_GUARD(agent) {
+ qemuAgentNotifyCloseLocked(agent);
+ }
+}
+
+
+void qemuAgentClose(qemuAgent *agent)
+{
+ if (!agent)
+ return;
+
+ VIR_DEBUG("agent=%p", agent);
+
+ VIR_WITH_OBJECT_LOCK_GUARD(agent) {
+ if (agent->socket) {
+ qemuAgentUnregister(agent);
+ g_clear_pointer(&agent->socket, g_object_unref);
+ agent->fd = -1;
+ }
+
+ qemuAgentNotifyCloseLocked(agent);
+ }
+
+ virObjectUnref(agent);
+}
+
+#define QEMU_AGENT_WAIT_TIME 5
+
+/**
+ * qemuAgentSend:
+ * @agent: agent object
+ * @msg: Message
+ * @seconds: number of seconds to wait for the result, it can be either
+ * -2, -1, 0 or positive.
+ * @report_sync: On timeout; report synchronization error instead of the normal error
+ *
+ * Send @msg to agent @agent. If @seconds is equal to
+ * VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK(-2), this function will block forever
+ * waiting for the result. The value of
+ * VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT(-1) means use default timeout value
+ * and VIR_DOMAIN_QEMU_AGENT_COMMAND_NOWAIT(0) makes this function return
+ * immediately without waiting. Any positive value means the number of seconds
+ * to wait for the result.
+ *
+ * Returns: 0 on success,
+ * -2 on timeout,
+ * -1 otherwise
+ */
+static int
+qemuAgentSend(qemuAgent *agent,
+ qemuAgentMessage *msg,
+ int seconds,
+ bool report_sync)
+{
+ int ret = -1;
+ unsigned long long then = 0;
+
+ VIR_INFO("qemuAgentSend: seconds=%d", seconds);
+
+ /* Check whether qemu quit unexpectedly */
+ if (agent->lastError.code != VIR_ERR_OK) {
+ VIR_DEBUG("Attempt to send command while error is set %s",
+ NULLSTR(agent->lastError.message));
+ virSetError(&agent->lastError);
+ return -1;
+ }
+
+ if (seconds > VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) {
+ unsigned long long now;
+ if (virTimeMillisNow(&now) < 0)
+ return -1;
+ if (seconds == VIR_DOMAIN_QEMU_AGENT_COMMAND_DEFAULT)
+ seconds = QEMU_AGENT_WAIT_TIME;
+ then = now + seconds * 1000ull;
+ }
+
+ agent->msg = msg;
+ qemuAgentUpdateWatch(agent);
+
+ while (!agent->msg->finished) {
+ if ((then && virCondWaitUntil(&agent->notify, &agent->parent.lock, then) < 0) ||
+ (!then && virCondWait(&agent->notify, &agent->parent.lock) < 0)) {
+ if (errno == ETIMEDOUT) {
+ if (report_sync) {
+ virReportError(VIR_ERR_AGENT_UNRESPONSIVE,
+ _("guest agent didn't respond to synchronization within '%1$d' seconds"),
+ seconds);
+ } else {
+ virReportError(VIR_ERR_AGENT_COMMAND_TIMEOUT,
+ _("guest agent didn't respond to command within '%1$d' seconds"),
+ seconds);
+ }
+ ret = -2;
+ } else {
+ virReportSystemError(errno, "%s",
+ _("Unable to wait on agent socket condition"));
+ }
+ agent->inSync = false;
+ goto cleanup;
+ }
+ }
+
+ if (agent->lastError.code != VIR_ERR_OK) {
+ VIR_DEBUG("Send command resulted in error %s",
+ NULLSTR(agent->lastError.message));
+ virSetError(&agent->lastError);
+ goto cleanup;
+ }
+
+ ret = 0;
+
+ cleanup:
+ agent->msg = NULL;
+ qemuAgentUpdateWatch(agent);
+
+ return ret;
+}
+
+
+/**
+ * qemuAgentGuestSyncSend:
+ * @agent: agent object
+ * @timeout: timeout for the command
+ * @first: true when this is the first invocation to drain possible leftovers
+ * from the pipe
+ *
+ * Sends a sync request to the guest agent.
+ * Returns: -1 on error
+ * 0 on successful send, but when no reply was received
+ * 1 when a reply was received
+ */
+static int
+qemuAgentGuestSyncSend(qemuAgent *agent,
+ int timeout,
+ bool first)
+{
+ g_autofree char *txMsg = NULL;
+ g_autoptr(virJSONValue) rxObj = NULL;
+ unsigned long long id;
+ qemuAgentMessage sync_msg = { 0 };
+ int rc;
+
+ if (virTimeMillisNow(&id) < 0)
+ return -1;
+
+ txMsg = g_strdup_printf("{\"execute\":\"guest-sync\", "
+ "\"arguments\":{\"id\":%llu}}\n", id);
+
+ sync_msg.txBuffer = txMsg;
+ sync_msg.txLength = strlen(txMsg);
+ sync_msg.sync = true;
+ sync_msg.id = id;
+ sync_msg.first = first;
+
+ VIR_DEBUG("Sending guest-sync command with ID: %llu", id);
+
+ rc = qemuAgentSend(agent, &sync_msg, timeout, true);
+ rxObj = g_steal_pointer(&sync_msg.rxObject);
+
+ VIR_DEBUG("qemuAgentSend returned: %d", rc);
+
+ if (rc < 0)
+ return -1;
+
+ if (rxObj)
+ return 1;
+
+ return 0;
+}
+
+
+/**
+ * qemuAgentGuestSync:
+ * @agent: agent object
+ * @seconds: qemu agent command timeout value
+ *
+ * Send guest-sync with unique ID
+ * and wait for reply. If we get one, check if
+ * received ID is equal to given.
+ *
+ * Returns: 0 on success,
+ * -1 otherwise
+ */
+static int
+qemuAgentGuestSync(qemuAgent *agent,
+ int seconds)
+{
+ int timeout = QEMU_AGENT_WAIT_TIME;
+ int rc;
+
+ if (agent->inSync)
+ return 0;
+
+ /* if user specified a custom agent timeout that is lower than the
+ * default timeout, use the shorter timeout instead */
+ if ((agent->timeout >= 0) && (agent->timeout < timeout))
+ timeout = agent->timeout;
+
+ /* If user specified a timeout parameter smaller than both default
+ * value and agent->timeout in qga APIs(such as qemu-agent-command),
+ * use the parameter timeout value */
+ if ((seconds >= 0) && (seconds < timeout))
+ timeout = seconds;
+
+ if ((rc = qemuAgentGuestSyncSend(agent, timeout, true)) < 0)
+ return -1;
+
+ /* successfully sync'd */
+ if (rc == 1) {
+ agent->inSync = true;
+ return 0;
+ }
+
+ /* send another sync */
+ if ((rc = qemuAgentGuestSyncSend(agent, timeout, false)) < 0)
+ return -1;
+
+ /* successfully sync'd */
+ if (rc == 1) {
+ agent->inSync = true;
+ return 0;
+ }
+
+ if (agent->running)
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Missing agent reply object"));
+ else
+ virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+ _("Guest agent disappeared while executing command"));
+
+ return -1;
+}
+
+static const char *
+qemuAgentStringifyErrorClass(const char *klass)
+{
+ if (STREQ_NULLABLE(klass, "BufferOverrun"))
+ return "Buffer overrun";
+ else if (STREQ_NULLABLE(klass, "CommandDisabled"))
+ return "The command has been disabled for this instance";
+ else if (STREQ_NULLABLE(klass, "CommandNotFound"))
+ return "The command has not been found";
+ else if (STREQ_NULLABLE(klass, "FdNotFound"))
+ return "File descriptor not found";
+ else if (STREQ_NULLABLE(klass, "InvalidParameter"))
+ return "Invalid parameter";
+ else if (STREQ_NULLABLE(klass, "InvalidParameterType"))
+ return "Invalid parameter type";
+ else if (STREQ_NULLABLE(klass, "InvalidParameterValue"))
+ return "Invalid parameter value";
+ else if (STREQ_NULLABLE(klass, "OpenFileFailed"))
+ return "Cannot open file";
+ else if (STREQ_NULLABLE(klass, "QgaCommandFailed"))
+ return "Guest agent command failed";
+ else if (STREQ_NULLABLE(klass, "QMPBadInputObjectMember"))
+ return "Bad QMP input object member";
+ else if (STREQ_NULLABLE(klass, "QMPExtraInputObjectMember"))
+ return "Unexpected extra object member";
+ else if (STREQ_NULLABLE(klass, "UndefinedError"))
+ return "An undefined error has occurred";
+ else if (STREQ_NULLABLE(klass, "Unsupported"))
+ return "this feature or command is not currently supported";
+ else if (klass)
+ return klass;
+ else
+ return "unknown QEMU command error";
+}
+
+/* Ignoring OOM in this method, since we're already reporting
+ * a more important error
+ *
+ * XXX see qerror.h for different klasses & fill out useful params
+ */
+static const char *
+qemuAgentStringifyError(virJSONValue *error)
+{
+ const char *klass = virJSONValueObjectGetString(error, "class");
+ const char *detail = virJSONValueObjectGetString(error, "desc");
+
+ /* The QMP 'desc' field is usually sufficient for our generic
+ * error reporting needs. However, if not present, translate
+ * the class into something readable.
+ */
+ if (!detail)
+ detail = qemuAgentStringifyErrorClass(klass);
+
+ return detail;
+}
+
+static const char *
+qemuAgentCommandName(virJSONValue *cmd)
+{
+ const char *name = virJSONValueObjectGetString(cmd, "execute");
+ if (name)
+ return name;
+ return "<unknown>";
+}
+
+static int
+qemuAgentCheckError(virJSONValue *cmd,
+ virJSONValue *reply,
+ bool report_unsupported)
+{
+ if (virJSONValueObjectHasKey(reply, "error")) {
+ virJSONValue *error = virJSONValueObjectGet(reply, "error");
+ g_autofree char *cmdstr = virJSONValueToString(cmd, false);
+ g_autofree char *replystr = virJSONValueToString(reply, false);
+
+ /* Log the full JSON formatted command & error */
+ VIR_DEBUG("unable to execute QEMU agent command %s: %s",
+ NULLSTR(cmdstr), NULLSTR(replystr));
+
+ /* Only send the user the command name + friendly error */
+ if (!error) {
+ virReportError(VIR_ERR_AGENT_COMMAND_FAILED,
+ _("unable to execute QEMU agent command '%1$s'"),
+ qemuAgentCommandName(cmd));
+ return -1;
+ }
+
+ if (!report_unsupported) {
+ const char *klass = virJSONValueObjectGetString(error, "class");
+
+ if (STREQ_NULLABLE(klass, "CommandNotFound") ||
+ STREQ_NULLABLE(klass, "CommandDisabled"))
+ return -2;
+ }
+
+ virReportError(VIR_ERR_AGENT_COMMAND_FAILED,
+ _("unable to execute QEMU agent command '%1$s': %2$s"),
+ qemuAgentCommandName(cmd),
+ qemuAgentStringifyError(error));
+
+ return -1;
+ }
+ if (!virJSONValueObjectHasKey(reply, "return")) {
+ g_autofree char *cmdstr = virJSONValueToString(cmd, false);
+ g_autofree char *replystr = virJSONValueToString(reply, false);
+
+ VIR_DEBUG("Neither 'return' nor 'error' is set in the JSON reply %s: %s",
+ NULLSTR(cmdstr), NULLSTR(replystr));
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("QEMU agent command '%1$s' returned neither error nor success"),
+ qemuAgentCommandName(cmd));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+qemuAgentCommandFull(qemuAgent *agent,
+ virJSONValue *cmd,
+ virJSONValue **reply,
+ int seconds,
+ bool report_unsupported)
+{
+ int ret = -1;
+ qemuAgentMessage msg = { 0 };
+ g_autofree char *cmdstr = NULL;
+ int await_event = agent->await_event;
+
+ *reply = NULL;
+
+ if (!agent->running) {
+ virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s",
+ _("Guest agent disappeared while executing command"));
+ goto cleanup;
+ }
+
+ if (qemuAgentGuestSync(agent, seconds) < 0)
+ goto cleanup;
+
+ if (!(cmdstr = virJSONValueToString(cmd, false)))
+ goto cleanup;
+ msg.txBuffer = g_strdup_printf("%s" LINE_ENDING, cmdstr);
+ msg.txLength = strlen(msg.txBuffer);
+
+ VIR_DEBUG("Send command '%s' for write, seconds = %d", cmdstr, seconds);
+
+ ret = qemuAgentSend(agent, &msg, seconds, false);
+
+ VIR_DEBUG("Receive command reply ret=%d rxObject=%p",
+ ret, msg.rxObject);
+
+ if (ret < 0)
+ goto cleanup;
+
+ /* If we haven't obtained any reply but we wait for an
+ * event, then don't report this as error */
+ if (!msg.rxObject) {
+ if (!await_event) {
+ if (agent->running) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Missing agent reply object"));
+ } else {
+ virReportError(VIR_ERR_AGENT_COMMAND_TIMEOUT, "%s",
+ _("Guest agent disappeared while executing command"));
+ }
+ ret = -1;
+ }
+ goto cleanup;
+ }
+
+ *reply = msg.rxObject;
+ ret = qemuAgentCheckError(cmd, *reply, report_unsupported);
+
+ cleanup:
+ VIR_FREE(msg.txBuffer);
+ agent->await_event = QEMU_AGENT_EVENT_NONE;
+
+ return ret;
+}
+
+static int
+qemuAgentCommand(qemuAgent *agent,
+ virJSONValue *cmd,
+ virJSONValue **reply,
+ int seconds)
+{
+ return qemuAgentCommandFull(agent, cmd, reply, seconds, true);
+}
+
+static virJSONValue *G_GNUC_NULL_TERMINATED
+qemuAgentMakeCommand(const char *cmdname,
+ ...)
+{
+ g_autoptr(virJSONValue) obj = NULL;
+ g_autoptr(virJSONValue) jargs = NULL;
+ va_list args;
+
+ va_start(args, cmdname);
+
+ if (virJSONValueObjectAddVArgs(&jargs, args) < 0) {
+ va_end(args);
+ return NULL;
+ }
+
+ va_end(args);
+
+ if (virJSONValueObjectAdd(&obj,
+ "s:execute", cmdname,
+ "A:arguments", &jargs,
+ NULL) < 0)
+ return NULL;
+
+ return g_steal_pointer(&obj);
+}
+
+static virJSONValue *
+qemuAgentMakeStringsArray(const char **strings, unsigned int len)
+{
+ size_t i;
+ g_autoptr(virJSONValue) ret = virJSONValueNewArray();
+
+ for (i = 0; i < len; i++) {
+ if (virJSONValueArrayAppendString(ret, strings[i]) < 0)
+ return NULL;
+ }
+
+ return g_steal_pointer(&ret);
+}
+
+void qemuAgentNotifyEvent(qemuAgent *agent,
+ qemuAgentEvent event)
+{
+ VIR_LOCK_GUARD lock = virObjectLockGuard(agent);
+
+ VIR_DEBUG("agent=%p event=%d await_event=%d", agent, event, agent->await_event);
+ if (agent->await_event == event) {
+ agent->await_event = QEMU_AGENT_EVENT_NONE;
+ /* somebody waiting for this event, wake him up. */
+ if (agent->msg && !agent->msg->finished) {
+ agent->msg->finished = true;
+ virCondSignal(&agent->notify);
+ }
+ }
+}
+
+
+int
+qemuAgentArbitraryCommand(qemuAgent *agent,
+ const char *cmd_str,
+ char **result,
+ int timeout)
+{
+ int rc;
+ g_autoptr(virJSONValue) cmd = NULL;
+ g_autoptr(virJSONValue) reply = NULL;
+
+ *result = NULL;
+ if (timeout < VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN) {
+ virReportError(VIR_ERR_INVALID_ARG,
+ _("guest agent timeout '%1$d' is less than the minimum '%2$d'"),
+ timeout, VIR_DOMAIN_QEMU_AGENT_COMMAND_MIN);
+ return -1;
+ }
+
+ if (!(cmd = virJSONValueFromString(cmd_str)))
+ return -1;
+
+ if ((rc = qemuAgentCommand(agent, cmd, &reply, timeout)) < 0)
+ return rc;
+
+ if (!(*result = virJSONValueToString(reply, false)))
+ return -1;
+
+ return rc;
+}
+
+
+/**
+ * qemuAgentGetHostname:
+ *
+ * Gets the guest hostname using the guest agent.
+ *
+ * Returns 0 on success and fills @hostname. On error -1 is returned with an
+ * error reported and if '@report_unsupported' is false -2 is returned if the
+ * guest agent does not support the command without reporting an error
+ */
+int
+qemuAgentGetHostname(qemuAgent *agent,
+ char **hostname,
+ bool report_unsupported)
+{
+ g_autoptr(virJSONValue) cmd = qemuAgentMakeCommand("guest-get-host-name", NULL);
+ g_autoptr(virJSONValue) reply = NULL;
+ virJSONValue *data = NULL;
+ const char *result = NULL;
+ int rc;
+
+ if (!cmd)
+ return -1;
+
+ if ((rc = qemuAgentCommandFull(agent, cmd, &reply, agent->timeout,
+ report_unsupported)) < 0)
+ return rc;
+
+ if (!(data = virJSONValueObjectGet(reply, "return"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("malformed return value"));
+ return -1;
+ }
+
+ if (!(result = virJSONValueObjectGetString(data, "host-name"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("'host-name' missing in guest-get-host-name reply"));
+ return -1;
+ }
+
+ *hostname = g_strdup(result);
+
+ return 0;
+}
+
+
+int
+qemuAgentGetTime(qemuAgent *agent,
+ long long *seconds,
+ unsigned int *nseconds)
+{
+ unsigned long long json_time;
+ g_autoptr(virJSONValue) cmd = NULL;
+ g_autoptr(virJSONValue) reply = NULL;
+
+ cmd = qemuAgentMakeCommand("guest-get-time",
+ NULL);
+ if (!cmd)
+ return -1;
+
+ if (qemuAgentCommand(agent, cmd, &reply, agent->timeout) < 0)
+ return -1;
+
+ if (virJSONValueObjectGetNumberUlong(reply, "return", &json_time) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("malformed return value"));
+ return -1;
+ }
+
+ /* guest agent returns time in nanoseconds,
+ * we need it in seconds here */
+ *seconds = json_time / 1000000000LL;
+ *nseconds = json_time % 1000000000LL;
+ return 0;
+}
diff --git a/src/bhyve/bhyve_qemu_agent.h b/src/bhyve/bhyve_qemu_agent.h
new file mode 100644
index 0000000000..0e1752a2cb
--- /dev/null
+++ b/src/bhyve/bhyve_qemu_agent.h
@@ -0,0 +1,197 @@
+/*
+ * bhyve_qemu_agent.h: interaction with QEMU guest agent
+ *
+ * Copyright (C) 2006-2012 Red Hat, Inc.
+ * Copyright (C) 2006 Daniel P. Berrange
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "internal.h"
+#include "domain_conf.h"
+
+typedef struct _qemuAgent qemuAgent;
+
+typedef struct _qemuAgentCallbacks qemuAgentCallbacks;
+struct _qemuAgentCallbacks {
+ void (*eofNotify)(qemuAgent *mon,
+ virDomainObj *vm);
+ void (*errorNotify)(qemuAgent *mon,
+ virDomainObj *vm);
+};
+
+
+qemuAgent *qemuAgentOpen(virDomainObj *vm,
+ const virDomainChrSourceDef *config,
+ GMainContext *context,
+ qemuAgentCallbacks *cb);
+
+void qemuAgentClose(qemuAgent *mon);
+
+void qemuAgentNotifyClose(qemuAgent *mon);
+
+typedef enum {
+ QEMU_AGENT_EVENT_NONE = 0,
+ QEMU_AGENT_EVENT_SHUTDOWN,
+ QEMU_AGENT_EVENT_SUSPEND,
+ QEMU_AGENT_EVENT_RESET,
+} qemuAgentEvent;
+
+void qemuAgentNotifyEvent(qemuAgent *mon,
+ qemuAgentEvent event);
+
+typedef enum {
+ QEMU_AGENT_SHUTDOWN_POWERDOWN,
+ QEMU_AGENT_SHUTDOWN_REBOOT,
+ QEMU_AGENT_SHUTDOWN_HALT,
+
+ QEMU_AGENT_SHUTDOWN_LAST,
+} qemuAgentShutdownMode;
+
+typedef struct _qemuAgentDiskAddress qemuAgentDiskAddress;
+struct _qemuAgentDiskAddress {
+ char *serial;
+ virPCIDeviceAddress pci_controller;
+ char *bus_type;
+ unsigned int bus;
+ unsigned int target;
+ unsigned int unit;
+ char *devnode;
+ virCCWDeviceAddress *ccw_addr;
+};
+void qemuAgentDiskAddressFree(qemuAgentDiskAddress *addr);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuAgentDiskAddress, qemuAgentDiskAddressFree);
+
+typedef struct _qemuAgentDiskInfo qemuAgentDiskInfo;
+struct _qemuAgentDiskInfo {
+ char *name;
+ bool partition;
+ char **dependencies;
+ qemuAgentDiskAddress *address;
+ char *alias;
+};
+void qemuAgentDiskInfoFree(qemuAgentDiskInfo *info);
+
+typedef struct _qemuAgentFSInfo qemuAgentFSInfo;
+struct _qemuAgentFSInfo {
+ char *mountpoint; /* path to mount point */
+ char *name; /* device name in the guest (e.g. "sda1") */
+ char *fstype; /* filesystem type */
+ long long total_bytes;
+ long long used_bytes;
+ size_t ndisks;
+ qemuAgentDiskAddress **disks;
+};
+void qemuAgentFSInfoFree(qemuAgentFSInfo *info);
+
+int qemuAgentShutdown(qemuAgent *mon,
+ qemuAgentShutdownMode mode);
+
+int qemuAgentFSFreeze(qemuAgent *mon,
+ const char **mountpoints, unsigned int nmountpoints);
+int qemuAgentFSThaw(qemuAgent *mon);
+int qemuAgentGetFSInfo(qemuAgent *mon,
+ qemuAgentFSInfo ***info,
+ bool report_unsupported);
+
+int qemuAgentSuspend(qemuAgent *mon,
+ unsigned int target);
+
+int qemuAgentArbitraryCommand(qemuAgent *mon,
+ const char *cmd,
+ char **result,
+ int timeout);
+int qemuAgentFSTrim(qemuAgent *mon,
+ unsigned long long minimum);
+
+
+typedef struct _qemuAgentCPUInfo qemuAgentCPUInfo;
+struct _qemuAgentCPUInfo {
+ unsigned int id; /* logical cpu ID */
+ bool online; /* true if the CPU is activated */
+ bool offlinable; /* true if the CPU can be offlined */
+
+ bool modified; /* set to true if the vcpu state needs to be changed */
+};
+
+int qemuAgentGetVCPUs(qemuAgent *mon, qemuAgentCPUInfo **info);
+int qemuAgentSetVCPUs(qemuAgent *mon, qemuAgentCPUInfo *cpus, size_t ncpus);
+int qemuAgentUpdateCPUInfo(unsigned int nvcpus,
+ qemuAgentCPUInfo *cpuinfo,
+ int ncpuinfo);
+
+int
+qemuAgentGetHostname(qemuAgent *mon,
+ char **hostname,
+ bool report_unsupported);
+
+int qemuAgentGetTime(qemuAgent *mon,
+ long long *seconds,
+ unsigned int *nseconds);
+int qemuAgentSetTime(qemuAgent *mon,
+ long long seconds,
+ unsigned int nseconds,
+ bool sync);
+
+int qemuAgentGetInterfaces(qemuAgent *mon,
+ virDomainInterfacePtr **ifaces,
+ bool report_unsupported);
+
+int qemuAgentSetUserPassword(qemuAgent *mon,
+ const char *user,
+ const char *password,
+ bool crypted);
+
+int qemuAgentGetUsers(qemuAgent *mon,
+ virTypedParamList *list,
+ bool report_unsupported);
+
+int qemuAgentGetOSInfo(qemuAgent *mon,
+ virTypedParamList *list,
+ bool report_unsupported);
+
+int qemuAgentGetTimezone(qemuAgent *mon,
+ virTypedParamList *list,
+ bool report_unsupported);
+
+void qemuAgentSetResponseTimeout(qemuAgent *mon,
+ int timeout);
+
+int qemuAgentSSHGetAuthorizedKeys(qemuAgent *agent,
+ const char *user,
+ char ***keys);
+
+int qemuAgentSSHAddAuthorizedKeys(qemuAgent *agent,
+ const char *user,
+ const char **keys,
+ size_t nkeys,
+ bool reset);
+
+int qemuAgentSSHRemoveAuthorizedKeys(qemuAgent *agent,
+ const char *user,
+ const char **keys,
+ size_t nkeys);
+
+int qemuAgentGetDisks(qemuAgent *mon,
+ qemuAgentDiskInfo ***disks,
+ bool report_unsupported);
+
+int qemuAgentGetLoadAvg(qemuAgent *agent,
+ double *load1m,
+ double *load5m,
+ double *load15m,
+ bool report_unsupported);
diff --git a/src/bhyve/meson.build b/src/bhyve/meson.build
index 11920d9c3e..a1f0aa63b2 100644
--- a/src/bhyve/meson.build
+++ b/src/bhyve/meson.build
@@ -2,13 +2,14 @@ bhyve_sources = files(
'bhyve_capabilities.c',
'bhyve_command.c',
'bhyve_conf.c',
- 'bhyve_firmware.c',
- 'bhyve_parse_command.c',
'bhyve_device.c',
'bhyve_domain.c',
'bhyve_driver.c',
+ 'bhyve_firmware.c',
'bhyve_monitor.c',
+ 'bhyve_parse_command.c',
'bhyve_process.c',
+ 'bhyve_qemu_agent.c',
)
driver_source_files += bhyve_sources
diff --git a/src/remote/qemu_protocol.x b/src/remote/qemu_protocol.x
index c7f3abfcbf..757e54efcc 100644
--- a/src/remote/qemu_protocol.x
+++ b/src/remote/qemu_protocol.x
@@ -44,17 +44,6 @@ struct qemu_domain_attach_ret {
remote_nonnull_domain dom;
};
-struct qemu_domain_agent_command_args {
- remote_nonnull_domain dom;
- remote_nonnull_string cmd;
- int timeout;
- unsigned int flags;
-};
-
-struct qemu_domain_agent_command_ret {
- remote_string result;
-};
-
struct qemu_connect_domain_monitor_event_register_args {
remote_domain dom;
@@ -135,13 +124,6 @@ enum qemu_procedure {
*/
QEMU_PROC_DOMAIN_ATTACH = 2,
- /**
- * @generate: both
- * @priority: low
- * @acl: domain:write
- */
- QEMU_PROC_DOMAIN_AGENT_COMMAND = 3,
-
/**
* @generate: none
* @priority: high
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 38a83c64ea..a3a0920061 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -4009,6 +4009,17 @@ struct remote_domain_event_nic_mac_change_msg {
remote_nonnull_string newMAC;
};
+struct remote_domain_qemu_agent_command_args {
+ remote_nonnull_domain dom;
+ remote_nonnull_string cmd;
+ int timeout;
+ unsigned int flags;
+};
+
+struct remote_domain_qemu_agent_command_ret {
+ remote_string result;
+};
+
/*----- Protocol. -----*/
/* Define the program number, protocol version and procedure numbers here. */
@@ -7120,5 +7131,12 @@ enum remote_procedure {
* @generate: both
* @acl: none
*/
- REMOTE_PROC_DOMAIN_EVENT_NIC_MAC_CHANGE = 453
+ REMOTE_PROC_DOMAIN_EVENT_NIC_MAC_CHANGE = 453,
+
+ /**
+ * @generate: both
+ * @priority: low
+ * @acl: domain:write
+ */
+ REMOTE_PROC_DOMAIN_QEMU_AGENT_COMMAND = 454
};
--
2.52.0
2
1
During the v10.2 development cycle we deprecated MIPS host
support (mostly because Debian which we use to test in our
CI dropped the architecture when releasing Debian 13).
This series removes host support for MIPS.
Philippe Mathieu-Daudé (5):
buildsys: Remove MIPS cross containers
buildsys: Remove support for MIPS hosts
hw/mips: Include missing 'cpu.h' header
buildsys: Remove MIPS KVM
buildsys: Remove MIPS TCG backend
MAINTAINERS | 15 -
docs/about/deprecated.rst | 8 -
docs/about/removed-features.rst | 6 +
docs/system/target-mips.rst | 2 -
configure | 20 -
meson.build | 12 +-
include/qemu/timer.h | 30 -
include/user/thunk.h | 2 +-
linux-user/include/host/mips64/host-signal.h | 75 -
target/mips/kvm_mips.h | 28 -
tcg/mips64/tcg-target-con-set.h | 30 -
tcg/mips64/tcg-target-con-str.h | 20 -
tcg/mips64/tcg-target-has.h | 69 -
tcg/mips64/tcg-target-mo.h | 13 -
tcg/mips64/tcg-target.h | 75 -
tcg/mips64/tcg-target-opc.h.inc | 1 -
hw/intc/mips_gic.c | 11 +-
hw/mips/loongson3_virt.c | 67 +-
hw/mips/malta.c | 1 -
hw/mips/mips_int.c | 7 +-
linux-user/mmap.c | 2 +-
target/mips/cpu.c | 5 -
target/mips/kvm.c | 1283 --------
target/mips/system/cp0_timer.c | 12 +-
target/mips/system/physaddr.c | 1 -
util/cacheflush.c | 16 -
tcg/mips64/tcg-target.c.inc | 2605 -----------------
.gitlab-ci.d/container-cross.yml | 6 -
.gitlab-ci.d/containers.yml | 1 -
.gitlab-ci.d/crossbuilds.yml | 22 -
target/mips/meson.build | 6 +-
tests/docker/Makefile.include | 1 -
.../dockerfiles/debian-mips64el-cross.docker | 187 --
.../dockerfiles/debian-mipsel-cross.docker | 187 --
tests/lcitool/refresh | 12 -
35 files changed, 32 insertions(+), 4806 deletions(-)
delete mode 100644 linux-user/include/host/mips64/host-signal.h
delete mode 100644 target/mips/kvm_mips.h
delete mode 100644 tcg/mips64/tcg-target-con-set.h
delete mode 100644 tcg/mips64/tcg-target-con-str.h
delete mode 100644 tcg/mips64/tcg-target-has.h
delete mode 100644 tcg/mips64/tcg-target-mo.h
delete mode 100644 tcg/mips64/tcg-target.h
delete mode 100644 tcg/mips64/tcg-target-opc.h.inc
delete mode 100644 target/mips/kvm.c
delete mode 100644 tcg/mips64/tcg-target.c.inc
delete mode 100644 tests/docker/dockerfiles/debian-mips64el-cross.docker
delete mode 100644 tests/docker/dockerfiles/debian-mipsel-cross.docker
--
2.53.0
2
8