Commit 7f70377841 for qemu.org

commit 7f7037784168020a8be4ea32bb0348fc8ddabb4c
Author: Roman Bogorodskiy <bogorodskiy@gmail.com>
Date:   Tue Sep 1 21:27:31 2026 +0200

    qga: implement 'guest-get-diskstats' for FreeBSD

    Implement the 'guest-get-diskstats' for FreeBSD.
    This implementation uses the devstat(3) library which is a part
    of the FreeBSD base system.

    The build system is updated to link qga with `-ldevstat`.

    Signed-off-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
    Acked-by: Markus Armbruster <armbru@redhat.com>
    Reviewed-by: Kostiantyn Kostiuk <kkostiuk@redhat.com>
    Link: https://lore.kernel.org/qemu-devel/20260901192732.46190-1-bogorodskiy@gmail.com
    Signed-off-by: Kostiantyn Kostiuk <kkostiuk@redhat.com>

diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
index 0c6caec4a3..7c7616699b 100644
--- a/qga/commands-bsd.c
+++ b/qga/commands-bsd.c
@@ -14,6 +14,7 @@
 #include "qga-qapi-commands.h"
 #include "qapi/error.h"
 #include "qemu/queue.h"
+#include "block/block-common.h"
 #include "commands-common.h"
 #include <sys/ioctl.h>
 #include <sys/param.h>
@@ -31,12 +32,14 @@
 #ifdef __FreeBSD__
 #include <ufs/ffs/fs.h>
 #endif
-
 #ifdef __NetBSD__
 typedef struct statvfs bsd_fsinfo_t;
 #else
 typedef struct statfs bsd_fsinfo_t;
 #endif
+#ifdef CONFIG_FREEBSD
+#include <devstat.h>
+#endif

 bool build_fs_mount_list(FsMountList *mounts, Error **errp)
 {
@@ -236,3 +239,119 @@ GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
     free_fs_mount_list(&mounts);
     return ret;
 }
