Re: [doc] using qemu guest agent on OpenBSD kvm/qemu guests

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Re: [doc] using qemu guest agent on OpenBSD kvm/qemu guests

Jeroen Derks
Hi there,

@Landry Thanks for the great article, it was just what I was looking
for!

Since I wanted to be able to retrieve the network interfaces from the
VM, I have applied the FreeBSD patches on qemu-5.1.0 (and slightly
modified them to work with OpenBSD 6.8) which enables the additional
commands:

"guest-get-fsinfo"
"guest-get-vcpus"
"guest-network-get-interfaces"

Which allows, in my case, proxmox to show the network interfaces and
addresses of an OpenBSD VM:

proxmox# qm agent 100 network-get-interfaces
[
    {
       "ip-addresses" : [
          {
             "ip-address" : "::1",
             "ip-address-type" : "ipv6",
             "prefix" : 128
          },
          {
             "ip-address" : "fe80:3::1",
             "ip-address-type" : "ipv6",
             "prefix" : 64
          },
          {
             "ip-address" : "127.0.0.1",
             "ip-address-type" : "ipv4",
             "prefix" : 8
          }
       ],
       "name" : "lo0"
    },
    {
       "hardware-address" : "12:34:56:78:9a:bc",
       "ip-addresses" : [
          {
             "ip-address" : "fe80:1::7075:8aff:def0:1234",
             "ip-address-type" : "ipv6",
             "prefix" : 64
          },
          {
             "ip-address" : "10.0.0.2",
             "ip-address-type" : "ipv4",
             "prefix" : 24
          }
       ],
       "name" : "vio0"
    },
    {
       "name" : "enc0"
    },
    {
       "hardware-address" : "00:00:00:00:00:00",
       "name" : "pflog0"
    }
]

If anybody is interested, I can share these patches, which might need
some polishing. Also, I'm ready to do what's necessary to get these
into stable as for my purposes I need this functionality.

Regards,
Jeroen Derks

On 13/05/2020 11:57, Landry Breuil wrote:

> Hi,
>
> experimenting with proxmox VE since some years (but that also applies to
> plain kvm/qemu OpenBSD vms), today i had a look at the qemu guest agent
> feature:
> https://pve.proxmox.com/wiki/Qemu-guest-agent
> https://wiki.qemu.org/Features/GuestAgent
>
> in the host config/webui, enabling the guest agent adds a virtio-serial
> device to the kvm cmdline:
> -chardev socket,path=/var/run/qemu-server/100.qga,server,nowait,id=qga0
> -device virtio-serial,id=qga0,bus=pci.0,addr=0x8
> -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0
>
> in the guest, this is detected as a new virtio device for which we have
> no dedicated driver, so it stays unconfigured:
>
> virtio1 at pci0 dev 8 function 0 "Qumranet Virtio Console" rev 0x00
> virtio1: no matching child driver; not configured
>
> but looking at the qemu-guest-agent & proxmox qm docs:
> https://www.qemu.org/docs/master/interop/qemu-ga.html
> https://pve.proxmox.com/wiki/Manual:_qm.conf
>
> there's a way to tell proxmox to use the 'isa-serial' device type:
> proxmox# grep agent /etc/pve/qemu-server/100.conf
> agent: 1,type=isa
>
> which adds another device type to the kvm cmdline:
> -chardev socket,path=/var/run/qemu-server/100.qga,server,nowait,id=qga0
> -device isa-serial,chardev=qga0
>
> and in the guest.. there's a new serial device (com0 is used by the qemu
> console):
> com0 at isa0 port 0x3f8/8 irq 4: ns16550a, 16 byte fifo
> com0: console
> com1 at isa0 port 0x2f8/8 irq 3: ns16550a, 16 byte fifo
>
> from that point, one can run qemu-ga, pointing at this serial port (-f
> and -t are used because otherwise qemu-ga hardcodes
> /usr/local/$something):
> guest$ doas qemu-ga -m isa-serial -p /dev/cua01 -f /tmp/xx.pid -t /tmp/ -v
> 1589361079.833754: debug: disabling command: guest-suspend-disk
> 1589361079.833788: debug: disabling command: guest-suspend-ram
> 1589361079.833799: debug: disabling command: guest-suspend-hybrid
> 1589361079.833809: debug: disabling command: guest-network-get-interfaces
> 1589361079.833818: debug: disabling command: guest-get-vcpus
> 1589361079.833827: debug: disabling command: guest-set-vcpus
> 1589361079.833836: debug: disabling command: guest-get-memory-blocks
> 1589361079.833852: debug: disabling command: guest-set-memory-blocks
> 1589361079.833862: debug: disabling command: guest-get-memory-block-size
> 1589361079.833872: debug: disabling command: guest-get-memory-block-info
> 1589361079.833881: debug: disabling command: guest-get-fsinfo
> 1589361079.833890: debug: disabling command: guest-fsfreeze-status
> 1589361079.833898: debug: disabling command: guest-fsfreeze-freeze
> 1589361079.833907: debug: disabling command: guest-fsfreeze-freeze-list
> 1589361079.833916: debug: disabling command: guest-fsfreeze-thaw
> 1589361079.833924: debug: disabling command: guest-get-fsinfo
> 1589361079.833933: debug: disabling command: guest-fstrim
>
> (yeah, some commands are disabled, something to probably fix w/
> upstream, freebsd has some work done in
> https://github.com/aborche/qemu-guest-agent)
>
> from that point one can send commands via the host:
>
> proxmox# qm agent 100 get-host-name
> {
>     "host-name" : "c64.proxmox2"
> }
>
> proxmox# qm agent 100 get-time
> 1589361360033920000
> proxmox# qm agent 100 get-osinfo
> {
>     "kernel-release" : "6.7",
>     "kernel-version" : "GENERIC.MP#182",
>     "machine" : "amd64"
> }
>
> sadly, sending 'shutdown' doesnt work, but that should be possible to
> fix.
>
> according to the 'info' command, those qemu commands should work:
> proxmox# qm guest cmd 100 info | jq '.supported_commands | .[] | select(.enabled == true) | select (."success-response" == true) | .name'
> "guest-get-osinfo"
> "guest-get-timezone"
> "guest-get-users"
> "guest-get-host-name"
> "guest-exec"
> "guest-exec-status"
> "guest-set-user-password"
> "guest-file-flush"
> "guest-file-seek"
> "guest-file-write"
> "guest-file-read"
> "guest-file-close"
> "guest-file-open"
> "guest-info"
> "guest-set-time"
> "guest-get-time"
> "guest-ping"
> "guest-sync"
> "guest-sync-delimited"
>
> testing shows some are broken at runtime, but that allows the host to
> list files within the guest:
>
> proxmox# qm guest exec 100 ls /tmp
> {
>     "exitcode" : 0,
>     "exited" : 1,
>     "out-data" : ".ICE-unix\n.X11-unix\nqga.state\nsndio\ntmux-1000\nvi.recover\n"
> }
>
> and finally, this allows a graceful shutdown of the guest from the host:
>
> proxmox# qm guest exec 100 halt
> QEMU guest agent is not running
>
> (and the vm is properly stopped)
>
> Hoping that can be useful for people who use OpenBSD in qemu/kvm VMs,
> havent found any kind of doc on that specific to OpenBSD so far.
>
> bits to consider portswise: i think it would make sense to subpackage
> qemu-ga binary, having to install the complete qemu package with all its
> dependencies in a VM if a bit much imo. Will see if that's feasible..
> fixing hardcoded paths and some commands would be nice too :)
>
> Landry
>

