[libvirt PATCH v2 06/16] qemu: implement persistent file cache for nbdkit caps

Peter Krempa pkrempa at redhat.com
Tue Sep 27 11:49:58 UTC 2022


On Wed, Aug 31, 2022 at 13:40:51 -0500, Jonathon Jongsma wrote:
> Implement the loadFile and saveFile virFileCacheHandlers callbacks so
> that nbdkit capabilities are cached perstistently across daemon
> restarts. The format and implementation is modeled on the qemu
> capabilities, but simplified slightly.
> 
> Signed-off-by: Jonathon Jongsma <jjongsma at redhat.com>
> ---
>  src/qemu/qemu_nbdkit.c | 253 ++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 251 insertions(+), 2 deletions(-)
> 
> diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c
> index 3faa16674a..ac498b948c 100644
> --- a/src/qemu/qemu_nbdkit.c
> +++ b/src/qemu/qemu_nbdkit.c
> @@ -339,11 +339,260 @@ virNbdkitCapsNewData(const char *binary,
>  }
>  
>  
> +static int
> +qemuNbdkitCapsValidateBinary(qemuNbdkitCaps *nbdkitCaps,
> +                             xmlXPathContextPtr ctxt)
> +{
> +    g_autofree char *str = NULL;
> +
> +    if (!(str = virXPathString("string(./path)", ctxt))) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                       _("missing path in nbdkit capabilities cache"));
> +        return -1;
> +    }
> +
> +    if (STRNEQ(str, nbdkitCaps->path)) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR,
> +                       _("Expected caps for '%s' but saw '%s'"),
> +                       nbdkitCaps->path, str);
> +        return -1;
> +    }
> +
> +    return 0;
> +}
> +
> +
> +static int
> +qemuNbdkitCapsParseFlags(qemuNbdkitCaps *nbdkitCaps,
> +                         xmlXPathContextPtr ctxt)
> +{
> +    g_autofree xmlNodePtr *nodes = NULL;
> +    size_t i;
> +    int n;
> +
> +    if ((n = virXPathNodeSet("./flag", ctxt, &nodes)) < 0) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                       _("failed to parse qemu capabilities flags"));
> +        return -1;
> +    }
> +
> +    VIR_DEBUG("Got flags %d", n);
> +    for (i = 0; i < n; i++) {
> +        g_autofree char *str = NULL;
> +        int flag;
> +
> +        if (!(str = virXMLPropString(nodes[i], "name"))) {
> +            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                           _("missing flag name in QEMU capabilities cache"));
> +            return -1;
> +        }
> +
> +        flag = qemuNbdkitCapsTypeFromString(str);
> +        if (flag < 0) {
> +            virReportError(VIR_ERR_INTERNAL_ERROR,
> +                           _("Unknown nbdkit capabilities flag %s"), str);
> +            return -1;
> +        }

You can use virXMLPropEnum instead of having two clauses.

