[libvirt] [PATCH v2 4/5] bhyve: implement bhyve argument parser
Fabian Freyer
fabian.freyer at physik.tu-berlin.de
Thu Jun 23 19:22:01 UTC 2016
On 12/06/16 15:29, Roman Bogorodskiy wrote:
> 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:
>>
>> * 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 | 494 +++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 492 insertions(+), 2 deletions(-)
>>
>> diff --git a/src/bhyve/bhyve_parse_command.c b/src/bhyve/bhyve_parse_command.c
>> index 72367bb..be4ff2a 100644
>> --- a/src/bhyve/bhyve_parse_command.c
>> +++ b/src/bhyve/bhyve_parse_command.c
>> @@ -23,6 +23,7 @@
>> */
>>
>> #include <config.h>
>> +#include <getopt_int.h>
>>
>> #include "bhyve_capabilities.h"
>> #include "bhyve_command.h"
>> @@ -225,10 +226,496 @@ 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;
>> + struct _getopt_data *parser;
>> +
>> + if (!argv)
>> + goto error;
>> +
>> + if (VIR_ALLOC(parser) < 0)
>> + goto error;
>> +
>> + while ((c = _getopt_internal_r(argc, argv, optstr,
>> + NULL, NULL, 0, parser, 0)) != -1) {
>> + switch (c) {
>> + case 'A':
>> + def->features[VIR_DOMAIN_FEATURE_ACPI] = VIR_TRISTATE_SWITCH_ON;
>> + break;
>> + case 'c':
>> + if (virStrToLong_i(parser->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, parser->optarg))
>> + goto error;
>> + break;
>> + case 's':
>> + if (bhyveParseBhyvePCIArg(def,
>> + xmlopt,
>> + caps,
>> + &nahcidisks,
>> + &nvirtiodisks,
>> + parser->optarg))
>> + goto error;
>> + break;
>> + case 'm':
>> + if (virStrToLong_ul(parser->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 */
>
> What's the reason behind this assumption? As I can see, the first
> version of bhyve imported into FreeBSD official tree used memory in
> megabytes by default:
>
> https://svnweb.freebsd.org/base/head/usr.sbin/bhyve/bhyverun.c?revision=221828&view=markup
See vm_parse_memsize:
https://svnweb.freebsd.org/base/head/lib/libvmmapi/vmmapi.c?view=markup#l157
>> + 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;
> ^^
>
> Extra 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;
>> + }
>
> I'm wondering if it actually makes sense to throw an error here? I mean,
> if a user had this flag, most likely it's supported by that bhyve binary
> and in unlikely case if it's not, there will be an error thrown anyway,
> but at the later stage.
True, but why not? I'm guessing a potential use case for this might not just
be using native command strings from the same host, but I may be mistaken.
>> + break;
>> + case 'U':
>> + if (virUUIDParse(parser->optarg, def->uuid) < 0) {
>> + virReportError(VIR_ERR_INTERNAL_ERROR, \
>> + _("cannot parse UUID '%s'"), parser->optarg);
>> + goto error;
>> + }
>> + break;
>> + }
>> + }
>> +
>> + if (argc != parser->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;
>> + }
>> +
>> + VIR_FREE(parser);
>> + return 0;
>> +
>> +error:
>> + VIR_FREE(parser);
>> + 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 +743,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);
>> --
>> 2.7.0
>>
>> --
>> libvir-list mailing list
>> libvir-list at redhat.com
>> https://www.redhat.com/mailman/listinfo/libvir-list
>
> Roman Bogorodskiy
>
-------------- 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/20160623/cd3bb7c6/attachment-0001.sig>
More information about the libvir-list
mailing list