/* * lxc_native.c: LXC native configuration import * * Copyright (c) 2014-2016 Red Hat, Inc. * Copyright (c) 2013-2015 SUSE LINUX Products GmbH, Nuernberg, Germany. * * 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 "internal.h" #include "lxc_container.h" #include "lxc_native.h" #include "util/viralloc.h" #include "util/virfile.h" #include "util/virlog.h" #include "util/virstring.h" #include "util/virconf.h" #include "conf/domain_conf.h" #define VIR_FROM_THIS VIR_FROM_LXC VIR_LOG_INIT("lxc.lxc_native"); VIR_ENUM_IMPL(virLXCNetworkConfigEntry, VIR_LXC_NETWORK_CONFIG_LAST, "name", "type", "link", "hwaddr", "flags", "macvlan.mode", "vlan.id", "ipv4", /* Legacy: LXC IPv4 address */ "ipv4.gateway", "ipv4.address", "ipv6", /* Legacy: LXC IPv6 address */ "ipv6.gateway", "ipv6.address" ); static virDomainFSDefPtr lxcCreateFSDef(int type, const char *src, const char* dst, bool readonly, unsigned long long usage) { virDomainFSDefPtr def; if (!(def = virDomainFSDefNew(NULL))) return NULL; def->type = type; def->accessmode = VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH; if (src) def->src->path = g_strdup(src); def->dst = g_strdup(dst); def->readonly = readonly; def->usage = usage; return def; } typedef struct _lxcFstab lxcFstab; typedef lxcFstab *lxcFstabPtr; struct _lxcFstab { lxcFstabPtr next; char *src; char *dst; char *type; char *options; }; static void lxcFstabFree(lxcFstabPtr fstab) { while (fstab) { lxcFstabPtr next = NULL; next = fstab->next; VIR_FREE(fstab->src); VIR_FREE(fstab->dst); VIR_FREE(fstab->type); VIR_FREE(fstab->options); VIR_FREE(fstab); fstab = next; } } static char ** lxcStringSplit(const char *string) { char *tmp; size_t i; size_t ntokens = 0; char **parts; char **result = NULL; tmp = g_strdup(string); /* Replace potential \t by a space */ for (i = 0; tmp[i]; i++) { if (tmp[i] == '\t') tmp[i] = ' '; } if (!(parts = virStringSplit(tmp, " ", 0))) goto error; /* Append NULL element */ if (VIR_EXPAND_N(result, ntokens, 1) < 0) goto error; for (i = 0; parts[i]; i++) { if (STREQ(parts[i], "")) continue; if (VIR_EXPAND_N(result, ntokens, 1) < 0) goto error; result[ntokens - 2] = g_strdup(parts[i]); } VIR_FREE(tmp); virStringListFree(parts); return result; error: VIR_FREE(tmp); virStringListFree(parts); virStringListFree(result); return NULL; } static lxcFstabPtr lxcParseFstabLine(char *fstabLine) { lxcFstabPtr fstab = NULL; char **parts; if (!fstabLine || VIR_ALLOC(fstab) < 0) return NULL; if (!(parts = lxcStringSplit(fstabLine))) goto error; if (!parts[0] || !parts[1] || !parts[2] || !parts[3]) goto error; fstab->src = g_strdup(parts[0]); fstab->dst = g_strdup(parts[1]); fstab->type = g_strdup(parts[2]); fstab->options = g_strdup(parts[3]); virStringListFree(parts); return fstab; error: lxcFstabFree(fstab); virStringListFree(parts); return NULL; } static int lxcAddFSDef(virDomainDefPtr def, int type, const char *src, const char *dst, bool readonly, unsigned long long usage) { virDomainFSDefPtr fsDef = NULL; if (!(fsDef = lxcCreateFSDef(type, src, dst, readonly, usage))) goto error; if (VIR_EXPAND_N(def->fss, def->nfss, 1) < 0) goto error; def->fss[def->nfss - 1] = fsDef; return 0; error: virDomainFSDefFree(fsDef); return -1; } static int lxcSetRootfs(virDomainDefPtr def, virConfPtr properties) { int type = VIR_DOMAIN_FS_TYPE_MOUNT; g_autofree char *value = NULL; if (virConfGetValueString(properties, "lxc.rootfs.path", &value) <= 0) { virResetLastError(); /* Check for pre LXC 3.0 legacy key */ if (virConfGetValueString(properties, "lxc.rootfs", &value) <= 0) return -1; } if (STRPREFIX(value, "/dev/")) type = VIR_DOMAIN_FS_TYPE_BLOCK; if (lxcAddFSDef(def, type, value, "/", false, 0) < 0) return -1; return 0; } static int lxcConvertSize(const char *size, unsigned long long *value) { char *unit = NULL; /* Split the string into value and unit */ if (virStrToLong_ull(size, &unit, 10, value) < 0) goto error; if (STREQ(unit, "%")) { virReportError(VIR_ERR_INTERNAL_ERROR, _("can't convert relative size: '%s'"), size); return -1; } else { if (virScaleInteger(value, unit, 1, ULLONG_MAX) < 0) goto error; } return 0; error: virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to convert size: '%s'"), size); return -1; } static int lxcAddFstabLine(virDomainDefPtr def, lxcFstabPtr fstab) { const char *src = NULL; char *dst = NULL; char **options = virStringSplit(fstab->options, ",", 0); bool readonly; int type = VIR_DOMAIN_FS_TYPE_MOUNT; unsigned long long usage = 0; int ret = -1; if (!options) return -1; if (fstab->dst[0] != '/') { dst = g_strdup_printf("/%s", fstab->dst); } else { dst = g_strdup(fstab->dst); } /* Check that we don't add basic mounts */ if (lxcIsBasicMountLocation(dst)) { ret = 0; goto cleanup; } if (STREQ(fstab->type, "tmpfs")) { char *sizeStr = NULL; size_t i; type = VIR_DOMAIN_FS_TYPE_RAM; for (i = 0; options[i]; i++) { if ((sizeStr = STRSKIP(options[i], "size="))) { if (lxcConvertSize(sizeStr, &usage) < 0) goto cleanup; break; } } if (!sizeStr) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing tmpfs size, set the size option")); goto cleanup; } } else { src = fstab->src; } /* Is it a block device that needs special favor? */ if (STRPREFIX(fstab->src, "/dev/")) type = VIR_DOMAIN_FS_TYPE_BLOCK; /* Do we have ro in options? */ readonly = virStringListHasString((const char **)options, "ro"); if (lxcAddFSDef(def, type, src, dst, readonly, usage) < 0) goto cleanup; ret = 1; cleanup: VIR_FREE(dst); virStringListFree(options); return ret; } static int lxcFstabWalkCallback(const char* name, virConfValuePtr value, void * data) { int ret = 0; lxcFstabPtr fstabLine; virDomainDefPtr def = data; /* We only care about lxc.mount.entry lines */ if (STRNEQ(name, "lxc.mount.entry")) return 0; fstabLine = lxcParseFstabLine(value->str); if (!fstabLine) return -1; if (lxcAddFstabLine(def, fstabLine) < 0) ret = -1; lxcFstabFree(fstabLine); return ret; } static virDomainNetDefPtr lxcCreateNetDef(const char *type, const char *linkdev, const char *mac, const char *flag, const char *macvlanmode, const char *name) { virDomainNetDefPtr net = NULL; virMacAddr macAddr; if (!(net = virDomainNetDefNew(NULL))) goto error; if (STREQ_NULLABLE(flag, "up")) net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_UP; else net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN; net->ifname_guest = g_strdup(name); if (mac && virMacAddrParse(mac, &macAddr) == 0) net->mac = macAddr; if (STREQ(type, "veth")) { if (linkdev) { net->type = VIR_DOMAIN_NET_TYPE_BRIDGE; net->data.bridge.brname = g_strdup(linkdev); } else { net->type = VIR_DOMAIN_NET_TYPE_ETHERNET; } } else if (STREQ(type, "macvlan")) { net->type = VIR_DOMAIN_NET_TYPE_DIRECT; if (!linkdev) goto error; net->data.direct.linkdev = g_strdup(linkdev); if (!macvlanmode || STREQ(macvlanmode, "private")) net->data.direct.mode = VIR_NETDEV_MACVLAN_MODE_PRIVATE; else if (STREQ(macvlanmode, "vepa")) net->data.direct.mode = VIR_NETDEV_MACVLAN_MODE_VEPA; else if (STREQ(macvlanmode, "bridge")) net->data.direct.mode = VIR_NETDEV_MACVLAN_MODE_BRIDGE; else VIR_WARN("Unknown macvlan type: %s", macvlanmode); } return net; error: virDomainNetDefFree(net); return NULL; } static virDomainHostdevDefPtr lxcCreateHostdevDef(int mode, int type, const char *data) { virDomainHostdevDefPtr hostdev = virDomainHostdevDefNew(); if (!hostdev) return NULL; hostdev->mode = mode; hostdev->source.caps.type = type; if (type == VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET) hostdev->source.caps.u.net.ifname = g_strdup(data); return hostdev; } typedef struct _lxcNetworkParseData lxcNetworkParseData; typedef lxcNetworkParseData *lxcNetworkParseDataPtr; struct _lxcNetworkParseData { char *type; char *link; char *mac; char *flag; char *macvlanmode; char *vlanid; char *name; virNetDevIPAddrPtr *ips; size_t nips; char *gateway_ipv4; char *gateway_ipv6; size_t index; }; typedef struct { size_t ndata; lxcNetworkParseDataPtr *parseData; } lxcNetworkParseDataArray; static int lxcAddNetworkRouteDefinition(const char *address, int family, virNetDevIPRoutePtr **routes, size_t *nroutes) { virNetDevIPRoutePtr route = NULL; char *familyStr = NULL; char *zero = NULL; zero = g_strdup(family == AF_INET ? VIR_SOCKET_ADDR_IPV4_ALL : VIR_SOCKET_ADDR_IPV6_ALL); familyStr = g_strdup(family == AF_INET ? "ipv4" : "ipv6"); if (!(route = virNetDevIPRouteCreate(_("Domain interface"), familyStr, zero, NULL, address, 0, false, 0, false))) goto error; if (VIR_APPEND_ELEMENT(*routes, *nroutes, route) < 0) goto error; VIR_FREE(familyStr); VIR_FREE(zero); return 0; error: VIR_FREE(familyStr); VIR_FREE(zero); virNetDevIPRouteFree(route); return -1; } static int lxcAddNetworkDefinition(virDomainDefPtr def, lxcNetworkParseData *data) { virDomainNetDefPtr net = NULL; virDomainHostdevDefPtr hostdev = NULL; bool isPhys, isVlan = false; size_t i; if ((data->type == NULL) || STREQ(data->type, "empty") || STREQ(data->type, "") || STREQ(data->type, "none")) return 0; isPhys = STREQ(data->type, "phys"); isVlan = STREQ(data->type, "vlan"); if (data->type != NULL && (isPhys || isVlan)) { if (!data->link) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Missing 'link' attribute for NIC")); goto error; } if (!(hostdev = lxcCreateHostdevDef(VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES, VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET, data->link))) goto error; /* This still requires the user to manually setup the vlan interface * on the host */ if (isVlan && data->vlanid) { VIR_FREE(hostdev->source.caps.u.net.ifname); hostdev->source.caps.u.net.ifname = g_strdup_printf("%s.%s", data->link, data->vlanid); } hostdev->source.caps.u.net.ip.ips = data->ips; hostdev->source.caps.u.net.ip.nips = data->nips; if (data->gateway_ipv4 && lxcAddNetworkRouteDefinition(data->gateway_ipv4, AF_INET, &hostdev->source.caps.u.net.ip.routes, &hostdev->source.caps.u.net.ip.nroutes) < 0) goto error; if (data->gateway_ipv6 && lxcAddNetworkRouteDefinition(data->gateway_ipv6, AF_INET6, &hostdev->source.caps.u.net.ip.routes, &hostdev->source.caps.u.net.ip.nroutes) < 0) goto error; if (VIR_EXPAND_N(def->hostdevs, def->nhostdevs, 1) < 0) goto error; def->hostdevs[def->nhostdevs - 1] = hostdev; } else { if (!(net = lxcCreateNetDef(data->type, data->link, data->mac, data->flag, data->macvlanmode, data->name))) goto error; net->guestIP.ips = data->ips; net->guestIP.nips = data->nips; if (data->gateway_ipv4 && lxcAddNetworkRouteDefinition(data->gateway_ipv4, AF_INET, &net->guestIP.routes, &net->guestIP.nroutes) < 0) goto error; if (data->gateway_ipv6 && lxcAddNetworkRouteDefinition(data->gateway_ipv6, AF_INET6, &net->guestIP.routes, &net->guestIP.nroutes) < 0) goto error; if (VIR_EXPAND_N(def->nets, def->nnets, 1) < 0) goto error; def->nets[def->nnets - 1] = net; } return 1; error: for (i = 0; i < data->nips; i++) VIR_FREE(data->ips[i]); VIR_FREE(data->ips); virDomainNetDefFree(net); virDomainHostdevDefFree(hostdev); return -1; } static int lxcNetworkParseDataIPs(const char *name, virConfValuePtr value, lxcNetworkParseData *parseData) { int family = AF_INET; char **ipparts = NULL; virNetDevIPAddrPtr ip = NULL; if (VIR_ALLOC(ip) < 0) return -1; if (STREQ(name, "ipv6") || STREQ(name, "ipv6.address")) family = AF_INET6; ipparts = virStringSplit(value->str, "/", 2); if (virStringListLength((const char * const *)ipparts) != 2 || virSocketAddrParse(&ip->address, ipparts[0], family) < 0 || virStrToLong_ui(ipparts[1], NULL, 10, &ip->prefix) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("Invalid CIDR address: '%s'"), value->str); virStringListFree(ipparts); VIR_FREE(ip); return -1; } virStringListFree(ipparts); if (VIR_APPEND_ELEMENT(parseData->ips, parseData->nips, ip) < 0) { VIR_FREE(ip); return -1; } return 0; } static int lxcNetworkParseDataSuffix(const char *entry, virConfValuePtr value, lxcNetworkParseData *parseData) { int elem = virLXCNetworkConfigEntryTypeFromString(entry); switch (elem) { case VIR_LXC_NETWORK_CONFIG_TYPE: parseData->type = value->str; break; case VIR_LXC_NETWORK_CONFIG_LINK: parseData->link = value->str; break; case VIR_LXC_NETWORK_CONFIG_HWADDR: parseData->mac = value->str; break; case VIR_LXC_NETWORK_CONFIG_FLAGS: parseData->flag = value->str; break; case VIR_LXC_NETWORK_CONFIG_MACVLAN_MODE: parseData->macvlanmode = value->str; break; case VIR_LXC_NETWORK_CONFIG_VLAN_ID: parseData->vlanid = value->str; break; case VIR_LXC_NETWORK_CONFIG_NAME: parseData->name = value->str; break; case VIR_LXC_NETWORK_CONFIG_IPV4: case VIR_LXC_NETWORK_CONFIG_IPV4_ADDRESS: case VIR_LXC_NETWORK_CONFIG_IPV6: case VIR_LXC_NETWORK_CONFIG_IPV6_ADDRESS: if (lxcNetworkParseDataIPs(entry, value, parseData) < 0) return -1; break; case VIR_LXC_NETWORK_CONFIG_IPV4_GATEWAY: parseData->gateway_ipv4 = value->str; break; case VIR_LXC_NETWORK_CONFIG_IPV6_GATEWAY: parseData->gateway_ipv6 = value->str; break; default: VIR_WARN("Unhandled network property: %s = %s", entry, value->str); return -1; } return 0; } static lxcNetworkParseDataPtr lxcNetworkGetParseDataByIndex(lxcNetworkParseDataArray *networks, unsigned int index) { size_t ndata = networks->ndata; size_t i; for (i = 0; i < ndata; i++) { if (networks->parseData[i]->index == index) return networks->parseData[i]; } /* Index was not found. So, it is time to add new * * interface and return this last position. */ if (VIR_EXPAND_N(networks->parseData, networks->ndata, 1) < 0) return NULL; networks->parseData[ndata] = g_new0(lxcNetworkParseData, 1); networks->parseData[ndata]->index = index; return networks->parseData[ndata]; } static int lxcNetworkParseDataEntry(const char *name, virConfValuePtr value, lxcNetworkParseDataArray *networks) { lxcNetworkParseData *parseData; const char *suffix_tmp = STRSKIP(name, "lxc.net."); char *suffix = NULL; unsigned long long index; if (virStrToLong_ull(suffix_tmp, &suffix, 10, &index) < 0) return -1; if (suffix[0] != '.') return -1; suffix++; if (!(parseData = lxcNetworkGetParseDataByIndex(networks, index))) return -1; return lxcNetworkParseDataSuffix(suffix, value, parseData); } static lxcNetworkParseDataPtr lxcNetworkGetParseDataByIndexLegacy(lxcNetworkParseDataArray *networks, const char *entry) { int elem = virLXCNetworkConfigEntryTypeFromString(entry); size_t ndata = networks->ndata; if (elem == VIR_LXC_NETWORK_CONFIG_TYPE) { /* Index was not found. So, it is time to add new * * interface and return this last position. */ if (VIR_EXPAND_N(networks->parseData, networks->ndata, 1) < 0) return NULL; networks->parseData[ndata] = g_new0(lxcNetworkParseData, 1); networks->parseData[ndata]->index = networks->ndata; return networks->parseData[ndata]; } /* Return last element added like a stack. */ if (ndata > 0) return networks->parseData[ndata - 1]; /* Not able to retrive an element */ return NULL; } static int lxcNetworkParseDataEntryLegacy(const char *name, virConfValuePtr value, lxcNetworkParseDataArray *networks) { const char *suffix = STRSKIP(name, "lxc.network."); lxcNetworkParseData *parseData; if (!(parseData = lxcNetworkGetParseDataByIndexLegacy(networks, suffix))) return -1; return lxcNetworkParseDataSuffix(suffix, value, parseData); } static int lxcNetworkWalkCallback(const char *name, virConfValuePtr value, void *data) { lxcNetworkParseDataArray *networks = data; if (STRPREFIX(name, "lxc.network.")) return lxcNetworkParseDataEntryLegacy(name, value, networks); if (STRPREFIX(name, "lxc.net.")) return lxcNetworkParseDataEntry(name, value, networks); return 0; } static int lxcConvertNetworkSettings(virDomainDefPtr def, virConfPtr properties) { int status; bool privnet = true; size_t i, j; lxcNetworkParseDataArray networks = {0, NULL}; int ret = -1; networks.parseData = g_new0(lxcNetworkParseDataPtr, 1); if (virConfWalk(properties, lxcNetworkWalkCallback, &networks) < 0) goto error; for (i = 0; i < networks.ndata; i++) { lxcNetworkParseDataPtr data = networks.parseData[i]; status = lxcAddNetworkDefinition(def, data); if (status < 0) goto error; else if (data->type != NULL && STREQ(data->type, "none")) privnet = false; } if (networks.ndata == 0 && privnet) { /* When no network type is provided LXC only adds loopback */ def->features[VIR_DOMAIN_FEATURE_PRIVNET] = VIR_TRISTATE_SWITCH_ON; } ret = 0; cleanup: for (i = 0; i < networks.ndata; i++) VIR_FREE(networks.parseData[i]); VIR_FREE(networks.parseData); return ret; error: for (i = 0; i < networks.ndata; i++) { lxcNetworkParseDataPtr data = networks.parseData[i]; for (j = 0; j < data->nips; j++) VIR_FREE(data->ips[j]); VIR_FREE(data->ips); } goto cleanup; } static int lxcCreateConsoles(virDomainDefPtr def, virConfPtr properties) { g_autofree char *value = NULL; int nbttys = 0; virDomainChrDefPtr console; size_t i; if (virConfGetValueString(properties, "lxc.tty.max", &value) <= 0) { virResetLastError(); /* Check for pre LXC 3.0 legacy key */ if (virConfGetValueString(properties, "lxc.tty", &value) <= 0) return 0; } if (virStrToLong_i(value, NULL, 10, &nbttys) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse int: '%s'"), value); return -1; } if (VIR_ALLOC_N(def->consoles, nbttys) < 0) return -1; def->nconsoles = nbttys; for (i = 0; i < nbttys; i++) { if (!(console = virDomainChrDefNew(NULL))) goto error; console->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE; console->targetType = VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_LXC; console->target.port = i; console->source->type = VIR_DOMAIN_CHR_TYPE_PTY; def->consoles[i] = console; } return 0; error: virDomainChrDefFree(console); return -1; } static int lxcIdmapWalkCallback(const char *name, virConfValuePtr value, void *data) { virDomainDefPtr def = data; virDomainIdMapEntryPtr idmap = NULL; char type; unsigned long start, target, count; /* LXC 3.0 uses "lxc.idmap", while legacy used "lxc.id_map" */ if (STRNEQ(name, "lxc.idmap") || !value->str) { if (!value->str || STRNEQ(name, "lxc.id_map")) return 0; } if (sscanf(value->str, "%c %lu %lu %lu", &type, &target, &start, &count) != 4) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid %s: '%s'"), name, value->str); return -1; } if (type == 'u') { if (VIR_EXPAND_N(def->idmap.uidmap, def->idmap.nuidmap, 1) < 0) return -1; idmap = &def->idmap.uidmap[def->idmap.nuidmap - 1]; } else if (type == 'g') { if (VIR_EXPAND_N(def->idmap.gidmap, def->idmap.ngidmap, 1) < 0) return -1; idmap = &def->idmap.gidmap[def->idmap.ngidmap - 1]; } else { return -1; } idmap->start = start; idmap->target = target; idmap->count = count; return 0; } static int lxcSetMemTune(virDomainDefPtr def, virConfPtr properties) { g_autofree char *value = NULL; unsigned long long size = 0; if (virConfGetValueString(properties, "lxc.cgroup.memory.limit_in_bytes", &value) > 0) { if (lxcConvertSize(value, &size) < 0) return -1; size = size / 1024; virDomainDefSetMemoryTotal(def, size); def->mem.hard_limit = virMemoryLimitTruncate(size); VIR_FREE(value); } if (virConfGetValueString(properties, "lxc.cgroup.memory.soft_limit_in_bytes", &value) > 0) { if (lxcConvertSize(value, &size) < 0) return -1; def->mem.soft_limit = virMemoryLimitTruncate(size / 1024); VIR_FREE(value); } if (virConfGetValueString(properties, "lxc.cgroup.memory.memsw.limit_in_bytes", &value) > 0) { if (lxcConvertSize(value, &size) < 0) return -1; def->mem.swap_hard_limit = virMemoryLimitTruncate(size / 1024); } return 0; } static int lxcSetCpuTune(virDomainDefPtr def, virConfPtr properties) { g_autofree char *value = NULL; if (virConfGetValueString(properties, "lxc.cgroup.cpu.shares", &value) > 0) { if (virStrToLong_ull(value, NULL, 10, &def->cputune.shares) < 0) goto error; def->cputune.sharesSpecified = true; VIR_FREE(value); } if (virConfGetValueString(properties, "lxc.cgroup.cpu.cfs_quota_us", &value) > 0) { if (virStrToLong_ll(value, NULL, 10, &def->cputune.quota) < 0) goto error; VIR_FREE(value); } if (virConfGetValueString(properties, "lxc.cgroup.cpu.cfs_period_us", &value) > 0) { if (virStrToLong_ull(value, NULL, 10, &def->cputune.period) < 0) goto error; } return 0; error: virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse integer: '%s'"), value); return -1; } static int lxcSetCpusetTune(virDomainDefPtr def, virConfPtr properties) { g_autofree char *value = NULL; virBitmapPtr nodeset = NULL; if (virConfGetValueString(properties, "lxc.cgroup.cpuset.cpus", &value) > 0) { if (virBitmapParse(value, &def->cpumask, VIR_DOMAIN_CPUMASK_LEN) < 0) return -1; def->placement_mode = VIR_DOMAIN_CPU_PLACEMENT_MODE_STATIC; VIR_FREE(value); } if (virConfGetValueString(properties, "lxc.cgroup.cpuset.mems", &value) > 0) { if (virBitmapParse(value, &nodeset, VIR_DOMAIN_CPUMASK_LEN) < 0) return -1; if (virDomainNumatuneSet(def->numa, def->placement_mode == VIR_DOMAIN_CPU_PLACEMENT_MODE_STATIC, VIR_DOMAIN_NUMATUNE_PLACEMENT_STATIC, VIR_DOMAIN_NUMATUNE_MEM_STRICT, nodeset) < 0) { virBitmapFree(nodeset); return -1; } virBitmapFree(nodeset); } return 0; } static int lxcBlkioDeviceWalkCallback(const char *name, virConfValuePtr value, void *data) { char **parts = NULL; virBlkioDevicePtr device = NULL; virDomainDefPtr def = data; size_t i = 0; char *path = NULL; int ret = -1; if (!STRPREFIX(name, "lxc.cgroup.blkio.") || STREQ(name, "lxc.cgroup.blkio.weight")|| !value->str) return 0; if (!(parts = lxcStringSplit(value->str))) return -1; if (!parts[0] || !parts[1]) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid %s value: '%s'"), name, value->str); goto cleanup; } path = g_strdup_printf("/dev/block/%s", parts[0]); /* Do we already have a device definition for this path? * Get that device or create a new one */ for (i = 0; !device && i < def->blkio.ndevices; i++) { if (STREQ(def->blkio.devices[i].path, path)) device = &def->blkio.devices[i]; } if (!device) { if (VIR_EXPAND_N(def->blkio.devices, def->blkio.ndevices, 1) < 0) goto cleanup; device = &def->blkio.devices[def->blkio.ndevices - 1]; device->path = path; path = NULL; } /* Set the value */ if (STREQ(name, "lxc.cgroup.blkio.device_weight")) { if (virStrToLong_ui(parts[1], NULL, 10, &device->weight) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse device weight: '%s'"), parts[1]); goto cleanup; } } else if (STREQ(name, "lxc.cgroup.blkio.throttle.read_bps_device")) { if (virStrToLong_ull(parts[1], NULL, 10, &device->rbps) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse read_bps_device: '%s'"), parts[1]); goto cleanup; } } else if (STREQ(name, "lxc.cgroup.blkio.throttle.write_bps_device")) { if (virStrToLong_ull(parts[1], NULL, 10, &device->wbps) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse write_bps_device: '%s'"), parts[1]); goto cleanup; } } else if (STREQ(name, "lxc.cgroup.blkio.throttle.read_iops_device")) { if (virStrToLong_ui(parts[1], NULL, 10, &device->riops) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse read_iops_device: '%s'"), parts[1]); goto cleanup; } } else if (STREQ(name, "lxc.cgroup.blkio.throttle.write_iops_device")) { if (virStrToLong_ui(parts[1], NULL, 10, &device->wiops) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse write_iops_device: '%s'"), parts[1]); goto cleanup; } } else { VIR_WARN("Unhandled blkio tune config: %s", name); } ret = 0; cleanup: virStringListFree(parts); VIR_FREE(path); return ret; } static int lxcSetBlkioTune(virDomainDefPtr def, virConfPtr properties) { g_autofree char *value = NULL; if (virConfGetValueString(properties, "lxc.cgroup.blkio.weight", &value) > 0) { if (virStrToLong_ui(value, NULL, 10, &def->blkio.weight) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse integer: '%s'"), value); return -1; } } if (virConfWalk(properties, lxcBlkioDeviceWalkCallback, def) < 0) return -1; return 0; } static void lxcSetCapDrop(virDomainDefPtr def, virConfPtr properties) { g_autofree char *value = NULL; char **toDrop = NULL; const char *capString; size_t i; if (virConfGetValueString(properties, "lxc.cap.drop", &value) > 0) toDrop = virStringSplit(value, " ", 0); for (i = 0; i < VIR_DOMAIN_PROCES_CAPS_FEATURE_LAST; i++) { capString = virDomainProcessCapsFeatureTypeToString(i); if (toDrop != NULL && virStringListHasString((const char **)toDrop, capString)) def->caps_features[i] = VIR_TRISTATE_SWITCH_OFF; } def->features[VIR_DOMAIN_FEATURE_CAPABILITIES] = VIR_DOMAIN_CAPABILITIES_POLICY_ALLOW; virStringListFree(toDrop); } virDomainDefPtr lxcParseConfigString(const char *config, virCapsPtr caps G_GNUC_UNUSED, virDomainXMLOptionPtr xmlopt) { virDomainDefPtr vmdef = NULL; g_autoptr(virConf) properties = NULL; g_autofree char *value = NULL; if (!(properties = virConfReadString(config, VIR_CONF_FLAG_LXC_FORMAT))) return NULL; if (!(vmdef = virDomainDefNew())) goto error; if (virUUIDGenerate(vmdef->uuid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("failed to generate uuid")); goto error; } vmdef->id = -1; virDomainDefSetMemoryTotal(vmdef, 64 * 1024); vmdef->onReboot = VIR_DOMAIN_LIFECYCLE_ACTION_RESTART; vmdef->onCrash = VIR_DOMAIN_LIFECYCLE_ACTION_DESTROY; vmdef->onPoweroff = VIR_DOMAIN_LIFECYCLE_ACTION_DESTROY; vmdef->virtType = VIR_DOMAIN_VIRT_LXC; /* Value not handled by the LXC driver, setting to * minimum required to make XML parsing pass */ if (virDomainDefSetVcpusMax(vmdef, 1, xmlopt) < 0) goto error; if (virDomainDefSetVcpus(vmdef, 1) < 0) goto error; vmdef->nfss = 0; vmdef->os.type = VIR_DOMAIN_OSTYPE_EXE; if (virConfGetValueString(properties, "lxc.arch", &value) > 0) { virArch arch = virArchFromString(value); if (arch == VIR_ARCH_NONE && STREQ(value, "x86")) arch = VIR_ARCH_I686; else if (arch == VIR_ARCH_NONE && STREQ(value, "amd64")) arch = VIR_ARCH_X86_64; vmdef->os.arch = arch; VIR_FREE(value); } vmdef->os.init = g_strdup("/sbin/init"); if (virConfGetValueString(properties, "lxc.uts.name", &value) <= 0) { virResetLastError(); /* Check for pre LXC 3.0 legacy key */ if (virConfGetValueString(properties, "lxc.utsname", &value) <= 0) goto error; } vmdef->name = g_strdup(value); if (!vmdef->name) vmdef->name = g_strdup("unnamed"); if (lxcSetRootfs(vmdef, properties) < 0) goto error; /* LXC 3.0 uses "lxc.mount.fstab", while legacy used just "lxc.mount". * In either case, generate the error to use "lxc.mount.entry" instead */ if (virConfGetValue(properties, "lxc.mount.fstab") || virConfGetValue(properties, "lxc.mount")) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("lxc.mount.fstab or lxc.mount found, use " "lxc.mount.entry lines instead")); goto error; } /* Loop over lxc.mount.entry to add filesystem devices for them */ if (virConfWalk(properties, lxcFstabWalkCallback, vmdef) < 0) goto error; /* Network configuration */ if (lxcConvertNetworkSettings(vmdef, properties) < 0) goto error; /* Consoles */ if (lxcCreateConsoles(vmdef, properties) < 0) goto error; /* lxc.idmap or legacy lxc.id_map */ if (virConfWalk(properties, lxcIdmapWalkCallback, vmdef) < 0) goto error; /* lxc.cgroup.memory.* */ if (lxcSetMemTune(vmdef, properties) < 0) goto error; /* lxc.cgroup.cpu.* */ if (lxcSetCpuTune(vmdef, properties) < 0) goto error; /* lxc.cgroup.cpuset.* */ if (lxcSetCpusetTune(vmdef, properties) < 0) goto error; /* lxc.cgroup.blkio.* */ if (lxcSetBlkioTune(vmdef, properties) < 0) goto error; /* lxc.cap.drop */ lxcSetCapDrop(vmdef, properties); if (virDomainDefPostParse(vmdef, VIR_DOMAIN_DEF_PARSE_ABI_UPDATE, xmlopt, NULL) < 0) goto error; return vmdef; error: virDomainDefFree(vmdef); return NULL; }