/* * lxc_native.c: LXC native configuration import * * Copyright (c) 2013 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 * . * * Author: Cedric Bosdonnat */ #include #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" #define VIR_FROM_THIS VIR_FROM_LXC static virDomainFSDefPtr lxcCreateFSDef(int type, const char *src, const char* dst, bool readonly, unsigned long long usage) { virDomainFSDefPtr def; if (VIR_ALLOC(def) < 0) return NULL; def->type = type; def->accessmode = VIR_DOMAIN_FS_ACCESSMODE_PASSTHROUGH; if (src && VIR_STRDUP(def->src, src) < 0) goto error; if (VIR_STRDUP(def->dst, dst) < 0) goto error; def->readonly = readonly; def->usage = usage; return def; error: virDomainFSDefFree(def); return NULL; } 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; if (VIR_STRDUP(tmp, string) < 0) return NULL; /* 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; if (VIR_STRDUP(result[ntokens-2], parts[i]) < 0) goto error; } VIR_FREE(tmp); virStringFreeList(parts); return result; error: VIR_FREE(tmp); virStringFreeList(parts); virStringFreeList(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; if (VIR_STRDUP(fstab->src, parts[0]) < 0 || VIR_STRDUP(fstab->dst, parts[1]) < 0 || VIR_STRDUP(fstab->type, parts[2]) < 0 || VIR_STRDUP(fstab->options, parts[3]) < 0) goto error; virStringFreeList(parts); return fstab; error: lxcFstabFree(fstab); virStringFreeList(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; virConfValuePtr value; if (!(value = virConfGetValue(properties, "lxc.rootfs")) || !value->str) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Missing lxc.rootfs configuration")); return -1; } if (STRPREFIX(value->str, "/dev/")) type = VIR_DOMAIN_FS_TYPE_BLOCK; if (lxcAddFSDef(def, type, value->str, "/", 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] != '/') { if (virAsprintf(&dst, "/%s", fstab->dst) < 0) goto cleanup; } else { if (VIR_STRDUP(dst, fstab->dst) < 0) goto cleanup; } /* 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; } /* Do we have ro in options? */ readonly = virStringArrayHasString(options, "ro"); if (lxcAddFSDef(def, type, src, dst, readonly, usage) < 0) goto cleanup; ret = 1; cleanup: VIR_FREE(dst); virStringFreeList(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 *link, const char *mac, const char *flag, const char *macvlanmode) { virDomainNetDefPtr net = NULL; virMacAddr macAddr; if (VIR_ALLOC(net) < 0) goto error; if (flag) { if (STREQ(flag, "up")) net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_UP; else net->linkstate = VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN; } if (mac && virMacAddrParse(mac, &macAddr) == 0) net->mac = macAddr; if (STREQ(type, "veth")) { if (!link) goto error; net->type = VIR_DOMAIN_NET_TYPE_BRIDGE; if (VIR_STRDUP(net->data.bridge.brname, link) < 0) goto error; } else if (STREQ(type, "macvlan")) { net->type = VIR_DOMAIN_NET_TYPE_DIRECT; if (!link || VIR_STRDUP(net->data.direct.linkdev, link) < 0) goto error; 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 = virDomainHostdevDefAlloc(); if (!hostdev) return NULL; hostdev->mode = mode; hostdev->source.caps.type = type; if (type == VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET && VIR_STRDUP(hostdev->source.caps.u.net.iface, data) < 0) { virDomainHostdevDefFree(hostdev); hostdev = NULL; } return hostdev; } static int lxcAddNetworkDefinition(virDomainDefPtr def, const char *type, const char *link, const char *mac, const char *flag, const char *macvlanmode) { virDomainNetDefPtr net = NULL; virDomainHostdevDefPtr hostdev = NULL; if ((type == NULL) || STREQ(type, "empty") || STREQ(type, "") || STREQ(type, "none")) return 0; if (type != NULL && STREQ(type, "phys")) { if (!link || !(hostdev = lxcCreateHostdevDef(VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES, VIR_DOMAIN_HOSTDEV_CAPS_TYPE_NET, link))) goto error; if (VIR_EXPAND_N(def->hostdevs, def->nhostdevs, 1) < 0) goto error; def->hostdevs[def->nhostdevs - 1] = hostdev; } else { if (!(net = lxcCreateNetDef(type, link, mac, flag, macvlanmode))) goto error; if (VIR_EXPAND_N(def->nets, def->nnets, 1) < 0) goto error; def->nets[def->nnets - 1] = net; } return 1; error: virDomainNetDefFree(net); virDomainHostdevDefFree(hostdev); return -1; } typedef struct { virDomainDefPtr def; char *type; char *link; char *mac; char *flag; char *macvlanmode; bool privnet; size_t networks; } lxcNetworkParseData; static int lxcNetworkWalkCallback(const char *name, virConfValuePtr value, void *data) { lxcNetworkParseData *parseData = data; int status; if (STREQ(name, "lxc.network.type")) { /* Store the previous NIC */ status = lxcAddNetworkDefinition(parseData->def, parseData->type, parseData->link, parseData->mac, parseData->flag, parseData->macvlanmode); if (status < 0) return -1; else if (status > 0) parseData->networks++; else if (parseData->type != NULL && STREQ(parseData->type, "none")) parseData->privnet = false; /* Start a new network interface config */ parseData->type = NULL; parseData->link = NULL; parseData->mac = NULL; parseData->flag = NULL; parseData->macvlanmode = NULL; /* Keep the new value */ parseData->type = value->str; } else if (STREQ(name, "lxc.network.link")) parseData->link = value->str; else if (STREQ(name, "lxc.network.hwaddr")) parseData->mac = value->str; else if (STREQ(name, "lxc.network.flags")) parseData->flag = value->str; else if (STREQ(name, "lxc.network.macvlan.mode")) parseData->macvlanmode = value->str; else if (STRPREFIX(name, "lxc.network")) VIR_WARN("Unhandled network property: %s = %s", name, value->str); return 0; } static int lxcConvertNetworkSettings(virDomainDefPtr def, virConfPtr properties) { int status; lxcNetworkParseData data = {def, NULL, NULL, NULL, NULL, NULL, true, 0}; virConfWalk(properties, lxcNetworkWalkCallback, &data); /* Add the last network definition found */ status = lxcAddNetworkDefinition(def, data.type, data.link, data.mac, data.flag, data.macvlanmode); if (status < 0) return -1; else if (status > 0) data.networks++; else if (data.type != NULL && STREQ(data.type, "none")) data.privnet = false; if (data.networks == 0 && data.privnet) { /* When no network type is provided LXC only adds loopback */ def->features[VIR_DOMAIN_FEATURE_PRIVNET] = VIR_DOMAIN_FEATURE_STATE_ON; } return 0; } static int lxcCreateConsoles(virDomainDefPtr def, virConfPtr properties) { virConfValuePtr value; int nbttys = 0; virDomainChrDefPtr console; size_t i; if (!(value = virConfGetValue(properties, "lxc.tty")) || !value->str) return 0; if (virStrToLong_i(value->str, NULL, 10, &nbttys) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse int: '%s'"), value->str); return -1; } if (VIR_ALLOC_N(def->consoles, nbttys) < 0) return -1; def->nconsoles = nbttys; for (i = 0; i < nbttys; i++) { if (!(console = virDomainChrDefNew())) 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; if (STRNEQ(name, "lxc.id_map") || !value->str) return 0; if (sscanf(value->str, "%c %lu %lu %lu", &type, &target, &start, &count) != 4) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid lxc.id_map: '%s'"), 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) { virConfValuePtr value; unsigned long long size = 0; if ((value = virConfGetValue(properties, "lxc.cgroup.memory.limit_in_bytes")) && value->str && STRNEQ(value->str, "-1")) { if (lxcConvertSize(value->str, &size) < 0) return -1; size = size / 1024; def->mem.max_balloon = size; def->mem.hard_limit = size; } if ((value = virConfGetValue(properties, "lxc.cgroup.memory.soft_limit_in_bytes")) && value->str && STRNEQ(value->str, "-1")) { if (lxcConvertSize(value->str, &size) < 0) return -1; def->mem.soft_limit = size / 1024; } if ((value = virConfGetValue(properties, "lxc.cgroup.memory.memsw.limit_in_bytes")) && value->str && STRNEQ(value->str, "-1")) { if (lxcConvertSize(value->str, &size) < 0) return -1; def->mem.swap_hard_limit = size / 1024; } return 0; } static int lxcSetCpuTune(virDomainDefPtr def, virConfPtr properties) { virConfValuePtr value; if ((value = virConfGetValue(properties, "lxc.cgroup.cpu.shares")) && value->str && virStrToLong_ul(value->str, NULL, 10, &def->cputune.shares) < 0) goto error; if ((value = virConfGetValue(properties, "lxc.cgroup.cpu.cfs_quota_us")) && value->str && virStrToLong_ll(value->str, NULL, 10, &def->cputune.quota) < 0) goto error; if ((value = virConfGetValue(properties, "lxc.cgroup.cpu.cfs_period_us")) && value->str && virStrToLong_ull(value->str, NULL, 10, &def->cputune.period) < 0) goto error; return 0; error: virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse integer: '%s'"), value->str); return -1; } static int lxcSetCpusetTune(virDomainDefPtr def, virConfPtr properties) { virConfValuePtr value; if ((value = virConfGetValue(properties, "lxc.cgroup.cpuset.cpus")) && value->str) { if (virBitmapParse(value->str, 0, &def->cpumask, VIR_DOMAIN_CPUMASK_LEN) < 0) return -1; def->placement_mode = VIR_DOMAIN_CPU_PLACEMENT_MODE_STATIC; } if ((value = virConfGetValue(properties, "lxc.cgroup.cpuset.mems")) && value->str) { def->numatune.memory.placement_mode = VIR_NUMA_TUNE_MEM_PLACEMENT_MODE_STATIC; def->numatune.memory.mode = VIR_DOMAIN_NUMATUNE_MEM_STRICT; if (virBitmapParse(value->str, 0, &def->numatune.memory.nodemask, VIR_DOMAIN_CPUMASK_LEN) < 0) return -1; } return 0; } static int lxcBlkioDeviceWalkCallback(const char *name, virConfValuePtr value, void *data) { char **parts = NULL; virBlkioDevicePtr device = NULL; virDomainDefPtr def = data; if (STRNEQ(name, "lxc.cgroup.blkio.device_weight") || !value->str) return 0; if (!(parts = lxcStringSplit(value->str))) return -1; if (!parts[0] || !parts[1]) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid blkio.device_weight value: '%s'"), value->str); goto error; } if (VIR_EXPAND_N(def->blkio.devices, def->blkio.ndevices, 1) < 0) goto error; device = &def->blkio.devices[def->blkio.ndevices - 1]; if (virAsprintf(&device->path, "/dev/block/%s", parts[0]) < 0) goto error; if (virStrToLong_ui(parts[1], NULL, 10, &device->weight) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse integer: '%s'"), parts[1]); goto error; } virStringFreeList(parts); return 0; error: if (parts) virStringFreeList(parts); return -1; } static int lxcSetBlkioTune(virDomainDefPtr def, virConfPtr properties) { virConfValuePtr value; if ((value = virConfGetValue(properties, "lxc.cgroup.blkio.weight")) && value->str && virStrToLong_ui(value->str, NULL, 10, &def->blkio.weight) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse integer: '%s'"), value->str); return -1; } if (virConfWalk(properties, lxcBlkioDeviceWalkCallback, def) < 0) return -1; return 0; } virDomainDefPtr lxcParseConfigString(const char *config) { virDomainDefPtr vmdef = NULL; virConfPtr properties = NULL; virConfValuePtr value; if (!(properties = virConfReadMem(config, 0, VIR_CONF_FLAG_LXC_FORMAT))) return NULL; if (VIR_ALLOC(vmdef) < 0) goto error; if (virUUIDGenerate(vmdef->uuid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("failed to generate uuid")); goto error; } vmdef->id = -1; vmdef->mem.max_balloon = 64 * 1024; vmdef->onReboot = VIR_DOMAIN_LIFECYCLE_RESTART; vmdef->onCrash = VIR_DOMAIN_LIFECYCLE_DESTROY; vmdef->onPoweroff = VIR_DOMAIN_LIFECYCLE_DESTROY; vmdef->virtType = VIR_DOMAIN_VIRT_LXC; /* Value not handled by the LXC driver, setting to * minimum required to make XML parsing pass */ vmdef->maxvcpus = 1; vmdef->nfss = 0; if (VIR_STRDUP(vmdef->os.type, "exe") < 0) goto error; if (VIR_STRDUP(vmdef->os.init, "/sbin/init") < 0) goto error; if (!(value = virConfGetValue(properties, "lxc.utsname")) || !value->str || (VIR_STRDUP(vmdef->name, value->str) < 0)) goto error; if (!vmdef->name && (VIR_STRDUP(vmdef->name, "unnamed") < 0)) goto error; if (lxcSetRootfs(vmdef, properties) < 0) goto error; /* Look for fstab: we shouldn't have it */ if (virConfGetValue(properties, "lxc.mount")) { virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", _("lxc.mount found, use lxc.mount.entry lines instead")); goto error; } /* Loop over lxc.mount.entry to add filesystem devices for them */ value = virConfGetValue(properties, "lxc.mount.entry"); 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.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; goto cleanup; error: virDomainDefFree(vmdef); vmdef = NULL; cleanup: virConfFree(properties); return vmdef; }