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'] }
+ }

Reply via email to