/*
* xen_xl.c: Xen XL parsing functions
*
* Copyright (c) 2015 SUSE LINUX Products GmbH, Nuernberg, Germany.
* Copyright (C) 2014 David Kiarie Kahurani
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*/
#include
#include
#include "virconf.h"
#include "virerror.h"
#include "virlog.h"
#include "domain_conf.h"
#include "domain_postparse.h"
#include "viralloc.h"
#include "virstring.h"
#include "storage_source_backingstore.h"
#include "xen_xl.h"
#include "libxl_capabilities.h"
#include "libxl_conf.h"
#include "cpu/cpu.h"
#define VIR_FROM_THIS VIR_FROM_XENXL
VIR_LOG_INIT("xen.xen_xl");
/*
* Xen provides a libxl utility library, with several useful functions,
* specifically xlu_disk_parse for parsing xl disk config strings.
* Although the libxlutil library is installed, until recently the
* corresponding header file wasn't. Use the header file if detected during
* configure, otherwise provide extern declarations for any functions used.
*/
#ifdef WITH_LIBXLUTIL_H
# include
#else
typedef struct XLU_Config XLU_Config;
extern XLU_Config *xlu_cfg_init(FILE *report,
const char *report_filename);
extern void xlu_cfg_destroy(XLU_Config*);
extern int xlu_disk_parse(XLU_Config *cfg,
int nspecs,
const char *const *specs,
libxl_device_disk *disk);
#endif
static int xenParseCmdline(virConf *conf, char **r_cmdline)
{
char *cmdline = NULL;
g_autofree char *root = NULL;
g_autofree char *extra = NULL;
g_autofree char *buf = NULL;
if (xenConfigGetString(conf, "cmdline", &buf, NULL) < 0)
return -1;
if (xenConfigGetString(conf, "root", &root, NULL) < 0)
return -1;
if (xenConfigGetString(conf, "extra", &extra, NULL) < 0)
return -1;
if (buf) {
cmdline = g_strdup(buf);
if (root || extra)
VIR_WARN("ignoring root= and extra= in favour of cmdline=");
} else {
if (root && extra) {
cmdline = g_strdup_printf("root=%s %s", root, extra);
} else if (root) {
cmdline = g_strdup_printf("root=%s", root);
} else if (extra) {
cmdline = g_strdup(extra);
}
}
*r_cmdline = cmdline;
return 0;
}
static int
xenParseXLOS(virConf *conf, virDomainDef *def, virCaps *caps)
{
size_t i;
if (def->os.type == VIR_DOMAIN_OSTYPE_HVM) {
g_autofree char *bios = NULL;
g_autofree char *bios_path = NULL;
g_autofree char *boot = NULL;
int val = 0;
if (xenConfigGetString(conf, "bios", &bios, NULL) < 0)
return -1;
if (xenConfigGetString(conf, "bios_path_override", &bios_path, NULL) < 0)
return -1;
if (bios && STREQ(bios, "ovmf")) {
def->os.loader = virDomainLoaderDefNew();
def->os.loader->format = VIR_STORAGE_FILE_RAW;
def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_PFLASH;
def->os.loader->readonly = VIR_TRISTATE_BOOL_YES;
if (bios_path)
def->os.loader->path = g_strdup(bios_path);
else
def->os.loader->path = g_strdup(LIBXL_FIRMWARE_DIR "/ovmf.bin");
} else {
for (i = 0; i < caps->nguests; i++) {
if (caps->guests[i]->ostype == VIR_DOMAIN_OSTYPE_HVM &&
caps->guests[i]->arch.id == def->os.arch) {
def->os.loader = virDomainLoaderDefNew();
def->os.loader->format = VIR_STORAGE_FILE_RAW;
def->os.loader->path = g_strdup(caps->guests[i]->arch.defaultInfo.loader);
}
}
}
if (xenConfigCopyStringOpt(conf, "acpi_firmware", &def->os.slic_table) < 0)
return -1;
if (xenConfigCopyStringOpt(conf, "kernel", &def->os.kernel) < 0)
return -1;
if (xenConfigCopyStringOpt(conf, "ramdisk", &def->os.initrd) < 0)
return -1;
if (xenParseCmdline(conf, &def->os.cmdline) < 0)
return -1;
if (xenConfigGetString(conf, "boot", &boot, "c") < 0)
return -1;
for (i = 0; i < VIR_DOMAIN_BOOT_LAST && boot[i]; i++) {
switch (boot[i]) {
case 'a':
def->os.bootDevs[i] = VIR_DOMAIN_BOOT_FLOPPY;
break;
case 'd':
def->os.bootDevs[i] = VIR_DOMAIN_BOOT_CDROM;
break;
case 'n':
def->os.bootDevs[i] = VIR_DOMAIN_BOOT_NET;
break;
case 'c':
default:
def->os.bootDevs[i] = VIR_DOMAIN_BOOT_DISK;
break;
}
def->os.nBootDevs++;
}
if (xenConfigGetBool(conf, "nestedhvm", &val, -1) < 0)
return -1;
if (val != -1) {
const char *vtfeature = "vmx";
if (caps && caps->host.cpu && ARCH_IS_X86(def->os.arch)) {
if (virCPUCheckFeature(caps->host.arch, caps->host.cpu, "vmx"))
vtfeature = "vmx";
else if (virCPUCheckFeature(caps->host.arch, caps->host.cpu, "svm"))
vtfeature = "svm";
}
if (!def->cpu) {
virCPUDef *cpu = virCPUDefNew();
cpu->mode = VIR_CPU_MODE_HOST_PASSTHROUGH;
cpu->type = VIR_CPU_TYPE_GUEST;
cpu->nfeatures = 0;
cpu->nfeatures_max = 0;
def->cpu = cpu;
}
if (val == 0) {
if (virCPUDefAddFeature(def->cpu,
vtfeature,
VIR_CPU_FEATURE_DISABLE) < 0)
return -1;
}
}
} else {
if (xenConfigCopyStringOpt(conf, "bootloader", &def->os.bootloader) < 0)
return -1;
if (xenConfigCopyStringOpt(conf, "bootargs", &def->os.bootloaderArgs) < 0)
return -1;
if (xenConfigCopyStringOpt(conf, "kernel", &def->os.kernel) < 0)
return -1;
if (xenConfigCopyStringOpt(conf, "ramdisk", &def->os.initrd) < 0)
return -1;
if (xenParseCmdline(conf, &def->os.cmdline) < 0)
return -1;
}
return 0;
}
/*
* Translate CPU feature name from libvirt to libxl (from_libxl=false) or from
* libxl to libvirt (from_libxl=true).
*/
const char *
xenTranslateCPUFeature(const char *feature_name, bool from_libxl)
{
static const char *translation_table[][2] = {
/* libvirt name, libxl name */
{ "cx16", "cmpxchg16" },
{ "cid", "cntxid" },
{ "ds_cpl", "dscpl" },
{ "pclmuldq", "pclmulqdq" },
{ "pni", "sse3" },
{ "ht", "htt" },
{ "pn", "psn" },
{ "clflush", "clfsh" },
{ "sep", "sysenter" },
{ "cx8", "cmpxchg8" },
{ "nodeid_msr", "nodeid" },
{ "cr8legacy", "altmovcr8" },
{ "lahf_lm", "lahfsahf" },
{ "cmp_legacy", "cmplegacy" },
{ "fxsr_opt", "ffxsr" },
{ "pdpe1gb", "page1gb" },
{ "spec-ctrl", "ibrsb" },
};
size_t i;
for (i = 0; i < G_N_ELEMENTS(translation_table); i++)
if (STREQ(translation_table[i][from_libxl], feature_name))
return translation_table[i][!from_libxl];
return feature_name;
}
static int
xenParseXLCPUID(virConf *conf, virDomainDef *def)
{
g_autofree char *cpuid_str = NULL;
g_auto(GStrv) cpuid_pairs = NULL;
size_t i;
int policy;
if (xenConfigGetString(conf, "cpuid", &cpuid_str, NULL) < 0)
return -1;
if (!cpuid_str)
return 0;
if (!def->cpu) {
def->cpu = virCPUDefNew();
def->cpu->mode = VIR_CPU_MODE_HOST_PASSTHROUGH;
def->cpu->type = VIR_CPU_TYPE_GUEST;
def->cpu->nfeatures = 0;
def->cpu->nfeatures_max = 0;
}
cpuid_pairs = g_strsplit(cpuid_str, ",", 0);
if (!cpuid_pairs)
return -1;
if (!cpuid_pairs[0])
return 0;
if (STRNEQ(cpuid_pairs[0], "host")) {
virReportError(VIR_ERR_CONF_SYNTAX,
_("cpuid starting with %1$s is not supported, only libxl format is"),
cpuid_pairs[0]);
return -1;
}
for (i = 1; cpuid_pairs[i]; i++) {
g_auto(GStrv) name_and_value = g_strsplit(cpuid_pairs[i], "=", 2);
if (!name_and_value)
return -1;
if (!name_and_value[0] || !name_and_value[1]) {
virReportError(VIR_ERR_CONF_SYNTAX,
_("Invalid libxl cpuid key=value element: %1$s"),
cpuid_pairs[i]);
return -1;
}
if (STREQ(name_and_value[1], "1")) {
policy = VIR_CPU_FEATURE_FORCE;
} else if (STREQ(name_and_value[1], "0")) {
policy = VIR_CPU_FEATURE_DISABLE;
} else if (STREQ(name_and_value[1], "x")) {
policy = VIR_CPU_FEATURE_OPTIONAL;
} else if (STREQ(name_and_value[1], "k")) {
policy = VIR_CPU_FEATURE_OPTIONAL;
} else if (STREQ(name_and_value[1], "s")) {
policy = VIR_CPU_FEATURE_OPTIONAL;
} else {
virReportError(VIR_ERR_CONF_SYNTAX,
_("Invalid libxl cpuid value: %1$s"),
cpuid_pairs[i]);
return -1;
}
if (virCPUDefAddFeature(def->cpu,
xenTranslateCPUFeature(name_and_value[0], true),
policy) < 0)
return -1;
}
return 0;
}
static int
xenParseXLSpice(virConf *conf, virDomainDef *def)
{
virDomainGraphicsDef *graphics = NULL;
unsigned long port;
int val;
if (def->os.type == VIR_DOMAIN_OSTYPE_HVM) {
g_autofree char *listenAddr = NULL;
if (xenConfigGetBool(conf, "spice", &val, 0) < 0)
return -1;
if (val) {
graphics = g_new0(virDomainGraphicsDef, 1);
graphics->type = VIR_DOMAIN_GRAPHICS_TYPE_SPICE;
if (xenConfigCopyStringOpt(conf, "spicehost", &listenAddr) < 0)
goto cleanup;
if (virDomainGraphicsListenAppendAddress(graphics, listenAddr) < 0)
goto cleanup;
if (xenConfigGetULong(conf, "spicetls_port", &port, 0) < 0)
goto cleanup;
graphics->data.spice.tlsPort = (int)port;
if (xenConfigGetULong(conf, "spiceport", &port, 0) < 0)
goto cleanup;
graphics->data.spice.port = (int)port;
if (!graphics->data.spice.tlsPort && !graphics->data.spice.port)
graphics->data.spice.autoport = 1;
if (xenConfigGetBool(conf, "spicedisable_ticketing", &val, 0) < 0)
goto cleanup;
if (!val) {
if (xenConfigCopyString(conf, "spicepasswd",
&graphics->data.spice.auth.passwd) < 0)
goto cleanup;
}
if (xenConfigGetBool(conf, "spiceagent_mouse",
&val, 0) < 0)
goto cleanup;
if (val) {
graphics->data.spice.mousemode = VIR_DOMAIN_MOUSE_MODE_CLIENT;
} else {
graphics->data.spice.mousemode = VIR_DOMAIN_MOUSE_MODE_SERVER;
}
if (xenConfigGetBool(conf, "spice_clipboard_sharing", &val, 0) < 0)
goto cleanup;
if (val)
graphics->data.spice.copypaste = VIR_TRISTATE_BOOL_YES;
else
graphics->data.spice.copypaste = VIR_TRISTATE_BOOL_NO;
def->graphics = g_new0(virDomainGraphicsDef *, 1);
def->graphics[0] = graphics;
def->ngraphics = 1;
}
}
return 0;
cleanup:
virDomainGraphicsDefFree(graphics);
return -1;
}
static int
xenParseXLVnuma(virConf *conf,
virDomainDef *def)
{
size_t vcpus = 0;
size_t nr_nodes = 0;
size_t vnodeCnt = 0;
g_autoptr(virCPUDef) cpu = NULL;
virConfValue *list;
virConfValue *vnode;
virDomainNuma *numa;
numa = def->numa;
if (numa == NULL)
return -1;
list = virConfGetValue(conf, "vnuma");
if (!list || list->type != VIR_CONF_LIST)
return 0;
vnode = list->list;
while (vnode && vnode->type == VIR_CONF_LIST) {
vnode = vnode->next;
nr_nodes++;
}
if (!virDomainNumaSetNodeCount(numa, nr_nodes))
return -1;
cpu = virCPUDefNew();
list = list->list;
while (list) {
int pnode = -1;
virBitmap *cpumask = NULL;
unsigned long long kbsize = 0;
/* Is there a sublist (vnode)? */
if (list->type == VIR_CONF_LIST) {
vnode = list->list;
while (vnode && vnode->type == VIR_CONF_STRING) {
const char *data;
const char *str = vnode->str;
if (!str ||
!(data = strrchr(str, '='))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("vnuma vnode invalid format '%1$s'"),
str);
return -1;
}
data++;
if (*data) {
if (STRPREFIX(str, "pnode")) {
unsigned int cellid;
if ((virStrToLong_ui(data, NULL, 10, &cellid) < 0) ||
(cellid >= nr_nodes)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("vnuma vnode %1$zu contains invalid pnode value '%2$s'"),
vnodeCnt, data);
return -1;
}
pnode = cellid;
} else if (STRPREFIX(str, "size")) {
if (virStrToLong_ull(data, NULL, 10, &kbsize) < 0)
return -1;
virDomainNumaSetNodeMemorySize(numa, vnodeCnt, (kbsize * 1024));
} else if (STRPREFIX(str, "vcpus")) {
if (virBitmapParse(data, &cpumask, VIR_DOMAIN_CPUMASK_LEN) < 0)
return -1;
virDomainNumaSetNodeCpumask(numa, vnodeCnt, cpumask);
vcpus += virBitmapCountBits(cpumask);
} else if (STRPREFIX(str, "vdistances")) {
g_auto(GStrv) token = NULL;
size_t i, ndistances;
unsigned int value;
if (!(token = g_strsplit(data, ",", 0)))
return -1;
ndistances = g_strv_length(token);
if (ndistances != nr_nodes) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("vnuma pnode %1$d configured '%2$s' (count %3$zu) doesn't fit the number of specified vnodes %4$zu"),
pnode, str, ndistances, nr_nodes);
return -1;
}
if (virDomainNumaSetNodeDistanceCount(numa, vnodeCnt, ndistances) != ndistances)
return -1;
for (i = 0; i < ndistances; i++) {
if ((virStrToLong_ui(token[i], NULL, 10, &value) < 0) ||
(virDomainNumaSetNodeDistance(numa, vnodeCnt, i, value) != value))
return -1;
}
} else {
virReportError(VIR_ERR_CONF_SYNTAX,
_("Invalid vnuma configuration for vnode %1$zu"),
vnodeCnt);
return -1;
}
}
vnode = vnode->next;
}
}
if ((pnode < 0) ||
(cpumask == NULL) ||
(kbsize == 0)) {
virReportError(VIR_ERR_CONF_SYNTAX,
_("Incomplete vnuma configuration for vnode %1$zu"),
vnodeCnt);
return -1;
}
list = list->next;
vnodeCnt++;
}
if (def->maxvcpus == 0)
def->maxvcpus = vcpus;
if (def->maxvcpus < vcpus) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("vnuma configuration contains %1$zu vcpus, which is greater than %2$zu maxvcpus"),
vcpus, def->maxvcpus);
return -1;
}
cpu->type = VIR_CPU_TYPE_GUEST;
def->cpu = g_steal_pointer(&cpu);
return 0;
}
static int
xenParseXLXenbusLimits(virConf *conf, virDomainDef *def)
{
int ctlr_idx;
virDomainControllerDef *xenbus_ctlr;
unsigned long limit;
ctlr_idx = virDomainControllerFindByType(def, VIR_DOMAIN_CONTROLLER_TYPE_XENBUS);
if (ctlr_idx == -1)
xenbus_ctlr = virDomainDefAddController(def, VIR_DOMAIN_CONTROLLER_TYPE_XENBUS, -1, -1);
else
xenbus_ctlr = def->controllers[ctlr_idx];
if (xenbus_ctlr == NULL)
return -1;
if (xenConfigGetULong(conf, "max_event_channels", &limit, 0) < 0)
return -1;
if (limit > 0)
xenbus_ctlr->opts.xenbusopts.maxEventChannels = limit;
#ifdef LIBXL_HAVE_BUILDINFO_GRANT_LIMITS
if (xenConfigGetULong(conf, "max_grant_frames", &limit, 0) < 0)
return -1;
if (limit > 0)
xenbus_ctlr->opts.xenbusopts.maxGrantFrames = limit;
#endif
return 0;
}
static int
xenParseXLDiskSrc(virDomainDiskDef *disk, char *srcstr)
{
/* A NULL source is valid, e.g. an empty CDROM */
if (srcstr == NULL)
return 0;
if (STRPREFIX(srcstr, "rbd:")) {
g_autofree char *tmpstr = NULL;
if (!(tmpstr = virStringReplace(srcstr, "\\\\", "\\")))
return -1;
virDomainDiskSetType(disk, VIR_STORAGE_TYPE_NETWORK);
disk->src->protocol = VIR_STORAGE_NET_PROTOCOL_RBD;
return virStorageSourceParseRBDColonString(tmpstr, disk->src);
}
virDomainDiskSetSource(disk, srcstr);
return 0;
}
/*
* For details on xl disk config syntax, see
* docs/misc/xl-disk-configuration.txt in the Xen sources. The important
* section of text is:
*
* More formally, the string is a series of comma-separated keyword/value
* pairs, flags and positional parameters. Parameters which are not bare
* keywords and which do not contain "=" symbols are assigned to the
* so-far-unspecified positional parameters, in the order below. The
* positional parameters may also be specified explicitly by name.
*
* Each parameter may be specified at most once, either as a positional
* parameter or a named parameter. Default values apply if the parameter
* is not specified, or if it is specified with an empty value (whether
* positionally or explicitly).
*
* Whitespace may appear before each parameter and will be ignored.
*
* The order of the positional parameters mentioned in the quoted text is:
*
* target,format,vdev,access
*
* The following options must be specified by key=value:
*
* devtype=
* backendtype=
*
* The following options are currently not supported:
*
* backend=
* script=