qemu for some time already sets node names automatically for the block
nodes. This patch adds code that attempts a best-effort detection of the
node names for the backing chain from the output of
'query-named-block-nodes'. The only drawback is that the data provided
by qemu needs to be matched by the filename as seen by qemu and thus
if two disks share a single backing store file the detection won't work.
This will allow us to use qemu commands such as
'block-set-write-threshold' which only accepts node names.
In this patch only the detection code is added, it will be used later.
---
src/Makefile.am | 1 +
src/qemu/qemu_block.c | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/qemu/qemu_block.h | 47 +++++++++
3 files changed, 328 insertions(+)
create mode 100644 src/qemu/qemu_block.c
create mode 100644 src/qemu/qemu_block.h
diff --git a/src/Makefile.am b/src/Makefile.am
index f0d8efe50..b4ecd6366 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -823,6 +823,7 @@ VBOX_DRIVER_EXTRA_DIST = \
QEMU_DRIVER_SOURCES = \
qemu/qemu_agent.c qemu/qemu_agent.h \
qemu/qemu_alias.c qemu/qemu_alias.h \
+ qemu/qemu_block.c qemu/qemu_block.h \
qemu/qemu_blockjob.c qemu/qemu_blockjob.h \
qemu/qemu_capabilities.c qemu/qemu_capabilities.h \
qemu/qemu_command.c qemu/qemu_command.h \
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c
new file mode 100644
index 000000000..91c04ab7f
--- /dev/null
+++ b/src/qemu/qemu_block.c
@@ -0,0 +1,280 @@
+/*
+ * qemu_block.c: helper functions for QEMU block subsystem
+ *
+ * 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
+ * <
http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "qemu_block.h"
+#include "qemu_domain.h"
+
+#include "viralloc.h"
+#include "virstring.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+
+static void
+qemuBlockNodeNameBackingChainDataFree(qemuBlockNodeNameBackingChainDataPtr data)
+{
+ size_t i;
+
+ if (!data)
+ return;
+
+ for (i = 0; i < data->nelems; i++)
+ virJSONValueFree(data->elems[i]);
+
+ VIR_FREE(data->nodeformat);
+ VIR_FREE(data->nodestorage);
+ VIR_FREE(data->nodebacking);
+
+ VIR_FREE(data->qemufilename);
+ VIR_FREE(data->backingstore);
+
+ VIR_FREE(data);
+}
+
+
+static void
+qemuBlockNodeNameBackingChainDataHashEntryFree(void *opaque,
+ const void *name ATTRIBUTE_UNUSED)
+{
+ qemuBlockNodeNameBackingChainDataFree(opaque);
+}
+
+
+struct qemuBlockNodeNameGetBackingChainData {
+ virHashTablePtr table;
+ qemuBlockNodeNameBackingChainDataPtr *entries;
+ size_t nentries;
+};
+
+
+static int
+qemuBlockNodeNameDetectProcessByFilename(size_t pos ATTRIBUTE_UNUSED,
+ virJSONValuePtr item,
+ void *opaque)
+{
+ struct qemuBlockNodeNameGetBackingChainData *data = opaque;
+ qemuBlockNodeNameBackingChainDataPtr entry;
+ const char *file;
+
+ if (!(file = virJSONValueObjectGetString(item, "file")))
+ return 1;
+
+ if (!(entry = virHashLookup(data->table, file))) {
+ if (VIR_ALLOC(entry) < 0)
+ return -1;
+
+ if (VIR_APPEND_ELEMENT_COPY(data->entries, data->nentries, entry) < 0)
{
+ VIR_FREE(entry);
+ return -1;
+ }
+
+ if (VIR_STRDUP(entry->qemufilename, file) < 0)
+ return -1;
+
+ if (virHashAddEntry(data->table, file, entry) < 0)
+ return -1;
+ }
+
+ if (VIR_APPEND_ELEMENT(entry->elems, entry->nelems, item) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+static const char *qemuBlockDriversFormat[] = {
+ "qcow2", "raw", "qcow", "luks",
"qed", "bochs", "cloop", "dmg",
"parallels",
+ "vdi", "vhdx", "vmdk", "vpc",
"vvfat", NULL};
+static const char *qemuBlockDriversStorage[] = {
+ "file", "iscsi", "nbd", "host_cdrom",
"host_device", "ftp", "ftps",
+ "gluster", "http", "https", "nfs",
"rbd", "sheepdog", "ssh", "tftp", NULL};
+
+
+static bool
+qemuBlockDriverMatch(const char *drvname,
+ const char **drivers)
+{
+ while (*drivers) {
+ if (STREQ(drvname, *drivers))
+ return true;
+
+ drivers++;
+ }
+
+ return false;
+}
+
+
+static int
+qemuBlockNodeNameDetectProcessExtract(qemuBlockNodeNameBackingChainDataPtr data)
+{
+ const char *drv;
+ const char *nodename;
+ const char *backingstore;
+ size_t i;
+
+ /* Since the only way to construct the backing chain is to look up the files
+ * by file name, if two disks share a backing image we can't know which node
+ * belongs to which backing chain. Refuse to detect such chains. */
+ if (data->nelems > 2)
+ return 0;
+
+ for (i = 0; i < data->nelems; i++) {
+ drv = virJSONValueObjectGetString(data->elems[i], "drv");
+ nodename = virJSONValueObjectGetString(data->elems[i],
"node-name");
+ backingstore = virJSONValueObjectGetString(data->elems[i],
"backing_file");
+
+ if (!drv || !nodename)
+ continue;
+
+ if (qemuBlockDriverMatch(drv, qemuBlockDriversFormat)) {
+ if (data->nodeformat)
+ continue;
+
+ if (VIR_STRDUP(data->nodeformat, nodename) < 0)
+ return -1;
+
+ /* extract the backing store file name for the protocol layer */
+ if (VIR_STRDUP(data->backingstore, backingstore) < 0)
+ return -1;
+ } else if (qemuBlockDriverMatch(drv, qemuBlockDriversStorage)) {
+ if (data->nodestorage)
+ continue;
+
+ if (VIR_STRDUP(data->nodestorage, nodename) < 0)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+qemuBlockNodeNameDetectProcessLinkBacking(qemuBlockNodeNameBackingChainDataPtr data,
+ virHashTablePtr table)
+{
+ qemuBlockNodeNameBackingChainDataPtr backing;
+
+ if (!data->backingstore)
+ return 0;
+
+ if (!(backing = virHashLookup(table, data->backingstore)))
+ return 0;
+
+ if (VIR_STRDUP(data->nodebacking, backing->nodeformat) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+static void
+qemuBlockNodeNameGetBackingChainDataClearLookup(qemuBlockNodeNameBackingChainDataPtr
data)
+{
+ size_t i;
+
+ for (i = 0; i < data->nelems; i++)
+ virJSONValueFree(data->elems[i]);
+
+ VIR_FREE(data->elems);
+ data->nelems = 0;
+}
+
+
+/**
+ * qemuBlockNodeNameGetBackingChain:
+ * @json: JSON array of data returned from 'query-named-block-nodes'
+ *
+ * Tries to reconstruct the backing chain from @json to allow detection of
+ * node names that were auto-assigned by qemu. This is a best-effort operation
+ * and may not be successful. The returned hash table contains the entries as
+ * qemuBlockNodeNameBackingChainDataPtr accessible by the node name. The fields
+ * then can be used to recover the full backing chain.
+ *
+ * Returns a hash table on success and NULL on failure.
+ */
+virHashTablePtr
+qemuBlockNodeNameGetBackingChain(virJSONValuePtr json)
+{
+ struct qemuBlockNodeNameGetBackingChainData data;
+ virHashTablePtr nodetable = NULL;
+ virHashTablePtr ret = NULL;
+ size_t i;
+
+ memset(&data, 0, sizeof(data));
+
+ /* hash table keeps the entries accessible by the 'file' in qemu */
+ if (!(data.table = virHashCreate(50, NULL)))
+ goto cleanup;
+
+ /* first group the named entries by the 'file' field */
+ if (virJSONValueArrayForeachSteal(json,
+ qemuBlockNodeNameDetectProcessByFilename,
+ &data) < 0)
+ goto cleanup;
+
+ /* extract the node names for the format and storage layer */
+ for (i = 0; i < data.nentries; i++) {
+ if (qemuBlockNodeNameDetectProcessExtract(data.entries[i]) < 0)
+ goto cleanup;
+ }
+
+ /* extract the node name for the backing file */
+ for (i = 0; i < data.nentries; i++) {
+ if (qemuBlockNodeNameDetectProcessLinkBacking(data.entries[i],
+ data.table) < 0)
+ goto cleanup;
+ }
+
+ /* clear JSON data necessary only for the lookup procedure */
+ for (i = 0; i < data.nentries; i++)
+ qemuBlockNodeNameGetBackingChainDataClearLookup(data.entries[i]);
+
+ /* create hash table hashed by the format node name */
+ if (!(nodetable = virHashCreate(50,
+ qemuBlockNodeNameBackingChainDataHashEntryFree)))
+ goto cleanup;
+
+ /* fill the entries */
+ for (i = 0; i < data.nentries; i++) {
+ if (!data.entries[i]->nodeformat)
+ continue;
+
+ if (virHashAddEntry(nodetable, data.entries[i]->nodeformat,
+ data.entries[i]) < 0)
+ goto cleanup;
+
+ /* hash table steals the entry and then frees it by itself */
+ data.entries[i] = NULL;
+ }
+
+ VIR_STEAL_PTR(ret, nodetable);
+
+ cleanup:
+ virHashFree(data.table);
+ virHashFree(nodetable);
+ for (i = 0; i < data.nentries; i++)
+ qemuBlockNodeNameBackingChainDataFree(data.entries[i]);
+
+ VIR_FREE(data.entries);
+
+ return ret;
+}
diff --git a/src/qemu/qemu_block.h b/src/qemu/qemu_block.h
new file mode 100644
index 000000000..26f5ae062
--- /dev/null
+++ b/src/qemu/qemu_block.h
@@ -0,0 +1,47 @@
+/*
+ * qemu_block.h: helper functions for QEMU block subsystem
+ *
+ * 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
+ * <
http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __QEMU_BLOCK_H__
+# define __QEMU_BLOCK_H__
+
+# include "internal.h"
+
+# include "qemu_conf.h"
+
+# include "virhash.h"
+# include "virjson.h"
+
+typedef struct qemuBlockNodeNameBackingChainData qemuBlockNodeNameBackingChainData;
+typedef qemuBlockNodeNameBackingChainData *qemuBlockNodeNameBackingChainDataPtr;
+struct qemuBlockNodeNameBackingChainData {
+ char *qemufilename; /* name of the image from qemu */
+ char *backingstore;
+ char *nodeformat; /* node name of the format layer */
+ char *nodestorage; /* node name of the storage backing the format node */
+
+ char *nodebacking; /* node name of the backing file format layer */
+
+ /* data necessary for detection of the node names from qemu */
+ virJSONValuePtr *elems;
+ size_t nelems;
+};
+
+virHashTablePtr
+qemuBlockNodeNameGetBackingChain(virJSONValuePtr data);
+
+#endif /* __QEMU_BLOCK_H__ */
--
2.12.0