Reply | Threaded
Open this post in threaded view
|

Re: [doc] using qemu guest agent on OpenBSD kvm/qemu guests

Landry Breuil-5
On Thu, Apr 08, 2021 at 01:04:51PM +0200, Jeroen Derks wrote:

> Hi there,
>
> @Landry Thanks for the great article, it was just what I was looking
> for!
>
> Since I wanted to be able to retrieve the network interfaces from the
> VM, I have applied the FreeBSD patches on qemu-5.1.0 (and slightly
> modified them to work with OpenBSD 6.8) which enables the additional
> commands:
>
> "guest-get-fsinfo"
> "guest-get-vcpus"
> "guest-network-get-interfaces"
>
> Which allows, in my case, proxmox to show the network interfaces and
> addresses of an OpenBSD VM:

well that's very nice, definitely interested in those freebsd patches !
not 100% sure those will get in 6.9 though as we're slowly moving towards
lock, but send them anyway, and we'll figure out what we can do from
there. I suppose those come from https://github.com/aborche/qemu-guest-agent ?

in all cases, emulators/qemu has a MAINTAINER, so make sure to directly
cc him in your next messages :)

Landry

Reply | Threaded
Open this post in threaded view
|

Re: [doc] using qemu guest agent on OpenBSD kvm/qemu guests

Jeroen Derks
On 08/04/2021 14:35, Landry Breuil wrote:>
> well that's very nice, definitely interested in those freebsd patches !
> not 100% sure those will get in 6.9 though as we're slowly moving towards
> lock, but send them anyway, and we'll figure out what we can do from
> there. I suppose those come from https://github.com/aborche/qemu-guest-agent ?

