Here is an update to QEMU 11.1.3.
Add QEMU guest agent patches for OpenBSD:
- guest-get-users support
- guest-network-get-route support
- guest-get-diskstats support
- don't try to run the Linux hwclock(8) from guest-set-time on
non-Linux systems
Index: Makefile
===================================================================
RCS file: /cvs/ports/emulators/qemu/Makefile,v
retrieving revision 1.269
diff -u -p -u -p -r1.269 Makefile
--- Makefile 24 Oct 2026 22:55:20 -0000 1.269
+++ Makefile 1 Nov 2026 14:37:09 -0000
@@ -6,9 +6,8 @@ USE_NOBTCFI= Yes
COMMENT-main= multi system emulator
COMMENT-ga= QEMU guest agent
-VERSION= 11.1.1
+VERSION= 11.1.3
DISTNAME= qemu-${VERSION}
-REVISION= 2
CATEGORIES= emulators
SITES= https://download.qemu.org/
EXTRACT_SUFX= .tar.xz
Index: distinfo
===================================================================
RCS file: /cvs/ports/emulators/qemu/distinfo,v
retrieving revision 1.89
diff -u -p -u -p -r1.89 distinfo
--- distinfo 3 Sep 2026 08:58:14 -0000 1.89
+++ distinfo 1 Nov 2026 14:37:09 -0000
@@ -1,2 +1,2 @@
-SHA256 (qemu-11.1.1.tar.xz) = B5/7/4pxEbvIkCIQfLq/O7/WFNX8nXzGdZkRlqyhJII=
-SIZE (qemu-11.1.1.tar.xz) = 141888716
+SHA256 (qemu-11.1.3.tar.xz) = WoKJx5QNx304c8gabCm6Ov4URvydNud/oWWM2+y4SyI=
+SIZE (qemu-11.1.3.tar.xz) = 141819712
Index: patches/patch-hw_usb_hcd-xhci_c
===================================================================
RCS file: /cvs/ports/emulators/qemu/patches/patch-hw_usb_hcd-xhci_c,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 patch-hw_usb_hcd-xhci_c
--- patches/patch-hw_usb_hcd-xhci_c 20 Aug 2026 08:46:20 -0000 1.1
+++ patches/patch-hw_usb_hcd-xhci_c 1 Nov 2026 14:37:09 -0000
@@ -24,7 +24,7 @@ Index: hw/usb/hcd-xhci.c
static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v);
static USBEndpoint *xhci_epid_to_usbep(XHCIEPContext *epctx);
-@@ -602,6 +608,304 @@ static inline int xhci_running(XHCIState *xhci)
+@@ -608,6 +614,304 @@ static inline int xhci_running(XHCIState *xhci)
return !(xhci->usbsts & USBSTS_HCH);
}
@@ -329,7 +329,7 @@ Index: hw/usb/hcd-xhci.c
static void xhci_write_event(XHCIState *xhci, XHCIEvent *event, int v)
{
XHCIInterrupter *intr = &xhci->intr[v];
-@@ -636,7 +940,7 @@ static void xhci_write_event(XHCIState *xhci, XHCIEven
+@@ -643,7 +947,7 @@ static void xhci_write_event(XHCIState *xhci, XHCIEven
}
}
@@ -338,7 +338,7 @@ Index: hw/usb/hcd-xhci.c
{
XHCIInterrupter *intr;
dma_addr_t erdp;
-@@ -649,10 +953,15 @@ static void xhci_event(XHCIState *xhci, XHCIEvent *eve
+@@ -656,10 +960,15 @@ static void xhci_event(XHCIState *xhci, XHCIEvent *eve
if (v >= xhci->numintrs) {
DPRINTF("intr nr out of range (%d >= %d)\n", v, xhci->numintrs);
@@ -355,7 +355,7 @@ Index: hw/usb/hcd-xhci.c
erdp = xhci_addr64(intr->erdp_low, intr->erdp_high);
if (erdp < intr->er_start ||
erdp >= (intr->er_start + TRB_SIZE*intr->er_size)) {
-@@ -660,25 +969,86 @@ static void xhci_event(XHCIState *xhci, XHCIEvent *eve
+@@ -667,25 +976,86 @@ static void xhci_event(XHCIState *xhci, XHCIEvent *eve
DPRINTF("xhci: ER[%d] at "DMA_ADDR_FMT" len %d\n",
v, intr->er_start, intr->er_size);
xhci_die(xhci);
@@ -448,7 +448,7 @@ Index: hw/usb/hcd-xhci.c
static void xhci_ring_init(XHCIState *xhci, XHCIRing *ring,
dma_addr_t base)
{
-@@ -802,6 +1172,7 @@ static void xhci_er_reset(XHCIState *xhci, int v)
+@@ -809,6 +1179,7 @@ static void xhci_er_reset(XHCIState *xhci, int v)
XHCIInterrupter *intr = &xhci->intr[v];
XHCIEvRingSeg seg;
dma_addr_t erstba = xhci_addr64(intr->erstba_low, intr->erstba_high);
@@ -456,7 +456,7 @@ Index: hw/usb/hcd-xhci.c
if (intr->erstsz == 0 || erstba == 0) {
/* disabled */
-@@ -839,13 +1210,25 @@ static void xhci_er_reset(XHCIState *xhci, int v)
+@@ -846,13 +1217,25 @@ static void xhci_er_reset(XHCIState *xhci, int v)
DPRINTF("xhci: event ring[%d]:" DMA_ADDR_FMT " [%d]\n",
v, intr->er_start, intr->er_size);
@@ -482,7 +482,7 @@ Index: hw/usb/hcd-xhci.c
}
static void xhci_stop(XHCIState *xhci)
-@@ -853,6 +1236,7 @@ static void xhci_stop(XHCIState *xhci)
+@@ -860,6 +1243,7 @@ static void xhci_stop(XHCIState *xhci)
trace_usb_xhci_stop();
xhci->usbsts |= USBSTS_HCH;
xhci->crcr_low &= ~CRCR_CRR;
@@ -490,7 +490,7 @@ Index: hw/usb/hcd-xhci.c
}
static XHCIStreamContext *xhci_alloc_stream_contexts(unsigned count,
-@@ -1287,6 +1671,7 @@ static TRBCCode xhci_disable_ep(XHCIState *xhci, unsig
+@@ -1301,6 +1685,7 @@ static TRBCCode xhci_disable_ep(XHCIState *xhci, unsig
return CC_SUCCESS;
}
@@ -498,7 +498,7 @@ Index: hw/usb/hcd-xhci.c
xhci_ep_nuke_xfers(xhci, slotid, epid, 0);
epctx = slot->eps[epid-1];
-@@ -1328,6 +1713,7 @@ static TRBCCode xhci_stop_ep(XHCIState *xhci, unsigned
+@@ -1342,6 +1727,7 @@ static TRBCCode xhci_stop_ep(XHCIState *xhci, unsigned
return CC_EP_NOT_ENABLED_ERROR;
}
@@ -506,7 +506,7 @@ Index: hw/usb/hcd-xhci.c
if (xhci_ep_nuke_xfers(xhci, slotid, epid, CC_STOPPED) > 0) {
DPRINTF("xhci: FIXME: endpoint stopped w/ xfers running, "
"data might be lost\n");
-@@ -1373,6 +1759,7 @@ static TRBCCode xhci_reset_ep(XHCIState *xhci, unsigne
+@@ -1387,6 +1773,7 @@ static TRBCCode xhci_reset_ep(XHCIState *xhci, unsigne
return CC_CONTEXT_STATE_ERROR;
}
@@ -514,7 +514,7 @@ Index: hw/usb/hcd-xhci.c
if (xhci_ep_nuke_xfers(xhci, slotid, epid, 0) > 0) {
DPRINTF("xhci: FIXME: endpoint reset w/ xfers running, "
"data might be lost\n");
-@@ -1893,6 +2280,11 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsi
+@@ -1905,6 +2292,11 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsi
trace_usb_xhci_ep_kick(epctx->slotid, epctx->epid, streamid);
assert(!epctx->kick_active);
@@ -526,7 +526,7 @@ Index: hw/usb/hcd-xhci.c
/* If the device has been detached, but the guest has not noticed this
yet the 2 above checks will succeed, but we must NOT continue */
if (!xhci_slot_ok(xhci, epctx->slotid)) {
-@@ -1943,6 +2335,11 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsi
+@@ -1944,6 +2336,11 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsi
epctx->retry = NULL;
}
@@ -538,7 +538,7 @@ Index: hw/usb/hcd-xhci.c
if (epctx->state == EP_HALTED) {
DPRINTF("xhci: ep halted, not running schedule\n");
return;
-@@ -2015,6 +2412,11 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsi
+@@ -2016,6 +2413,11 @@ static void xhci_kick_epctx(XHCIEPContext *epctx, unsi
xfer = NULL;
}
@@ -550,7 +550,7 @@ Index: hw/usb/hcd-xhci.c
if (epctx->state == EP_HALTED) {
break;
}
-@@ -2490,6 +2892,9 @@ static void xhci_process_commands(XHCIState *xhci)
+@@ -2492,6 +2894,9 @@ static void xhci_process_commands(XHCIState *xhci)
}
xhci->crcr_low |= CRCR_CRR;
@@ -560,7 +560,7 @@ Index: hw/usb/hcd-xhci.c
while ((type = xhci_ring_fetch(xhci, &xhci->cmd_ring, &trb, &addr))) {
event.ptr = addr;
-@@ -2600,7 +3005,9 @@ static void xhci_process_commands(XHCIState *xhci)
+@@ -2602,7 +3007,9 @@ static void xhci_process_commands(XHCIState *xhci)
break;
}
event.slotid = slotid;
@@ -571,7 +571,7 @@ Index: hw/usb/hcd-xhci.c
if (count++ > COMMAND_LIMIT) {
trace_usb_xhci_enforced_limit("commands");
-@@ -2716,6 +3123,8 @@ static void xhci_reset(DeviceState *dev)
+@@ -2718,6 +3125,8 @@ static void xhci_reset(DeviceState *dev)
xhci->dcbaap_low = 0;
xhci->dcbaap_high = 0;
xhci->config = 0;
@@ -580,7 +580,7 @@ Index: hw/usb/hcd-xhci.c
for (i = 0; i < xhci->numslots; i++) {
xhci_disable_slot(xhci, i+1);
-@@ -2736,6 +3145,11 @@ static void xhci_reset(DeviceState *dev)
+@@ -2738,6 +3147,11 @@ static void xhci_reset(DeviceState *dev)
xhci->intr[i].er_ep_idx = 0;
xhci->intr[i].er_pcs = 1;
@@ -592,7 +592,7 @@ Index: hw/usb/hcd-xhci.c
xhci->intr[i].ev_buffer_put = 0;
xhci->intr[i].ev_buffer_get = 0;
}
-@@ -3139,6 +3553,11 @@ static void xhci_runtime_write(void *ptr, hwaddr reg,
+@@ -3141,6 +3555,11 @@ static void xhci_runtime_write(void *ptr, hwaddr reg,
intr->erdp_low &= ~ERDP_EHB;
}
intr->erdp_low = (val & ~ERDP_EHB) | (intr->erdp_low & ERDP_EHB);
@@ -604,7 +604,7 @@ Index: hw/usb/hcd-xhci.c
if (val & ERDP_EHB) {
dma_addr_t erdp = xhci_addr64(intr->erdp_low, intr->erdp_high);
unsigned int dp_idx = (erdp - intr->er_start) / TRB_SIZE;
-@@ -3151,6 +3570,7 @@ static void xhci_runtime_write(void *ptr, hwaddr reg,
+@@ -3153,6 +3572,7 @@ static void xhci_runtime_write(void *ptr, hwaddr reg,
break;
case 0x1c: /* ERDP high */
intr->erdp_high = val;
@@ -612,7 +612,7 @@ Index: hw/usb/hcd-xhci.c
break;
default:
trace_usb_xhci_unimplemented("oper write", reg);
-@@ -3425,6 +3845,15 @@ static void usb_xhci_realize(DeviceState *dev, Error *
+@@ -3454,6 +3874,15 @@ static void usb_xhci_realize(DeviceState *dev, Error *
if (xhci->numslots < 1) {
xhci->numslots = 1;
}
@@ -628,7 +628,7 @@ Index: hw/usb/hcd-xhci.c
if (xhci_get_flag(xhci, XHCI_FLAG_ENABLE_STREAMS)) {
xhci->max_pstreams_mask = 7; /* == 256 primary streams */
} else {
-@@ -3470,6 +3899,14 @@ static void usb_xhci_unrealize(DeviceState *dev)
+@@ -3499,6 +3928,14 @@ static void usb_xhci_unrealize(DeviceState *dev)
xhci_disable_slot(xhci, i + 1);
}
@@ -643,7 +643,7 @@ Index: hw/usb/hcd-xhci.c
if (xhci->mfwrap_timer) {
timer_free(xhci->mfwrap_timer);
xhci->mfwrap_timer = NULL;
-@@ -3488,17 +3925,70 @@ static void usb_xhci_unrealize(DeviceState *dev)
+@@ -3517,17 +3954,70 @@ static void usb_xhci_unrealize(DeviceState *dev)
usb_bus_release(&xhci->bus);
}
@@ -715,7 +715,7 @@ Index: hw/usb/hcd-xhci.c
dcbaap = xhci_addr64(xhci->dcbaap_low, xhci->dcbaap_high);
for (slotid = 1; slotid <= xhci->numslots; slotid++) {
-@@ -3531,11 +4021,19 @@ static int usb_xhci_post_load(void *opaque, int versio
+@@ -3560,11 +4050,19 @@ static int usb_xhci_post_load(void *opaque, int versio
xhci_init_epctx(epctx, pctx, ep_ctx);
epctx->state = state;
if (state == EP_RUNNING) {
@@ -737,7 +737,7 @@ Index: hw/usb/hcd-xhci.c
return 0;
}
-@@ -3583,6 +4081,38 @@ static const VMStateDescription vmstate_xhci_event = {
+@@ -3612,6 +4110,38 @@ static const VMStateDescription vmstate_xhci_event = {
}
};
@@ -776,7 +776,7 @@ Index: hw/usb/hcd-xhci.c
static bool xhci_er_full(void *opaque, int version_id)
{
return false;
-@@ -3617,12 +4147,37 @@ static const VMStateDescription vmstate_xhci_intr = {
+@@ -3646,12 +4176,37 @@ static const VMStateDescription vmstate_xhci_intr = {
vmstate_xhci_event, XHCIEvent),
VMSTATE_END_OF_LIST()
@@ -814,7 +814,7 @@ Index: hw/usb/hcd-xhci.c
.post_load = usb_xhci_post_load,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT_VARRAY_UINT32(ports, XHCIState, numports, 1,
-@@ -3648,6 +4203,10 @@ const VMStateDescription vmstate_xhci = {
+@@ -3677,6 +4232,10 @@ const VMStateDescription vmstate_xhci = {
VMSTATE_STRUCT(cmd_ring, XHCIState, 1, vmstate_xhci_ring, XHCIRing),
VMSTATE_END_OF_LIST()
Index: patches/patch-meson_build
===================================================================
RCS file: /cvs/ports/emulators/qemu/patches/patch-meson_build,v
retrieving revision 1.24
diff -u -p -u -p -r1.24 patch-meson_build
--- patches/patch-meson_build 21 Sep 2026 09:48:01 -0000 1.24
+++ patches/patch-meson_build 1 Nov 2026 14:37:09 -0000
@@ -1,4 +1,7 @@
- util/cutils: Use getexecpath() on OpenBSD for qemu_init_exec_dir()
+- qga: implement 'guest-network-get-route' for FreeBSD
+ 0ae3840f612ba00330940f26758ae03754f6c6b6
+- qga: implement 'guest-get-users' for OpenBSD
- localstatedir does not belong under prefix
- remove hardcoding of optimization
@@ -15,7 +18,28 @@ Index: meson.build
config_host_data.set_quoted('CONFIG_QEMU_MODDIR', get_option('prefix') /
qemu_moddir)
config_host_data.set_quoted('CONFIG_SYSCONFDIR', get_option('prefix') /
get_option('sysconfdir'))
-@@ -3030,6 +3030,14 @@ config_host_data.set('CONFIG_ELF_AUX_INFO', cc.links(o
+@@ -2433,6 +2433,7 @@ config_host_data.set('CONFIG_BDRV_WHITELIST_TOOLS', ge
+ config_host_data.set('CONFIG_BRLAPI', brlapi.found())
+ config_host_data.set('CONFIG_BSD', host_os in bsd_oses)
+ config_host_data.set('CONFIG_FREEBSD', host_os == 'freebsd')
++config_host_data.set('CONFIG_OPENBSD', host_os == 'openbsd')
+ config_host_data.set('CONFIG_CAPSTONE', capstone.found())
+ config_host_data.set('CONFIG_COCOA', cocoa.found())
+ config_host_data.set('CONFIG_DARWIN', host_os == 'darwin')
+@@ -2755,6 +2756,12 @@ config_host_data.set('HAVE_STRUCT_STAT_ST_ATIM',
+ config_host_data.set('HAVE_BLK_ZONE_REP_CAPACITY',
+ cc.has_member('struct blk_zone', 'capacity',
+ prefix: '#include <linux/blkzoned.h>'))
++config_host_data.set('HAVE_STRUCT_RT_METRICS_RMX_METRIC',
++ cc.has_member('struct rt_metrics', 'rmx_metric',
++ prefix: '''
++ #include <sys/types.h>
++ #include <sys/socket.h>
++ #include <net/route.h>'''))
+
+ # has_type
+ config_host_data.set('CONFIG_IOVEC',
+@@ -3030,6 +3037,14 @@ config_host_data.set('CONFIG_ELF_AUX_INFO', cc.links(o
return hwcap;
}'''))
@@ -30,7 +54,7 @@ Index: meson.build
config_host_data.set('CONFIG_USBFS', have_linux_user and cc.compiles('''
#include <linux/usbdevice_fs.h>
-@@ -4753,9 +4761,6 @@ if have_rust
+@@ -4753,9 +4768,6 @@ if have_rust
summary_info += {'bindgen version': bindgen.version()}
endif
option_cflags = (get_option('debug') ? ['-g'] : [])
Index: patches/patch-qga_commands-bsd_c
===================================================================
RCS file: /cvs/ports/emulators/qemu/patches/patch-qga_commands-bsd_c,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 patch-qga_commands-bsd_c
--- patches/patch-qga_commands-bsd_c 21 Sep 2026 09:48:01 -0000 1.5
+++ patches/patch-qga_commands-bsd_c 1 Nov 2026 14:37:09 -0000
@@ -1,16 +1,22 @@
- qga: implement 'guest-get-fsinfo' for FreeBSD
2a454b5391ad4241c8d59d8325d8bacac9af7a8c
- qga: implement 'guest-get-fsinfo' for NetBSD and OpenBSD
+ 6a24fc259da4b1efa8e53dc9a655d89474d270cc
+- qga: implement 'guest-network-get-route' for FreeBSD
+ 0ae3840f612ba00330940f26758ae03754f6c6b6
+- qga: implement 'guest-network-get-route' for NetBSD and OpenBSD
+- qga: implement 'guest-get-diskstats' for OpenBSD
- qga: implement 'guest-get-vcpus' for the BSDs
+- qga: implement 'guest-get-users' for OpenBSD
Index: qga/commands-bsd.c
--- qga/commands-bsd.c.orig
+++ qga/commands-bsd.c
-@@ -13,13 +13,13 @@
- #include "qemu/osdep.h"
+@@ -14,12 +14,17 @@
#include "qga-qapi-commands.h"
#include "qapi/error.h"
--#include "qemu/queue.h"
+ #include "qemu/queue.h"
++#include "block/block-common.h"
#include "commands-common.h"
#include <sys/ioctl.h>
#include <sys/param.h>
@@ -18,16 +24,25 @@ Index: qga/commands-bsd.c
#include <sys/mount.h>
#include <net/if_dl.h>
+#include <sys/statvfs.h>
++#include <sys/sysctl.h>
++#include <net/route.h>
++#include <net/if.h>
#if defined(__NetBSD__) || defined(__OpenBSD__)
#include <net/if_arp.h>
#include <netinet/if_ether.h>
-@@ -27,12 +27,20 @@
+@@ -27,12 +32,40 @@
#include <net/ethernet.h>
#endif
#include <paths.h>
+#ifdef __FreeBSD__
+#include <ufs/ffs/fs.h>
+#endif
++#ifdef __OpenBSD__
++#include <sys/sysctl.h>
++#include <sys/disk.h>
++#include <util.h>
++#include <utmp.h>
++#endif
-#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
+#ifdef __NetBSD__
@@ -36,6 +51,20 @@ Index: qga/commands-bsd.c
+typedef struct statfs bsd_fsinfo_t;
+#endif
+
++#if defined(__OpenBSD__)
++#define RT_FIRST_SA(rtm) \
++ ((struct sockaddr *)((char *)(rtm) + (rtm)->rtm_hdrlen))
++#define RT_SA_SIZE(sa) \
++ ((sa)->sa_len > 0 ? 1 + (((sa)->sa_len - 1) | (sizeof(long) - 1)) \
++ : sizeof(long))
++#elif defined(__NetBSD__)
++#define RT_FIRST_SA(rtm) ((struct sockaddr *)((rtm) + 1))
++#define RT_SA_SIZE(sa) RT_ROUNDUP((sa)->sa_len)
++#else /* FreeBSD, DragonFly */
++#define RT_FIRST_SA(rtm) ((struct sockaddr *)((rtm) + 1))
++#define RT_SA_SIZE(sa) SA_SIZE(sa)
++#endif
++
bool build_fs_mount_list(FsMountList *mounts, Error **errp)
{
FsMount *mount;
@@ -44,7 +73,7 @@ Index: qga/commands-bsd.c
struct stat statbuf;
int i, count, ret;
-@@ -55,16 +63,18 @@ bool build_fs_mount_list(FsMountList *mounts, Error **
+@@ -55,16 +88,18 @@ bool build_fs_mount_list(FsMountList *mounts, Error **
mount->dirname = g_strdup(mntp->f_mntonname);
mount->devtype = g_strdup(mntp->f_fstypename);
@@ -65,7 +94,7 @@ Index: qga/commands-bsd.c
#if defined(CONFIG_FSFREEZE)
static int ufssuspend_fd = -1;
-@@ -178,3 +188,79 @@ bool guest_get_hw_addr(struct ifaddrs *ifa, unsigned c
+@@ -178,3 +213,461 @@ bool guest_get_hw_addr(struct ifaddrs *ifa, unsigned c
return true;
}
#endif /* HAVE_GETIFADDRS */
@@ -144,4 +173,386 @@ Index: qga/commands-bsd.c
+
+ free_fs_mount_list(&mounts);
+ return ret;
++}
++
++#ifdef __OpenBSD__
++static uint64_t timeval_to_msec(const struct timeval *tv)
++{
++ return (uint64_t)tv->tv_sec * 1000ULL + tv->tv_usec / 1000;
++}
++
++static GuestDiskStatsInfo *
++guest_diskstats_info_new(const struct diskstats *ds, int rawpart)
++{
++ GuestDiskStatsInfo *diskstatinfo;
++ GuestDiskStats *diskstat;
++ struct stat st;
++
++ diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
++ diskstatinfo->name = g_strndup(ds->ds_name, sizeof(ds->ds_name));
++
++ /*
++ * The kernel disk statistics carry no device numbers; take them
++ * from the raw partition device node, which covers the whole disk.
++ * If that node is missing (e.g. a minimal /dev), report 0.
++ */
++ if (rawpart >= 0) {
++ g_autofree char *devpath = g_strdup_printf(_PATH_DEV "%s%c",
++ diskstatinfo->name,
++ 'a' + rawpart);
++
++ if (stat(devpath, &st) == 0 && S_ISBLK(st.st_mode)) {
++ diskstatinfo->major = major(st.st_rdev);
++ diskstatinfo->minor = minor(st.st_rdev);
++ }
++ }
++
++ /*
++ * OpenBSD tracks transfer counts and bytes per direction, but busy
++ * time only in aggregate, and has no discard, flush or merge
++ * counters.
++ */
++ diskstat = g_new0(GuestDiskStats, 1);
++ diskstat->has_read_ios = true;
++ diskstat->read_ios = ds->ds_rxfer;
++ diskstat->has_read_sectors = true;
++ diskstat->read_sectors = ds->ds_rbytes / BDRV_SECTOR_SIZE;
++
++ diskstat->has_write_ios = true;
++ diskstat->write_ios = ds->ds_wxfer;
++ diskstat->has_write_sectors = true;
++ diskstat->write_sectors = ds->ds_wbytes / BDRV_SECTOR_SIZE;
++
++ if (ds->ds_busy >= 0) {
++ diskstat->has_ios_pgr = true;
++ diskstat->ios_pgr = ds->ds_busy;
++ }
++
++ diskstat->has_total_ticks = true;
++ diskstat->total_ticks = timeval_to_msec(&ds->ds_time);
++
++ diskstatinfo->stats = diskstat;
++ return diskstatinfo;
++}
++
++#define DISKSTATS_MAX_TRIES 5
++
++GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
++{
++ GuestDiskStatsInfoList *head = NULL, **tail = &head;
++ int mib[] = { CTL_HW, HW_DISKSTATS };
++ g_autofree struct diskstats *stats = NULL;
++ size_t len, i;
++ int rawpart = getrawpartition();
++ int tries;
++
++ /* Disks may attach or detach between the two sysctl calls; retry. */
++ for (tries = 0; tries < DISKSTATS_MAX_TRIES; tries++) {
++ if (sysctl(mib, ARRAY_SIZE(mib), NULL, &len, NULL, 0) == -1) {
++ error_setg_errno(errp, errno, "cannot get size of hw.diskstats");
++ return NULL;
++ }
++ if (len == 0) {
++ return NULL;
++ }
++ len += 4 * sizeof(struct diskstats);
++ g_free(stats);
++ stats = g_malloc(len);
++ if (sysctl(mib, ARRAY_SIZE(mib), stats, &len, NULL, 0) == 0) {
++ break;
++ }
++ if (errno != ENOMEM) {
++ error_setg_errno(errp, errno, "cannot read hw.diskstats");
++ return NULL;
++ }
++ }
++ if (tries == DISKSTATS_MAX_TRIES) {
++ error_setg(errp, "hw.diskstats keeps changing size");
++ return NULL;
++ }
++
++ for (i = 0; i < len / sizeof(struct diskstats); i++) {
++ QAPI_LIST_APPEND(tail, guest_diskstats_info_new(&stats[i], rawpart));
++ }
++
++ return head;
++}
++
++GuestUserList *qmp_guest_get_users(Error **errp)
++{
++ GHashTable *cache;
++ GuestUserList *head = NULL, **tail = &head;
++ struct utmp ut;
++ FILE *fp = NULL;
++
++ fp = fopen(_PATH_UTMP, "r");
++ if (!fp) {
++ error_setg_errno(errp, errno, "failed to open %s", _PATH_UTMP);
++ return NULL;
++ }
++
++ cache = g_hash_table_new(g_str_hash, g_str_equal);
++
++ while (fread(&ut, sizeof(ut), 1, fp) == 1) {
++ g_autofree char *name = NULL;
++ GuestUser *user;
++ double login_time;
++
++ /* logout(3) clears ut_name; skip empty slots like who(1) does */
++ if (ut.ut_name[0] == '\0' || ut.ut_line[0] == '\0') {
++ continue;
++ }
++
++ /* ut_name is not NUL-terminated if it fills UT_NAMESIZE */
++ name = g_strndup(ut.ut_name, sizeof(ut.ut_name));
++ login_time = (double)ut.ut_time;
++
++ user = g_hash_table_lookup(cache, name);
++ if (user) {
++ /* We're ensuring the earliest login time to be sent */
++ if (login_time < user->login_time) {
++ user->login_time = login_time;
++ }
++ continue;
++ }
++
++ user = g_new0(GuestUser, 1);
++ user->user = g_steal_pointer(&name);
++ user->login_time = login_time;
++
++ g_hash_table_insert(cache, user->user, user);
++ QAPI_LIST_APPEND(tail, user);
++ }
++
++ fclose(fp);
++ g_hash_table_destroy(cache);
++ return head;
++}
++#endif /* __OpenBSD__ */
++
++static void sockaddr_in6_normalize(const struct sockaddr *sa,
++ struct sockaddr_in6 *sin6)
++{
++ size_t len = MIN(sa->sa_len, sizeof(*sin6));
++
++ memset(sin6, 0, sizeof(*sin6));
++ memcpy(sin6, sa, len);
++
++ sin6->sin6_len = sizeof(*sin6);
++
++ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) ||
++ (sin6->sin6_addr.s6_addr[0] == 0xff &&
++ ((sin6->sin6_addr.s6_addr[1] & 0x0f) == 1 ||
++ (sin6->sin6_addr.s6_addr[1] & 0x0f) == 2))) {
++ if (sin6->sin6_scope_id == 0) {
++ sin6->sin6_scope_id =
++ (sin6->sin6_addr.s6_addr[2] << 8) |
++ sin6->sin6_addr.s6_addr[3];
++ sin6->sin6_addr.s6_addr[2] = 0;
++ sin6->sin6_addr.s6_addr[3] = 0;
++ }
++ }
++}
++
++GuestNetworkRouteList *qmp_guest_network_get_route(Error **errp)
++{
++ GuestNetworkRouteList *head = NULL, **tail = &head;
++#ifdef __NetBSD__
++ int mib[] = { CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_DUMP, 0 };
++#else
++ int mib[] = { CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_DUMP, 0, 0 };
++#endif
++ g_autofree char *buf = NULL;
++ size_t len;
++
++ if (sysctl(mib, ARRAY_SIZE(mib), NULL, &len, NULL, 0) == -1) {
++ error_setg_errno(errp, errno, "sysctl size");
++ return NULL;
++ }
++
++ buf = g_malloc0(len);
++
++ if (sysctl(mib, ARRAY_SIZE(mib), buf, &len, NULL, 0) == -1) {
++ error_setg_errno(errp, errno, "sysctl routes");
++ return NULL;
++ }
++
++ for (char *p = buf, *end = buf + len; p < end; ) {
++ struct rt_msghdr *rtm = (struct rt_msghdr *)p;
++ struct sockaddr *addresses[RTAX_MAX] = { NULL };
++ struct sockaddr *sa;
++ struct sockaddr_in6 sin6;
++ char destination[NI_MAXHOST];
++ char gateway[NI_MAXHOST];
++ char ifname[IFNAMSIZ];
++ const struct sockaddr *dst = NULL;
++ const struct sockaddr *gw = NULL;
++ const struct sockaddr *netmask = NULL;
++ const struct sockaddr *fmt_sa;
++ struct sockaddr_in normalized_mask = { 0 };
++ char netmask_buf[INET_ADDRSTRLEN];
++ socklen_t salen;
++
++ g_autoptr(GuestNetworkRoute) route = g_new0(GuestNetworkRoute, 1);
++
++ if ((size_t)(end - p) < sizeof(*rtm) ||
++ rtm->rtm_msglen < sizeof(*rtm) ||
++ rtm->rtm_msglen > (size_t)(end - p)) {
++ break;
++ }
++
++ p += rtm->rtm_msglen;
++
++ if (rtm->rtm_version != RTM_VERSION) {
++ continue;
++ }
++
++#if defined(RTF_LLINFO)
++ /* OpenBSD: skip ARP/NDP entries */
++ if (rtm->rtm_flags & RTF_LLINFO) {
++ continue;
++ }
++#elif defined(RTF_LLDATA)
++ /* NetBSD: skip ARP/NDP entries */
++ if (rtm->rtm_flags & RTF_LLDATA) {
++ continue;
++ }
++#endif
++
++ sa = RT_FIRST_SA(rtm);
++ for (int i = 0; i < RTAX_MAX; i++) {
++ if (rtm->rtm_addrs & (1U << i)) {
++ addresses[i] = sa;
++ sa = (struct sockaddr *)((char *)sa + RT_SA_SIZE(sa));
++ }
++ }
++
++ if (if_indextoname(rtm->rtm_index, ifname)) {
++ route->iface = g_strdup(ifname);
++ } else {
++ route->iface = g_strdup_printf("if%u", rtm->rtm_index);
++ }
++
++#if defined(HAVE_STRUCT_RT_METRICS_RMX_METRIC)
++ route->metric = rtm->rtm_rmx.rmx_metric;
++#endif
++
++ dst = addresses[RTAX_DST];
++ if (!dst) {
++ continue;
++ }
++
++ if (dst->sa_family == AF_INET) {
++ route->version = 4;
++ } else if (dst->sa_family == AF_INET6) {
++ route->version = 6;
++ } else {
++ continue;
++ }
++
++ fmt_sa = dst;
++ salen = dst->sa_len;
++ if (dst->sa_family == AF_INET6) {
++ sockaddr_in6_normalize(dst, &sin6);
++ fmt_sa = (struct sockaddr *)&sin6;
++ salen = sizeof(sin6);
++ }
++ if (getnameinfo(fmt_sa, salen,
++ destination, NI_MAXHOST,
++ NULL, 0, NI_NUMERICHOST) != 0) {
++ continue;
++ }
++ route->destination = g_strdup(destination);
++
++ gw = addresses[RTAX_GATEWAY];
++ if (gw && (gw->sa_family == AF_INET || gw->sa_family == AF_INET6)) {
++ fmt_sa = gw;
++ salen = gw->sa_len;
++ if (gw->sa_family == AF_INET6) {
++ sockaddr_in6_normalize(gw, &sin6);
++ fmt_sa = (struct sockaddr *)&sin6;
++ salen = sizeof(sin6);
++ }
++ if (getnameinfo(fmt_sa, salen, gateway,
++ NI_MAXHOST, NULL, 0,
++ NI_NUMERICHOST) != 0) {
++ continue;
++ }
++ if (gw->sa_family == AF_INET) {
++ route->gateway = g_strdup(gateway);
++ } else if (gw->sa_family == AF_INET6) {
++ route->nexthop = g_strdup(gateway);
++ }
++ }
++
++ netmask = addresses[RTAX_NETMASK];
++ if (dst->sa_family == AF_INET) {
++ if (netmask && netmask->sa_len != 0) {
++ memcpy(&normalized_mask, netmask,
++ MIN(netmask->sa_len, sizeof(normalized_mask)));
++ } else if (rtm->rtm_flags & RTF_HOST) {
++ normalized_mask.sin_addr.s_addr = 0xFFFFFFFF;
++ }
++
++ if (inet_ntop(AF_INET, &normalized_mask.sin_addr,
++ netmask_buf, sizeof(netmask_buf)) != NULL) {
++ route->mask = g_strdup(netmask_buf);
++ } else {
++ route->mask = NULL;
++ }
++ } else if (dst->sa_family == AF_INET6) {
++ struct sockaddr_in6 normalized_mask6;
++ memset(&normalized_mask6, 0, sizeof(normalized_mask6));
++
++ if (netmask && netmask->sa_len != 0) {
++ int prefix_len = 0;
++ bool valid_mask = true;
++ bool zero_found = false;
++
++ memcpy(&normalized_mask6, netmask,
++ MIN(netmask->sa_len, sizeof(normalized_mask6)));
++
++ for (int i = 0; i < sizeof(normalized_mask6.sin6_addr); i++) {
++ uint8_t byte = normalized_mask6.sin6_addr.s6_addr[i];
++ for (int bit = 7; bit >= 0; bit--) {
++ if ((byte >> bit) & 1) {
++ if (zero_found) {
++ valid_mask = false;
++ break;
++ }
++ prefix_len++;
++ } else {
++ zero_found = true;
++ }
++ }
++ if (!valid_mask) {
++ break;
++ }
++ }
++
++ if (valid_mask) {
++ route->desprefixlen = g_strdup_printf("%d", prefix_len);
++ } else {
++ route->desprefixlen = NULL;
++ }
++ } else if (rtm->rtm_flags & RTF_HOST) {
++ route->desprefixlen = g_strdup("128");
++ } else {
++ route->desprefixlen = g_strdup("0");
++ }
++ }
++
++ if (rtm->rtm_rmx.rmx_mtu) {
++ route->mtu = rtm->rtm_rmx.rmx_mtu;
++ route->has_mtu = true;
++ }
++
++ route->flags = (uint32_t)rtm->rtm_flags;
++ route->has_flags = true;
++
++ QAPI_LIST_APPEND(tail, route);
++ route = NULL;
++ }
++
++ return head;
+}
Index: patches/patch-qga_commands-common_h
===================================================================
RCS file: /cvs/ports/emulators/qemu/patches/patch-qga_commands-common_h,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 patch-qga_commands-common_h
--- patches/patch-qga_commands-common_h 21 Sep 2026 09:48:01 -0000 1.1
+++ patches/patch-qga_commands-common_h 1 Nov 2026 14:37:09 -0000
@@ -1,6 +1,7 @@
- qga: implement 'guest-get-fsinfo' for FreeBSD
2a454b5391ad4241c8d59d8325d8bacac9af7a8c
- qga: implement 'guest-get-fsinfo' for NetBSD and OpenBSD
+ 6a24fc259da4b1efa8e53dc9a655d89474d270cc
Index: qga/commands-common.h
--- qga/commands-common.h.orig
Index: patches/patch-qga_commands-posix_c
===================================================================
RCS file: /cvs/ports/emulators/qemu/patches/patch-qga_commands-posix_c,v
retrieving revision 1.13
diff -u -p -u -p -r1.13 patch-qga_commands-posix_c
--- patches/patch-qga_commands-posix_c 21 Sep 2026 09:48:01 -0000 1.13
+++ patches/patch-qga_commands-posix_c 1 Nov 2026 14:37:09 -0000
@@ -1,11 +1,35 @@
- qga: implement 'guest-get-fsinfo' for FreeBSD
2a454b5391ad4241c8d59d8325d8bacac9af7a8c
- qga: implement 'guest-get-fsinfo' for NetBSD and OpenBSD
+ 6a24fc259da4b1efa8e53dc9a655d89474d270cc
+- qga: do not run hwclock(8) on non-Linux POSIX OSes
+ e1d00ea443da6f15dc10aea8c3ab4ad7ffeb68cc
Index: qga/commands-posix.c
--- qga/commands-posix.c.orig
+++ qga/commands-posix.c
-@@ -718,7 +718,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp
+@@ -322,6 +322,20 @@ void qmp_guest_set_time(bool has_time, int64_t time_ns
+ }
+ }
+
++#if !defined(CONFIG_LINUX)
++ /*
++ * BSDs (FreeBSD, OpenBSD, NetBSD) and illumos do not have the
++ * hwclock(8) tool. These systems update hardware clock when
++ * settimeofday() sets system time, so no additional actions
++ * are needed. Setting system time from RTC is not supported.
++ */
++ if (!has_time) {
++ error_setg(errp, "Setting system time from RTC is not supported");
++ }
++
++ return;
++#endif
++
+ /* Now, if user has passed a time to set and the system time is set, we
+ * just need to synchronize the hardware clock. However, if no time was
+ * passed, user is requesting the opposite: set the system time from the
+@@ -718,7 +732,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp
}
}
@@ -14,7 +38,7 @@ Index: qga/commands-posix.c
void free_fs_mount_list(FsMountList *mounts)
{
FsMount *mount, *temp;
-@@ -731,6 +731,9 @@ void free_fs_mount_list(FsMountList *mounts)
+@@ -731,6 +745,9 @@ void free_fs_mount_list(FsMountList *mounts)
QTAILQ_REMOVE(mounts, mount, next);
g_free(mount->dirname);
g_free(mount->devtype);
Index: patches/patch-qga_meson_build
===================================================================
RCS file: patches/patch-qga_meson_build
diff -N patches/patch-qga_meson_build
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-qga_meson_build 1 Nov 2026 14:37:09 -0000
@@ -0,0 +1,15 @@
+- qga: implement 'guest-get-diskstats' for OpenBSD
+
+Index: qga/meson.build
+--- qga/meson.build.orig
++++ qga/meson.build
+@@ -93,6 +93,9 @@ qga_ss = qga_ss.apply({})
+
+ gen_tlb = []
+ qga_libs = []
++if host_os == 'openbsd'
++ qga_libs += ['-lutil']
++endif
+ if host_os == 'windows'
+ qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32',
'-lwininet', '-liphlpapi', '-lnetapi32',
+ '-lsetupapi', '-lcfgmgr32', '-luserenv', '-lpdh' ]
Index: patches/patch-qga_qapi-schema_json
===================================================================
RCS file: /cvs/ports/emulators/qemu/patches/patch-qga_qapi-schema_json,v
retrieving revision 1.5
diff -u -p -u -p -r1.5 patch-qga_qapi-schema_json
--- patches/patch-qga_qapi-schema_json 21 Sep 2026 09:48:01 -0000 1.5
+++ patches/patch-qga_qapi-schema_json 1 Nov 2026 14:37:09 -0000
@@ -1,6 +1,11 @@
- qga: implement 'guest-get-fsinfo' for FreeBSD
2a454b5391ad4241c8d59d8325d8bacac9af7a8c
- qga: implement 'guest-get-fsinfo' for NetBSD and OpenBSD
+ 6a24fc259da4b1efa8e53dc9a655d89474d270cc
+- qga: implement 'guest-network-get-route' for FreeBSD
+ 0ae3840f612ba00330940f26758ae03754f6c6b6
+- qga: implement 'guest-network-get-route' for NetBSD and OpenBSD
+- qga: implement 'guest-get-diskstats' for OpenBSD
- qga: implement 'guest-get-vcpus' for the BSDs
Index: qga/qapi-schema.json
@@ -78,3 +83,122 @@ Index: qga/qapi-schema.json
##
# @guest-set-user-password:
+@@ -1407,7 +1407,7 @@
+ ##
+ { 'struct': 'GuestUser',
+ 'data': { 'user': 'str', 'login-time': 'number', '*domain': 'str' },
+- 'if': { 'any': ['CONFIG_WIN32', 'HAVE_UTMPX' ] } }
++ 'if': { 'any': ['CONFIG_WIN32', 'HAVE_UTMPX', 'CONFIG_OPENBSD' ] } }
+
+ ##
+ # @guest-get-users:
+@@ -1420,7 +1420,7 @@
+ ##
+ { 'command': 'guest-get-users',
+ 'returns': ['GuestUser'],
+- 'if': { 'any': ['CONFIG_WIN32', 'HAVE_UTMPX' ] } }
++ 'if': { 'any': ['CONFIG_WIN32', 'HAVE_UTMPX', 'CONFIG_OPENBSD' ] } }
+
+ ##
+ # @GuestTimezone:
+@@ -1714,7 +1714,7 @@
+ '*total-ticks': 'uint64',
+ '*weight-ticks': 'uint64'
+ },
+- 'if': 'CONFIG_LINUX' }
++ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_BSD'] } }
+
+ ##
+ # @GuestDiskStatsInfo:
+@@ -1732,7 +1732,7 @@
+ 'major': 'uint64',
+ 'minor': 'uint64',
+ 'stats': 'GuestDiskStats' },
+- 'if': 'CONFIG_LINUX' }
++ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_BSD'] } }
+
+ ##
+ # @guest-get-diskstats:
+@@ -1745,7 +1745,7 @@
+ ##
+ { 'command': 'guest-get-diskstats',
+ 'returns': ['GuestDiskStatsInfo'],
+- 'if': 'CONFIG_LINUX'
++ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_BSD'] }
+ }
+
+ ##
+@@ -1879,8 +1879,8 @@
+ ##
+ # @GuestNetworkRoute:
+ #
+-# Route information. Supported on Linux (since 9.1) and Windows
+-# (since 11.0).
++# Route information. Supported on Linux (since 9.1), Windows
++# (since 11.0), and BSD (since 11.2).
+ #
+ # @iface: The destination network or host's egress network interface
+ # in the routing table
+@@ -1894,30 +1894,31 @@
+ #
+ # @mask: Subnet Mask (IPv4 only)
+ #
+-# @irtt: Initial round-trip delay (not for windows, IPv4 only)
++# @irtt: Initial round-trip delay (not for Windows and BSD,
++# IPv4 only)
+ #
+-# @flags: Route flags (not for windows)
++# @flags: Route flags (not for Windows)
+ #
+-# @refcnt: The route's reference count (not for windows)
++# @refcnt: The route's reference count (not for Windows and BSD)
+ #
+-# @use: Route usage count (not for windows)
++# @use: Route usage count (not for Windows and BSD)
+ #
+-# @window: TCP window size, used for flow control (not for windows,
+-# IPv4 only)
++# @window: TCP window size, used for flow control (not for Windows
++# and BSD, IPv4 only)
+ #
+-# @mtu: Data link layer maximum packet size (not for windows)
++# @mtu: Data link layer maximum packet size (not for Windows)
+ #
+ # @desprefixlen: Destination prefix length (for IPv6)
+ #
+-# @source: Source IP address (for IPv6)
++# @source: Source IP address (not for BSD, IPv6 only)
+ #
+-# @srcprefixlen: Source prefix length (for IPv6)
++# @srcprefixlen: Source prefix length (not for BSD, IPv6 only)
+ #
+ # @nexthop: Next hop IP address (for IPv6)
+ #
+ # @version: IP version (4 or 6)
+ #
+-# Since: 9.1 (Linux only); 11.0 (Windows)
++# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (BSD)
+ ##
+ { 'struct': 'GuestNetworkRoute',
+ 'data': {'iface': 'str',
+@@ -1937,7 +1938,7 @@
+ '*nexthop': 'str',
+ 'version': 'int'
+ },
+- 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32'] } }
++ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_BSD'] } }
+
+ ##
+ # @guest-network-get-route:
+@@ -1946,9 +1947,9 @@
+ #
+ # Returns: List of route info of guest.
+ #
+-# Since: 9.1 (Linux only); 11.0 (Windows)
++# Since: 9.1 (Linux only); 11.0 (Windows); 11.2 (BSD)
+ ##
+ { 'command': 'guest-network-get-route',
+ 'returns': ['GuestNetworkRoute'],
+- 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32'] }
++ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_WIN32', 'CONFIG_BSD'] }
+ }