On Fri, Apr 03, 2026 at 14:33:29 +0200, Roman Bogorodskiy via Devel wrote:
> Bhyve supports virtio-console devices using the following syntax:
> 
>  -s 
> 2:0,virtio-console,org.qemu.guest_agent.0=/path/to/unix/socket,other.port=/other/socket,...
> 
> There are two details about that to consider.
> 
> The first one is that only up to 16 ports per console is supported. This
> is different from the default (31), so update the code to manually add
> the virtio-serial controllers with 16 ports. For the existing
> controllers, make sure to set max ports to 16 or error out if ports
> count greater than 16 was specified.
> 
> The second one is that bhyve does not clean up UNIX sockets for these
> devices. So update virBhyveProcessStop() to remove leftover sockets.
> 
> Not adding capabilities probing as the virtio-console device is
> available on all supported FreeBSD versions and on all supported arches.
> 
> Signed-off-by: Roman Bogorodskiy <[email protected]>
> ---

Disclaimer: I don't have setup to test this. Unfortunately not even the
test suite (bhyvexml2argvtest) seems to run on Linux.


> diff --git a/src/bhyve/bhyve_command.c b/src/bhyve/bhyve_command.c
> index 931d7dd551..8671644cc8 100644
> --- a/src/bhyve/bhyve_command.c
> +++ b/src/bhyve/bhyve_command.c
> @@ -490,6 +490,43 @@ bhyveBuildNVMeControllerArgStr(const virDomainDef *def,
>      return 0;
>  }
>  
> +static int
> +bhyveBuildVirtioSerialControllerArgStr(const virDomainDef *def,
> +                                       virDomainControllerDef *controller,
> +                                       struct _bhyveConn *driver 
> G_GNUC_UNUSED,
> +                                       virCommand *cmd)
> +{
> +    g_auto(virBuffer) opt = VIR_BUFFER_INITIALIZER;
> +    size_t i;
> +
> +    for (i = 0; i < def->nchannels; i++) {
> +        virDomainChrDef *channel = def->channels[i];
> +
> +        if (channel->info.addr.vioserial.controller != controller->idx)
> +            continue;
> +
> +        if (channel->source->type != VIR_DOMAIN_CHR_TYPE_UNIX)
> +            continue;
> +
> +        if (channel->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO)
> +            continue;
> +
> +        virBufferAsprintf(&opt,
> +                          ",%s=%s",
> +                          channel->target.name,
> +                          channel->source->data.nix.path);
> +    }

Doesn't bhyve, similarly to qemu need to escape any occurence of ',' in
the fiule name?


> +
> +    if (virBufferUse(&opt) > 0) {
> +        virCommandAddArg(cmd, "-s");
> +        virCommandAddArgFormat(cmd, "%d:0,virtio-console%s",
> +                               controller->info.addr.pci.slot,
> +                               virBufferContentAndReset(&opt));

This results in:

 +-s 
2:0,virtio-console,org.qemu.guest_agent.0=/var/run/libvirt/bhyve/bhyve.agent,org.qemu.guest_agent.1=/var/run/libvirt/bhyve/bhyve.agent-2
 \

Since the paths are passed from the XML and AFAIU not actually validated
to be devoid of ',' chars, not escaping them will break the output if
the path contains a ','.


> +    }
> +
> +    return 0;
> +}
> +
>  static int
>  bhyveBuildVirtIODiskArgStr(const virDomainDef *def G_GNUC_UNUSED,
>                             virDomainDiskDef *disk,
> @@ -606,9 +643,12 @@ bhyveBuildControllerArgStr(const virDomainDef *def,
>          if (bhyveBuildNVMeControllerArgStr(def, controller, driver, cmd) < 0)
>              return -1;
>          break;
> +    case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL:
> +        if (bhyveBuildVirtioSerialControllerArgStr(def, controller, driver, 
> cmd) < 0)
> +            return -1;
> +        break;
>      case VIR_DOMAIN_CONTROLLER_TYPE_IDE:
>      case VIR_DOMAIN_CONTROLLER_TYPE_FDC:
> -    case VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL:
>      case VIR_DOMAIN_CONTROLLER_TYPE_CCID:
>      case VIR_DOMAIN_CONTROLLER_TYPE_XENBUS:
>      case VIR_DOMAIN_CONTROLLER_TYPE_LAST:
> diff --git a/src/bhyve/bhyve_device.c b/src/bhyve/bhyve_device.c
> index 54511383bb..cf3a9fa4fb 100644
> --- a/src/bhyve/bhyve_device.c
> +++ b/src/bhyve/bhyve_device.c
> @@ -31,6 +31,36 @@
>  
>  VIR_LOG_INIT("bhyve.bhyve_device");
>  
> +
> +static int
> +bhyveDomainAssignVirtioSerialAddresses(virDomainDef *def)
> +{
> +    int ret = -1;
> +    size_t i;
> +    virDomainVirtioSerialAddrSet *addrs = NULL;
> +
> +    if (!(addrs = virDomainVirtioSerialAddrSetCreateFromDomain(def)))
> +        goto cleanup;
> +
> +    VIR_DEBUG("Finished reserving existing ports");
> +
> +    for (i = 0; i < def->nchannels; i++) {
> +        virDomainChrDef *chr = def->channels[i];
> +        if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL &&
> +            chr->targetType == VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO &&
> +            !virDomainVirtioSerialAddrIsComplete(&chr->info) &&
> +            virDomainVirtioSerialAddrAutoAssignFromCache(def, addrs,
> +                                                         &chr->info, false) 
> < 0)
> +            goto cleanup;
> +    }
> +
> +    ret = 0;
> +
> + cleanup:
> +    virDomainVirtioSerialAddrSetFree(addrs);
> +    return ret;
> +}
> +
>  static int
>  bhyveCollectPCIAddress(virDomainDef *def G_GNUC_UNUSED,
>                         virDomainDeviceDef *device G_GNUC_UNUSED,
> @@ -39,7 +69,8 @@ bhyveCollectPCIAddress(virDomainDef *def G_GNUC_UNUSED,
>  {
>      virDomainPCIAddressSet *addrs = NULL;
>      virPCIDeviceAddress *addr = NULL;
> -    if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
> +
> +    if (info->type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI)
>          return 0;
>  

This almost qualifies as a separate fix :)


> @@ -116,6 +147,7 @@ bhyveAssignDevicePCISlots(virDomainDef *def,
>              (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_SATA) ||
>              (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_NVME) ||
>              (def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_SCSI) ||
> +            (def->controllers[i]->type == 
> VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL) ||
>              ((def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_USB) &&
>               (def->controllers[i]->model == 
> VIR_DOMAIN_CONTROLLER_MODEL_USB_NEC_XHCI)) ||
>              def->controllers[i]->type == VIR_DOMAIN_CONTROLLER_TYPE_ISA) {
> @@ -242,5 +274,11 @@ int bhyveDomainAssignPCIAddresses(virDomainDef *def,
>  
>  int bhyveDomainAssignAddresses(virDomainDef *def, virDomainObj *obj)
>  {
> -    return bhyveDomainAssignPCIAddresses(def, obj);
> +    if (bhyveDomainAssignVirtioSerialAddresses(def) < 0)
> +        return -1;
> +
> +    if (bhyveDomainAssignPCIAddresses(def, obj) < 0)
> +        return -1;
> +
> +    return 0;
>  }
> diff --git a/src/bhyve/bhyve_domain.c b/src/bhyve/bhyve_domain.c
> index 4594d7673f..e64be1f4a7 100644
> --- a/src/bhyve/bhyve_domain.c
> +++ b/src/bhyve/bhyve_domain.c
> @@ -89,6 +89,9 @@ bhyveDomainDefPostParse(virDomainDef *def,
>  {
>      struct _bhyveConn *driver = opaque;
>      g_autoptr(virCaps) caps = bhyveDriverGetCapabilities(driver);
> +    size_t i, virtio_channels = 0;
> +    size_t virtio_serial_controllers = 0, virtio_serial_existing_controllers 
> = 0;
> +    size_t virtio_serial_controllers_to_create = 0;

Avoid multiple declarations on a signle line.

>      if (!caps)
>          return -1;
>  
> @@ -141,6 +144,27 @@ bhyveDomainDefPostParse(virDomainDef *def,
>          def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_ROM;
>      }
>  
> +    for (i = 0; i < def->nchannels; i++)
> +        if (def->channels[i]->targetType == 
> VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO)
> +            virtio_channels++;
> +
> +    for (i = 0; i < def->ncontrollers; i++)
> +        if (def->controllers[i]->type == 
> VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL)
> +            virtio_serial_existing_controllers++;
> +
> +    /* bhyve supports 16 ports per virtio-console device */
> +    virtio_serial_controllers = (virtio_channels / 16) + (virtio_channels % 
> 16 != 0);
> +    if (virtio_serial_controllers > virtio_serial_existing_controllers) {
> +        virtio_serial_controllers_to_create = virtio_serial_controllers - 
> virtio_serial_existing_controllers;
> +
> +        for (i = 0; i < virtio_serial_controllers_to_create; i++) {
> +            virDomainControllerDef *cont;
> +
> +            cont = virDomainDefAddController(def, 
> VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL, -1, -1);
> +            cont->opts.vioserial.ports = 16;
> +        }
> +    }
> +
>      return 0;
>  }
>  
> @@ -225,6 +249,10 @@ bhyveDomainDeviceDefPostParse(virDomainDeviceDef *dev,
>              virReportError(VIR_ERR_XML_ERROR, "%s",
>                             _("pci-root and pcie-root controllers should have 
> index 0"));
>              return -1;
> +        } else if (cont->type == VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL) {
> +            /* bhyve supports 16 ports per controller */
> +            if (cont->opts.vioserial.ports == -1)
> +                cont->opts.vioserial.ports = 16;
>          }
>      }
>  
> @@ -287,13 +315,21 @@ bhyveDomainDeviceDefValidate(const virDomainDeviceDef 
> *dev,
>                               void *parseOpaque G_GNUC_UNUSED)
>  {
>      switch (dev->type) {
> -    case VIR_DOMAIN_DEVICE_CONTROLLER:
> -        if (dev->data.controller->type == VIR_DOMAIN_CONTROLLER_TYPE_ISA &&
> -            dev->data.controller->idx != 0) {
> +    case VIR_DOMAIN_DEVICE_CONTROLLER: {
> +        virDomainControllerDef *controller = dev->data.controller;
> +
> +        if (controller->type == VIR_DOMAIN_CONTROLLER_TYPE_ISA &&
> +            controller->idx != 0) {
>              return -1;

This branch doesn't report errors ...

> +        } else if (controller->type == 
> VIR_DOMAIN_CONTROLLER_TYPE_VIRTIO_SERIAL) {
> +            if (controller->opts.vioserial.ports > 16) {
> +                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> +                               _("Bhyve virtio-serial controller supports up 
> to 16 ports"));
> +                return -1;

... but this does.


> +            }
>          }
>          break;
> -
> +    }
>      case VIR_DOMAIN_DEVICE_RNG:
>          if (dev->data.rng->model == VIR_DOMAIN_RNG_MODEL_VIRTIO) {
>              if (dev->data.rng->backend == VIR_DOMAIN_RNG_BACKEND_RANDOM) {
> diff --git a/src/bhyve/bhyve_process.c b/src/bhyve/bhyve_process.c
> index 1d436da609..711d1b66ea 100644
> --- a/src/bhyve/bhyve_process.c
> +++ b/src/bhyve/bhyve_process.c
> @@ -473,6 +473,7 @@ virBhyveProcessStop(struct _bhyveConn *driver,
>                      virDomainObj *vm,
>                      virDomainShutoffReason reason)
>  {
> +    size_t i = 0;
>      int ret = -1;
>      g_autoptr(virCommand) cmd = NULL;
>      bhyveDomainObjPrivate *priv = vm->privateData;
> @@ -512,6 +513,19 @@ virBhyveProcessStop(struct _bhyveConn *driver,
>          }
>      }
>  
> +    /* UNIX sockets cleanup */
> +    for (i = 0; i < vm->def->nchannels; i++) {
> +        virDomainChrDef *channel = vm->def->channels[i];
> +
> +        if (channel->source->type != VIR_DOMAIN_CHR_TYPE_UNIX)
> +            continue;
> +        if (channel->targetType != VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO)
> +            continue;
> +
> +        if (virFileExists(channel->source->data.nix.path))
> +            virFileRemove(channel->source->data.nix.path, 0, 0);

Not a problem of this patch but the 3 code paths at the beginning of
this function skip the rest of the cleanup so some resources such as
allocated ports could stay around if the function fails.

    if (!virDomainObjIsActive(vm)) {
        VIR_DEBUG("VM '%s' not active", vm->def->name);
        return 0;
    }

    if (vm->pid == 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Invalid PID %1$d for VM"),
                       (int)vm->pid);
        return -1;
    }

    if (!(cmd = virBhyveProcessBuildDestroyCmd(driver, vm->def)))
        return -1;




> +    }
> +
>      ret = 0;
>  
>      virCloseCallbacksDomainRemove(vm, NULL, bhyveProcessAutoDestroy);

The rest looks good. The open question is (of this patch) is only wrt
the escaping of the formatted string.

Reply via email to