Indeed, here they are (if there's interest I can also send a diff of the patches
compared to the FreeBSD patches to see what I've done):

--- qga/Makefile.objs.orig
+++ qga/Makefile.objs
@@ -1,3 +1,5 @@
+CFLAGS += -DBSD_GUEST_AGENT
+
  commands-posix.o-libs := $(LIBUDEV_LIBS)
  qga-obj-y = commands.o guest-agent-command-state.o main.o
  qga-obj-$(CONFIG_POSIX) += commands-posix.o channel-posix.o
--- qga/commands-posix.c.orig
+++ qga/commands-posix.c
@@ -28,6 +28,26 @@
  #include "qemu/cutils.h"
  #include "commands-common.h"
 
+#ifdef BSD_GUEST_AGENT
+#include <ifaddrs.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <ifaddrs.h>
+#include <sys/statvfs.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <netinet/if_ether.h>
+#include <net/if_var.h>
+
+#ifdef __OpenBSD__
+#define SIOCGIFLLADDR   _IOWR('i', 32, struct ifreq)     /* get link level addr */
+#define SIOCGHWADDR     SIOCGIFLLADDR
+#endif
+#endif
+
  #ifdef HAVE_UTMPX
  #include <utmpx.h>
  #endif
@@ -85,14 +105,23 @@ void qmp_guest_shutdown(bool has_mode, const char *mod
  {
      const char *shutdown_flag;
      Error *local_err = NULL;
+    char *shutdown_mode;
      pid_t pid;
      int status;
 
      slog("guest-shutdown called, mode: %s", mode);
      if (!has_mode || strcmp(mode, "powerdown") == 0) {
+#ifdef BSD_GUEST_AGENT
+        shutdown_flag = "-p";
+#else
          shutdown_flag = "-P";
+#endif // BSD_GUEST_AGENT
      } else if (strcmp(mode, "halt") == 0) {
+#ifdef BSD_GUEST_AGENT
+        shutdown_flag = "-h";
+#else
          shutdown_flag = "-H";
+#endif // BSD_GUEST_AGENT
      } else if (strcmp(mode, "reboot") == 0) {
          shutdown_flag = "-r";
      } else {
@@ -109,8 +138,15 @@ void qmp_guest_shutdown(bool has_mode, const char *mod
          reopen_fd_to_null(1);
          reopen_fd_to_null(2);
 
+        shutdown_mode = g_strdup_printf("hypervisor initiated %s", mode);
+
+#ifdef BSD_GUEST_AGENT
+        execle("/sbin/shutdown", "-h", shutdown_flag, "+0",
+               shutdown_mode, (char*)NULL, environ);
+#else
          execle("/sbin/shutdown", "shutdown", "-h", shutdown_flag, "+0",
-               "hypervisor initiated shutdown", (char*)NULL, environ);
+               shutdown_mode, (char*)NULL, environ);
+#endif // BSD_GUEST_AGENT
          _exit(EXIT_FAILURE);
      } else if (pid < 0) {
          error_setg_errno(errp, errno, "failed to create child process");
@@ -153,9 +189,11 @@ int64_t qmp_guest_get_time(Error **errp)
  void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
  {
      int ret;
+#ifndef BSD_GUEST_AGENT
      int status;
      pid_t pid;
      Error *local_err = NULL;
+#endif
      struct timeval tv;
      static const char hwclock_path[] = "/sbin/hwclock";
      static int hwclock_available = -1;
@@ -193,7 +231,7 @@ void qmp_guest_set_time(bool has_time, int64_t time_ns
              return;
          }
      }
-
+#ifndef BSD_GUEST_AGENT
      /* 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
@@ -230,6 +268,7 @@ void qmp_guest_set_time(bool has_time, int64_t time_ns
          error_setg(errp, "hwclock failed to set hardware clock to system time");
          return;
      }
+#endif // BSD_GUEST_AGENT
  }
 
  typedef enum {
@@ -789,7 +828,7 @@ static void build_fs_mount_list(FsMountList *mounts, E
 
          QTAILQ_INSERT_TAIL(mounts, mount, next);
      }
-    free(line);
+    g_free(line);
 
      fclose(fp);
  }
@@ -2627,25 +2666,263 @@ void qmp_guest_suspend_hybrid(Error **errp)
  {
      error_setg(errp, QERR_UNSUPPORTED);
  }
-
+#ifndef BSD_GUEST_AGENT
  GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
  {
      error_setg(errp, QERR_UNSUPPORTED);
      return NULL;
  }
+#else
+static GuestNetworkInterfaceList *
+guest_find_interface(GuestNetworkInterfaceList *head,
+                     const char *name)
+{
+    for (; head; head = head->next) {
+        if (strcmp(head->value->name, name) == 0) {
+            break;
+        }
+    }
 
+    return head;
+}
+
+static int guest_get_network_stats(const char *name,
+                       GuestNetworkInterfaceStat *stats)
+{
+ return -1;
+}
+
+GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
+{
+    GuestNetworkInterfaceList *head = NULL, *cur_item = NULL;
+    struct ifaddrs *ifap, *ifa;
+
+    if (getifaddrs(&ifap) < 0) {
+        error_setg_errno(errp, errno, "getifaddrs failed");
+        goto error;
+    }
+
+    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+        GuestNetworkInterfaceList *info;
+        GuestIpAddressList **address_list = NULL, *address_item = NULL;
+        GuestNetworkInterfaceStat  *interface_stat = NULL;
+        char addr4[INET_ADDRSTRLEN];
+        char addr6[INET6_ADDRSTRLEN];
+        int sock;
+        struct ifreq ifr;
+        // struct ifnet ifp;
+        // struct if_data ifd;
+        // unsigned char *mac_addr;
+        char mac_addr[18]="00:00:00:00:00:00"; // = char[18];
+        void *p;
+
+        g_debug("Processing %s interface", ifa->ifa_name);
+
+        info = guest_find_interface(head, ifa->ifa_name);
+
+        if (!info) {
+            info = g_malloc0(sizeof(*info));
+            info->value = g_malloc0(sizeof(*info->value));
+            info->value->name = g_strdup(ifa->ifa_name);
+
+            if (!cur_item) {
+                head = cur_item = info;
+            } else {
+                cur_item->next = info;
+                cur_item = info;
+            }
+        }
+
+        if (!info->value->has_hardware_address &&
+            ifa->ifa_flags & SIOCGHWADDR) {
+            /* we haven't obtained HW address yet */
+            sock = socket(PF_INET, SOCK_STREAM, 0);
+            if (sock == -1) {
+                error_setg_errno(errp, errno, "failed to create socket");
+                goto error;
+            }
+            close(sock);
+
+            memset(&ifr, 0, sizeof(ifr));
+            pstrcpy(ifr.ifr_name, IF_NAMESIZE, info->value->name);
+            // memset(&ifp, 0, sizeof(ifp));
+            // memset(&ifd, 0, sizeof(ifd));
+
+            if (ifa->ifa_addr->sa_family == AF_LINK) {
+             struct sockaddr_dl *sdl =
+             (struct sockaddr_dl *)ifa->ifa_addr;
+
+                info->value->has_hardware_address = false;
+
+                if (sdl->sdl_type == IFT_ETHER &&
+                 sdl->sdl_alen == ETHER_ADDR_LEN) {
+                        snprintf(mac_addr, 18, "%s", ether_ntoa((struct ether_addr *)LLADDR(sdl)));
+                        info->value->has_hardware_address = true;
+                }
+                else
+                {
+                    info->value->has_hardware_address = true;
+                }
+                info->value->hardware_address = g_strdup_printf("%s", mac_addr);
+             }
+
+        }
+
+        if (ifa->ifa_addr &&
+            ifa->ifa_addr->sa_family == AF_INET) {
+            /* interface with IPv4 address */
+            p = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
+            if (!inet_ntop(AF_INET, p, addr4, sizeof(addr4))) {
+                error_setg_errno(errp, errno, "inet_ntop failed");
+                goto error;
+            }
+
+            address_item = g_malloc0(sizeof(*address_item));
+            address_item->value = g_malloc0(sizeof(*address_item->value));
+            address_item->value->ip_address = g_strdup(addr4);
+            address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV4;
+
+            if (ifa->ifa_netmask) {
+                /* Count the number of set bits in netmask.
+                 * This is safe as '1' and '0' cannot be shuffled in netmask. */
+                p = &((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr;
+                address_item->value->prefix = ctpop32(((uint32_t *) p)[0]);
+            }
+        } else if (ifa->ifa_addr &&
+                   ifa->ifa_addr->sa_family == AF_INET6) {
+            /* interface with IPv6 address */
+            p = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+            if (!inet_ntop(AF_INET6, p, addr6, sizeof(addr6))) {
+                error_setg_errno(errp, errno, "inet_ntop failed");
+                goto error;
+            }
+
+            address_item = g_malloc0(sizeof(*address_item));
+            address_item->value = g_malloc0(sizeof(*address_item->value));
+            address_item->value->ip_address = g_strdup(addr6);
+            address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV6;
+
+            if (ifa->ifa_netmask) {
+                /* Count the number of set bits in netmask.
+                 * This is safe as '1' and '0' cannot be shuffled in netmask. */
+                p = &((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr;
+                address_item->value->prefix =
+                    ctpop32(((uint32_t *) p)[0]) +
+                    ctpop32(((uint32_t *) p)[1]) +
+                    ctpop32(((uint32_t *) p)[2]) +
+                    ctpop32(((uint32_t *) p)[3]);
+            }
+        }
+
+        if (!address_item) {
+            continue;
+        }
+
+        address_list = &info->value->ip_addresses;
+
+        while (*address_list && (*address_list)->next) {
+            address_list = &(*address_list)->next;
+        }
+
+        if (!*address_list) {
+            *address_list = address_item;
+        } else {
+            (*address_list)->next = address_item;
+        }
+
+        info->value->has_ip_addresses = true;
+
+        if (!info->value->has_statistics) {
+            interface_stat = g_malloc0(sizeof(*interface_stat));
+            if (guest_get_network_stats(info->value->name,
+                interface_stat) == -1) {
+                info->value->has_statistics = false;
+                g_free(interface_stat);
+            } else {
+                info->value->statistics = interface_stat;
+                info->value->has_statistics = true;
+            }
+        }
+    }
+
+    freeifaddrs(ifap);
+    return head;
+
+error:
+    freeifaddrs(ifap);
+    qapi_free_GuestNetworkInterfaceList(head);
+    return NULL;
+}
+#endif // BSD_GUEST_AGENT
+
+#ifndef BSD_GUEST_AGENT
  GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
  {
      error_setg(errp, QERR_UNSUPPORTED);
      return NULL;
  }
+#else
+GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
+{
+    int64_t current;
+    GuestLogicalProcessorList *head, **link;
+    long sc_max;
+    Error *local_err = NULL;
+    int Query[2];
+    int NumCpu = 0;
+    size_t Length = sizeof(NumCpu);
+    Query[0] = CTL_HW;
+    Query[1] = HW_NCPU;
 
+    current = 0;
+    head = NULL;
+    link = &head;
+    if (sysctl(Query, 2, &NumCpu, &Length, NULL, 0) == -1) {
+        error_setg(errp, "sysctl get CTL_HW.HW_NCPU failed");
+    }
+    sc_max = NumCpu;
+
+    while (local_err == NULL && current < sc_max) {
+        GuestLogicalProcessor *vcpu;
+        GuestLogicalProcessorList *entry;
+        int64_t id = current++;
+        // char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
+        //                              id);
+
+        // if (g_file_test(path, G_FILE_TEST_EXISTS)) {
+        vcpu = g_malloc0(sizeof *vcpu);
+        vcpu->logical_id = id;
+        vcpu->has_can_offline = false; /* lolspeak ftw */
+        vcpu->online = true;
+        vcpu->can_offline = false;
+        // transfer_vcpu(vcpu, true, path, &local_err);
+        entry = g_malloc0(sizeof *entry);
+        entry->value = vcpu;
+        *link = entry;
+        link = &entry->next;
+        // }
+        // g_free(path);
+    }
+
+    if (local_err == NULL) {
+        /* there's no guest with zero VCPUs */
+        g_assert(head != NULL);
+        return head;
+    }
+
+    qapi_free_GuestLogicalProcessorList(head);
+    error_propagate(errp, local_err);
+    return NULL;
+}
+#endif // BSD_GUEST_AGENT
+
  int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
  {
      error_setg(errp, QERR_UNSUPPORTED);
      return -1;
  }
 
+#ifndef BSD_GUEST_AGENT
  void qmp_guest_set_user_password(const char *username,
                                   const char *password,
                                   bool crypted,
@@ -2653,7 +2930,114 @@ void qmp_guest_set_user_password(const char *username,
  {
      error_setg(errp, QERR_UNSUPPORTED);
  }
+#else
+void qmp_guest_set_user_password(const char *username,
+                                 const char *password,
+                                 bool crypted,
+                                 Error **errp)
+{
+    Error *local_err = NULL;
+    char *pw_path = NULL;
+    pid_t pid;
+    int status;
+    int datafd[2] = { -1, -1 };
+    char *rawpasswddata = NULL;
+    size_t rawpasswdlen;
+    char *chpasswddata = NULL;
+    size_t chpasswdlen;
 
+    rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
+    if (!rawpasswddata) {
+        return;
+    }
+
+    rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
+    rawpasswddata[rawpasswdlen] = '\0';
+
+    if (strchr(rawpasswddata, '\n')) {
+        error_setg(errp, "forbidden characters in raw password");
+        goto out;
+    }
+
+    if (strchr(username, '\n') ||
+        strchr(username, ':')) {
+        error_setg(errp, "forbidden characters in username");
+        goto out;
+    }
+
+    chpasswddata = g_strdup_printf("%s", rawpasswddata);
+    chpasswdlen = strlen(chpasswddata);
+
+    pw_path = g_find_program_in_path("pw");
+
+    if (!pw_path) {
+        error_setg(errp, "cannot find 'pw' program in PATH");
+        goto out;
+    }
+
+    if (pipe(datafd) < 0) {
+        error_setg(errp, "cannot create pipe FDs");
+        goto out;
+    }
+
+    pid = fork();
+    if (pid == 0) {
+        close(datafd[1]);
+        /* child */
+        setsid();
+        dup2(datafd[0], 0);
+        reopen_fd_to_null(1);
+        reopen_fd_to_null(2);
+
+        if (crypted) {
+            execle(pw_path, "pw", "usermod", username, "-H", "0", (char*)NULL, environ);
+        } else {
+            execle(pw_path, "pw", "usermod", username, "-h", "0", (char*)NULL, environ);
+        }
+        _exit(EXIT_FAILURE);
+    } else if (pid < 0) {
+        error_setg_errno(errp, errno, "failed to create child process");
+        goto out;
+    }
+    close(datafd[0]);
+    datafd[0] = -1;
+
+    if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) {
+        error_setg_errno(errp, errno, "cannot write new account password");
+        goto out;
+    }
+    close(datafd[1]);
+    datafd[1] = -1;
+
+    ga_wait_child(pid, &status, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        goto out;
+    }
+
+    if (!WIFEXITED(status)) {
+        error_setg(errp, "child process has terminated abnormally");
+        goto out;
+    }
+
+    if (WEXITSTATUS(status)) {
+        error_setg(errp, "child process has failed to set user password");
+        goto out;
+    }
+
+out:
+    g_free(chpasswddata);
+    g_free(rawpasswddata);
+    g_free(pw_path);
+    if (datafd[0] != -1) {
+        close(datafd[0]);
+    }
+    if (datafd[1] != -1) {
+        close(datafd[1]);
+    }
+}
+#endif // BSD_GUEST_AGENT
+
  GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
  {
      error_setg(errp, QERR_UNSUPPORTED);
@@ -2677,12 +3061,325 @@ GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(
 
  #if !defined(CONFIG_FSFREEZE)
 
+#ifndef BSD_GUEST_AGENT
  GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
  {
      error_setg(errp, QERR_UNSUPPORTED);
      return NULL;
  }
+#else
 
+/* ===================================================================== */
+typedef struct FsMount {
+    char *dirname;
+    char *devtype;
+    char *size;
+    char *used;
+    char *free;
+    char *load;
+    char *mntpoint;
+    unsigned int devmajor, devminor;
+    QTAILQ_ENTRY(FsMount) next;
+} FsMount;
+
+typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList;
+
+static void free_fs_mount_list(FsMountList *mounts)
+{
+     FsMount *mount, *temp;
+
+     if (!mounts) {
+         return;
+     }
+
+     QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
+         QTAILQ_REMOVE(mounts, mount, next);
+         g_free(mount->dirname);
+         g_free(mount->devtype);
+         g_free(mount->size);
+         g_free(mount->used);
+ g_free(mount->free);
+ g_free(mount->load);
+ g_free(mount->mntpoint);
+         g_free(mount);
+     }
+}
+
+
+//#define BUFSIZE 2048
+
+static void build_fs_mount_list(FsMountList *mounts, Error **errp)
+{
+    FsMount *mount;
+#ifdef __OpenBSD__
+    char const *dfcmd = "/bin/df";
+#else // defined(__OpenBSD__)
+    char const *dfcmd = "/bin/df -hT";
+#endif // defined(__OpenBSD__)
+    //char buf[BUFSIZE];
+
+    FILE *fp;
+    char *line = NULL;
+    //, *dash;
+    size_t n;
+    int ret;
+    //, dir_s, dir_e, type_s, type_e, dev_s, dev_e;
+    char dev_name[128], size[12], used[12], free[12], load[10], mounted[128];
+#ifndef __OpenBSD__
+    char fstype[12] = "";
+#endif // !defined(__OpenBSD__)
+    //int dev_name, fstype, size, used, free, load, mounted;
+
+    if ((fp = popen(dfcmd, "r")) == NULL) {
+        g_debug("Cannot open '%s'!!\n", dfcmd);
+        error_setg_errno(errp, errno,
+                         "failed to create child process for command: %s",
+                         dfcmd);
+        return;
+    }
+
+    while (getline(&line, &n, fp) != -1) {
+        //g_debug("line '%s'", line);
+#ifdef __OpenBSD__
+        ret = sscanf(line, "%127s%11s%11s%11s%9s%127s",
+                     dev_name, size, used, free, load, mounted);
+#else // defined(__OpenBSD__)
+        ret = sscanf(line, "%127s%11s%11s%11s%11s%9s%127s",
+                     dev_name, fstype, size, used, free, load, mounted);
+        //g_debug("ret %d, dev_name '%s', fstype '%s', size '%s', used '%s', free '%s', load '%s', mounted '%s'",
+        //        ret, dev_name, fstype, size, used, free, load, mounted);
+#endif // defined(__OpenBSD__)
+        if (g_str_equal(dev_name, "Filesystem")
+#ifndef __OpenBSD__
+                ||g_str_equal(fstype,"devfs")
+                ||g_str_equal(fstype,"procfs")
+                ||g_str_equal(fstype,"fdescfs")
+#endif // !defined(__OpenBSD__)
+          ) {
+            continue;
+        }
+
+#ifdef __OpenBSD__
+        if (ret < 6) {
+#else // defined(__OpenBSD__)
+        if (ret < 7) {
+#endif // defined(__OpenBSD__)
+            continue;
+        }
+
+        mount = g_new0(FsMount, 1);
+        mount->dirname = g_strdup(dev_name);
+#ifndef __OpenBSD__
+        mount->devtype = g_strdup(fstype);
+#endif // defined(__OpenBSD__)
+        mount->free = g_strdup(free);
+        mount->load = g_strdup(load);
+        mount->size = g_strdup(size);
+        mount->used = g_strdup(used);
+        mount->mntpoint = g_strdup(mounted);
+        mount->devmajor = 0;
+        mount->devminor = 0;
+
+        QTAILQ_INSERT_TAIL(mounts, mount, next);
+    }
+    g_free(line);
+
+    fclose(fp);
+}
+
+#ifdef __OpenBSD__
+
+static void add_type_fs_mount_list(FsMountList *mounts, Error **errp)
+{
+    FILE *fp;
+    char const *mountcmd = "/sbin/mount";
+    char *line = NULL;
+    size_t n;
+    int ret;
+    char mnt_fsname[128], mnt_dir[128], mnt_type[32], mnt_opts[128];
+    struct FsMount *mount;
+
+    // get mounts from mount command
+    if ((fp = popen(mountcmd, "r")) == NULL) {
+        g_debug("Cannot open '%s'!!\n", mountcmd);
+        error_setg_errno(errp, errno, "failed to create child process for command: %s", mountcmd);
+        return;
+    }
+
+    // loop through mounts from mount command
+    while (getline(&line, &n, fp) != -1) {
+        //g_debug("line '%s'", line);
+
+        ret = sscanf(line, "%127s on %127s type %31s (%127s)",
+                     mnt_fsname, mnt_dir, mnt_type, mnt_opts);
+        //g_debug("ret %d, fsname '%s', dir '%s', type '%s', opts '%s'",
+        //        ret, mnt_fsname, mnt_dir, mnt_type, mnt_opts);
+
+        if (4 != ret ||
+            '/' != mnt_fsname[0] ||
+            '/' != mnt_dir[0] ||
+            g_str_equal("smbfs", mnt_type) ||
+            g_str_equal("cifs", mnt_type)) {
+            continue;
+        }
+
+        // find mount in supplied mounts list and update device type
+        QTAILQ_FOREACH(mount, mounts, next) {
+            if (NULL == mount->devtype && g_str_equal(mount->dirname, mnt_fsname)) {
+                mount->devtype = g_strdup(mnt_type);
+                break;
+            }
+        }
+    }
+    g_free(line);
+
+    fclose(fp);
+}
+
+#endif // defined(__OpenBSD__)
+
+/* ======================================================= */
+
+
+/* Return a list of the disk device(s)' info which @mount lies on */
+static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
+                                               Error **errp)
+{
+    GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
+    struct statvfs buf;
+    unsigned long used, nonroot_total, fr_size;
+
+    fs->name = g_strdup(mount->dirname);
+    fs->mountpoint = g_strdup(mount->mntpoint);
+    fs->type = g_strdup(mount->devtype);
+
+    /*
+     * if you need extended structure like this
+     *
+    {
+      "name": "/dev/vtbd0p2",
+      "total-bytes": 9079570432,
+      "mountpoint": "/",
+      "disk": [
+        {
+          "serial": "serial",
+          "bus-type": "virtio",
+          "bus": 0,
+          "unit": 0,
+          "pci-controller": {
+            "bus": 0,
+            "slot": 0,
+            "domain": 0,
+            "function": 0
+          },
+          "dev": "/dev/vtbd0p2",
+          "target": 0
+        }
+      ],
+      "used-bytes": 7378640896,
+      "type": "ufs"
+    }
+
+    instead
+
+    {
+      "name": "/dev/vtbd0p2",
+      "total-bytes": 9079570432,
+      "mountpoint": "/",
+      "disk": [],
+      "used-bytes": 7378747392,
+      "type": "ufs"
+    }
+
+    uncomment next block with pciaddr
+
+    */
+    /*
+    GuestPCIAddress *pciaddr = NULL;
+    GuestDiskAddressList *list = NULL;
+    GuestDiskAddress *disk;
+    pciaddr = g_malloc0(sizeof(*pciaddr));
+    pciaddr->domain = 0;
+    pciaddr->bus = 0;
+    pciaddr->slot = 0;
+    pciaddr->function = 0;
+
+    disk = g_malloc0(sizeof(*disk));
+    disk->pci_controller = pciaddr;
+
+    disk->dev = g_strdup(mount->dirname);
+    disk->has_dev = true;
+    disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
+    disk->serial = g_strdup("serial");
+    disk->has_serial = true;
+
+    list = g_malloc0(sizeof(*list));
+    list->value = disk;
+
+    list->next = fs->disk;
+    fs->disk = list;
+ */
+
+    if (statvfs(fs->mountpoint, &buf) == 0) {
+        fr_size = buf.f_frsize;
+        used = buf.f_blocks - buf.f_bfree;
+        nonroot_total = used + buf.f_bavail;
+        fs->used_bytes = used * fr_size;
+        fs->total_bytes = nonroot_total * fr_size;
+
+        fs->has_total_bytes = true;
+        fs->has_used_bytes = true;
+    }
+
+    //g_free(devpath);
+
+    return fs;
+}
+
+GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
+{
+    FsMountList mounts;
+    struct FsMount *mount;
+    GuestFilesystemInfoList *new, *ret = NULL;
+    Error *local_err = NULL;
+
+    QTAILQ_INIT(&mounts);
+
+    g_debug("Entering to guest_get_fsinfo");
+    build_fs_mount_list(&mounts, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return NULL;
+    }
+
+#ifdef __OpenBSD__
+    add_type_fs_mount_list(&mounts, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return NULL;
+    }
+#endif // defined(__OpenBSD__)
+
+    QTAILQ_FOREACH(mount, &mounts, next) {
+        //g_debug("Building guest fsinfo for '%s'", mount->dirname);
+        //g_debug("Devtype '%s'", mount->devtype);
+        new = g_malloc0(sizeof(*ret));
+        new->value = build_guest_fsinfo(mount, &local_err);
+        new->next = ret;
+        ret = new;
+        if (local_err) {
+            error_propagate(errp, local_err);
+            qapi_free_GuestFilesystemInfoList(ret);
+            ret = NULL;
+            break;
+        }
+    }
+
+    free_fs_mount_list(&mounts);
+    return ret;
+}
+#endif // BSD_GUEST_AGENT
+
  GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
  {
      error_setg(errp, QERR_UNSUPPORTED);
@@ -2728,6 +3425,7 @@ GList *ga_command_blacklist_init(GList *blacklist)
  {
  #if !defined(__linux__)
      {
+#ifndef BSD_GUEST_AGENT
          const char *list[] = {
              "guest-suspend-disk", "guest-suspend-ram",
              "guest-suspend-hybrid", "guest-network-get-interfaces",
@@ -2735,6 +3433,15 @@ GList *ga_command_blacklist_init(GList *blacklist)
              "guest-get-memory-blocks", "guest-set-memory-blocks",
              "guest-get-memory-block-size", "guest-get-memory-block-info",
              NULL};
+#else
+     const char *list[] = {
+            "guest-suspend-disk", "guest-suspend-ram",
+            "guest-suspend-hybrid",
+            "guest-set-vcpus",
+            "guest-get-memory-blocks", "guest-set-memory-blocks",
+            "guest-get-memory-block-size", "guest-get-memory-block-info",
+            NULL};
+#endif // BSD_GUEST_AGENT
          char **p = (char **)list;
 
          while (*p) {
@@ -2745,10 +3452,17 @@ GList *ga_command_blacklist_init(GList *blacklist)
 
  #if !defined(CONFIG_FSFREEZE)
      {
+#ifndef BSD_GUEST_AGENT
          const char *list[] = {
              "guest-get-fsinfo", "guest-fsfreeze-status",
              "guest-fsfreeze-freeze", "guest-fsfreeze-freeze-list",
              "guest-fsfreeze-thaw", "guest-get-fsinfo", NULL};
+#else
+        const char *list[] = {
+            "guest-fsfreeze-status",
+            "guest-fsfreeze-freeze", "guest-fsfreeze-freeze-list",
+            "guest-fsfreeze-thaw",  NULL};
+#endif // BSD_GUEST_AGENT
          char **p = (char **)list;
 
          while (*p) {
--- qga/main.c.orig
+++ qga/main.c
@@ -46,9 +46,15 @@
  #endif
 
  #ifndef _WIN32
+#ifdef BSD_GUEST_AGENT
+#define QGA_VIRTIO_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
+#define QGA_STATE_RELATIVE_DIR  "run"
+#define QGA_SERIAL_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0"
+#else
  #define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0"
  #define QGA_STATE_RELATIVE_DIR  "run"
  #define QGA_SERIAL_PATH_DEFAULT "/dev/ttyS0"
+#endif // BSD_GUEST_AGENT
  #else
  #define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0"
  #define QGA_STATE_RELATIVE_DIR  "qemu-ga"
@@ -1478,7 +1484,11 @@ int main(int argc, char **argv)
      }
 
      if (config->method == NULL) {
+#ifdef BSD_GUEST_AGENT
+        config->method = g_strdup("isa-serial");
+#else
          config->method = g_strdup("virtio-serial");
+#endif // BSD_GUEST_AGENT
      }
 
      socket_activation = check_socket_activation();






Reply | Threaded
Open this post in threaded view
|

Re: [doc] using qemu guest agent on OpenBSD kvm/qemu guests

Landry Breuil-5
On Thu, Apr 08, 2021 at 06:19:58PM +0200, Jeroen Derks wrote:
> On 08/04/2021 14:35, Landry Breuil wrote:>
> > well that's very nice, definitely interested in those freebsd patches !
> > not 100% sure those will get in 6.9 though as we're slowly moving towards
> > lock, but send them anyway, and we'll figure out what we can do from
> > there. I suppose those come from https://github.com/aborche/qemu-guest-agent ?
>
> Indeed, here they are (if there's interest I can also send a diff of the patches
> compared to the FreeBSD patches to see what I've done):

oh, well that looks nice. A bit rought around the edges imo, and im not
sure we want to maintain a rather large patch like this in the
portstree. according to https://wiki.qemu.org/Contribute/SubmitAPatch i
think it should be pushed upstream, do you have any idea if the original
freebsd patches were proposed there or qemu-devel list ?

i'll let brad decide, but as is this doesnt apply as-is against 6.0 rcs
which we're testing for post-6.9....

Landry

Reply | Threaded
Open this post in threaded view
|

Re: [doc] using qemu guest agent on OpenBSD kvm/qemu guests

Landry Breuil-5
On Fri, Apr 09, 2021 at 04:53:17PM +0200, Landry Breuil wrote:

> On Thu, Apr 08, 2021 at 06:19:58PM +0200, Jeroen Derks wrote:
> > On 08/04/2021 14:35, Landry Breuil wrote:>
> > > well that's very nice, definitely interested in those freebsd patches !
> > > not 100% sure those will get in 6.9 though as we're slowly moving towards
> > > lock, but send them anyway, and we'll figure out what we can do from
> > > there. I suppose those come from https://github.com/aborche/qemu-guest-agent ?
> >
> > Indeed, here they are (if there's interest I can also send a diff of the patches
> > compared to the FreeBSD patches to see what I've done):
>
> oh, well that looks nice. A bit rought around the edges imo, and im not
> sure we want to maintain a rather large patch like this in the
> portstree. according to https://wiki.qemu.org/Contribute/SubmitAPatch i
> think it should be pushed upstream, do you have any idea if the original
> freebsd patches were proposed there or qemu-devel list ?
>
> i'll let brad decide, but as is this doesnt apply as-is against 6.0 rcs
> which we're testing for post-6.9....
so i've backported your patch on top of brad's 6.0rc2 qemu update, and
fixed things here and there, removed commented code, and yeah get-vcpus,
get-fsinfo and network-get-interfaces seems to work fine !

i'm not sure we want the set user password thing in the patch, as it
wont work on openbsd (uses the pw command which is freebsd ?). It's also
a bit gross to exec df to fetch the mount point list..

im also not sure what to do with the localstatedir thing (the -t flag),
i've added --localstatedir=/tmp/ to CONFIGURE_ARGS but that feels wrong.
qga needs it to store its qga.state file and probably to store temp
files..

to build the 6.0rc2 update with those two attached patches (to drop
under patches/), just add to the port Makefile:

# qemu-ga support for more operations
CFLAGS+=       -DBSD_GUEST_AGENT

Landry

[08:50] c64:~/mystuff/emulators/qemu/ $mkdir /tmp/run
[08:50] c64:~/mystuff/emulators/qemu/ $doas qemu-ga -v
1618037419.280400: debug: disabling command: guest-suspend-disk
1618037419.280439: debug: disabling command: guest-suspend-ram
1618037419.280452: debug: disabling command: guest-suspend-hybrid
1618037419.280463: debug: disabling command: guest-set-vcpus
1618037419.280474: debug: disabling command: guest-get-memory-blocks
1618037419.280485: debug: disabling command: guest-set-memory-blocks
1618037419.280496: debug: disabling command: guest-get-memory-block-size
1618037419.280507: debug: disabling command: guest-get-memory-block-info
1618037419.280519: debug: disabling command: guest-fsfreeze-status
1618037419.280530: debug: disabling command: guest-fsfreeze-freeze
1618037419.280541: debug: disabling command: guest-fsfreeze-freeze-list
1618037419.280551: debug: disabling command: guest-fsfreeze-thaw
1618037419.280564: debug: disabling command: guest-fstrim
1618037419.280578: debug: disabling command: guest-get-devices


root@openbsd-amd64:~# qm agent 100 get-vcpus
[    
   {
      "logical-id" : 0,
      "online" : true
   },
   {
...
   {
      "logical-id" : 7,
      "online" : true
   }
]
...
root@openbsd-amd64:~# qm agent 100 get-fsinfo
[
   {
      "disk" : [],
      "mountpoint" : "/build",
      "name" : "/dev/sd1a",
      "total-bytes" : 50195449856,
      "type" : "ffs",
      "used-bytes" : 28191496192
   },
   {
      "disk" : [],
      "mountpoint" : "/var",
      "name" : "/dev/sd0e",
      "total-bytes" : 4011888640,
      "type" : "ffs",
      "used-bytes" : 3049684992
   },
...

root@openbsd-amd64:~# qm agent 100 network-get-interfaces
[
   {
      "hardware-address" : "a2:6e:16:9f:42:70",
      "ip-addresses" : [
         {
            "ip-address" : "10.0.3.254",
            "ip-address-type" : "ipv4",
            "prefix" : 24
         }
      ],
      "name" : "vio0"
   },
   {
      "hardware-address" : "be:10:50:5f:d0:78",
      "ip-addresses" : [
         {
            "ip-address" : "10.0.2.2",
            "ip-address-type" : "ipv4",
            "prefix" : 28
         }
      ],
      "name" : "vio1"
   },
...

patch-qga_main_c (921 bytes) Download Attachment
patch-qga_commands-posix_c (24K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: [doc] using qemu guest agent on OpenBSD kvm/qemu guests

Brad Smith-14
On 4/10/2021 3:05 AM, Landry Breuil wrote:

> On Fri, Apr 09, 2021 at 04:53:17PM +0200, Landry Breuil wrote:
>> On Thu, Apr 08, 2021 at 06:19:58PM +0200, Jeroen Derks wrote:
>>> On 08/04/2021 14:35, Landry Breuil wrote:>
>>>> well that's very nice, definitely interested in those freebsd patches !
>>>> not 100% sure those will get in 6.9 though as we're slowly moving towards
>>>> lock, but send them anyway, and we'll figure out what we can do from
>>>> there. I suppose those come from https://github.com/aborche/qemu-guest-agent ?
>>> Indeed, here they are (if there's interest I can also send a diff of the patches
>>> compared to the FreeBSD patches to see what I've done):
>> oh, well that looks nice. A bit rought around the edges imo, and im not
>> sure we want to maintain a rather large patch like this in the
>> portstree. according to https://wiki.qemu.org/Contribute/SubmitAPatch i
>> think it should be pushed upstream, do you have any idea if the original
>> freebsd patches were proposed there or qemu-devel list ?
>>
>> i'll let brad decide, but as is this doesnt apply as-is against 6.0 rcs
>> which we're testing for post-6.9....
> so i've backported your patch on top of brad's 6.0rc2 qemu update, and
> fixed things here and there, removed commented code, and yeah get-vcpus,
> get-fsinfo and network-get-interfaces seems to work fine !

Thanks.

I don't mind keeping this or some variation on it around. It is small
enough.

I would very much hope that there is an attempt to upstream this in some
form.

> i'm not sure we want the set user password thing in the patch, as it
> wont work on openbsd (uses the pw command which is freebsd ?). It's also
> a bit gross to exec df to fetch the mount point list..
>
> im also not sure what to do with the localstatedir thing (the -t flag),
> i've added --localstatedir=/tmp/ to CONFIGURE_ARGS but that feels wrong.
> qga needs it to store its qga.state file and probably to store temp
> files..
>
> to build the 6.0rc2 update with those two attached patches (to drop
> under patches/), just add to the port Makefile:
>
> # qemu-ga support for more operations
> CFLAGS+=       -DBSD_GUEST_AGENT
>
> Landry
>
> [08:50] c64:~/mystuff/emulators/qemu/ $mkdir /tmp/run
> [08:50] c64:~/mystuff/emulators/qemu/ $doas qemu-ga -v
> 1618037419.280400: debug: disabling command: guest-suspend-disk
> 1618037419.280439: debug: disabling command: guest-suspend-ram
> 1618037419.280452: debug: disabling command: guest-suspend-hybrid
> 1618037419.280463: debug: disabling command: guest-set-vcpus
> 1618037419.280474: debug: disabling command: guest-get-memory-blocks
> 1618037419.280485: debug: disabling command: guest-set-memory-blocks
> 1618037419.280496: debug: disabling command: guest-get-memory-block-size
> 1618037419.280507: debug: disabling command: guest-get-memory-block-info
> 1618037419.280519: debug: disabling command: guest-fsfreeze-status
> 1618037419.280530: debug: disabling command: guest-fsfreeze-freeze
> 1618037419.280541: debug: disabling command: guest-fsfreeze-freeze-list
> 1618037419.280551: debug: disabling command: guest-fsfreeze-thaw
> 1618037419.280564: debug: disabling command: guest-fstrim
> 1618037419.280578: debug: disabling command: guest-get-devices
>
>
> root@openbsd-amd64:~# qm agent 100 get-vcpus
> [
>     {
>        "logical-id" : 0,
>        "online" : true
>     },
>     {
> ...
>     {
>        "logical-id" : 7,
>        "online" : true
>     }
> ]
> ...
> root@openbsd-amd64:~# qm agent 100 get-fsinfo
> [
>     {
>        "disk" : [],
>        "mountpoint" : "/build",
>        "name" : "/dev/sd1a",
>        "total-bytes" : 50195449856,
>        "type" : "ffs",
>        "used-bytes" : 28191496192
>     },
>     {
>        "disk" : [],
>        "mountpoint" : "/var",
>        "name" : "/dev/sd0e",
>        "total-bytes" : 4011888640,
>        "type" : "ffs",
>        "used-bytes" : 3049684992
>     },
> ...
>
> root@openbsd-amd64:~# qm agent 100 network-get-interfaces
> [
>     {
>        "hardware-address" : "a2:6e:16:9f:42:70",
>        "ip-addresses" : [
>           {
>              "ip-address" : "10.0.3.254",
>              "ip-address-type" : "ipv4",
>              "prefix" : 24
>           }
>        ],
>        "name" : "vio0"
>     },
>     {
>        "hardware-address" : "be:10:50:5f:d0:78",
>        "ip-addresses" : [
>           {
>              "ip-address" : "10.0.2.2",
>              "ip-address-type" : "ipv4",
>              "prefix" : 28
>           }
>        ],
>        "name" : "vio1"
>     },
> ...