diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index df5581fb65..c589a570c3 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -25,6 +25,7 @@ #include "qemu_capabilities.h" #include "viralloc.h" +#include "vircrypto.h" #include "virlog.h" #include "virerror.h" #include "virfile.h" @@ -253,6 +254,14 @@ VIR_ENUM_IMPL(virQEMUCaps, QEMU_CAPS_LAST, "usb-kbd", /* 165 */ ); + +/* + * Update the XML parser/formatter when adding more + * information to this struct so that it gets cached + * correctly. It does not have to be ABI-stable, as + * the cache will be discarded & repopulated if the + * timestamp on the libvirtd binary changes. + */ struct _virQEMUCaps { virObject object; @@ -281,7 +290,7 @@ struct _virQEMUCapsCache { virMutex lock; virHashTablePtr binaries; char *libDir; - char *runDir; + char *cacheDir; uid_t runUid; gid_t runGid; }; @@ -2339,6 +2348,406 @@ int virQEMUCapsProbeQMP(virQEMUCapsPtr qemuCaps, } +/* + * Parsing a doc that looks like + * + * + * 234235253 + * 234235253 + * + * + * + * ... + * + * ... + * + * ... + * + */ +static int +virQEMUCapsLoadCache(virQEMUCapsPtr qemuCaps, const char *filename, + time_t *qemuctime, time_t *selfctime) +{ + xmlDocPtr doc = NULL; + int ret = -1; + size_t i; + int n; + xmlNodePtr *nodes = NULL; + xmlXPathContextPtr ctxt = NULL; + char *str; + long long int l; + + if (!(doc = virXMLParseFile(filename))) + goto cleanup; + + if (!(ctxt = xmlXPathNewContext(doc))) { + virReportOOMError(); + goto cleanup; + } + + ctxt->node = xmlDocGetRootElement(doc); + + if (STRNEQ((const char *)ctxt->node->name, "qemuCaps")) { + virReportError(VIR_ERR_XML_ERROR, + _("unexpected root element <%s>, " + "expecting "), + ctxt->node->name); + goto cleanup; + } + + if (virXPathLongLong("string(./qemuctime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing qemuctime in QEMU capabilities XML")); + goto cleanup; + } + *qemuctime = (time_t)l; + + if (virXPathLongLong("string(./selfctime)", ctxt, &l) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing selfctime in QEMU capabilities XML")); + goto cleanup; + } + *selfctime = (time_t)l; + + qemuCaps->usedQMP = virXPathBoolean("count(./usedQMP) > 0", + ctxt) > 0; + + if ((n = virXPathNodeSet("./flag", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse qemu capabilities flags")); + goto cleanup; + } + VIR_DEBUG("Got flags %d", n); + if (n > 0) { + for (i = 0; i < n; i++) { + int flag; + if (!(str = virXMLPropString(nodes[i], "name"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing flag name in QEMU capabilities cache")); + goto cleanup; + } + flag = virQEMUCapsTypeFromString(str); + if (flag < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown qemu capabilities flag %s"), str); + VIR_FREE(str); + goto cleanup; + } + VIR_FREE(str); + virQEMUCapsSet(qemuCaps, flag); + } + } + VIR_FREE(nodes); + + if (virXPathUInt("string(./version)", ctxt, &qemuCaps->version) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing version in QEMU capabilities cache")); + goto cleanup; + } + + if (virXPathUInt("string(./kvmVersion)", ctxt, &qemuCaps->kvmVersion) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing version in QEMU capabilities cache")); + goto cleanup; + } + + if (!(str = virXPathString("string(./arch)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing arch in QEMU capabilities cache")); + goto cleanup; + } + if (!(qemuCaps->arch = virArchFromString(str))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown arch %s in QEMU capabilities cache"), str); + goto cleanup; + } + + if ((n = virXPathNodeSet("./cpu", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse qemu capabilities cpus")); + goto cleanup; + } + if (n > 0) { + qemuCaps->ncpuDefinitions = n; + if (VIR_ALLOC_N(qemuCaps->cpuDefinitions, + qemuCaps->ncpuDefinitions) < 0) + goto cleanup; + + for (i = 0; i < n; i++) { + if (!(str = virXMLPropString(nodes[i], "name"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing cpu name in QEMU capabilities cache")); + goto cleanup; + } + qemuCaps->cpuDefinitions[i] = str; + } + } + VIR_FREE(nodes); + + + if ((n = virXPathNodeSet("./machine", ctxt, &nodes)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse qemu capabilities machines")); + goto cleanup; + } + if (n > 0) { + qemuCaps->nmachineTypes = n; + if (VIR_ALLOC_N(qemuCaps->machineTypes, + qemuCaps->nmachineTypes) < 0 || + VIR_ALLOC_N(qemuCaps->machineAliases, + qemuCaps->nmachineTypes) < 0 || + VIR_ALLOC_N(qemuCaps->machineMaxCpus, + qemuCaps->nmachineTypes) < 0) + goto cleanup; + + for (i = 0; i < n; i++) { + if (!(str = virXMLPropString(nodes[i], "name"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing machine name in QEMU capabilities cache")); + goto cleanup; + } + qemuCaps->machineTypes[i] = str; + + qemuCaps->machineAliases[i] = virXMLPropString(nodes[i], "alias"); + + str = virXMLPropString(nodes[i], "maxCpus"); + if (str && + virStrToLong_ui(str, NULL, 10, &(qemuCaps->machineMaxCpus[i])) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed machine cpu count in QEMU capabilities cache")); + goto cleanup; + } + } + } + VIR_FREE(nodes); + + ret = 0; + cleanup: + VIR_FREE(nodes); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(doc); + return ret; +} + + +static int +virQEMUCapsSaveCache(virQEMUCapsPtr qemuCaps, const char *filename) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *xml = NULL; + int ret = -1; + size_t i; + + virBufferAddLit(&buf, "\n"); + + virBufferAsprintf(&buf, " %llu\n", + (long long)qemuCaps->ctime); + virBufferAsprintf(&buf, " %llu\n", + (long long)virGetSelfLastChanged()); + + if (qemuCaps->usedQMP) + virBufferAddLit(&buf, " \n"); + + for (i = 0; i < QEMU_CAPS_LAST; i++) { + if (virQEMUCapsGet(qemuCaps, i)) { + virBufferAsprintf(&buf, " \n", + virQEMUCapsTypeToString(i)); + } + } + + virBufferAsprintf(&buf, " %d\n", + qemuCaps->version); + + virBufferAsprintf(&buf, " %d\n", + qemuCaps->kvmVersion); + + virBufferAsprintf(&buf, " %s\n", + virArchToString(qemuCaps->arch)); + + for (i = 0; i < qemuCaps->ncpuDefinitions; i++) { + virBufferEscapeString(&buf, " \n", + qemuCaps->cpuDefinitions[i]); + } + + for (i = 0; i < qemuCaps->nmachineTypes; i++) { + virBufferEscapeString(&buf, " machineTypes[i]); + if (qemuCaps->machineAliases[i]) + virBufferEscapeString(&buf, " alias='%s'", + qemuCaps->machineAliases[i]); + virBufferAsprintf(&buf, " maxCpus='%u'/>\n", + qemuCaps->machineMaxCpus[i]); + } + + virBufferAddLit(&buf, "\n"); + + if (virBufferError(&buf)) + goto cleanup; + + xml = virBufferContentAndReset(&buf); + + if (virFileWriteStr(filename, xml, 0600) < 0) { + virReportSystemError(errno, + _("Failed to save '%s' for '%s'"), + filename, qemuCaps->binary); + goto cleanup; + } + + VIR_DEBUG("Saved caps '%s' for '%s' with (%lld, %lld)", + filename, qemuCaps->binary, + (long long)qemuCaps->ctime, + (long long)virGetSelfLastChanged()); + + ret = 0; + cleanup: + VIR_FREE(xml); + return ret; +} + +static int +virQEMUCapsRememberCached(virQEMUCapsPtr qemuCaps, const char *cacheDir) +{ + char *capsdir = NULL; + char *capsfile = NULL; + int ret = -1; + char *binaryhash = NULL; + + if (virAsprintf(&capsdir, "%s/capabilities", cacheDir) < 0) + goto cleanup; + + if (virCryptoHashString(VIR_CRYPTO_HASH_SHA256, + qemuCaps->binary, + &binaryhash) < 0) + goto cleanup; + + if (virAsprintf(&capsfile, "%s/%s.xml", capsdir, binaryhash) < 0) + goto cleanup; + + if (virFileMakePath(capsdir) < 0) { + virReportSystemError(errno, + _("Unable to create directory '%s'"), + capsdir); + goto cleanup; + } + + if (virQEMUCapsSaveCache(qemuCaps, capsfile) < 0) + goto cleanup; + + ret = 0; + cleanup: + VIR_FREE(binaryhash); + VIR_FREE(capsfile); + VIR_FREE(capsdir); + return ret; +} + + +static void +virQEMUCapsReset(virQEMUCapsPtr qemuCaps) +{ + size_t i; + + virBitmapClearAll(qemuCaps->flags); + qemuCaps->version = qemuCaps->kvmVersion = 0; + qemuCaps->arch = VIR_ARCH_NONE; + qemuCaps->usedQMP = false; + + for (i = 0; i < qemuCaps->ncpuDefinitions; i++) { + VIR_FREE(qemuCaps->cpuDefinitions[i]); + } + VIR_FREE(qemuCaps->cpuDefinitions); + qemuCaps->ncpuDefinitions = 0; + + for (i = 0; i < qemuCaps->nmachineTypes; i++) { + VIR_FREE(qemuCaps->machineTypes[i]); + VIR_FREE(qemuCaps->machineAliases[i]); + } + VIR_FREE(qemuCaps->machineTypes); + VIR_FREE(qemuCaps->machineAliases); + qemuCaps->nmachineTypes = 0; +} + + +static int +virQEMUCapsInitCached(virQEMUCapsPtr qemuCaps, const char *cacheDir) +{ + char *capsdir = NULL; + char *capsfile = NULL; + int ret = -1; + char *binaryhash = NULL; + struct stat sb; + time_t qemuctime; + time_t selfctime; + + if (virAsprintf(&capsdir, "%s/capabilities", cacheDir) < 0) + goto cleanup; + + if (virCryptoHashString(VIR_CRYPTO_HASH_SHA256, + qemuCaps->binary, + &binaryhash) < 0) + goto cleanup; + + if (virAsprintf(&capsfile, "%s/%s.xml", capsdir, binaryhash) < 0) + goto cleanup; + + if (virFileMakePath(capsdir) < 0) { + virReportSystemError(errno, + _("Unable to create directory '%s'"), + capsdir); + goto cleanup; + } + + if (stat(capsfile, &sb) < 0) { + if (errno == ENOENT) { + VIR_DEBUG("No cached capabilities '%s' for '%s'", + capsfile, qemuCaps->binary); + ret = 0; + goto cleanup; + } + virReportSystemError(errno, + _("Unable to access cache '%s' for '%s'"), + capsfile, qemuCaps->binary); + goto cleanup; + } + + if (virQEMUCapsLoadCache(qemuCaps, capsfile, &qemuctime, &selfctime) < 0) { + virErrorPtr err = virGetLastError(); + VIR_WARN("Failed to load cached caps from '%s' for '%s': %s", + capsfile, qemuCaps->binary, err ? NULLSTR(err->message) : + _("unknown error")); + virResetLastError(); + ret = 0; + virQEMUCapsReset(qemuCaps); + goto cleanup; + } + + /* Discard if cache is older that QEMU binary */ + if (qemuctime != qemuCaps->ctime || + selfctime < virGetSelfLastChanged()) { + VIR_DEBUG("Outdated cached capabilities '%s' for '%s' " + "(%lld vs %lld, %lld vs %lld)", + capsfile, qemuCaps->binary, + (long long)qemuctime, (long long)qemuCaps->ctime, + (long long)selfctime, (long long)virGetSelfLastChanged()); + ignore_value(unlink(capsfile)); + virQEMUCapsReset(qemuCaps); + ret = 0; + goto cleanup; + } + + VIR_DEBUG("Loaded '%s' for '%s' ctime %lld usedQMP=%d", + capsfile, qemuCaps->binary, + (long long)qemuCaps->ctime, qemuCaps->usedQMP); + + ret = 1; + cleanup: + VIR_FREE(binaryhash); + VIR_FREE(capsfile); + VIR_FREE(capsdir); + return ret; +} + + #define QEMU_SYSTEM_PREFIX "qemu-system-" static int @@ -2779,6 +3188,7 @@ virQEMUCapsLogProbeFailure(const char *binary) virQEMUCapsPtr virQEMUCapsNewForBinary(const char *binary, const char *libDir, + const char *cacheDir, uid_t runUid, gid_t runGid) { @@ -2808,15 +3218,23 @@ virQEMUCapsPtr virQEMUCapsNewForBinary(const char *binary, goto error; } - if ((rv = virQEMUCapsInitQMP(qemuCaps, libDir, runUid, runGid)) < 0) { - virQEMUCapsLogProbeFailure(binary); + if ((rv = virQEMUCapsInitCached(qemuCaps, cacheDir)) < 0) goto error; - } - if (!qemuCaps->usedQMP && - virQEMUCapsInitHelp(qemuCaps, runUid, runGid) < 0) { - virQEMUCapsLogProbeFailure(binary); - goto error; + if (rv == 0) { + if (virQEMUCapsInitQMP(qemuCaps, libDir, runUid, runGid) < 0) { + virQEMUCapsLogProbeFailure(binary); + goto error; + } + + if (!qemuCaps->usedQMP && + virQEMUCapsInitHelp(qemuCaps, runUid, runGid) < 0) { + virQEMUCapsLogProbeFailure(binary); + goto error; + } + + if (virQEMUCapsRememberCached(qemuCaps, cacheDir) < 0) + goto error; } return qemuCaps; @@ -2851,6 +3269,7 @@ virQEMUCapsHashDataFree(void *payload, const void *key ATTRIBUTE_UNUSED) virQEMUCapsCachePtr virQEMUCapsCacheNew(const char *libDir, + const char *cacheDir, uid_t runUid, gid_t runGid) { @@ -2870,6 +3289,8 @@ virQEMUCapsCacheNew(const char *libDir, goto error; if (VIR_STRDUP(cache->libDir, libDir) < 0) goto error; + if (VIR_STRDUP(cache->cacheDir, cacheDir) < 0) + goto error; cache->runUid = runUid; cache->runGid = runGid; @@ -2899,6 +3320,7 @@ virQEMUCapsCacheLookup(virQEMUCapsCachePtr cache, const char *binary) VIR_DEBUG("Creating capabilities for %s", binary); ret = virQEMUCapsNewForBinary(binary, cache->libDir, + cache->cacheDir, cache->runUid, cache->runGid); if (ret) { VIR_DEBUG("Caching capabilities %p for %s", @@ -2938,6 +3360,7 @@ virQEMUCapsCacheFree(virQEMUCapsCachePtr cache) return; VIR_FREE(cache->libDir); + VIR_FREE(cache->cacheDir); virHashFree(cache->binaries); virMutexDestroy(&cache->lock); VIR_FREE(cache); diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index b5445e7ad2..a9082d5429 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -218,6 +218,7 @@ virQEMUCapsPtr virQEMUCapsNew(void); virQEMUCapsPtr virQEMUCapsNewCopy(virQEMUCapsPtr qemuCaps); virQEMUCapsPtr virQEMUCapsNewForBinary(const char *binary, const char *libDir, + const char *cacheDir, uid_t runUid, gid_t runGid); @@ -262,6 +263,7 @@ bool virQEMUCapsIsValid(virQEMUCapsPtr qemuCaps); virQEMUCapsCachePtr virQEMUCapsCacheNew(const char *libDir, + const char *cacheDir, uid_t uid, gid_t gid); virQEMUCapsPtr virQEMUCapsCacheLookup(virQEMUCapsCachePtr cache, const char *binary); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8a54b8a8d2..7fea07c72d 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -744,6 +744,7 @@ qemuStateInitialize(bool privileged, } qemu_driver->qemuCapsCache = virQEMUCapsCacheNew(cfg->libDir, + cfg->cacheDir, run_uid, run_gid); if (!qemu_driver->qemuCapsCache)