+
+#ifdef CONFIG_FREEBSD
+static uint64_t bintime_to_msec(const struct bintime *bt)
+{
+    return (uint64_t)bt->sec * 1000ULL + (((bt->frac >> 32) * 1000ULL) >> 32);
+}
+
+static GuestDiskStatsInfo *
+guest_diskstats_info_new(const struct devstat *dev)
+{
+    GuestDiskStatsInfo *diskstatinfo;
+    GuestDiskStats *diskstat;
+
+    diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
+    diskstatinfo->name = g_strdup_printf("%s%d", dev->device_name,
+                                         dev->unit_number);
+    /*
+     * devstat does not expose Linux-style major/minor numbers.  Report the
+     * devstat device number and unit number in these mandatory QAPI fields.
+     */
+    diskstatinfo->major = dev->device_number;
+    diskstatinfo->minor = dev->unit_number;
+
+    diskstat = g_new0(GuestDiskStats, 1);
+    diskstat->has_read_ios = true;
+    diskstat->read_ios = dev->operations[DEVSTAT_READ];
+    diskstat->has_read_sectors = true;
+    diskstat->read_sectors = dev->bytes[DEVSTAT_READ] / BDRV_SECTOR_SIZE;
+    diskstat->has_read_ticks = true;
+    diskstat->read_ticks = bintime_to_msec(&dev->duration[DEVSTAT_READ]);
+
+    diskstat->has_write_ios = true;
+    diskstat->write_ios = dev->operations[DEVSTAT_WRITE];
+    diskstat->has_write_sectors = true;
+    diskstat->write_sectors = dev->bytes[DEVSTAT_WRITE] / BDRV_SECTOR_SIZE;
+    diskstat->has_write_ticks = true;
+    diskstat->write_ticks = bintime_to_msec(&dev->duration[DEVSTAT_WRITE]);
+
+    diskstat->has_discard_ios = true;
+    diskstat->discard_ios = dev->operations[DEVSTAT_FREE];
+    diskstat->has_discard_sectors = true;
+    diskstat->discard_sectors = dev->bytes[DEVSTAT_FREE] / BDRV_SECTOR_SIZE;
+    diskstat->has_discard_ticks = true;
+    diskstat->discard_ticks = bintime_to_msec(&dev->duration[DEVSTAT_FREE]);
+
+    diskstat->has_ios_pgr = true;
+    diskstat->ios_pgr = dev->start_count - dev->end_count;
+
+    diskstat->has_total_ticks = true;
+    diskstat->total_ticks = bintime_to_msec(&dev->busy_time);
+
+    diskstatinfo->stats = diskstat;
+    return diskstatinfo;
+}
+
+GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
+{
+    GuestDiskStatsInfoList *head = NULL, **tail = &head;
+    struct devinfo dinfo = { 0 };
+    struct statinfo stats = { .dinfo = &dinfo };
+    struct device_selection *dev_select = NULL;
+    struct devstat_match matches[] = {
+        {
+            .match_fields = DEVSTAT_MATCH_TYPE,
+            .device_type = DEVSTAT_TYPE_DIRECT,
+            .num_match_categories = 1,
+        },
+    };
+    int num_selected = 0;
+    int num_selections = 0;
+    long select_generation = 0;
+    int i;
+
+    if (devstat_checkversion(NULL) == -1) {
+        error_setg(errp, "%s", devstat_errbuf);
+        return NULL;
+    }
+
+    if (devstat_getdevs(NULL, &stats) == -1) {
+        error_setg(errp, "%s", devstat_errbuf);
+        goto error;
+    }
+
+    if (devstat_selectdevs(&dev_select, &num_selected, &num_selections,
+                           &select_generation, stats.dinfo->generation,
+                           stats.dinfo->devices, stats.dinfo->numdevs,
+                           matches, ARRAY_SIZE(matches), NULL, 0,
+                           DS_SELECT_ONLY,
+                           stats.dinfo->numdevs, 0) == -1) {
+        error_setg(errp, "%s", devstat_errbuf);
+        goto error;
+    }
+
+    for (i = 0; i < num_selections; i++) {
+        const struct devstat *dev;
+
+        if (dev_select[i].selected == 0) {
+            continue;
+        }
+
+        dev = &stats.dinfo->devices[dev_select[i].position];
+        QAPI_LIST_APPEND(tail, guest_diskstats_info_new(dev));
+    }
+
+    free(stats.dinfo->mem_ptr);
+    free(dev_select);
+    return head;
+
+error:
+    qapi_free_GuestDiskStatsInfoList(head);
+    free(stats.dinfo->mem_ptr);
+    free(dev_select);
+    return NULL;
+}
+
+#endif /* CONFIG_FREEBSD */
diff --git a/qga/meson.build b/qga/meson.build
index cfa2157efb..a046ebe305 100644
--- a/qga/meson.build
+++ b/qga/meson.build
@@ -93,6 +93,9 @@ qga_ss = qga_ss.apply({})

 gen_tlb = []
 qga_libs = []
+if host_os == 'freebsd'
+  qga_libs += ['-ldevstat']
+endif
 if host_os == 'windows'
   qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', '-lwininet', '-liphlpapi', '-lnetapi32',
                '-lsetupapi', '-lcfgmgr32', '-luserenv', '-lpdh' ]
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index d81b57de53..308ed9acf1 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -1666,7 +1666,7 @@
            '*total-ticks': 'uint64',
            '*weight-ticks': 'uint64'
            },
-  'if': 'CONFIG_LINUX' }
+  'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] } }

 ##
 # @GuestDiskStatsInfo:
@@ -1684,7 +1684,7 @@
            'major': 'uint64',
            'minor': 'uint64',
            'stats': 'GuestDiskStats' },
-  'if': 'CONFIG_LINUX' }
+  'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] } }

 ##
 # @guest-get-diskstats:
@@ -1696,7 +1696,7 @@
 ##
 { 'command': 'guest-get-diskstats',
   'returns': ['GuestDiskStatsInfo'],
-  'if': 'CONFIG_LINUX'
+  'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] }
 }

 ##