> +
> +        qemuNbdkitCapsSet(nbdkitCaps, flag);
> +    }
> +
> +    return 0;
> +}
> +
> +
> +/*
> + * Parsing a doc that looks like
> + *
> + * <nbdkitCaps>
> + *   <path>/some/path</path>
> + *   <nbdkitctime>234235253</nbdkitctime>
> + *   <plugindirmtime>234235253</plugindirmtime>
> + *   <filterdirmtime>234235253</filterdirmtime>
> + *   <selfctime>234235253</selfctime>
> + *   <selfvers>1002016</selfvers>
> + *   <flag name='foo'/>
> + *   <flag name='bar'/>
> + *   ...
> + * </nbdkitCaps>
> + *
> + * Returns 0 on success, 1 if outdated, -1 on error
> + */
> +static int
> +qemuNbdkitCapsLoadCache(qemuNbdkitCaps *nbdkitCaps,
> +                        const char *filename)
> +{
> +    g_autoptr(xmlDoc) doc = NULL;
> +    g_autoptr(xmlXPathContext) ctxt = NULL;
> +    long long int l;
> +    unsigned long lu;
> +
> +    if (!(doc = virXMLParseFile(filename)))

I'll be posting soon patches that make 'virXMLParse' usable here without
the need to ...

> +        return -1;
> +
> +    if (!(ctxt = virXMLXPathContextNew(doc)))
> +        return -1;

... fetch the context ...

> +
> +    ctxt->node = xmlDocGetRootElement(doc);

... get the root node...

> +
> +    if (STRNEQ((const char*)ctxt->node->name, "nbdkitCaps")) {
> +        virReportError(VIR_ERR_XML_ERROR,
> +                       _("unexpected root element <%s>, "
> +                         "expecting <nbdkitCaps>"),
> +                       ctxt->node->name);
> +        return -1;
> +    }

... and also do this validation by doing all of that in the virXMLParse
function.

> +
> +    if (virXPathLongLong("string(./selfctime)", ctxt, &l) < 0) {
> +        virReportError(VIR_ERR_XML_ERROR, "%s",
> +                       _("missing selfctime in nbdkit capabilities XML"));
> +        return -1;
> +    }
> +    nbdkitCaps->libvirtCtime = (time_t)l;
> +
> +    nbdkitCaps->libvirtVersion = 0;
> +    if (virXPathULong("string(./selfvers)", ctxt, &lu) == 0)
> +        nbdkitCaps->libvirtVersion = lu;
> +
> +    if (nbdkitCaps->libvirtCtime != virGetSelfLastChanged() ||
> +        nbdkitCaps->libvirtVersion != LIBVIR_VERSION_NUMBER) {
> +        VIR_DEBUG("Outdated capabilities in %s: libvirt changed "
> +                  "(%lld vs %lld, %lu vs %lu), stopping load",
> +                  nbdkitCaps->path,
> +                  (long long)nbdkitCaps->libvirtCtime,
> +                  (long long)virGetSelfLastChanged(),
> +                  (unsigned long)nbdkitCaps->libvirtVersion,
> +                  (unsigned long)LIBVIR_VERSION_NUMBER);
> +        return 1;
> +    }
> +
> +    if (qemuNbdkitCapsValidateBinary(nbdkitCaps, ctxt) < 0)
> +        return -1;
> +
> +    if (virXPathLongLong("string(./nbdkitctime)", ctxt, &l) < 0) {
> +        virReportError(VIR_ERR_XML_ERROR, "%s",
> +                       _("missing nbdkitctime in nbdkit capabilities XML"));
> +        return -1;
> +    }
> +    nbdkitCaps->ctime = (time_t)l;
> +
> +    if (virXPathLongLong("string(./plugindirmtime)", ctxt, &l) == 0)
> +        nbdkitCaps->pluginDirMtime = (time_t)l;
> +
> +    if (virXPathLongLong("string(./filterdirmtime)", ctxt, &l) == 0)
> +        nbdkitCaps->filterDirMtime = (time_t)l;

Any reason why these are optional but the ctime of nbdkit is not?

> +
> +    if (qemuNbdkitCapsParseFlags(nbdkitCaps, ctxt) < 0)
> +        return -1;
> +
> +    if ((nbdkitCaps->version = virXPathString("string(./version)", ctxt)) == NULL) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                       _("missing version in nbdkit capabilities cache"));
> +        return -1;
> +    }
> +
> +    return 0;
> +}
> +
> +
> +static void*
> +virNbdkitCapsLoadFile(const char *filename,
> +                      const char *binary,
> +                      void *privData G_GNUC_UNUSED,
> +                      bool *outdated)
> +{
> +    g_autoptr(qemuNbdkitCaps) nbdkitCaps = qemuNbdkitCapsNew(binary);
> +    int ret;
> +
> +    if (!nbdkitCaps)
> +        return NULL;
> +
> +    ret = qemuNbdkitCapsLoadCache(nbdkitCaps, filename);
> +    if (ret < 0)
> +        return NULL;
> +    if (ret == 1) {
> +        *outdated = true;
> +        return NULL;
> +    }
> +
> +    return g_steal_pointer(&nbdkitCaps);
> +}
> +
> +
> +static char*
> +qemuNbdkitCapsFormatCache(qemuNbdkitCaps *nbdkitCaps)
> +{
> +    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
> +    size_t i;
> +
> +    virBufferAddLit(&buf, "<nbdkitCaps>\n");
> +    virBufferAdjustIndent(&buf, 2);
> +
> +    virBufferEscapeString(&buf, "<path>%s</path>\n",
> +                          nbdkitCaps->path);
> +    virBufferAsprintf(&buf, "<nbdkitctime>%llu</nbdkitctime>\n",
> +                      (long long)nbdkitCaps->ctime);
> +    if (nbdkitCaps->pluginDirMtime > 0) {
> +        virBufferAsprintf(&buf, "<plugindirmtime>%llu</plugindirmtime>\n",

The format substitution is specifying an unsigned long long ...

> +                          (long long)nbdkitCaps->pluginDirMtime);

while you typecast to long long. Similarly above in the parser you parse
as long long instead of unsigned long long.

> +    }
> +    if (nbdkitCaps->filterDirMtime > 0) {
> +        virBufferAsprintf(&buf, "<filterdirmtime>%llu</filterdirmtime>\n",
> +                          (long long)nbdkitCaps->filterDirMtime);
> +    }
> +    virBufferAsprintf(&buf, "<selfctime>%llu</selfctime>\n",
> +                      (long long)nbdkitCaps->libvirtCtime);
> +    virBufferAsprintf(&buf, "<selfvers>%lu</selfvers>\n",
> +                      (unsigned long)nbdkitCaps->libvirtVersion);
> +
> +    for (i = 0; i < QEMU_NBDKIT_CAPS_LAST; i++) {
> +        if (qemuNbdkitCapsGet(nbdkitCaps, i)) {
> +            virBufferAsprintf(&buf, "<flag name='%s'/>\n",
> +                              qemuNbdkitCapsTypeToString(i));
> +        }
> +    }
> +
> +    virBufferAsprintf(&buf, "<version>%s</version>\n",
> +                      nbdkitCaps->version);
> +
> +    virBufferAdjustIndent(&buf, -2);
> +    virBufferAddLit(&buf, "</nbdkitCaps>\n");
> +
> +    return virBufferContentAndReset(&buf);
> +}
> +
> +
> +static int
> +virNbdkitCapsSaveFile(void *data,
> +                      const char *filename,
> +                      void *privData G_GNUC_UNUSED)
> +{
> +    qemuNbdkitCaps *nbdkitCaps = data;
> +    g_autofree char *xml = NULL;
> +
> +    xml = qemuNbdkitCapsFormatCache(nbdkitCaps);
> +
> +    if (virFileWriteStr(filename, xml, 0600) < 0) {
> +        virReportSystemError(errno,
> +                             _("Failed to save '%s' for '%s'"),
> +                             filename, nbdkitCaps->path);
> +        return -1;
> +    }
> +
> +    VIR_DEBUG("Saved caps '%s' for '%s' with (%lld, %lld)",
> +              filename, nbdkitCaps->path,
> +              (long long)nbdkitCaps->ctime,
> +              (long long)nbdkitCaps->libvirtCtime);
> +
> +    return 0;
> +}
> +
> +
>  virFileCacheHandlers nbdkitCapsCacheHandlers = {
>      .isValid = virNbdkitCapsIsValid,
>      .newData = virNbdkitCapsNewData,
> -    .loadFile = NULL,
> -    .saveFile = NULL,
> +    .loadFile = virNbdkitCapsLoadFile,
> +    .saveFile = virNbdkitCapsSaveFile,
>      .privFree = NULL,
>  };
>  
> -- 
> 2.37.1
> 


More information about the libvir-list mailing list