[libvirt] [PATCH 2/3] bhyve: implement bhyve argument parser

Fabian Freyer fabian.freyer at physik.tu-berlin.de
Wed Jun 1 10:03:01 UTC 2016


On 01/06/16 10:21, Fabian Freyer wrote:
> A simpe getopt-based argument parser is added for the /usr/sbin/bhyve command,
> loosely based on its argument parser, which reads the following from the bhyve
> command line string:

getopt is not thread safe, so can't use that here. There are a number of possible
solutions:

- use gnulib's getopt which provides a reentrant interface:
  https://lists.gnu.org/archive/html/bug-gnulib/2004-03/msg00018.html
- use string comparisons

I'll fix that in a v2.

> 
> * vm name
> * number of vcpus
> * memory size
> * the time offset (UTC or localtime). This includes a capability check to see
>   if this is actually supported by the bhyve version.
> * features:
>   * acpi
>   * ioapic: While this flag is deprecated in FreeBSD r257423, keep checking for
>     it for backwards compatibiility.
> * the domain UUID; if not explicitely given, one will be generated.
> * lpc devices: for now only the com1 and com2 are supported. It is required for
>    these to be /dev/nmdm[\d+][AB], and the slave devices are automatically
>    inferred from these to be the corresponding end of the virtual null-modem
>    cable: /dev/nmdm<N>A <-> /dev/nmdm<N>B
> * PCI devices:
>   * Disks: these are numbered in the order they are found, for virtio and ahci
>     disks separately. The destination is set to sdX or vdX with X='a'+index;
>     therefore only 'z'-'a' disks are supported.
>     Disks are considered to be block devices if the path
>     starts with /dev, otherwise they are considered to be files.
>   * Networks: only tap devices are supported. Since it isn't possible to tell
>     the type of the network, VIR_DOMAIN_NET_TYPE_ETHERNET is assumed, since it
>     is the most generic. If no mac is specified, one will be generated.
> 
> Signed-off-by: Fabian Freyer <fabian.freyer at physik.tu-berlin.de>
> ---
>  src/bhyve/bhyve_parse_command.c | 491 +++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 489 insertions(+), 2 deletions(-)
> 
> diff --git a/src/bhyve/bhyve_parse_command.c b/src/bhyve/bhyve_parse_command.c
> index 72367bb..0fadb6a 100644
> --- a/src/bhyve/bhyve_parse_command.c
> +++ b/src/bhyve/bhyve_parse_command.c
> @@ -23,6 +23,7 @@
>   */
>  
>  #include <config.h>
> +#include <getopt.h>
>  
>  #include "bhyve_capabilities.h"
>  #include "bhyve_command.h"
> @@ -225,10 +226,493 @@ bhyveCommandLine2argv(const char *nativeConfig,
>      return -1;
>  }
>  
> +static int
> +bhyveParseBhyveLPCArg(virDomainDefPtr def,
> +                      unsigned caps ATTRIBUTE_UNUSED,
> +                      const char *arg)
> +{
> +    /* -l emulation[,config] */
> +    const char *separator = NULL;
> +    const char *param = NULL;
> +    size_t last = 0;
> +    virDomainChrDefPtr chr = NULL;
> +    char *type = NULL;
> +
> +    separator = strchr(arg, ',');
> +    param = separator + 1;
> +
> +    if (!separator)
> +        goto error;
> +
> +    if (VIR_STRNDUP(type, arg, separator - arg) < 0)
> +        goto error;
> +
> +    /* Only support com%d */
> +    if (STRPREFIX(type, "com") && type[4] == 0) {
> +        if (!(chr = virDomainChrDefNew()))
> +            goto error;
> +
> +        chr->source.type = VIR_DOMAIN_CHR_TYPE_NMDM;
> +        chr->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL;
> +
> +        if (!STRPREFIX(param, "/dev/nmdm")) {
> +            virReportError(VIR_ERR_OPERATION_FAILED,
> +                           _("Failed to set com port %s: does not start with "
> +                             "'/dev/nmdm'."), type);
> +                goto error;
> +        }
> +
> +        if (VIR_STRDUP(chr->source.data.file.path, param) < 0) {
> +            virDomainChrDefFree(chr);
> +            goto error;
> +        }
> +
> +        if (VIR_STRDUP(chr->source.data.nmdm.slave, chr->source.data.file.path)
> +            < 0) {
> +            virDomainChrDefFree(chr);
> +            goto error;
> +        }
> +
> +        /* If the last character of the master is 'A', the slave will be 'B'
> +         * and vice versa */
> +        last = strlen(chr->source.data.file.path) - 1;
> +        switch (chr->source.data.file.path[last]) {
> +            case 'A':
> +                chr->source.data.file.path[last] = 'B';
> +                break;
> +            case 'B':
> +                chr->source.data.file.path[last] = 'A';
> +                break;
> +            default:
> +                virReportError(VIR_ERR_OPERATION_FAILED,
> +                               _("Failed to set slave for %s: last letter not "
> +                                 "'A' or 'B'"),
> +                               chr->source.data.file.path);
> +                goto error;
> +        }
> +
> +        switch (type[3]-'0') {
> +        case 1:
> +        case 2:
> +            chr->target.port = type[3] - '1';
> +            break;
> +        default:
> +            virReportError(VIR_ERR_OPERATION_FAILED,
> +                           _("Failed to parse %s: only com1 and com2"
> +                             "supported."), type);
> +            virDomainChrDefFree(chr);
> +            goto error;
> +            break;
> +        }
> +
> +        if (VIR_APPEND_ELEMENT(def->serials, def->nserials, chr) < 0) {
> +            virDomainChrDefFree(chr);
> +            goto error;
> +        }
> +    }
> +
> +    VIR_FREE(type);
> +    return 0;
> +
> +error:
> +    VIR_FREE(chr);
> +    VIR_FREE(type);
> +    return -1;
> +}
> +
> +static int
> +bhyveParsePCISlot(const char *slotdef,
> +                  unsigned *pcislot,
> +                  unsigned *bus,
> +                  unsigned *function)
> +{
> +    /* slot[:function] | bus:slot:function */
> +    const char *curr = NULL;
> +    const char *next = NULL;
> +    unsigned values[3];
> +    int i;
> +
> +    curr = slotdef;
> +    for (i = 0; i < 3; i++) {
> +       char *val = NULL;
> +
> +       next = strchr(curr, ':');
> +
> +       if (VIR_STRNDUP(val, curr, next? next - curr : -1) < 0)
> +           goto error;
> +
> +       if (virStrToLong_ui(val, NULL, 10, &values[i]) < 0)
> +           goto error;
> +
> +       VIR_FREE(val);
> +
> +       if (!next)
> +           break;
> +
> +       curr = next +1;
> +    }
> +
> +    *bus = 0;
> +    *pcislot = 0;
> +    *function = 0;
> +
> +    switch (i + 1) {
> +    case 2:
> +        /* pcislot[:function] */
> +        *function = values[1];
> +    case 1:
> +        *pcislot = values[0];
> +        break;
> +    case 3:
> +        /* bus:pcislot:function */
> +        *bus = values[0];
> +        *pcislot = values[1];
> +        *function = values[2];
> +        break;
> +    }
> +
> +    return 0;
> +error:
> +    return -1;
> +}
> +
> +static int
> +bhyveParsePCIDisk(virDomainDefPtr def,
> +                  unsigned caps ATTRIBUTE_UNUSED,
> +                  unsigned pcislot,
> +                  unsigned pcibus,
> +                  unsigned function,
> +                  int bus,
> +                  int device,
> +                  unsigned *nvirtiodisk,
> +                  unsigned *nahcidisk,
> +                  char *config)
> +{
> +    /* -s slot,virtio-blk|ahci-cd|ahci-hd,/path/to/file */
> +    const char *separator = NULL;
> +    int index = -1;
> +    virDomainDiskDefPtr disk = NULL;
> +
> +    if (VIR_ALLOC(disk) < 0)
> +        goto cleanup;
> +    if (VIR_ALLOC(disk->src) < 0)
> +        goto error;
> +
> +    disk->bus = bus;
> +    disk->device = device;
> +
> +    disk->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
> +    disk->info.addr.pci.slot = pcislot;
> +    disk->info.addr.pci.bus = pcibus;
> +    disk->info.addr.pci.function = function;
> +
> +    if (STRPREFIX(config, "/dev/"))
> +        disk->src->type = VIR_STORAGE_TYPE_BLOCK;
> +    else
> +        disk->src->type = VIR_STORAGE_TYPE_FILE;
> +
> +    if (!config)
> +        goto error;
> +
> +    separator = strchr(config, ',');
> +    if (VIR_STRNDUP(disk->src->path, config,
> +                    separator? separator - config : -1) < 0)
> +        goto error;
> +
> +    if (bus == VIR_DOMAIN_DISK_BUS_VIRTIO) {
> +        index = *nvirtiodisk++;
> +        if (VIR_STRDUP(disk->dst, "vda") < 0)
> +            goto error;
> +    }
> +
> +    else if (bus == VIR_DOMAIN_DISK_BUS_SATA) {
> +        index = *nahcidisk++;
> +        if (VIR_STRDUP(disk->dst, "sda") < 0)
> +            goto error;
> +    }
> +
> +    if (index > 'z' - 'a')
> +        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> +                       _("too many disks"));
> +
> +    disk->dst[2] = 'a' + index;
> +
> +    if (VIR_APPEND_ELEMENT(def->disks, def->ndisks, disk) < 0)
> +        goto error;
> +
> +cleanup:
> +    return 0;
> +
> +error:
> +    virDomainDiskDefFree(disk);
> +    return -1;
> +}
> +
> +static int
> +bhyveParsePCINet(virDomainDefPtr def,
> +                 virDomainXMLOptionPtr xmlopt,
> +                 unsigned caps ATTRIBUTE_UNUSED,
> +                 unsigned pcislot,
> +                 unsigned pcibus,
> +                 unsigned function,
> +                 const char *config)
> +{
> +    /* -s slot,virtio-net,tapN[,mac=xx:xx:xx:xx:xx:xx] */
> +
> +    virDomainNetDefPtr net = NULL;
> +    const char *separator = NULL;
> +    const char *mac = NULL;
> +
> +    if (VIR_ALLOC(net) < 0)
> +        goto cleanup;
> +
> +    /* Let's just assume it is VIR_DOMAIN_NET_TYPE_ETHERNET, it could also be
> +     * a bridge, but this is the most generic option. */
> +    net->type = VIR_DOMAIN_NET_TYPE_ETHERNET;
> +
> +    net->info.type = VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
> +    net->info.addr.pci.slot = pcislot;
> +    net->info.addr.pci.bus = pcibus;
> +    net->info.addr.pci.function = function;
> +
> +    if (!config)
> +        goto error;
> +
> +    if (!STRPREFIX(config, "tap")) {
> +        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> +                       _("Only tap devices supported."));
> +        goto error;
> +    }
> +
> +    separator = strchr(config, ',');
> +    if (VIR_STRNDUP(net->ifname, config,
> +                    separator? separator - config : -1) < 0)
> +        goto error;
> +
> +    if (!separator)
> +        goto cleanup;
> +
> +    if (!STRPREFIX(++separator, "mac=")) {
> +        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> +                       _("Only mac option can be specified for virt-net"));
> +        goto error;
> +    }
> +    mac = separator + 4;
> +
> +    if (virMacAddrParse(mac, &net->mac) < 0) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR,
> +                       _("unable to parse mac address '%s'"),
> +                       mac);
> +        goto cleanup;
> +     }
> +
> +cleanup:
> +    if (!mac)
> +        virDomainNetGenerateMAC(xmlopt, &net->mac);
> +
> +    if (VIR_APPEND_ELEMENT(def->nets, def->nnets, net) < 0)
> +        goto error;
> +    return 0;
> +
> +error:
> +    virDomainNetDefFree(net);
> +    return -1;
> +}
> +
> +static int
> +bhyveParseBhyvePCIArg(virDomainDefPtr def,
> +                      virDomainXMLOptionPtr xmlopt,
> +                      unsigned caps,
> +                      unsigned *nvirtiodisk,
> +                      unsigned *nahcidisk,
> +                      const char *arg)
> +{
> +    /* -s slot,emulation[,conf] */
> +    const char *separator = NULL;
> +    char *slotdef = NULL;
> +    char *emulation = NULL;
> +    char *conf = NULL;
> +    unsigned pcislot, bus, function;
> +
> +    separator = strchr(arg, ',');
> +
> +    if (!separator)
> +        goto error;
> +    else
> +        separator++; /* Skip comma */
> +
> +    if (VIR_STRNDUP(slotdef, arg, separator - arg - 1) < 0)
> +        goto error;
> +
> +    conf = strchr(separator+1, ',');
> +    if (conf)
> +        conf++; /* Skip initial comma */
> +
> +    if (VIR_STRNDUP(emulation, separator, conf? conf - separator - 1 : -1) < 0)
> +        goto error;
> +
> +    if (bhyveParsePCISlot(slotdef, &pcislot, &bus, &function) < 0)
> +        goto error;
> +
> +    if (STREQ(emulation, "ahci-cd"))
> +        bhyveParsePCIDisk(def, caps, pcislot, bus, function,
> +                          VIR_DOMAIN_DISK_BUS_SATA,
> +                          VIR_DOMAIN_DISK_DEVICE_CDROM,
> +                          nvirtiodisk,
> +                          nahcidisk,
> +                          conf);
> +    else if (STREQ(emulation, "ahci-hd"))
> +        bhyveParsePCIDisk(def, caps, pcislot, bus, function,
> +                          VIR_DOMAIN_DISK_BUS_SATA,
> +                          VIR_DOMAIN_DISK_DEVICE_DISK,
> +                          nvirtiodisk,
> +                          nahcidisk,
> +                          conf);
> +    else if (STREQ(emulation, "virtio-blk"))
> +        bhyveParsePCIDisk(def, caps, pcislot, bus, function,
> +                          VIR_DOMAIN_DISK_BUS_VIRTIO,
> +                          VIR_DOMAIN_DISK_DEVICE_DISK,
> +                          nvirtiodisk,
> +                          nahcidisk,
> +                          conf);
> +    else if (STREQ(emulation, "virtio-net"))
> +        bhyveParsePCINet(def, xmlopt, caps, pcislot, bus, function, conf);
> +
> +    VIR_FREE(emulation);
> +    VIR_FREE(slotdef);
> +    return 0;
> +error:
> +    VIR_FREE(emulation);
> +    VIR_FREE(slotdef);
> +    return -1;
> +}
> +
> +/*
> + * Parse the /usr/sbin/bhyve command line.
> + */
> +static int
> +bhyveParseBhyveCommandLine(virDomainDefPtr def,
> +                           virDomainXMLOptionPtr xmlopt,
> +                           unsigned caps,
> +                           int argc, char **argv)
> +{
> +    int c;
> +    const char optstr[] = "abehuwxACHIPSWYp:g:c:s:m:l:U:";
> +    int vcpus = 1;
> +    size_t memory = 0;
> +    unsigned nahcidisks = 0;
> +    unsigned nvirtiodisks = 0;
> +    int opterr_saved = opterr;
> +
> +    if (!argv)
> +        goto error;
> +
> +    optind = 1;
> +    opterr = 0;
> +    while ((c = getopt(argc, argv, optstr)) != -1) {
> +        switch (c) {
> +        case 'A':
> +            def->features[VIR_DOMAIN_FEATURE_ACPI] = VIR_TRISTATE_SWITCH_ON;
> +            break;
> +        case 'c':
> +            if (virStrToLong_i(optarg, NULL, 10, &vcpus) < 0) {
> +                virReportError(VIR_ERR_OPERATION_FAILED,
> +                               _("Failed to parse number of vCPUs."));
> +                goto error;
> +            }
> +            if (virDomainDefSetVcpusMax(def, vcpus) < 0)
> +                goto error;
> +            if (virDomainDefSetVcpus(def, vcpus) < 0)
> +                goto error;
> +            break;
> +        case 'l':
> +            if (bhyveParseBhyveLPCArg(def, caps, optarg))
> +                goto error;
> +            break;
> +        case 's':
> +            if (bhyveParseBhyvePCIArg(def,
> +                                      xmlopt,
> +                                      caps,
> +                                      &nahcidisks,
> +                                      &nvirtiodisks,
> +                                      optarg))
> +                goto error;
> +            break;
> +        case 'm':
> +            if (virStrToLong_ul(optarg, NULL, 10, &memory) < 0) {
> +                virReportError(VIR_ERR_OPERATION_FAILED,
> +                               _("Failed to parse Memory."));
> +                goto error;
> +            }
> +            /* For compatibility reasons, assume memory is givin in MB
> +             * when < 1024, otherwise it is given in bytes */
> +            if (memory < 1024)
> +                memory *= 1024;
> +            else
> +                memory /= 1024UL;
> +            if (def->mem.cur_balloon != 0 && def->mem.cur_balloon != memory) {
> +                virReportError(VIR_ERR_OPERATION_FAILED,
> +                           _("Failed to parse Memory: Memory size mismatch."));
> +                goto error;
> +            }
> +            def->mem.cur_balloon = memory;
> +            virDomainDefSetMemoryTotal(def, memory);
> +            break;
> +            break;
> +        case 'I':
> +            /* While this flag was deprecated in FreeBSD r257423, keep checking
> +             * for it for backwards compatibility. */
> +            def->features[VIR_DOMAIN_FEATURE_APIC] = VIR_TRISTATE_SWITCH_ON;
> +            break;
> +        case 'u':
> +            if ((caps & BHYVE_CAP_RTC_UTC) != 0) {
> +                def->clock.offset = VIR_DOMAIN_CLOCK_OFFSET_UTC;
> +            } else {
> +                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
> +                               _("Installed bhyve binary does not support "
> +                              "UTC clock"));
> +                goto error;
> +            }
> +            break;
> +        case 'U':
> +            if (virUUIDParse(optarg, def->uuid) < 0) {
> +                virReportError(VIR_ERR_INTERNAL_ERROR, \
> +                               _("cannot parse UUID '%s'"), optarg);
> +                goto error;
> +            }
> +            break;
> +        }
> +    }
> +
> +    if (argc != optind) {
> +        virReportError(VIR_ERR_OPERATION_FAILED,
> +                       _("Failed to parse arguments for bhyve command."));
> +        goto error;
> +    }
> +
> +    if (def->name == NULL) {
> +        if (VIR_STRDUP(def->name, argv[argc]) < 0)
> +            goto error;
> +    }
> +    else if (STRNEQ(def->name, argv[argc])) {
> +        /* the vm name of the loader and the bhyverun command differ, throw an
> +         * error here */
> +        virReportError(VIR_ERR_OPERATION_FAILED,
> +                       _("Failed to parse arguments: VM name mismatch."));
> +        goto error;
> +    }
> +
> +    opterr = opterr_saved;
> +    return 0;
> +error:
> +    opterr = opterr_saved;
> +    return -1;
> +}
> +
>  virDomainDefPtr
>  bhyveParseCommandLineString(const char* nativeConfig,
> -                            unsigned caps ATTRIBUTE_UNUSED,
> -                            virDomainXMLOptionPtr xmlopt ATTRIBUTE_UNUSED)
> +                            unsigned caps,
> +                            virDomainXMLOptionPtr xmlopt)
>  {
>      virDomainDefPtr def = NULL;
>      int bhyve_argc = 0;
> @@ -256,6 +740,9 @@ bhyveParseCommandLineString(const char* nativeConfig,
>          goto cleanup;
>      }
>  
> +    if (bhyveParseBhyveCommandLine(def, xmlopt, caps, bhyve_argc, bhyve_argv))
> +        goto cleanup;
> +
>  cleanup:
>      virStringFreeList(loader_argv);
>      virStringFreeList(bhyve_argv);
> 


-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 842 bytes
Desc: OpenPGP digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20160601/b5369cd5/attachment-0001.sig>


More information about the libvir-list mailing list