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)
* 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(a)physik.tu-berlin.de>
---
src/bhyve/bhyve_parse_command.c | 514 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 512 insertions(+), 2 deletions(-)
diff --git a/src/bhyve/bhyve_parse_command.c b/src/bhyve/bhyve_parse_command.c
index e3bc1eb..20b7fce 100644
--- a/src/bhyve/bhyve_parse_command.c
+++ b/src/bhyve/bhyve_parse_command.c
@@ -3,6 +3,7 @@
*
* Copyright (C) 2006-2016 Red Hat, Inc.
* Copyright (C) 2006 Daniel P. Berrange
+ * Copyright (c) 2011 NetApp, Inc.
* Copyright (C) 2016 Fabian Freyer
*
* This library is free software; you can redistribute it and/or
@@ -23,6 +24,8 @@
*/
#include <config.h>
+#include <libutil.h>
+#include <getopt_int.h>
#include "bhyve_capabilities.h"
#include "bhyve_command.h"
@@ -85,6 +88,36 @@ bhyveParseCommandLineUnescape(const char *command)
}
/*
+ * This function is adapted from vm_parse_memsize in
+ * /lib/libvmmapi/vmmapi.c in the FreeBSD Source tree.
+ */
+static int
+bhyveParseMemsize(const char *arg, size_t *ret_memsize)
+{
+ size_t val;
+ int error;
+
+ if (virStrToLong_ul(arg, NULL, 10, &val) == 0) {
+ /*
+ * For the sake of backward compatibility if the memory size
+ * specified on the command line is less than a megabyte then
+ * it is interpreted as being in units of MB.
+ */
+ if (val < 1024 * 1024UL)
+ val *= 1024 * 1024UL;
+ *ret_memsize = val;
+ error = 0;
+ } else {
+ error = expand_number(arg, ret_memsize);
+ }
+
+ /* use memory in KiB here */
+ *ret_memsize /= 1024UL;
+
+ return error;
+}
+
+/*
* Try to extract loader and bhyve argv lists from a command line string.
*/
static int
@@ -231,10 +264,484 @@ bhyveCommandLineToArgv(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->source.data.nmdm.master = NULL;
+ chr->source.data.nmdm.slave = NULL;
+ 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.nmdm.master, 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.nmdm.master) - 1;
+ switch (chr->source.data.file.path[last]) {
+ case 'A':
+ chr->source.data.nmdm.slave[last] = 'B';
+ break;
+ case 'B':
+ chr->source.data.nmdm.slave[last] = 'A';
+ break;
+ default:
+ virReportError(VIR_ERR_OPERATION_FAILED,
+ _("Failed to set slave for %s: last letter not
"
+ "'A' or 'B'"),
+ NULLSTR(chr->source.data.nmdm.master));
+ 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);
+ goto error;
+ break;
+ }
+
+ if (VIR_APPEND_ELEMENT(def->serials, def->nserials, chr) < 0) {
+ virDomainChrDefFree(chr);
+ goto error;
+ }
+ }
+
+ VIR_FREE(type);
+ return 0;
+
+ error:
+ virDomainChrDefFree(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];
+ size_t 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 idx = -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) {
+ idx = *nvirtiodisk;
+ *nvirtiodisk += 1;
+ if (VIR_STRDUP(disk->dst, "vda") < 0)
+ goto error;
+ } else if (bus == VIR_DOMAIN_DISK_BUS_SATA) {
+ idx = *nahcidisk;
+ *nahcidisk += 1;
+ if (VIR_STRDUP(disk->dst, "sda") < 0)
+ goto error;
+ }
+
+ if (idx > 'z' - 'a') {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("too many disks"));
+ goto error;
+ }
+
+ disk->dst[2] = 'a' + idx;
+
+ 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, "%s",
+ _("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, "%s",
+ _("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, "%s",
+ _("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 (bhyveParseMemsize(parser->optarg, &memory)) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("Failed to parse memory"));
+ goto error;
+ }
+ if (def->mem.cur_balloon != 0 && def->mem.cur_balloon !=
memory) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("Failed to parse memory: size mismatch"));
+ goto error;
+ }
+ def->mem.cur_balloon = memory;
+ virDomainDefSetMemoryTotal(def, memory);
+ 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':
+ def->clock.offset = VIR_DOMAIN_CLOCK_OFFSET_UTC;
+ 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, "%s",
+ _("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, "%s",
+ _("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;
@@ -265,6 +772,9 @@ bhyveParseCommandLineString(const char* nativeConfig,
goto error;
}
+ if (bhyveParseBhyveCommandLine(def, xmlopt, caps, bhyve_argc, bhyve_argv))
+ goto error;
+
cleanup:
virStringFreeList(loader_argv);
virStringFreeList(bhyve_argv);
--
2.5.5