[PATCH v2 0/6] Add guest device info to virDomainGetGuestInfo
Rebased and slightly amended version of: https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/6V6K7... diff to v1: - Rebase, because the original series no longer applies cleanly - Implement the device info for the bhyve driver too Michal Prívozník (6): Add guest device info to virDomainGetGuestInfo qemu_agent: Introduce guest-get-info qemuagenttest: Introduce GetGuestDeviceInfo test case qemu: Implement device info for virDomainGetGuestInfo() API bhyve: Implement device info for virDomainGetGuestInfo() API virsh: Add support for VIR_DOMAIN_GUEST_INFO_DEVICES docs/manpages/virsh.rst | 17 +++- include/libvirt/libvirt-domain.h | 78 ++++++++++++++++ src/bhyve/bhyve_driver.c | 21 ++++- src/hypervisor/qemu_agent.c | 155 +++++++++++++++++++++++++++++++ src/hypervisor/qemu_agent.h | 26 ++++++ src/libvirt-domain.c | 5 + src/libvirt_private.syms | 3 + src/qemu/qemu_driver.c | 21 ++++- tests/qemuagenttest.c | 123 ++++++++++++++++++++++++ tools/virsh-domain.c | 6 ++ 10 files changed, 449 insertions(+), 6 deletions(-) -- 2.54.0
From: Michal Privoznik <mprivozn@redhat.com> QEMU guest agent has 'guest-get-devices` command, which returns information on guest devices (driver name, version, release date, and so on). In this commit the public API part is introduced. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- include/libvirt/libvirt-domain.h | 78 ++++++++++++++++++++++++++++++++ src/libvirt-domain.c | 5 ++ 2 files changed, 83 insertions(+) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index f4dfe9fb1a..874fc4b455 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -8867,6 +8867,83 @@ int virDomainSetLaunchSecurityState(virDomainPtr domain, */ # define VIR_DOMAIN_GUEST_INFO_LOAD_15M "load.15m" +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_COUNT: + * + * The number of guest devices that detailed information is fetched for as + * unsigned int. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_COUNT "device.count" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX: + * + * The parameter name prefix to access each device entry. Concatenate the + * prefix, the entry number formatted as an unsigned integer and one of the + * device suffix parameters to form a complete parameter name. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX "device." + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_NAME: + * + * Name of the device driver (e.g. ``VirtIO Balloon Driver``) as a string. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_NAME ".driverName" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_DATE: + * + * Driver release date in seconds since the epoch (e.g. 1736726400000) as long long. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_DATE ".driverDate" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_VERSION: + * + * Driver version (e.g. ``100.100.104.27100``) as a string. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_VERSION ".driverVersion" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE: + * + * Device identification (e.g. ``pci``) as a string. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE ".idType" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_VENDOR: + * + * PCI device (a device with VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE equal + * to ``pci``) vendor id (e.g. 6900) as a unsigned int. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_VENDOR ".pciVendor" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_DEVICE: + * + * PCI device (a device with VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE equal + * to ``pci``) device id (e.g. 4165) as a unsigned int. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_DEVICE ".pciDevice" + /** * virDomainGuestInfoTypes: * @@ -8881,6 +8958,7 @@ typedef enum { VIR_DOMAIN_GUEST_INFO_DISKS = (1 << 5), /* return disks information (Since: 7.0.0) */ VIR_DOMAIN_GUEST_INFO_INTERFACES = (1 << 6), /* return interfaces information (Since: 7.10.0) */ VIR_DOMAIN_GUEST_INFO_LOAD = (1 << 7), /* return load averages (Since: 11.2.0) */ + VIR_DOMAIN_GUEST_INFO_DEVICES = (1 << 8), /* return devices information (Since: 12.7.0) */ } virDomainGuestInfoTypes; int virDomainGetGuestInfo(virDomainPtr domain, diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 60d84b24a1..acd9ffc44b 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -13260,6 +13260,11 @@ virDomainSetVcpu(virDomainPtr domain, * The VIR_DOMAIN_GUEST_INFO_LOAD_* constants define the known typed parameter * keys. * + * VIR_DOMAIN_GUEST_INFO_DEVICES: + * Returns information on guest devices. + * The VIR_DOMAIN_GUEST_INFO_DEVICE_* constants define the known typed + * parameter keys. + * * Using 0 for @types returns all information groups supported by the given * hypervisor. * -- 2.54.0
On Fri, Aug 14, 2026 at 13:47:47 +0200, Michal Privoznik via Devel wrote:
From: Michal Privoznik <mprivozn@redhat.com>
QEMU guest agent has 'guest-get-devices` command, which returns information on guest devices (driver name, version, release date, and so on). In this commit the public API part is introduced.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- include/libvirt/libvirt-domain.h | 78 ++++++++++++++++++++++++++++++++ src/libvirt-domain.c | 5 ++ 2 files changed, 83 insertions(+)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index f4dfe9fb1a..874fc4b455 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -8867,6 +8867,83 @@ int virDomainSetLaunchSecurityState(virDomainPtr domain, */ # define VIR_DOMAIN_GUEST_INFO_LOAD_15M "load.15m"
+/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_COUNT: + * + * The number of guest devices that detailed information is fetched for as + * unsigned int. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_COUNT "device.count" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX: + * + * The parameter name prefix to access each device entry. Concatenate the + * prefix, the entry number formatted as an unsigned integer and one of the + * device suffix parameters to form a complete parameter name. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX "device." + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_NAME: + * + * Name of the device driver (e.g. ``VirtIO Balloon Driver``) as a string. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_NAME ".driverName" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_DATE: + * + * Driver release date in seconds since the epoch (e.g. 1736726400000) as long long.
The example number seems to be in milliseconds (13 january 2025). QEMU documents the passed value as nanoseconds. You document it as seconds. So, which one is it? :D
+ * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_DATE ".driverDate" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_VERSION: + * + * Driver version (e.g. ``100.100.104.27100``) as a string. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_VERSION ".driverVersion" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE: + * + * Device identification (e.g. ``pci``) as a string. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE ".idType" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_VENDOR: + * + * PCI device (a device with VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE equal + * to ``pci``) vendor id (e.g. 6900) as a unsigned int. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_VENDOR ".pciVendor" + +/** + * VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_DEVICE: + * + * PCI device (a device with VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE equal + * to ``pci``) device id (e.g. 4165) as a unsigned int. + * + * Since: 12.7.0 + */ +# define VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_DEVICE ".pciDevice"
Here it's okay since it's numeric, but in virsh we'll have to explicitly document that this is not in the usual hexadecimal format.
From: Michal Privoznik <mprivozn@redhat.com> QEMU agent has 'guest-get-info` command. Even though it's currently implemented only for Windows, it shows some interesting information from inside the guest, like device drivers, their versions, and so on. Looking at the command definition in qga/qapi-schema.json the only non-optional field in returned data is 'driver-name'. The rest is optional. Although looking at the current implementation either all fields are set or device is ignored completely. Nevertheless, our code should follow QMP schema. New qemuAgentGuestDeviceInfo structure is introduced among with qemuAgentGetGuestDeviceInfo() function which parses reply from agent and fills the structure. Optional fields are either set to NULL or -1, if missing. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/hypervisor/qemu_agent.c | 119 ++++++++++++++++++++++++++++++++++++ src/hypervisor/qemu_agent.h | 21 +++++++ src/libvirt_private.syms | 2 + 3 files changed, 142 insertions(+) diff --git a/src/hypervisor/qemu_agent.c b/src/hypervisor/qemu_agent.c index 418386317d..fbfaafc1bb 100644 --- a/src/hypervisor/qemu_agent.c +++ b/src/hypervisor/qemu_agent.c @@ -2843,3 +2843,122 @@ qemuAgentInterfaceFormatParams(virDomainInterfacePtr *ifaces, } } } + + +void +qemuAgentGuestDeviceInfoFree(qemuAgentGuestDeviceInfo *info) +{ + if (!info) + return; + + g_free(info->driverName); + g_free(info->driverVersion); + g_free(info->pci); + g_free(info); +} + + +int +qemuAgentGetGuestDeviceInfo(qemuAgent *agent, + qemuAgentGuestDeviceInfo ***info, + bool report_unsupported) +{ + g_autoptr(virJSONValue) cmd = NULL; + g_autoptr(virJSONValue) reply = NULL; + virJSONValue *data = NULL; + size_t ndata; + size_t i; + int rc; + + if (!(cmd = qemuAgentMakeCommand("guest-get-devices", NULL))) + return -1; + + if ((rc = qemuAgentCommandFull(agent, cmd, &reply, agent->timeout, + report_unsupported)) < 0) + return rc; + + if (!(data = virJSONValueObjectGetArray(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of devices")); + return -1; + } + + ndata = virJSONValueArraySize(data); + + *info = g_new0(qemuAgentGuestDeviceInfo *, ndata); + + for (i = 0; i < ndata; i++) { + g_autoptr(qemuAgentGuestDeviceInfo) oneInfo = NULL; + virJSONValue *entry = virJSONValueArrayGet(data, i); + virJSONValue *dDate = NULL; + virJSONValue *idObj = NULL; + + if (!entry) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("array element missing in guest-get-devices return value")); + goto error; + } + + oneInfo = g_new0(qemuAgentGuestDeviceInfo, 1); + + oneInfo->driverName = g_strdup(virJSONValueObjectGetString(entry, "driver-name")); + if (!oneInfo->driverName) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'driver-name' missing in reply of guest-get-devices")); + goto error; + } + + if ((dDate = virJSONValueObjectGet(entry, "driver-date"))) { + if (virJSONValueGetNumberLong(dDate, &oneInfo->driverDate) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed 'driver-date' in reply of guest-get-devices")); + goto error; + } + } else { + oneInfo->driverDate = -1; + } + + oneInfo->driverVersion = g_strdup(virJSONValueObjectGetString(entry, "driver-version")); + + if ((idObj = virJSONValueObjectGet(entry, "id"))) { + const char *type = NULL; + + if (!(type = virJSONValueObjectGetString(idObj, "type"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing 'type' in reply of guest-get-devices")); + goto error; + } + + if (STREQ("pci", type)) { + g_autofree qemuAgentGuestDeviceInfoPCI *pci = NULL; + + pci = g_new0(qemuAgentGuestDeviceInfoPCI, 1); + + if (virJSONValueObjectGetNumberUint(idObj, "vendor-id", &pci->vendorID) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing or malformed 'vendor-id' in reply of guest-get-devices")); + goto error; + } + + if (virJSONValueObjectGetNumberUint(idObj, "device-id", &pci->deviceID) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing or malformed 'device-id' in reply of guest-get-devices")); + goto error; + } + + oneInfo->pci = g_steal_pointer(&pci); + } + } + + (*info)[i] = g_steal_pointer(&oneInfo); + } + + return ndata; + + error: + for (i = 0; i < ndata; i++) { + qemuAgentGuestDeviceInfoFree((*info)[i]); + } + g_clear_pointer(info, g_free); + return -1; +} diff --git a/src/hypervisor/qemu_agent.h b/src/hypervisor/qemu_agent.h index def6f983d4..35d2057bac 100644 --- a/src/hypervisor/qemu_agent.h +++ b/src/hypervisor/qemu_agent.h @@ -218,3 +218,24 @@ void qemuAgentInterfaceFormatParams(virDomainInterfacePtr *ifaces, int nifaces, virTypedParamList *list); + +typedef struct _qemuAgentGuestDeviceInfoPCI qemuAgentGuestDeviceInfoPCI; +struct _qemuAgentGuestDeviceInfoPCI { + unsigned int vendorID; + unsigned int deviceID; +}; + +typedef struct _qemuAgentGuestDeviceInfo qemuAgentGuestDeviceInfo; +struct _qemuAgentGuestDeviceInfo { + char *driverName; + long long driverDate; + char *driverVersion; + qemuAgentGuestDeviceInfoPCI *pci; +}; + +void qemuAgentGuestDeviceInfoFree(qemuAgentGuestDeviceInfo *info); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuAgentGuestDeviceInfo, qemuAgentGuestDeviceInfoFree); + +int qemuAgentGetGuestDeviceInfo(qemuAgent *agent, + qemuAgentGuestDeviceInfo ***info, + bool report_unsupported); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 02bd505749..0bd8c6f781 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1726,6 +1726,7 @@ qemuAgentFSThaw; qemuAgentFSTrim; qemuAgentGetDisks; qemuAgentGetFSInfo; +qemuAgentGetGuestDeviceInfo; qemuAgentGetHostname; qemuAgentGetInterfaces; qemuAgentGetLoadAvg; @@ -1734,6 +1735,7 @@ qemuAgentGetTime; qemuAgentGetTimezone; qemuAgentGetUsers; qemuAgentGetVCPUs; +qemuAgentGuestDeviceInfoFree; qemuAgentInterfaceFormatParams; qemuAgentNotifyClose; qemuAgentNotifyEvent; -- 2.54.0
On Fri, Aug 14, 2026 at 13:47:48 +0200, Michal Privoznik via Devel wrote:
From: Michal Privoznik <mprivozn@redhat.com>
QEMU agent has 'guest-get-info` command. Even though it's currently implemented only for Windows, it shows some interesting information from inside the guest, like device drivers, their versions, and so on.
Looking at the command definition in qga/qapi-schema.json the only non-optional field in returned data is 'driver-name'. The rest is optional. Although looking at the current implementation either all fields are set or device is ignored completely. Nevertheless, our code should follow QMP schema.
New qemuAgentGuestDeviceInfo structure is introduced among with qemuAgentGetGuestDeviceInfo() function which parses reply from agent and fills the structure. Optional fields are either set to NULL or -1, if missing.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/hypervisor/qemu_agent.c | 119 ++++++++++++++++++++++++++++++++++++ src/hypervisor/qemu_agent.h | 21 +++++++ src/libvirt_private.syms | 2 + 3 files changed, 142 insertions(+)
diff --git a/src/hypervisor/qemu_agent.c b/src/hypervisor/qemu_agent.c index 418386317d..fbfaafc1bb 100644 --- a/src/hypervisor/qemu_agent.c +++ b/src/hypervisor/qemu_agent.c @@ -2843,3 +2843,122 @@ qemuAgentInterfaceFormatParams(virDomainInterfacePtr *ifaces, } } } + + +void +qemuAgentGuestDeviceInfoFree(qemuAgentGuestDeviceInfo *info) +{ + if (!info) + return; + + g_free(info->driverName); + g_free(info->driverVersion); + g_free(info->pci); + g_free(info); +} + + +int +qemuAgentGetGuestDeviceInfo(qemuAgent *agent, + qemuAgentGuestDeviceInfo ***info, + bool report_unsupported) +{ + g_autoptr(virJSONValue) cmd = NULL; + g_autoptr(virJSONValue) reply = NULL; + virJSONValue *data = NULL; + size_t ndata; + size_t i; + int rc; + + if (!(cmd = qemuAgentMakeCommand("guest-get-devices", NULL))) + return -1; + + if ((rc = qemuAgentCommandFull(agent, cmd, &reply, agent->timeout, + report_unsupported)) < 0) + return rc; + + if (!(data = virJSONValueObjectGetArray(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of devices")); + return -1; + } + + ndata = virJSONValueArraySize(data); + + *info = g_new0(qemuAgentGuestDeviceInfo *, ndata); + + for (i = 0; i < ndata; i++) { + g_autoptr(qemuAgentGuestDeviceInfo) oneInfo = NULL; + virJSONValue *entry = virJSONValueArrayGet(data, i); + virJSONValue *dDate = NULL; + virJSONValue *idObj = NULL; + + if (!entry) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("array element missing in guest-get-devices return value")); + goto error; + } + + oneInfo = g_new0(qemuAgentGuestDeviceInfo, 1); + + oneInfo->driverName = g_strdup(virJSONValueObjectGetString(entry, "driver-name")); + if (!oneInfo->driverName) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'driver-name' missing in reply of guest-get-devices")); + goto error; + } + + if ((dDate = virJSONValueObjectGet(entry, "driver-date"))) { + if (virJSONValueGetNumberLong(dDate, &oneInfo->driverDate) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed 'driver-date' in reply of guest-get-devices")); + goto error; + } + } else { + oneInfo->driverDate = -1; + } + + oneInfo->driverVersion = g_strdup(virJSONValueObjectGetString(entry, "driver-version")); + + if ((idObj = virJSONValueObjectGet(entry, "id"))) { + const char *type = NULL; + + if (!(type = virJSONValueObjectGetString(idObj, "type"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing 'type' in reply of guest-get-devices")); + goto error; + } + + if (STREQ("pci", type)) { + g_autofree qemuAgentGuestDeviceInfoPCI *pci = NULL; + + pci = g_new0(qemuAgentGuestDeviceInfoPCI, 1); + + if (virJSONValueObjectGetNumberUint(idObj, "vendor-id", &pci->vendorID) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing or malformed 'vendor-id' in reply of guest-get-devices")); + goto error; + } + + if (virJSONValueObjectGetNumberUint(idObj, "device-id", &pci->deviceID) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing or malformed 'device-id' in reply of guest-get-devices")); + goto error; + } + + oneInfo->pci = g_steal_pointer(&pci); + } + } + + (*info)[i] = g_steal_pointer(&oneInfo); + } + + return ndata; + + error: + for (i = 0; i < ndata; i++) { + qemuAgentGuestDeviceInfoFree((*info)[i]); + } + g_clear_pointer(info, g_free); + return -1; +}
diff --git a/src/hypervisor/qemu_agent.h b/src/hypervisor/qemu_agent.h index def6f983d4..35d2057bac 100644 --- a/src/hypervisor/qemu_agent.h +++ b/src/hypervisor/qemu_agent.h @@ -218,3 +218,24 @@ void qemuAgentInterfaceFormatParams(virDomainInterfacePtr *ifaces, int nifaces, virTypedParamList *list); + +typedef struct _qemuAgentGuestDeviceInfoPCI qemuAgentGuestDeviceInfoPCI; +struct _qemuAgentGuestDeviceInfoPCI { + unsigned int vendorID; + unsigned int deviceID; +}; + +typedef struct _qemuAgentGuestDeviceInfo qemuAgentGuestDeviceInfo; +struct _qemuAgentGuestDeviceInfo { + char *driverName; + long long driverDate;
Please add the unit as a comment so it's obvious. You parse it as-is from qemu so the value I suppose is 'nanoseconds since epoch' if the docs are correct.
+ char *driverVersion; + qemuAgentGuestDeviceInfoPCI *pci; +};
From: Michal Privoznik <mprivozn@redhat.com> Introduce a test case for newly introduced qemuAgentGetGuestDeviceInfo(). The expected data started as a genuine reply from a qemu-ga running inside a Windows VM. But then was modified to cover more cases (like an optional key missing). Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tests/qemuagenttest.c | 123 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c index 74cd317e74..7c726a6b85 100644 --- a/tests/qemuagenttest.c +++ b/tests/qemuagenttest.c @@ -1394,6 +1394,128 @@ testQemuAgentGetLoadAvg(const void *data) } +static const char *testQemuAgentGetGuestDeviceInfoResponse = +"{" +" \"return\": [" +" {" +" \"driver-date\": 1736726400000000000," +" \"driver-name\": \"Red Hat VirtIO Ethernet Adapter\"," +" \"driver-version\": \"100.100.104.27100\"," +" \"id\": {" +" \"device-id\": 4161," +" \"vendor-id\": 6900," +" \"type\": \"pci\"" +" }" +" }," +" {" +" \"driver-name\": \"VirtIO Serial Driver\"," +" \"driver-version\": \"100.100.104.27100\"," +" \"id\": {" +" \"device-id\": 4163," +" \"vendor-id\": 6900," +" \"type\": \"pci\"" +" }" +" }," +" {" +" \"driver-date\": 1736726400000000000," +" \"driver-name\": \"VirtIO Balloon Driver\"," +" \"id\": {" +" \"device-id\": 4165," +" \"vendor-id\": 6900," +" \"type\": \"pci\"" +" }" +" }," +" {" +" \"driver-date\": 1736726400000000000," +" \"driver-name\": \"Red Hat VirtIO GPU DOD controller\"," +" \"driver-version\": \"100.100.104.27100\"" +" }" +" ]" +"}"; + + +static int +testQemuAgentGetGuestDeviceInfo(const void *data) +{ + virDomainXMLOption *xmlopt = (virDomainXMLOption *)data; + g_autoptr(qemuMonitorTest) test = qemuMonitorTestNewAgent(xmlopt); + qemuAgentGuestDeviceInfo **devices = NULL; + int ret = -1; + size_t i; + int ndevices; + + if (!test) + return -1; + + if (qemuMonitorTestAddAgentSyncResponse(test) < 0) + return -1; + + if (qemuMonitorTestAddItem(test, "guest-get-devices", + testQemuAgentGetGuestDeviceInfoResponse) < 0) + return -1; + + ndevices = qemuAgentGetGuestDeviceInfo(qemuMonitorTestGetAgent(test), + &devices, true); + + if (ndevices < 0) + return -1; + + if (ndevices != 4) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "unexpected number of guest devices returned (%d), expected 4", + ndevices); + goto cleanup; + } + + if (STRNEQ(devices[0]->driverName, "Red Hat VirtIO Ethernet Adapter") || + STRNEQ(devices[0]->driverVersion, "100.100.104.27100") || + devices[0]->driverDate != 1736726400000000000LL || + devices[0]->pci->vendorID != 6900 || + devices[0]->pci->deviceID != 4161) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected device info returned for device #0"); + goto cleanup; + } + + if (STRNEQ(devices[1]->driverName, "VirtIO Serial Driver") || + STRNEQ(devices[1]->driverVersion, "100.100.104.27100") || + devices[1]->driverDate != -1LL || + devices[1]->pci->vendorID != 6900 || + devices[1]->pci->deviceID != 4163) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected device info returned for device #1"); + goto cleanup; + } + + if (STRNEQ(devices[2]->driverName, "VirtIO Balloon Driver") || + devices[2]->driverVersion || + devices[2]->driverDate != 1736726400000000000LL || + devices[2]->pci->vendorID != 6900 || + devices[2]->pci->deviceID != 4165) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected device info returned for device #2"); + goto cleanup; + } + + if (STRNEQ(devices[3]->driverName, "Red Hat VirtIO GPU DOD controller") || + STRNEQ(devices[3]->driverVersion, "100.100.104.27100") || + devices[3]->driverDate != 1736726400000000000LL || + devices[3]->pci) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected device info returned for device #3"); + goto cleanup; + } + + ret = 0; + cleanup: + for (i = 0; i < ndevices; i++) { + qemuAgentGuestDeviceInfoFree(devices[i]); + } + g_free(devices); + return ret; +} + + static int mymain(void) { @@ -1431,6 +1553,7 @@ mymain(void) DO_TEST(SSHKeys); DO_TEST(GetDisks); DO_TEST(GetLoadAvg); + DO_TEST(GetGuestDeviceInfo); DO_TEST(Timeout); /* Timeout should always be called last */ -- 2.54.0
From: Michal Privoznik <mprivozn@redhat.com> Use freshly introduced qemuAgentGetGuestDeviceInfo() to implement support of VIR_DOMAIN_GUEST_INFO_DEVICES type of virDomainGetGuestInfo() API in the QEMU driver. Resolves: https://redhat.atlassian.net/browse/RHEL-235731 Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/hypervisor/qemu_agent.c | 36 ++++++++++++++++++++++++++++++++++++ src/hypervisor/qemu_agent.h | 5 +++++ src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 21 ++++++++++++++++++++- 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/hypervisor/qemu_agent.c b/src/hypervisor/qemu_agent.c index fbfaafc1bb..ae20ee2b22 100644 --- a/src/hypervisor/qemu_agent.c +++ b/src/hypervisor/qemu_agent.c @@ -2962,3 +2962,39 @@ qemuAgentGetGuestDeviceInfo(qemuAgent *agent, g_clear_pointer(info, g_free); return -1; } + + +void +qemuAgentGuestDeviceInfoFormatParams(qemuAgentGuestDeviceInfo **devices, + size_t ndevices, + virTypedParamList *list) +{ + size_t i; + + virTypedParamListAddUInt(list, ndevices, VIR_DOMAIN_GUEST_INFO_DEVICE_COUNT); + + for (i = 0; i < ndevices; i++) { + virTypedParamListAddString(list, devices[i]->driverName, + VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX "%zu" VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_NAME, i); + + if (devices[i]->driverDate != -1) { + /* Guest agent reports this in nanoseconds, our API in seconds. */ + virTypedParamListAddLLong(list, devices[i]->driverDate / 1000000, + VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX "%zu" VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_DATE, i); + } + + if (devices[i]->driverVersion) { + virTypedParamListAddString(list, devices[i]->driverVersion, + VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX "%zu" VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_DRIVER_VERSION, i); + } + + if (devices[i]->pci) { + virTypedParamListAddString(list, "pci", + VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX "%zu" VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_ID_TYPE, i); + virTypedParamListAddUInt(list, devices[i]->pci->vendorID, + VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX "%zu" VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_VENDOR, i); + virTypedParamListAddUInt(list, devices[i]->pci->deviceID, + VIR_DOMAIN_GUEST_INFO_DEVICE_PREFIX "%zu" VIR_DOMAIN_GUEST_INFO_DEVICE_SUFFIX_PCI_DEVICE, i); + } + } +} diff --git a/src/hypervisor/qemu_agent.h b/src/hypervisor/qemu_agent.h index 35d2057bac..df1a7cce63 100644 --- a/src/hypervisor/qemu_agent.h +++ b/src/hypervisor/qemu_agent.h @@ -239,3 +239,8 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuAgentGuestDeviceInfo, qemuAgentGuestDeviceInfo int qemuAgentGetGuestDeviceInfo(qemuAgent *agent, qemuAgentGuestDeviceInfo ***info, bool report_unsupported); + +void +qemuAgentGuestDeviceInfoFormatParams(qemuAgentGuestDeviceInfo **devices, + size_t ndevices, + virTypedParamList *list); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0bd8c6f781..6c6cc7b4ff 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1735,6 +1735,7 @@ qemuAgentGetTime; qemuAgentGetTimezone; qemuAgentGetUsers; qemuAgentGetVCPUs; +qemuAgentGuestDeviceInfoFormatParams; qemuAgentGuestDeviceInfoFree; qemuAgentInterfaceFormatParams; qemuAgentNotifyClose; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 97d17952b7..a928e4a839 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -19991,7 +19991,8 @@ static const unsigned int qemuDomainGetGuestInfoSupportedTypes = VIR_DOMAIN_GUEST_INFO_FILESYSTEM | VIR_DOMAIN_GUEST_INFO_DISKS | VIR_DOMAIN_GUEST_INFO_INTERFACES | - VIR_DOMAIN_GUEST_INFO_LOAD; + VIR_DOMAIN_GUEST_INFO_LOAD | + VIR_DOMAIN_GUEST_INFO_DEVICES; static int qemuDomainGetGuestInfoCheckSupport(unsigned int types, @@ -20039,6 +20040,8 @@ qemuDomainGetGuestInfo(virDomainPtr dom, double load5m = 0; double load15m = 0; bool format_load = false; + qemuAgentGuestDeviceInfo **devices = NULL; + size_t ndevices = 0; size_t i; g_autoptr(virTypedParamList) list = virTypedParamListNew(); @@ -20118,6 +20121,14 @@ qemuDomainGetGuestInfo(virDomainPtr dom, format_load = true; } + if (supportedTypes & VIR_DOMAIN_GUEST_INFO_DEVICES) { + rc = qemuAgentGetGuestDeviceInfo(agent, &devices, report_unsupported); + if (rc == -1) + goto exitagent; + if (rc >= 0) + ndevices = rc; + } + qemuDomainObjExitAgent(vm, agent); virDomainObjEndAgentJob(vm); @@ -20150,6 +20161,8 @@ qemuDomainGetGuestInfo(virDomainPtr dom, virTypedParamListAddDouble(list, load15m, VIR_DOMAIN_GUEST_INFO_LOAD_15M); } + qemuAgentGuestDeviceInfoFormatParams(devices, ndevices, list); + if (virTypedParamListSteal(list, params, nparams) < 0) goto cleanup; @@ -20167,6 +20180,12 @@ qemuDomainGetGuestInfo(virDomainPtr dom, virDomainInterfaceFree(ifaces[i]); } g_free(ifaces); + if (devices && ndevices > 0) { + for (i = 0; i < ndevices; i++) { + qemuAgentGuestDeviceInfoFree(devices[i]); + } + g_free(devices); + } virDomainObjEndAPI(&vm); return ret; -- 2.54.0
From: Michal Privoznik <mprivozn@redhat.com> Use freshly introduced qemuAgentGetGuestDeviceInfo() to implement support of VIR_DOMAIN_GUEST_INFO_DEVICES type of virDomainGetGuestInfo() API in the QEMU driver. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/bhyve/bhyve_driver.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/bhyve/bhyve_driver.c b/src/bhyve/bhyve_driver.c index 74c3c9491c..997c89daae 100644 --- a/src/bhyve/bhyve_driver.c +++ b/src/bhyve/bhyve_driver.c @@ -2862,7 +2862,8 @@ static const unsigned int bhyveDomainGetGuestInfoSupportedTypes = VIR_DOMAIN_GUEST_INFO_FILESYSTEM | VIR_DOMAIN_GUEST_INFO_DISKS | VIR_DOMAIN_GUEST_INFO_INTERFACES | - VIR_DOMAIN_GUEST_INFO_LOAD; + VIR_DOMAIN_GUEST_INFO_LOAD | + VIR_DOMAIN_GUEST_INFO_DEVICES; static int bhyveDomainGetGuestInfoCheckSupport(unsigned int types, @@ -2910,6 +2911,8 @@ bhyveDomainGetGuestInfo(virDomainPtr domain, double load5m = 0; double load15m = 0; bool format_load = false; + qemuAgentGuestDeviceInfo **devices = NULL; + size_t ndevices = 0; size_t i; g_autoptr(virTypedParamList) list = virTypedParamListNew(); @@ -2984,6 +2987,14 @@ bhyveDomainGetGuestInfo(virDomainPtr domain, format_load = true; } + if (supportedTypes & VIR_DOMAIN_GUEST_INFO_DEVICES) { + rc = qemuAgentGetGuestDeviceInfo(agent, &devices, report_unsupported); + if (rc == -1) + goto exitagent; + if (rc >= 0) + ndevices = rc; + } + bhyveDomainObjExitAgent(vm, agent); virDomainObjEndAgentJob(vm); @@ -3016,6 +3027,8 @@ bhyveDomainGetGuestInfo(virDomainPtr domain, virTypedParamListAddDouble(list, load15m, VIR_DOMAIN_GUEST_INFO_LOAD_15M); } + qemuAgentGuestDeviceInfoFormatParams(devices, ndevices, list); + if (virTypedParamListSteal(list, params, nparams) < 0) goto cleanup; @@ -3033,6 +3046,12 @@ bhyveDomainGetGuestInfo(virDomainPtr domain, virDomainInterfaceFree(ifaces[i]); } g_free(ifaces); + if (devices && ndevices > 0) { + for (i = 0; i < ndevices; i++) { + qemuAgentGuestDeviceInfoFree(devices[i]); + } + g_free(devices); + } virDomainObjEndAPI(&vm); return ret; -- 2.54.0
Michal Privoznik via Devel wrote:
From: Michal Privoznik <mprivozn@redhat.com>
Use freshly introduced qemuAgentGetGuestDeviceInfo() to implement support of VIR_DOMAIN_GUEST_INFO_DEVICES type of virDomainGetGuestInfo() API in the QEMU driver.
s/QEMU/bhyve/
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> ---
<snip>
+ if (supportedTypes & VIR_DOMAIN_GUEST_INFO_DEVICES) { + rc = qemuAgentGetGuestDeviceInfo(agent, &devices, report_unsupported); + if (rc == -1) + goto exitagent; + if (rc >= 0) + ndevices = rc; + } + bhyveDomainObjExitAgent(vm, agent); virDomainObjEndAgentJob(vm);
@@ -3016,6 +3027,8 @@ bhyveDomainGetGuestInfo(virDomainPtr domain, virTypedParamListAddDouble(list, load15m, VIR_DOMAIN_GUEST_INFO_LOAD_15M); }
+ qemuAgentGuestDeviceInfoFormatParams(devices, ndevices, list); +
I wonder if we should call qemuAgentGuestDeviceInfoFormatParams() only if qemuAgentGetGuestDeviceInfo() succeeds? For example, qemu guest agent on FreeBSD does not support 'guest-get-devices', so running `guestinfo --devices <domain>` fails with "error: guest agent command failed: ...". However, when running `guestinfo <domain>`, it works fine, but prints: device.count : 0 I guess it would be better to omit this line in this case. I think this applies to the QEMU driver too.
From: Michal Privoznik <mprivozn@redhat.com> The virDomainGetGuestInfo() API is exposed as 'guestinfo' command. Introduce new --devices option for it to reflect introduction of VIR_DOMAIN_GUEST_INFO_DEVICES type. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- docs/manpages/virsh.rst | 17 +++++++++++++---- tools/virsh-domain.c | 6 ++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst index a10d29e0ea..6803bbb57a 100644 --- a/docs/manpages/virsh.rst +++ b/docs/manpages/virsh.rst @@ -3277,7 +3277,7 @@ guestinfo :: guestinfo domain [--user] [--os] [--timezone] [--hostname] [--filesystem] - [--disk] [--interface] + [--disk] [--interface] [--devices] Print information about the guest from the point of view of the guest agent. Note that this command requires a guest agent to be configured and running in @@ -3289,9 +3289,9 @@ Success is always reported in this case. You can limit the types of information that are returned by specifying one or more flags. Available information types flags are *--user*, *--os*, -*--timezone*, *--hostname*, *--filesystem*, *--disk*, *--interface* and *--load*. -If an explicitly requested information type is not supported by the guest agent -at that point, the processes will provide an exit code of 1. +*--timezone*, *--hostname*, *--filesystem*, *--disk*, *--interface*, *--load* +and *--devices*. If an explicitly requested information type is not supported +by the guest agent at that point, the processes will provide an exit code of 1. Note that depending on the hypervisor type and the version of the guest agent running within the domain, not all of the following information may be @@ -3374,6 +3374,15 @@ returned: * ``load.5m`` - average load in guest for last 5 minutes * ``load.15m`` - average load in guest for last 15 minutes +*--devices* returns: +* ``device.count`` - the number of devices that info is returned for +* ``device.<num>.driverName`` - name of the driver associated with device +* ``device.<num>.driverDate`` - driver release date in seconds since the epoch +* ``device.<num>.driverVersion`` - version of the driver associated with the device +* ``device.<num>.idType`` - device identification type (e.g. pci) +* ``device.<num>.pciVendor`` - vendor ID for PCI device +* ``device.<num>.pciDevice`` - device ID for PCI device + guestvcpus ---------- diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 688df75f81..0a36575626 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -13763,6 +13763,10 @@ static const vshCmdOptDef opts_guestinfo[] = { .type = VSH_OT_BOOL, .help = N_("report load averages information"), }, + {.name = "devices", + .type = VSH_OT_BOOL, + .help = N_("report devices information"), + }, {.name = NULL} }; @@ -13792,6 +13796,8 @@ cmdGuestInfo(vshControl *ctl, const vshCmd *cmd) types |= VIR_DOMAIN_GUEST_INFO_INTERFACES; if (vshCommandOptBool(cmd, "load")) types |= VIR_DOMAIN_GUEST_INFO_LOAD; + if (vshCommandOptBool(cmd, "devices")) + types |= VIR_DOMAIN_GUEST_INFO_DEVICES; if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) return false; -- 2.54.0
On Fri, Aug 14, 2026 at 13:47:52 +0200, Michal Privoznik via Devel wrote:
From: Michal Privoznik <mprivozn@redhat.com>
The virDomainGetGuestInfo() API is exposed as 'guestinfo' command. Introduce new --devices option for it to reflect introduction of VIR_DOMAIN_GUEST_INFO_DEVICES type.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- docs/manpages/virsh.rst | 17 +++++++++++++---- tools/virsh-domain.c | 6 ++++++ 2 files changed, 19 insertions(+), 4 deletions(-)
[...]
@@ -3374,6 +3374,15 @@ returned: * ``load.5m`` - average load in guest for last 5 minutes * ``load.15m`` - average load in guest for last 15 minutes
+*--devices* returns: +* ``device.count`` - the number of devices that info is returned for +* ``device.<num>.driverName`` - name of the driver associated with device +* ``device.<num>.driverDate`` - driver release date in seconds since the epoch +* ``device.<num>.driverVersion`` - version of the driver associated with the device +* ``device.<num>.idType`` - device identification type (e.g. pci) +* ``device.<num>.pciVendor`` - vendor ID for PCI device +* ``device.<num>.pciDevice`` - device ID for PCI device
Here you should make it more obvious that the printed value will be decimal instead of hex.
On Fri, Aug 14, 2026 at 13:47:46 +0200, Michal Privoznik via Devel wrote:
Rebased and slightly amended version of:
https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/thread/6V6K7...
diff to v1: - Rebase, because the original series no longer applies cleanly - Implement the device info for the bhyve driver too
Michal Prívozník (6): Add guest device info to virDomainGetGuestInfo qemu_agent: Introduce guest-get-info qemuagenttest: Introduce GetGuestDeviceInfo test case qemu: Implement device info for virDomainGetGuestInfo() API bhyve: Implement device info for virDomainGetGuestInfo() API virsh: Add support for VIR_DOMAIN_GUEST_INFO_DEVICES
Series: Reviewed-by: Peter Krempa <pkrempa@redhat.com> provided that you fix the example timestamp in the API docs and add emphasis on the address not being in hex format in the virsh docs and document which unit for the timestamp exists in the code. Other than that further patches explained my questions.
participants (3)
-
Michal Privoznik -
Peter Krempa -
Roman Bogorodskiy