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__ */