/* * Copyright (C) 2010-2014 Red Hat, Inc. * Copyright IBM Corp. 2008 * * lxc_domain.h: LXC domain helpers * * 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 "lxc_domain.h" #include "viralloc.h" #include "virlog.h" #include "virerror.h" #include "virstring.h" #include "virfile.h" #include "virtime.h" #include "virsystemd.h" #include "virinitctl.h" #define VIR_FROM_THIS VIR_FROM_LXC VIR_ENUM_IMPL(virLXCDomainJob, LXC_JOB_LAST, "none", "query", "destroy", "modify", ); VIR_LOG_INIT("lxc.lxc_domain"); static int virLXCDomainObjInitJob(virLXCDomainObjPrivatePtr priv) { memset(&priv->job, 0, sizeof(priv->job)); if (virCondInit(&priv->job.cond) < 0) return -1; return 0; } static void virLXCDomainObjResetJob(virLXCDomainObjPrivatePtr priv) { struct virLXCDomainJobObj *job = &priv->job; job->active = LXC_JOB_NONE; job->owner = 0; } static void virLXCDomainObjFreeJob(virLXCDomainObjPrivatePtr priv) { ignore_value(virCondDestroy(&priv->job.cond)); } /* Give up waiting for mutex after 30 seconds */ #define LXC_JOB_WAIT_TIME (1000ull * 30) /* * obj must be locked before calling, virLXCDriverPtr must NOT be locked * * This must be called by anything that will change the VM state * in any way * * Upon successful return, the object will have its ref count increased. * Successful calls must be followed by EndJob eventually. */ int virLXCDomainObjBeginJob(virLXCDriverPtr driver G_GNUC_UNUSED, virDomainObjPtr obj, enum virLXCDomainJob job) { virLXCDomainObjPrivatePtr priv = obj->privateData; unsigned long long now; unsigned long long then; if (virTimeMillisNow(&now) < 0) return -1; then = now + LXC_JOB_WAIT_TIME; while (priv->job.active) { VIR_DEBUG("Wait normal job condition for starting job: %s", virLXCDomainJobTypeToString(job)); if (virCondWaitUntil(&priv->job.cond, &obj->parent.lock, then) < 0) goto error; } virLXCDomainObjResetJob(priv); VIR_DEBUG("Starting job: %s", virLXCDomainJobTypeToString(job)); priv->job.active = job; priv->job.owner = virThreadSelfID(); return 0; error: VIR_WARN("Cannot start job (%s) for domain %s;" " current job is (%s) owned by (%d)", virLXCDomainJobTypeToString(job), obj->def->name, virLXCDomainJobTypeToString(priv->job.active), priv->job.owner); if (errno == ETIMEDOUT) virReportError(VIR_ERR_OPERATION_TIMEOUT, "%s", _("cannot acquire state change lock")); else virReportSystemError(errno, "%s", _("cannot acquire job mutex")); return -1; } /* * obj must be locked and have a reference before calling * * To be called after completing the work associated with the * earlier virLXCDomainBeginJob() call */ void virLXCDomainObjEndJob(virLXCDriverPtr driver G_GNUC_UNUSED, virDomainObjPtr obj) { virLXCDomainObjPrivatePtr priv = obj->privateData; enum virLXCDomainJob job = priv->job.active; VIR_DEBUG("Stopping job: %s", virLXCDomainJobTypeToString(job)); virLXCDomainObjResetJob(priv); virCondSignal(&priv->job.cond); } static void * virLXCDomainObjPrivateAlloc(void *opaque G_GNUC_UNUSED) { virLXCDomainObjPrivatePtr priv; if (VIR_ALLOC(priv) < 0) return NULL; if (virLXCDomainObjInitJob(priv) < 0) { VIR_FREE(priv); return NULL; } return priv; } static void virLXCDomainObjPrivateFree(void *data) { virLXCDomainObjPrivatePtr priv = data; virCgroupFree(&priv->cgroup); virLXCDomainObjFreeJob(priv); VIR_FREE(priv); } VIR_ENUM_IMPL(virLXCDomainNamespace, VIR_LXC_DOMAIN_NAMESPACE_LAST, "sharenet", "shareipc", "shareuts", ); VIR_ENUM_IMPL(virLXCDomainNamespaceSource, VIR_LXC_DOMAIN_NAMESPACE_SOURCE_LAST, "none", "name", "pid", "netns", ); static void lxcDomainDefNamespaceFree(void *nsdata) { size_t i; lxcDomainDefPtr lxcDef = nsdata; for (i = 0; i < VIR_LXC_DOMAIN_NAMESPACE_LAST; i++) VIR_FREE(lxcDef->ns_val[i]); VIR_FREE(nsdata); } static int lxcDomainDefNamespaceParse(xmlXPathContextPtr ctxt, void **data) { lxcDomainDefPtr lxcDef = NULL; xmlNodePtr *nodes = NULL; bool uses_lxc_ns = false; xmlNodePtr node; int feature; int n; char *tmp = NULL; size_t i; if (VIR_ALLOC(lxcDef) < 0) return -1; node = ctxt->node; if ((n = virXPathNodeSet("./lxc:namespace/*", ctxt, &nodes)) < 0) goto error; uses_lxc_ns |= n > 0; for (i = 0; i < n; i++) { if ((feature = virLXCDomainNamespaceTypeFromString( (const char *)nodes[i]->name)) < 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unsupported Namespace feature: %s"), nodes[i]->name); goto error; } ctxt->node = nodes[i]; if (!(tmp = virXMLPropString(nodes[i], "type"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("No lxc environment type specified")); goto error; } if ((lxcDef->ns_source[feature] = virLXCDomainNamespaceSourceTypeFromString(tmp)) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown LXC namespace source '%s'"), tmp); VIR_FREE(tmp); goto error; } VIR_FREE(tmp); if (!(lxcDef->ns_val[feature] = virXMLPropString(nodes[i], "value"))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("No lxc environment type specified")); goto error; } } VIR_FREE(nodes); ctxt->node = node; if (uses_lxc_ns) *data = lxcDef; else VIR_FREE(lxcDef); return 0; error: VIR_FREE(nodes); lxcDomainDefNamespaceFree(lxcDef); return -1; } static int lxcDomainDefNamespaceFormatXML(virBufferPtr buf, void *nsdata) { lxcDomainDefPtr lxcDef = nsdata; size_t i; if (!lxcDef) return 0; virBufferAddLit(buf, "\n"); virBufferAdjustIndent(buf, 2); for (i = 0; i < VIR_LXC_DOMAIN_NAMESPACE_LAST; i++) { if (lxcDef->ns_source[i] == VIR_LXC_DOMAIN_NAMESPACE_SOURCE_NONE) continue; virBufferAsprintf(buf, "\n", virLXCDomainNamespaceTypeToString(i), virLXCDomainNamespaceSourceTypeToString( lxcDef->ns_source[i]), lxcDef->ns_val[i]); } virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "\n"); return 0; } virXMLNamespace virLXCDriverDomainXMLNamespace = { .parse = lxcDomainDefNamespaceParse, .free = lxcDomainDefNamespaceFree, .format = lxcDomainDefNamespaceFormatXML, .prefix = "lxc", .uri = "http://libvirt.org/schemas/domain/lxc/1.0", }; static int virLXCDomainObjPrivateXMLFormat(virBufferPtr buf, virDomainObjPtr vm) { virLXCDomainObjPrivatePtr priv = vm->privateData; virBufferAsprintf(buf, "\n", (long long)priv->initpid); return 0; } static int virLXCDomainObjPrivateXMLParse(xmlXPathContextPtr ctxt, virDomainObjPtr vm, virDomainDefParserConfigPtr config G_GNUC_UNUSED) { virLXCDomainObjPrivatePtr priv = vm->privateData; long long thepid; if (virXPathLongLong("string(./init[1]/@pid)", ctxt, &thepid) < 0) { VIR_WARN("Failed to load init pid from state %s", virGetLastErrorMessage()); priv->initpid = 0; } else { priv->initpid = thepid; } return 0; } virDomainXMLPrivateDataCallbacks virLXCDriverPrivateDataCallbacks = { .alloc = virLXCDomainObjPrivateAlloc, .free = virLXCDomainObjPrivateFree, .format = virLXCDomainObjPrivateXMLFormat, .parse = virLXCDomainObjPrivateXMLParse, }; static int virLXCDomainDefPostParse(virDomainDefPtr def, unsigned int parseFlags G_GNUC_UNUSED, void *opaque, void *parseOpaque G_GNUC_UNUSED) { virLXCDriverPtr driver = opaque; g_autoptr(virCaps) caps = virLXCDriverGetCapabilities(driver, false); if (!caps) return -1; if (!virCapabilitiesDomainSupported(caps, def->os.type, def->os.arch, def->virtType)) return -1; /* check for emulator and create a default one if needed */ if (!def->emulator && !(def->emulator = virDomainDefGetDefaultEmulator(def, caps))) return -1; return 0; } static int virLXCDomainDeviceDefPostParse(virDomainDeviceDefPtr dev, const virDomainDef *def G_GNUC_UNUSED, unsigned int parseFlags G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED, void *parseOpaque G_GNUC_UNUSED) { if (dev->type == VIR_DOMAIN_DEVICE_CHR && dev->data.chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE && dev->data.chr->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_NONE) dev->data.chr->targetType = VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_LXC; return 0; } virDomainDefParserConfig virLXCDriverDomainDefParserConfig = { .domainPostParseCallback = virLXCDomainDefPostParse, .devicesPostParseCallback = virLXCDomainDeviceDefPostParse, }; char * virLXCDomainGetMachineName(virDomainDefPtr def, pid_t pid) { char *ret = NULL; if (pid) { ret = virSystemdGetMachineNameByPID(pid); if (!ret) virResetLastError(); } if (!ret) ret = virDomainGenerateMachineName("lxc", NULL, def->id, def->name, true); return ret; } typedef struct _lxcDomainInitctlCallbackData lxcDomainInitctlCallbackData; struct _lxcDomainInitctlCallbackData { int runlevel; bool *st_valid; struct stat *st; }; static int lxcDomainInitctlCallback(pid_t pid G_GNUC_UNUSED, void *opaque) { lxcDomainInitctlCallbackData *data = opaque; size_t i; for (i = 0; virInitctlFifos[i]; i++) { const char *fifo = virInitctlFifos[i]; struct stat cont_sb; if (stat(fifo, &cont_sb) < 0) { if (errno == ENOENT) continue; virReportSystemError(errno, _("Unable to stat %s"), fifo); return -1; } /* Check if the init fifo is not the very one that's on * the host. We don't want to change the host's runlevel. */ if (data->st_valid[i] && data->st[i].st_dev == cont_sb.st_dev && data->st[i].st_ino == cont_sb.st_ino) continue; return virInitctlSetRunLevel(fifo, data->runlevel); } /* If no usable fifo was found then declare success. Caller * will try killing the domain with signal. */ return 0; } int virLXCDomainSetRunlevel(virDomainObjPtr vm, int runlevel) { virLXCDomainObjPrivatePtr priv = vm->privateData; lxcDomainInitctlCallbackData data; size_t nfifos = 0; size_t i; int ret = -1; memset(&data, 0, sizeof(data)); data.runlevel = runlevel; for (nfifos = 0; virInitctlFifos[nfifos]; nfifos++) ; if (VIR_ALLOC_N(data.st, nfifos) < 0 || VIR_ALLOC_N(data.st_valid, nfifos) < 0) goto cleanup; for (i = 0; virInitctlFifos[i]; i++) { const char *fifo = virInitctlFifos[i]; if (stat(fifo, &(data.st[i])) < 0) { if (errno == ENOENT) continue; virReportSystemError(errno, _("Unable to stat %s"), fifo); goto cleanup; } data.st_valid[i] = true; } ret = virProcessRunInMountNamespace(priv->initpid, lxcDomainInitctlCallback, &data); cleanup: VIR_FREE(data.st); VIR_FREE(data.st_valid); return ret; }