/* * Copyright IBM Corp. 2008 * * lxc_conf.c: config functions for managing linux containers * * Authors: * David L. Leskovec * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* includes */ #include #ifdef WITH_LXC #include #include #include #include #include #include #include #include #include #include "buf.h" #include "util.h" #include "uuid.h" #include "lxc_conf.h" /* debug macros */ #define DEBUG(fmt,...) VIR_DEBUG(__FILE__, fmt, __VA_ARGS__) #define DEBUG0(msg) VIR_DEBUG(__FILE__, "%s", msg) /* Functions */ void lxcError(virConnectPtr conn, virDomainPtr dom, int code, const char *fmt, ...) { va_list args; char errorMessage[LXC_MAX_ERROR_LEN]; const char *codeErrorMessage; if (fmt) { va_start(args, fmt); vsnprintf(errorMessage, LXC_MAX_ERROR_LEN-1, fmt, args); va_end(args); } else { errorMessage[0] = '\0'; } codeErrorMessage = __virErrorMsg(code, fmt); __virRaiseError(conn, dom, NULL, VIR_FROM_LXC, code, VIR_ERR_ERROR, codeErrorMessage, errorMessage, NULL, 0, 0, codeErrorMessage, errorMessage); } static inline int lxcIsEmptyXPathStringObj(xmlXPathObjectPtr xpathObj) { if ((xpathObj == NULL) || (xpathObj->type != XPATH_STRING) || (xpathObj->stringval == NULL) || (xpathObj->stringval[0] == 0)) { return 1; } else { return 0; } } static int lxcParseMountXML(virConnectPtr conn, xmlNodePtr nodePtr, lxc_mount_t *lxcMount) { xmlChar *fsType = NULL; xmlNodePtr curNode; xmlChar *mountSource = NULL; xmlChar *mountTarget = NULL; int strLen; int rc = -1; if (NULL == (fsType = xmlGetProp(nodePtr, BAD_CAST "type"))) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("missing filesystem type")); goto error; } if (xmlStrEqual(fsType, BAD_CAST "mount") == 0) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("invalid filesystem type")); goto error; } for (curNode = nodePtr->children; NULL != curNode; curNode = curNode->next) { if (curNode->type != XML_ELEMENT_NODE) { continue; } if ((mountSource == NULL) && (xmlStrEqual(curNode->name, BAD_CAST "source"))) { mountSource = xmlGetProp(curNode, BAD_CAST "dir"); } else if ((mountTarget == NULL) && (xmlStrEqual(curNode->name, BAD_CAST "target"))) { mountTarget = xmlGetProp(curNode, BAD_CAST "dir"); } } if (mountSource == NULL) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("missing mount source")); goto error; } strLen = xmlStrlen(mountSource); if ((strLen > (PATH_MAX-1)) || (0 == strLen)) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("empty or invalid mount source")); goto error; } strncpy(lxcMount->source, (char *)mountSource, strLen); lxcMount->source[strLen] = '\0'; if (mountTarget == NULL) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("missing mount target")); goto error; } strLen = xmlStrlen(mountTarget); if ((strLen > (PATH_MAX-1)) || (0 == strLen)) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("empty or invalid mount target")); goto error; } strncpy(lxcMount->target, (char *)mountTarget, strLen); lxcMount->target[strLen] = '\0'; rc = 0; error: xmlFree(mountSource); xmlFree(mountTarget); return rc; } static int lxcParseDomainName(virConnectPtr conn, char **name, xmlXPathContextPtr contextPtr) { int rc = -1; xmlXPathObjectPtr xpathObj = NULL; xpathObj = xmlXPathEval(BAD_CAST "string(/domain/name[1])", contextPtr); if (lxcIsEmptyXPathStringObj(xpathObj)) { lxcError(conn, NULL, VIR_ERR_NO_NAME, NULL); goto parse_complete; } *name = strdup((const char *)xpathObj->stringval); if (NULL == *name) { lxcError(conn, NULL, VIR_ERR_NO_MEMORY, NULL); goto parse_complete; } rc = 0; parse_complete: xmlXPathFreeObject(xpathObj); return rc; } static int lxcParseDomainUUID(virConnectPtr conn, unsigned char *uuid, xmlXPathContextPtr contextPtr) { int rc = -1; xmlXPathObjectPtr xpathObj = NULL; xpathObj = xmlXPathEval(BAD_CAST "string(/domain/uuid[1])", contextPtr); if (lxcIsEmptyXPathStringObj(xpathObj)) { if ((rc = virUUIDGenerate(uuid))) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("failed to generate uuid: %s"), strerror(rc)); goto parse_complete; } } else { if (virUUIDParse((const char *)xpathObj->stringval, uuid) < 0) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("invalid uuid element")); goto parse_complete; } } rc = 0; parse_complete: xmlXPathFreeObject(xpathObj); return rc; } static int lxcParseDomainMounts(virConnectPtr conn, lxc_mount_t **mounts, xmlXPathContextPtr contextPtr) { int rc = -1; xmlXPathObjectPtr xpathObj = NULL; int i; lxc_mount_t *mountObj; lxc_mount_t *prevObj = NULL; int nmounts = 0; xpathObj = xmlXPathEval(BAD_CAST "/domain/devices/filesystem", contextPtr); if ((xpathObj != NULL) && (xpathObj->type == XPATH_NODESET) && (xpathObj->nodesetval != NULL) && (xpathObj->nodesetval->nodeNr >= 0)) { for (i = 0; i < xpathObj->nodesetval->nodeNr; ++i) { mountObj = calloc(1, sizeof(lxc_mount_t)); if (NULL == mountObj) { lxcError(conn, NULL, VIR_ERR_NO_MEMORY, "mount"); goto parse_complete; } rc = lxcParseMountXML(conn, xpathObj->nodesetval->nodeTab[i], mountObj); if (0 > rc) { free(mountObj); goto parse_complete; } /* set the linked list pointers */ nmounts++; mountObj->next = NULL; if (0 == i) { *mounts = mountObj; } else { prevObj->next = mountObj; } prevObj = mountObj; } } rc = nmounts; parse_complete: xmlXPathFreeObject(xpathObj); return rc; } static int lxcParseDomainInit(virConnectPtr conn, char* init, xmlXPathContextPtr contextPtr) { int rc = -1; xmlXPathObjectPtr xpathObj = NULL; xpathObj = xmlXPathEval(BAD_CAST "string(/domain/os/init[1])", contextPtr); if (lxcIsEmptyXPathStringObj(xpathObj)) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("invalid or missing init element")); goto parse_complete; } if (strlen((const char*)xpathObj->stringval) >= PATH_MAX - 1) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("init string too long")); goto parse_complete; } strcpy(init, (const char *)xpathObj->stringval); rc = 0; parse_complete: xmlXPathFreeObject(xpathObj); return rc; } static int lxcParseDomainTty(virConnectPtr conn, char *tty, xmlXPathContextPtr contextPtr) { int rc = -1; xmlXPathObjectPtr xpathObj = NULL; xpathObj = xmlXPathEval(BAD_CAST "string(/domain/devices/console[1]/@tty)", contextPtr); if (lxcIsEmptyXPathStringObj(xpathObj)) { /* make sure the tty string is empty */ tty[0] = 0x00; } else { /* check the source string length */ if (strlen((const char*)xpathObj->stringval) >= LXC_MAX_TTY_NAME - 1) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("tty name is too long")); goto parse_complete; } strcpy(tty, (const char *)xpathObj->stringval); } rc = 0; parse_complete: xmlXPathFreeObject(xpathObj); return rc; } static int lxcParseDomainMemory(virConnectPtr conn, int* memory, xmlXPathContextPtr contextPtr) { int rc = -1; xmlXPathObjectPtr xpathObj = NULL; char *endChar = NULL; xpathObj = xmlXPathEval(BAD_CAST "string(/domain/memory[1])", contextPtr); if (lxcIsEmptyXPathStringObj(xpathObj)) { /* not an error, default to an invalid value so it's not used */ *memory = -1; } else { *memory = strtoll((const char*)xpathObj->stringval, &endChar, 10); if ((endChar == (const char*)xpathObj->stringval) || (*endChar != '\0')) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("invalid memory value")); goto parse_complete; } } rc = 0; parse_complete: xmlXPathFreeObject(xpathObj); return rc; } static lxc_vm_def_t * lxcParseXML(virConnectPtr conn, xmlDocPtr docPtr) { xmlNodePtr rootNodePtr = NULL; xmlXPathContextPtr contextPtr = NULL; xmlChar *xmlProp = NULL; lxc_vm_def_t *containerDef; if (!(containerDef = calloc(1, sizeof(*containerDef)))) { lxcError(conn, NULL, VIR_ERR_NO_MEMORY, "containerDef"); return NULL; } /* Prepare parser / xpath context */ rootNodePtr = xmlDocGetRootElement(docPtr); if ((rootNodePtr == NULL) || (!xmlStrEqual(rootNodePtr->name, BAD_CAST "domain"))) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("invalid root element")); goto error; } contextPtr = xmlXPathNewContext(docPtr); if (contextPtr == NULL) { lxcError(conn, NULL, VIR_ERR_NO_MEMORY, "context"); goto error; } /* Verify the domain type is linuxcontainer */ if (!(xmlProp = xmlGetProp(rootNodePtr, BAD_CAST "type"))) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("missing domain type")); goto error; } if (!(xmlStrEqual(xmlProp, BAD_CAST LXC_DOMAIN_TYPE))) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("invalid domain type")); goto error; } free(xmlProp); xmlProp = NULL; if ((xmlProp = xmlGetProp(rootNodePtr, BAD_CAST "id"))) { if (0 > virStrToLong_i((char*)xmlProp, NULL, 10, &(containerDef->id))) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, "invalid domain id"); goto error; } } else { containerDef->id = -1; } free(xmlProp); xmlProp = NULL; if (lxcParseDomainName(conn, &(containerDef->name), contextPtr) < 0) { goto error; } if (lxcParseDomainInit(conn, containerDef->init, contextPtr) < 0) { goto error; } if (lxcParseDomainUUID(conn, containerDef->uuid, contextPtr) < 0) { goto error; } containerDef->nmounts = lxcParseDomainMounts(conn, &(containerDef->mounts), contextPtr); if (0 > containerDef->nmounts) { goto error; } if (lxcParseDomainTty(conn, containerDef->tty, contextPtr) < 0) { goto error; } if (lxcParseDomainMemory(conn, &(containerDef->maxMemory), contextPtr) < 0) { goto error; } xmlXPathFreeContext(contextPtr); return containerDef; error: free(xmlProp); xmlXPathFreeContext(contextPtr); lxcFreeVMDef(containerDef); return NULL; } lxc_vm_def_t * lxcParseVMDef(virConnectPtr conn, const char* xmlString, const char* fileName) { xmlDocPtr xml; lxc_vm_def_t *containerDef; xml = xmlReadDoc(BAD_CAST xmlString, fileName ? fileName : "domain.xml", NULL, XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_NOERROR | XML_PARSE_NOWARNING); if (!xml) { lxcError(conn, NULL, VIR_ERR_XML_ERROR, NULL); return NULL; } containerDef = lxcParseXML(conn, xml); xmlFreeDoc(xml); return containerDef; } lxc_vm_t * lxcAssignVMDef(virConnectPtr conn, lxc_driver_t *driver, lxc_vm_def_t *def) { lxc_vm_t *vm = NULL; if ((vm = lxcFindVMByName(driver, def->name))) { if (!lxcIsActiveVM(vm)) { lxcFreeVMDef(vm->def); vm->def = def; } else { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("Can't redefine active VM with name %s"), def->name); return NULL; } return vm; } if (!(vm = calloc(1, sizeof(lxc_vm_t)))) { lxcError(conn, NULL, VIR_ERR_NO_MEMORY, "vm"); return NULL; } vm->pid = -1; vm->def = def; vm->next = driver->vms; driver->vms = vm; if (lxcIsActiveVM(vm)) { vm->state = VIR_DOMAIN_RUNNING; driver->nactivevms++; } else { vm->state = VIR_DOMAIN_SHUTOFF; driver->ninactivevms++; } return vm; } void lxcRemoveInactiveVM(lxc_driver_t *driver, lxc_vm_t *vm) { lxc_vm_t *prevVm = NULL; lxc_vm_t *curVm; for (curVm = driver->vms; (curVm != vm) && (NULL != curVm); curVm = curVm->next) { prevVm = curVm; } if (curVm) { if (prevVm) { prevVm->next = curVm->next; } else { driver->vms = curVm->next; } driver->ninactivevms--; } lxcFreeVM(vm); } /* Save a container's config data into a persistent file */ int lxcSaveConfig(virConnectPtr conn, lxc_driver_t *driver, lxc_vm_t *vm, lxc_vm_def_t *def) { int rc = -1; char *xmlDef; int fd = -1; int amtToWrite; if (!(xmlDef = lxcGenerateXML(conn, driver, vm, def))) { return -1; } if ((fd = open(vm->configFile, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR )) < 0) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot create config file %s: %s"), vm->configFile, strerror(errno)); goto cleanup; } amtToWrite = strlen(xmlDef); if (safewrite(fd, xmlDef, amtToWrite) < 0) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot write config file %s: %s"), vm->configFile, strerror(errno)); goto cleanup; } if (close(fd) < 0) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot save config file %s: %s"), vm->configFile, strerror(errno)); goto cleanup; } rc = 0; cleanup: if (fd != -1) { close(fd); } free(xmlDef); return rc; } int lxcSaveVMDef(virConnectPtr conn, lxc_driver_t *driver, lxc_vm_t *vm, lxc_vm_def_t *def) { int rc = -1; char uuidstr[VIR_UUID_STRING_BUFLEN]; if (vm->configFile[0] == '\0') { if ((rc = virFileMakePath(driver->configDir))) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot create config directory %s: %s"), driver->configDir, strerror(rc)); goto save_complete; } virUUIDFormat(def->uuid, uuidstr); if (virFileBuildPath(driver->configDir, uuidstr, ".xml", vm->configFile, PATH_MAX) < 0) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot construct config file path")); goto save_complete; } strncpy(vm->configFileBase, uuidstr, PATH_MAX); strncat(vm->configFileBase, ".xml", PATH_MAX - strlen(uuidstr)); } rc = lxcSaveConfig(conn, driver, vm, def); save_complete: return rc; } static lxc_vm_t * lxcLoadConfig(lxc_driver_t *driver, const char *file, const char *fullFilePath, const char *xmlData) { lxc_vm_def_t *containerDef; lxc_vm_t * vm; char uuidstr[VIR_UUID_STRING_BUFLEN]; containerDef = lxcParseVMDef(NULL, xmlData, file); if (NULL == containerDef) { DEBUG0("Error parsing container config"); return NULL; } virUUIDFormat(containerDef->uuid, uuidstr); if (!virFileMatchesNameSuffix(file, uuidstr, ".xml")) { DEBUG0("Container uuid does not match config file name"); lxcFreeVMDef(containerDef); return NULL; } vm = lxcAssignVMDef(NULL, driver, containerDef); if (NULL == vm) { DEBUG0("Failed to load container config"); lxcFreeVMDef(containerDef); return NULL; } strncpy(vm->configFile, fullFilePath, PATH_MAX); vm->configFile[PATH_MAX-1] = '\0'; strncpy(vm->configFileBase, file, PATH_MAX); vm->configFile[PATH_MAX-1] = '\0'; return vm; } int lxcLoadDriverConfig(virConnectPtr conn) { lxc_driver_t *driverPtr = (lxc_driver_t*)conn->privateData; /* Set the container configuration directory */ driverPtr->configDir = strdup(SYSCONF_DIR "/libvirt/lxc"); if (NULL == driverPtr->configDir) { lxcError(conn, NULL, VIR_ERR_NO_MEMORY, "configDir"); return -1; } return 0; } int lxcLoadContainerConfigFile(lxc_driver_t *driver, const char *file) { int rc = -1; char tempPath[PATH_MAX]; char* xmlData; rc = virFileBuildPath(driver->configDir, file, NULL, tempPath, PATH_MAX); if (0 > rc) { DEBUG0("config file name too long"); goto load_complete; } if ((rc = virFileReadAll(tempPath, LXC_MAX_XML_LENGTH, &xmlData)) < 0) { goto load_complete; } lxcLoadConfig(driver, file, tempPath, xmlData); free(xmlData); load_complete: return rc; } int lxcLoadContainerInfo(virConnectPtr conn) { int rc = -1; lxc_driver_t *driverPtr = (lxc_driver_t*)conn->privateData; DIR *dir; struct dirent *dirEntry; if (!(dir = opendir(driverPtr->configDir))) { if (ENOENT == errno) { /* no config dir => no containers */ rc = 0; } else { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("failed to open config directory: %s"), strerror(errno)); } goto load_complete; } while ((dirEntry = readdir(dir))) { if (dirEntry->d_name[0] == '.') { continue; } if (!virFileHasSuffix(dirEntry->d_name, ".xml")) { continue; } lxcLoadContainerConfigFile(driverPtr, dirEntry->d_name); } closedir(dir); rc = 0; load_complete: return rc; } /* Generate an XML document describing the vm's configuration */ char *lxcGenerateXML(virConnectPtr conn, lxc_driver_t *driver ATTRIBUTE_UNUSED, lxc_vm_t *vm, lxc_vm_def_t *def) { virBufferPtr buf = 0; unsigned char *uuid; char uuidstr[VIR_UUID_STRING_BUFLEN]; lxc_mount_t *mount; buf = virBufferNew(LXC_MAX_XML_LENGTH); if (!buf) { goto no_memory; } if (lxcIsActiveVM(vm)) { if (virBufferVSprintf(buf, "\n", vm->def->id) < 0) { goto no_memory; } } else { if (virBufferAddLit(buf, "\n") < 0) { goto no_memory; } } if (virBufferVSprintf(buf, " %s\n", def->name) < 0) { goto no_memory; } uuid = def->uuid; virUUIDFormat(uuid, uuidstr); if (virBufferVSprintf(buf, " %s\n", uuidstr) < 0) { goto no_memory; } if (virBufferAddLit(buf, " \n") < 0) { goto no_memory; } if (virBufferVSprintf(buf, " %s\n", def->init) < 0) { goto no_memory; } if (virBufferAddLit(buf, " \n") < 0) { goto no_memory; } if (virBufferVSprintf(buf, " %d\n", def->maxMemory) < 0) { goto no_memory; } if (virBufferAddLit(buf, " \n") < 0) { goto no_memory; } /* loop adding mounts */ for (mount = def->mounts; mount; mount = mount->next) { if (virBufferAddLit(buf, " \n") < 0) { goto no_memory; } if (virBufferVSprintf(buf, " \n", mount->source) < 0) { goto no_memory; } if (virBufferVSprintf(buf, " \n", mount->target) < 0) { goto no_memory; } if (virBufferAddLit(buf, " \n") < 0) { goto no_memory; } } if (virBufferVSprintf(buf, " \n", def->tty) < 0) { goto no_memory; } if (virBufferAddLit(buf, " \n") < 0) { goto no_memory; } if (virBufferAddLit(buf, "\n") < 0) { goto no_memory; } return virBufferContentAndFree(buf); no_memory: lxcError(conn, NULL, VIR_ERR_NO_MEMORY, "generateXml"); virBufferFree(buf); return NULL; } void lxcFreeVMDef(lxc_vm_def_t *vmdef) { lxc_mount_t *curMount = vmdef->mounts; lxc_mount_t *nextMount; while (curMount) { nextMount = curMount->next; free(curMount); curMount = nextMount; } free(vmdef->name); vmdef->name = NULL; } void lxcFreeVMs(lxc_vm_t *vms) { lxc_vm_t *curVm = vms; lxc_vm_t *nextVm; while (curVm) { lxcFreeVM(curVm); nextVm = curVm->next; free(curVm); curVm = nextVm; } } void lxcFreeVM(lxc_vm_t *vm) { lxcFreeVMDef(vm->def); free(vm->def); } lxc_vm_t *lxcFindVMByID(const lxc_driver_t *driver, int id) { lxc_vm_t *vm; for (vm = driver->vms; vm; vm = vm->next) { if (lxcIsActiveVM(vm) && (vm->def->id == id)) { return vm; } } return NULL; } lxc_vm_t *lxcFindVMByUUID(const lxc_driver_t *driver, const unsigned char *uuid) { lxc_vm_t *vm; for (vm = driver->vms; vm; vm = vm->next) { if (!memcmp(vm->def->uuid, uuid, VIR_UUID_BUFLEN)) { return vm; } } return NULL; } lxc_vm_t *lxcFindVMByName(const lxc_driver_t *driver, const char *name) { lxc_vm_t *vm; for (vm = driver->vms; vm; vm = vm->next) { if (STREQ(vm->def->name, name)) { return vm; } } return NULL; } int lxcDeleteConfig(virConnectPtr conn, lxc_driver_t *driver ATTRIBUTE_UNUSED, const char *configFile, const char *name) { if (!configFile[0]) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("no config file for %s"), name); return -1; } if (unlink(configFile) < 0) { lxcError(conn, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot remove config for %s"), name); return -1; } return 0; } #endif /* WITH_LXC */