diff --git a/po/POTFILES.in b/po/POTFILES.in
index b3891b5877..c1fa23427e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -211,6 +211,7 @@ src/util/vireventpoll.c
src/util/virfcp.c
src/util/virfdstream.c
src/util/virfile.c
+src/util/virfilecache.c
src/util/virfirewall.c
src/util/virfirmware.c
src/util/virhash.c
diff --git a/src/Makefile.am b/src/Makefile.am
index e637dfd910..d86b282519 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -194,6 +194,7 @@ UTIL_SOURCES = \
util/virxdrdefs.h \
util/virxml.c util/virxml.h \
util/virmdev.c util/virmdev.h \
+ util/virfilecache.c util/virfilecache.h \
$(NULL)
EXTRA_DIST += \
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index fa2cd08fe3..0dca0a8da3 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -1712,6 +1712,15 @@ virFileWriteStr;
virFindFileInPath;
+# util/virfilecache.h
+virFileCacheGetPriv;
+virFileCacheInsertData;
+virFileCacheLookup;
+virFileCacheLookupByFunc;
+virFileCacheNew;
+virFileCacheSetPriv;
+
+
# util/virfirewall.h
virFirewallAddRuleFull;
virFirewallApply;
diff --git a/src/util/virfilecache.c b/src/util/virfilecache.c
new file mode 100644
index 0000000000..2577d711bc
--- /dev/null
+++ b/src/util/virfilecache.c
@@ -0,0 +1,437 @@
+/*
+ * virfilecache.c: file caching for data
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * 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 "internal.h"
+
+#include "viralloc.h"
+#include "virbuffer.h"
+#include "vircrypto.h"
+#include "virerror.h"
+#include "virfile.h"
+#include "virfilecache.h"
+#include "virhash.h"
+#include "virlog.h"
+#include "virobject.h"
+#include "virstring.h"
+
+#include
+#include
+#include
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+VIR_LOG_INIT("util.filecache")
+
+
+struct _virFileCache {
+ virObjectLockable object;
+
+ virHashTablePtr table;
+
+ char *dir;
+ char *suffix;
+
+ void *priv;
+
+ virFileCacheHandlers handlers;
+};
+
+
+static virClassPtr virFileCacheClass;
+
+
+static void
+virFileCachePrivFree(virFileCachePtr cache)
+{
+ if (cache->priv && cache->handlers.privFree)
+ cache->handlers.privFree(cache->priv);
+}
+
+
+static void
+virFileCacheDispose(void *obj)
+{
+ virFileCachePtr cache = obj;
+
+ VIR_FREE(cache->dir);
+ VIR_FREE(cache->suffix);
+
+ virHashFree(cache->table);
+
+ virFileCachePrivFree(cache);
+}
+
+
+static int
+virFileCacheOnceInit(void)
+{
+ if (!(virFileCacheClass = virClassNew(virClassForObjectLockable(),
+ "virFileCache",
+ sizeof(virFileCache),
+ virFileCacheDispose)))
+ return -1;
+
+ return 0;
+}
+
+
+VIR_ONCE_GLOBAL_INIT(virFileCache)
+
+
+static char *
+virFileCacheGetFileName(virFileCachePtr cache,
+ const char *name)
+{
+ char *file = NULL;
+ char *namehash = NULL;
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+
+ if (virCryptoHashString(VIR_CRYPTO_HASH_SHA256, name, &namehash) < 0)
+ goto cleanup;
+
+ if (virFileMakePath(cache->dir) < 0) {
+ virReportSystemError(errno,
+ _("Unable to create directory '%s'"),
+ cache->dir);
+ goto cleanup;
+ }
+
+ virBufferAsprintf(&buf, "%s/%s", cache->dir, namehash);
+
+ if (cache->suffix)
+ virBufferAsprintf(&buf, ".%s", cache->suffix);
+
+ if (virBufferCheckError(&buf) < 0)
+ goto cleanup;
+
+ file = virBufferContentAndReset(&buf);
+
+ cleanup:
+ VIR_FREE(namehash);
+ return file;
+}
+
+
+static int
+virFileCacheLoad(virFileCachePtr cache,
+ const char *name,
+ void **data)
+{
+ char *file = NULL;
+ int ret = -1;
+ void *loadData = NULL;
+
+ *data = NULL;
+
+ if (!(file = virFileCacheGetFileName(cache, name)))
+ return ret;
+
+ if (!virFileExists(file)) {
+ if (errno == ENOENT) {
+ VIR_DEBUG("No cached data '%s' for '%s'", file, name);
+ ret = 0;
+ goto cleanup;
+ }
+ virReportSystemError(errno,
+ _("Unable to access cache '%s' for '%s'"),
+ file, name);
+ goto cleanup;
+ }
+
+ if (!(loadData = cache->handlers.loadFile(file, name, cache->priv))) {
+ virErrorPtr err = virGetLastError();
+ VIR_WARN("Failed to load cached data from '%s' for '%s': %s",
+ file, name, err ? NULLSTR(err->message) : "unknown error");
+ virResetLastError();
+ ret = 0;
+ goto cleanup;
+ }
+
+ if (!cache->handlers.isValid(loadData, cache->priv)) {
+ VIR_DEBUG("Outdated cached capabilities '%s' for '%s'", file, name);
+ ignore_value(unlink(file));
+ ret = 0;
+ goto cleanup;
+ }
+
+ VIR_DEBUG("Loaded cached data '%s' for '%s'", file, name);
+
+ ret = 1;
+ VIR_STEAL_PTR(*data, loadData);
+
+ cleanup:
+ virObjectUnref(loadData);
+ VIR_FREE(file);
+ return ret;
+}
+
+
+static int
+virFileCacheSave(virFileCachePtr cache,
+ const char *name,
+ void *data)
+{
+ char *file = NULL;
+ int ret = -1;
+
+ if (!(file = virFileCacheGetFileName(cache, name)))
+ return ret;
+
+ if (cache->handlers.saveFile(data, file, cache->priv) < 0)
+ goto cleanup;
+
+ ret = 0;
+
+ cleanup:
+ VIR_FREE(file);
+ return ret;
+}
+
+
+static void *
+virFileCacheNewData(virFileCachePtr cache,
+ const char *name)
+{
+ void *data = NULL;
+ int rv;
+
+ if ((rv = virFileCacheLoad(cache, name, &data)) < 0)
+ return NULL;
+
+ if (rv == 0) {
+ if (!(data = cache->handlers.newData(name, cache->priv)))
+ return NULL;
+
+ if (virFileCacheSave(cache, name, data) < 0) {
+ virObjectUnref(data);
+ data = NULL;
+ }
+ }
+
+ return data;
+}
+
+
+/**
+ * virFileCacheNew:
+ * @dir: the cache directory where all the cache files will be stored
+ * @suffix: the cache file suffix or NULL if no suffix is required
+ * @handlers: filled structure with all required handlers
+ *
+ * Creates a new cache object which handles caching any data to files
+ * stored on a filesystem.
+ *
+ * Returns new cache object or NULL on error.
+ */
+virFileCachePtr
+virFileCacheNew(const char *dir,
+ const char *suffix,
+ virFileCacheHandlers *handlers)
+{
+ virFileCachePtr cache;
+
+ if (virFileCacheInitialize() < 0)
+ return NULL;
+
+ if (!(cache = virObjectNew(virFileCacheClass)))
+ return NULL;
+
+ if (!(cache->table = virHashCreate(10, virObjectFreeHashData)))
+ goto cleanup;
+
+ if (VIR_STRDUP(cache->dir, dir) < 0)
+ goto cleanup;
+
+ if (VIR_STRDUP(cache->suffix, suffix) < 0)
+ goto cleanup;
+
+ cache->handlers = *handlers;
+
+ return cache;
+
+ cleanup:
+ virObjectUnref(cache);
+ return NULL;
+}
+
+
+static void
+virFileCacheValidate(virFileCachePtr cache,
+ const char *name,
+ void **data)
+{
+ if (*data && !cache->handlers.isValid(*data, cache->priv)) {
+ VIR_DEBUG("Cached data '%p' no longer valid for '%s'",
+ *data, NULLSTR(name));
+ if (name)
+ virHashRemoveEntry(cache->table, name);
+ *data = NULL;
+ }
+
+ if (!*data && name) {
+ VIR_DEBUG("Creating data for '%s'", name);
+ *data = virFileCacheNewData(cache, name);
+ if (*data) {
+ VIR_DEBUG("Caching data '%p' for '%s'", *data, name);
+ if (virHashAddEntry(cache->table, name, *data) < 0) {
+ virObjectUnref(*data);
+ *data = NULL;
+ }
+ }
+ }
+}
+
+
+/**
+ * virFileCacheLookup:
+ * @cache: existing cache object
+ * @name: name of the data stored in a cache
+ *
+ * Lookup a data specified by name. This tries to find a file with
+ * cached data, if it doesn't exist or is no longer valid new data
+ * is created.
+ *
+ * Returns data object or NULL on error. The caller is responsible for
+ * unrefing the data.
+ */
+void *
+virFileCacheLookup(virFileCachePtr cache,
+ const char *name)
+{
+ void *data = NULL;
+
+ virObjectLock(cache);
+
+ data = virHashLookup(cache->table, name);
+ virFileCacheValidate(cache, name, &data);
+
+ virObjectRef(data);
+ virObjectUnlock(cache);
+
+ return data;
+}
+
+
+/**
+ * virFileCacheLookupByFunc:
+ * @cache: existing cache object
+ * @iter: an iterator to identify the desired data
+ * @iterData: extra opaque information passed to the @iter
+ *
+ * Similar to virFileCacheLookup() except it search by @iter.
+ *
+ * Returns data object or NULL on error. The caller is responsible for
+ * unrefing the data.
+ */
+void *
+virFileCacheLookupByFunc(virFileCachePtr cache,
+ virHashSearcher iter,
+ const void *iterData)
+{
+ void *data = NULL;
+ char *name = NULL;
+
+ virObjectLock(cache);
+
+ data = virHashSearch(cache->table, iter, iterData, (void **)&name);
+ virFileCacheValidate(cache, name, &data);
+
+ virObjectRef(data);
+ virObjectUnlock(cache);
+
+ VIR_FREE(name);
+
+ return data;
+}
+
+
+/**
+ * virFileCacheGetPriv:
+ * @cache: existing cache object
+ *
+ * Returns private data used by @handlers.
+ */
+void *
+virFileCacheGetPriv(virFileCachePtr cache)
+{
+ void *priv;
+
+ virObjectLock(cache);
+
+ priv = cache->priv;
+
+ virObjectUnlock(cache);
+
+ return priv;
+}
+
+
+/**
+ * virFileCacheSetPriv:
+ * @cache: existing cache object
+ * @priv: private data to set
+ *
+ * Sets private data used by @handlers. If there is already some @priv
+ * set, privFree() will be called on the old @priv before setting a new one.
+ */
+void
+virFileCacheSetPriv(virFileCachePtr cache,
+ void *priv)
+{
+ virObjectLock(cache);
+
+ virFileCachePrivFree(cache);
+
+ cache->priv = priv;
+
+ virObjectUnlock(cache);
+}
+
+
+/**
+ * virFileCacheInsertData:
+ * @cache: existing cache object
+ * @name: name of the new data
+ * @data: the actual data object to store in cache
+ *
+ * Adds a new data into a cache but doesn't store the data into
+ * a file. This function should be used only by testing code.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+int
+virFileCacheInsertData(virFileCachePtr cache,
+ const char *name,
+ void *data)
+{
+ int ret;
+
+ virObjectLock(cache);
+
+ ret = virHashUpdateEntry(cache->table, name, data);
+
+ virObjectUnlock(cache);
+
+ return ret;
+}
diff --git a/src/util/virfilecache.h b/src/util/virfilecache.h
new file mode 100644
index 0000000000..af6a189d7f
--- /dev/null
+++ b/src/util/virfilecache.h
@@ -0,0 +1,137 @@
+/*
+ * virfilecache.h: file caching for data
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * 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
+ * .
+ *
+ */
+
+#ifndef __VIR_FILE_CACHE_H__
+# define __VIR_FILE_CACHE_H__
+
+# include "internal.h"
+
+# include "virobject.h"
+# include "virhash.h"
+
+typedef struct _virFileCache virFileCache;
+typedef virFileCache *virFileCachePtr;
+
+/**
+ * virFileCacheIsValidPtr:
+ * @data: data object to validate
+ * @priv: private data created together with cache
+ *
+ * Validates the cached data whether it needs to be refreshed
+ * or no.
+ *
+ * Returns *true* if it's valid or *false* if not valid.
+ */
+typedef bool
+(*virFileCacheIsValidPtr)(void *data,
+ void *priv);
+
+/**
+ * virFileCacheNewDataPtr:
+ * @name: name of the new data
+ * @priv: private data created together with cache
+ *
+ * Creates a new data based on the @name. The returned data must be
+ * an instance of virObject.
+ *
+ * Returns data object or NULL on error.
+ */
+typedef void *
+(*virFileCacheNewDataPtr)(const char *name,
+ void *priv);
+
+/**
+ * virFileCacheLoadFilePtr:
+ * @filename: name of a file with cached data
+ * @name: name of the cached data
+ * @priv: private data created together with cache
+ *
+ * Loads the cached data from a file @filename.
+ *
+ * Returns cached data object or NULL on error.
+ */
+typedef void *
+(*virFileCacheLoadFilePtr)(const char *filename,
+ const char *name,
+ void *priv);
+
+/**
+ * virFileCacheSaveFilePtr:
+ * @data: data object to save into a file
+ * @filename: name of the file where to store the cached data
+ * @priv: private data created together with cache
+ *
+ * Stores the cached to a file @filename.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+typedef int
+(*virFileCacheSaveFilePtr)(void *data,
+ const char *filename,
+ void *priv);
+
+/**
+ * virFileCachePrivFreePtr:
+ * @priv: private data created together with cache
+ *
+ * This is used to free the private data when the cache object
+ * is removed.
+ */
+typedef void
+(*virFileCachePrivFreePtr)(void *priv);
+
+typedef struct _virFileCacheHandlers virFileCacheHandlers;
+typedef virFileCacheHandlers *virFileCacheHandlersPtr;
+struct _virFileCacheHandlers {
+ virFileCacheIsValidPtr isValid;
+ virFileCacheNewDataPtr newData;
+ virFileCacheLoadFilePtr loadFile;
+ virFileCacheSaveFilePtr saveFile;
+ virFileCachePrivFreePtr privFree;
+};
+
+virFileCachePtr
+virFileCacheNew(const char *dir,
+ const char *suffix,
+ virFileCacheHandlers *handlers);
+
+void *
+virFileCacheLookup(virFileCachePtr cache,
+ const char *name);
+
+void *
+virFileCacheLookupByFunc(virFileCachePtr cache,
+ virHashSearcher iter,
+ const void *iterData);
+
+void *
+virFileCacheGetPriv(virFileCachePtr cache);
+
+void
+virFileCacheSetPriv(virFileCachePtr cache,
+ void *priv);
+
+int
+virFileCacheInsertData(virFileCachePtr cache,
+ const char *name,
+ void *data);
+
+#endif /* __VIR_FILE_CACHE_H__ */