From: Marc-André Lureau <marcandre.lureau(a)redhat.com>
Add qemuVhostUserFetchConfigs() to discover vhost-user helpers.
qemuVhostUserFillDomainGPU() will find the first matching GPU helper
with the required capabilities and set the associated
vhost_user_binary.
Signed-off-by: Marc-André Lureau <marcandre.lureau(a)redhat.com>
Signed-off-by: Cole Robinson <crobinso(a)redhat.com>
---
src/conf/device_conf.c | 1 +
src/conf/device_conf.h | 1 +
src/qemu/Makefile.inc.am | 2 +
src/qemu/qemu_vhost_user.c | 394 ++++++++++++++++++
src/qemu/qemu_vhost_user.h | 48 +++
tests/Makefile.am | 9 +
.../etc/qemu/vhost-user/40-gpu.json | 1 +
.../etc/qemu/vhost-user/50-gpu.json | 0
.../qemu/vhost-user/test-vhost-user-gpu | 11 +
.../usr/share/qemu/vhost-user/30-gpu.json | 1 +
.../usr/share/qemu/vhost-user/50-gpu.json | 8 +
.../usr/share/qemu/vhost-user/60-gpu.json | 1 +
tests/qemuvhostusertest.c | 132 ++++++
13 files changed, 609 insertions(+)
create mode 100644 src/qemu/qemu_vhost_user.c
create mode 100644 src/qemu/qemu_vhost_user.h
create mode 120000 tests/qemuvhostuserdata/etc/qemu/vhost-user/40-gpu.json
create mode 100644 tests/qemuvhostuserdata/etc/qemu/vhost-user/50-gpu.json
create mode 100755
tests/qemuvhostuserdata/usr/libexec/qemu/vhost-user/test-vhost-user-gpu
create mode 120000 tests/qemuvhostuserdata/usr/share/qemu/vhost-user/30-gpu.json
create mode 100644 tests/qemuvhostuserdata/usr/share/qemu/vhost-user/50-gpu.json
create mode 120000 tests/qemuvhostuserdata/usr/share/qemu/vhost-user/60-gpu.json
create mode 100644 tests/qemuvhostusertest.c
diff --git a/src/conf/device_conf.c b/src/conf/device_conf.c
index 4c57f0995f..2f7077ddc4 100644
--- a/src/conf/device_conf.c
+++ b/src/conf/device_conf.c
@@ -96,6 +96,7 @@ virDomainDeviceInfoClear(virDomainDeviceInfoPtr info)
VIR_FREE(info->loadparm);
info->isolationGroup = 0;
info->isolationGroupLocked = false;
+ VIR_FREE(info->vhost_user_binary);
}
void
diff --git a/src/conf/device_conf.h b/src/conf/device_conf.h
index d0854925e3..0b0c525ad8 100644
--- a/src/conf/device_conf.h
+++ b/src/conf/device_conf.h
@@ -179,6 +179,7 @@ struct _virDomainDeviceInfo {
* cases we might want to prevent that from happening by
* locking the isolation group */
bool isolationGroupLocked;
+ char *vhost_user_binary;
};
void virDomainDeviceInfoClear(virDomainDeviceInfoPtr info);
diff --git a/src/qemu/Makefile.inc.am b/src/qemu/Makefile.inc.am
index f7a0fa4a84..18a9220d01 100644
--- a/src/qemu/Makefile.inc.am
+++ b/src/qemu/Makefile.inc.am
@@ -60,6 +60,8 @@ QEMU_DRIVER_SOURCES = \
qemu/qemu_qapi.h \
qemu/qemu_tpm.c \
qemu/qemu_tpm.h \
+ qemu/qemu_vhost_user.c \
+ qemu/qemu_vhost_user.h \
$(NULL)
diff --git a/src/qemu/qemu_vhost_user.c b/src/qemu/qemu_vhost_user.c
new file mode 100644
index 0000000000..7a97052ab9
--- /dev/null
+++ b/src/qemu/qemu_vhost_user.c
@@ -0,0 +1,394 @@
+/*
+ * qemu_vhost_user.c: QEMU vhost-user
+ *
+ * Copyright (C) 2019 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
+ * <
http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "qemu_vhost_user.h"
+#include "qemu_configs.h"
+#include "virjson.h"
+#include "virlog.h"
+#include "virstring.h"
+#include "viralloc.h"
+#include "virenum.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("qemu.qemu_vhost_user");
+
+typedef enum {
+ QEMU_VHOST_USER_TYPE_NONE = 0,
+ QEMU_VHOST_USER_TYPE_9P,
+ QEMU_VHOST_USER_TYPE_BALLOON,
+ QEMU_VHOST_USER_TYPE_BLOCK,
+ QEMU_VHOST_USER_TYPE_CAIF,
+ QEMU_VHOST_USER_TYPE_CONSOLE,
+ QEMU_VHOST_USER_TYPE_CRYPTO,
+ QEMU_VHOST_USER_TYPE_GPU,
+ QEMU_VHOST_USER_TYPE_INPUT,
+ QEMU_VHOST_USER_TYPE_NET,
+ QEMU_VHOST_USER_TYPE_RNG,
+ QEMU_VHOST_USER_TYPE_RPMSG,
+ QEMU_VHOST_USER_TYPE_RPROC_SERIAL,
+ QEMU_VHOST_USER_TYPE_SCSI,
+ QEMU_VHOST_USER_TYPE_VSOCK,
+
+ QEMU_VHOST_USER_TYPE_LAST
+} qemuVhostUserType;
+
+VIR_ENUM_DECL(qemuVhostUserType);
+VIR_ENUM_IMPL(qemuVhostUserType,
+ QEMU_VHOST_USER_TYPE_LAST,
+ "",
+ "9p",
+ "balloon",
+ "block",
+ "caif",
+ "console",
+ "crypto",
+ "gpu",
+ "input",
+ "net",
+ "rng",
+ "rpmsg",
+ "rproc-serial",
+ "scsi",
+ "vsock",
+);
+
+typedef enum {
+ QEMU_VHOST_USER_GPU_FEATURE_NONE = 0,
+ QEMU_VHOST_USER_GPU_FEATURE_VIRGL,
+ QEMU_VHOST_USER_GPU_FEATURE_RENDER_NODE,
+
+ QEMU_VHOST_USER_GPU_FEATURE_LAST
+} qemuVhostUserGPUFeature;
+
+VIR_ENUM_DECL(qemuVhostUserGPUFeature);
+VIR_ENUM_IMPL(qemuVhostUserGPUFeature,
+ QEMU_VHOST_USER_GPU_FEATURE_LAST,
+ "",
+ "virgl",
+ "render-node",
+);
+
+typedef struct _qemuVhostUserGPU qemuVhostUserGPU;
+typedef qemuVhostUserGPU *qemuVhostUserGPUPtr;
+struct _qemuVhostUserGPU {
+ size_t nfeatures;
+ qemuVhostUserGPUFeature *features;
+};
+
+struct _qemuVhostUser {
+ /* Description intentionally not parsed. */
+ qemuVhostUserType type;
+ char *binary;
+ /* Tags intentionally not parsed. */
+
+ union {
+ qemuVhostUserGPU gpu;
+ } capabilities;
+};
+
+static void
+qemuVhostUserGPUFeatureFree(qemuVhostUserGPUFeature *features)
+{
+ VIR_FREE(features);
+}
+
+
+VIR_DEFINE_AUTOPTR_FUNC(qemuVhostUserGPUFeature, qemuVhostUserGPUFeatureFree);
+
+void
+qemuVhostUserFree(qemuVhostUserPtr vu)
+{
+ if (!vu)
+ return;
+
+ if (vu->type == QEMU_VHOST_USER_TYPE_GPU)
+ VIR_FREE(vu->capabilities.gpu.features);
+
+ VIR_FREE(vu->binary);
+
+ VIR_FREE(vu);
+}
+
+/* 1MiB should be enough for everybody (TM) */
+#define DOCUMENT_SIZE (1024 * 1024)
+
+static int
+qemuVhostUserTypeParse(const char *path,
+ virJSONValuePtr doc,
+ qemuVhostUserPtr vu)
+{
+ const char *type = virJSONValueObjectGetString(doc, "type");
+ int tmp;
+
+ VIR_DEBUG("vhost-user description path '%s' type : %s",
+ path, type);
+
+ if ((tmp = qemuVhostUserTypeTypeFromString(type)) <= 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unknown vhost-user type: '%s'"),
+ type);
+ return -1;
+ }
+
+ vu->type = tmp;
+
+ return 0;
+}
+
+static int
+qemuVhostUserBinaryParse(const char *path,
+ virJSONValuePtr doc,
+ qemuVhostUserPtr vu)
+{
+ const char *binary = virJSONValueObjectGetString(doc, "binary");
+
+ VIR_DEBUG("vhost-user description path '%s' binary : %s",
+ path, binary);
+
+ if (VIR_STRDUP(vu->binary, binary) < 0)
+ return -1;
+
+ return 0;
+}
+
+qemuVhostUserPtr
+qemuVhostUserParse(const char *path)
+{
+ VIR_AUTOFREE(char *) cont = NULL;
+ VIR_AUTOPTR(virJSONValue) doc = NULL;
+ VIR_AUTOPTR(qemuVhostUser) vu = NULL;
+ qemuVhostUserPtr ret = NULL;
+
+ if (virFileReadAll(path, DOCUMENT_SIZE, &cont) < 0)
+ return NULL;
+
+ if (!(doc = virJSONValueFromString(cont))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to parse json file '%s'"),
+ path);
+ return NULL;
+ }
+
+ if (VIR_ALLOC(vu) < 0)
+ return NULL;
+
+ if (qemuVhostUserTypeParse(path, doc, vu) < 0)
+ return NULL;
+
+ if (qemuVhostUserBinaryParse(path, doc, vu) < 0)
+ return NULL;
+
+ VIR_STEAL_PTR(ret, vu);
+ return ret;
+}
+
+char *
+qemuVhostUserFormat(qemuVhostUserPtr vu)
+{
+ VIR_AUTOPTR(virJSONValue) doc = NULL;
+
+ if (!vu)
+ return NULL;
+
+ if (!(doc = virJSONValueNewObject()))
+ return NULL;
+
+ if (virJSONValueObjectAppendString(doc, "type",
+ qemuVhostUserTypeTypeToString(vu->type)) <
0)
+ return NULL;
+
+ if (virJSONValueObjectAppendString(doc, "binary", vu->binary) < 0)
+ return NULL;
+
+ return virJSONValueToString(doc, true);
+}
+
+int
+qemuVhostUserFetchConfigs(char ***configs,
+ bool privileged)
+{
+ return qemuFetchConfigs("vhost-user", configs, privileged);
+}
+
+static ssize_t
+qemuVhostUserFetchParsedConfigs(bool privileged,
+ qemuVhostUserPtr **vhostuserRet,
+ char ***pathsRet)
+{
+ VIR_AUTOSTRINGLIST paths = NULL;
+ size_t npaths;
+ qemuVhostUserPtr *vus = NULL;
+ size_t i;
+
+ if (qemuVhostUserFetchConfigs(&paths, privileged) < 0)
+ return -1;
+
+ npaths = virStringListLength((const char **)paths);
+
+ if (VIR_ALLOC_N(vus, npaths) < 0)
+ return -1;
+
+ for (i = 0; i < npaths; i++) {
+ if (!(vus[i] = qemuVhostUserParse(paths[i])))
+ goto error;
+ }
+
+ VIR_STEAL_PTR(*vhostuserRet, vus);
+ if (pathsRet)
+ VIR_STEAL_PTR(*pathsRet, paths);
+ return npaths;
+
+ error:
+ while (i > 0)
+ qemuVhostUserFree(vus[--i]);
+ VIR_FREE(vus);
+ return -1;
+}
+
+
+static int
+qemuVhostUserGPUFillCapabilities(qemuVhostUserPtr vu,
+ virJSONValuePtr doc)
+{
+ qemuVhostUserGPUPtr gpu = &vu->capabilities.gpu;
+ virJSONValuePtr featuresJSON;
+ size_t nfeatures;
+ size_t i;
+ VIR_AUTOPTR(qemuVhostUserGPUFeature) features = NULL;
+
+ if (!(featuresJSON = virJSONValueObjectGetArray(doc, "features"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("failed to get features from '%s'"),
+ vu->binary);
+ return -1;
+ }
+
+ nfeatures = virJSONValueArraySize(featuresJSON);
+ if (VIR_ALLOC_N(features, nfeatures) < 0)
+ return -1;
+
+ for (i = 0; i < nfeatures; i++) {
+ virJSONValuePtr item = virJSONValueArrayGet(featuresJSON, i);
+ const char *tmpStr = virJSONValueGetString(item);
+ int tmp;
+
+ if ((tmp = qemuVhostUserGPUFeatureTypeFromString(tmpStr)) <= 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unknown feature %s"),
+ tmpStr);
+ continue;
+ }
+
+ features[i] = tmp;
+ }
+
+ VIR_STEAL_PTR(gpu->features, features);
+ gpu->nfeatures = nfeatures;
+
+ return 0;
+}
+
+
+static bool
+qemuVhostUserGPUHasFeature(qemuVhostUserGPUPtr gpu,
+ qemuVhostUserGPUFeature feature)
+{
+ size_t i;
+
+ for (i = 0; i < gpu->nfeatures; i++) {
+ if (gpu->features[i] == feature)
+ return true;
+ }
+
+ return false;
+}
+
+
+int
+qemuVhostUserFillDomainGPU(virQEMUDriverPtr driver,
+ virDomainVideoDefPtr video)
+{
+ qemuVhostUserPtr *vus = NULL;
+ ssize_t nvus = 0;
+ ssize_t i;
+ int ret = -1;
+
+ if ((nvus = qemuVhostUserFetchParsedConfigs(driver->privileged,
+ &vus, NULL)) < 0)
+ goto end;
+
+ for (i = 0; i < nvus; i++) {
+ qemuVhostUserPtr vu = vus[i];
+ VIR_AUTOPTR(virJSONValue) doc = NULL;
+ VIR_AUTOFREE(char *) output = NULL;
+ VIR_AUTOPTR(virCommand) cmd = NULL;
+
+ if (vu->type != QEMU_VHOST_USER_TYPE_GPU)
+ continue;
+
+ cmd = virCommandNewArgList(vu->binary, "--print-capabilities",
NULL);
+ virCommandSetOutputBuffer(cmd, &output);
+ if (virCommandRun(cmd, NULL) < 0)
+ continue;
+
+ if (!(doc = virJSONValueFromString(output))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to parse json capabilities
'%s'"),
+ vu->binary);
+ continue;
+ }
+
+ if (qemuVhostUserGPUFillCapabilities(vu, doc) < 0)
+ continue;
+
+ if (video->accel) {
+ if (video->accel->accel3d &&
+ !qemuVhostUserGPUHasFeature(&vu->capabilities.gpu,
+ QEMU_VHOST_USER_GPU_FEATURE_VIRGL))
+ continue;
+
+ if (video->accel->rendernode &&
+ !qemuVhostUserGPUHasFeature(&vu->capabilities.gpu,
+ QEMU_VHOST_USER_GPU_FEATURE_RENDER_NODE))
+ continue;
+ }
+
+ VIR_FREE(video->info.vhost_user_binary);
+ if (VIR_STRDUP(video->info.vhost_user_binary, vu->binary) < 0)
+ goto end;
+
+ ret = 0;
+ break;
+ }
+
+ if (i == nvus) {
+ virReportError(VIR_ERR_OPERATION_FAILED, "%s",
+ _("Unable to find a satisfying vhost-user-gpu"));
+ }
+
+ end:
+ for (i = 0; i < nvus; i++)
+ qemuVhostUserFree(vus[i]);
+ VIR_FREE(vus);
+ return ret;
+}
diff --git a/src/qemu/qemu_vhost_user.h b/src/qemu/qemu_vhost_user.h
new file mode 100644
index 0000000000..76701dd1fa
--- /dev/null
+++ b/src/qemu/qemu_vhost_user.h
@@ -0,0 +1,48 @@
+/*
+ * qemu_vhost_user.h: QEMU vhost-user
+ *
+ * Copyright (C) 2019 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
+ * <
http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "domain_conf.h"
+#include "qemu_conf.h"
+#include "virautoclean.h"
+#include "virarch.h"
+
+typedef struct _qemuVhostUser qemuVhostUser;
+typedef qemuVhostUser *qemuVhostUserPtr;
+
+void
+qemuVhostUserFree(qemuVhostUserPtr fw);
+
+VIR_DEFINE_AUTOPTR_FUNC(qemuVhostUser, qemuVhostUserFree);
+
+qemuVhostUserPtr
+qemuVhostUserParse(const char *path);
+
+char *
+qemuVhostUserFormat(qemuVhostUserPtr fw);
+
+int
+qemuVhostUserFetchConfigs(char ***configs,
+ bool privileged);
+
+int
+qemuVhostUserFillDomainGPU(virQEMUDriverPtr driver,
+ virDomainVideoDefPtr video);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f92710db43..4f4a5236ea 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -290,6 +290,7 @@ test_programs += qemuxml2argvtest qemuxml2xmltest \
qemumigparamstest \
qemusecuritytest \
qemufirmwaretest \
+ qemuvhostusertest \
$(NULL)
test_helpers += qemucapsprobe
test_libraries += libqemumonitortestutils.la \
@@ -692,6 +693,13 @@ qemufirmwaretest_SOURCES = \
$(NULL)
qemufirmwaretest_LDADD = $(qemu_LDADDS)
+qemuvhostusertest_SOURCES = \
+ qemuvhostusertest.c \
+ testutils.h testutils.c \
+ virfilewrapper.c virfilewrapper.h \
+ $(NULL)
+qemuvhostusertest_LDADD = $(qemu_LDADDS)
+
else ! WITH_QEMU
EXTRA_DIST += qemuxml2argvtest.c qemuxml2xmltest.c \
qemudomaincheckpointxml2xmltest.c qemudomainsnapshotxml2xmltest.c \
@@ -706,6 +714,7 @@ EXTRA_DIST += qemuxml2argvtest.c qemuxml2xmltest.c \
qemusecuritytest.c qemusecuritytest.h \
qemusecuritymock.c \
qemufirmwaretest.c \
+ qemuvhostusertest.c \
$(QEMUMONITORTESTUTILS_SOURCES)
endif ! WITH_QEMU
diff --git a/tests/qemuvhostuserdata/etc/qemu/vhost-user/40-gpu.json
b/tests/qemuvhostuserdata/etc/qemu/vhost-user/40-gpu.json
new file mode 120000
index 0000000000..aa93864aa7
--- /dev/null
+++ b/tests/qemuvhostuserdata/etc/qemu/vhost-user/40-gpu.json
@@ -0,0 +1 @@
+../../../usr/share/qemu/vhost-user/50-gpu.json
\ No newline at end of file
diff --git a/tests/qemuvhostuserdata/etc/qemu/vhost-user/50-gpu.json
b/tests/qemuvhostuserdata/etc/qemu/vhost-user/50-gpu.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qemuvhostuserdata/usr/libexec/qemu/vhost-user/test-vhost-user-gpu
b/tests/qemuvhostuserdata/usr/libexec/qemu/vhost-user/test-vhost-user-gpu
new file mode 100755
index 0000000000..a2c2ee0713
--- /dev/null
+++ b/tests/qemuvhostuserdata/usr/libexec/qemu/vhost-user/test-vhost-user-gpu
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+cat <<EOF
+{
+ "type": "gpu",
+ "features": [
+ "render-node",
+ "virgl"
+ ]
+}
+EOF
diff --git a/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/30-gpu.json
b/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/30-gpu.json
new file mode 120000
index 0000000000..7051776593
--- /dev/null
+++ b/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/30-gpu.json
@@ -0,0 +1 @@
+50-gpu.json
\ No newline at end of file
diff --git a/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/50-gpu.json
b/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/50-gpu.json
new file mode 100644
index 0000000000..4c751971db
--- /dev/null
+++ b/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/50-gpu.json
@@ -0,0 +1,8 @@
+{
+ "description": "QEMU vhost-user-gpu",
+ "type": "gpu",
+ "binary": "/usr/libexec/qemu/vhost-user/test-vhost-user-gpu",
+ "tags": [
+ "CONFIG_OPENGL_DMABUF=y"
+ ]
+}
diff --git a/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/60-gpu.json
b/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/60-gpu.json
new file mode 120000
index 0000000000..7051776593
--- /dev/null
+++ b/tests/qemuvhostuserdata/usr/share/qemu/vhost-user/60-gpu.json
@@ -0,0 +1 @@
+50-gpu.json
\ No newline at end of file
diff --git a/tests/qemuvhostusertest.c b/tests/qemuvhostusertest.c
new file mode 100644
index 0000000000..52adf9834a
--- /dev/null
+++ b/tests/qemuvhostusertest.c
@@ -0,0 +1,132 @@
+#include <config.h>
+
+#include <inttypes.h>
+
+#include "testutils.h"
+#include "virfilewrapper.h"
+#include "qemu/qemu_vhost_user.h"
+#include "configmake.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+/* A very basic test. Parse given JSON vhostuser description into
+ * an internal structure, format it back and compare with the
+ * contents of the file (minus some keys that are not parsed).
+ */
+static int
+testParseFormatVU(const void *opaque)
+{
+ const char *filename = opaque;
+ VIR_AUTOFREE(char *) path = NULL;
+ VIR_AUTOPTR(qemuVhostUser) vu = NULL;
+ VIR_AUTOFREE(char *) buf = NULL;
+ VIR_AUTOPTR(virJSONValue) json = NULL;
+ VIR_AUTOFREE(char *) expected = NULL;
+ VIR_AUTOFREE(char *) actual = NULL;
+
+ if (virAsprintf(&path, "%s/qemuvhostuserdata/%s",
+ abs_srcdir, filename) < 0)
+ return -1;
+
+ if (!(vu = qemuVhostUserParse(path)))
+ return -1;
+
+ if (virFileReadAll(path,
+ 1024 * 1024, /* 1MiB */
+ &buf) < 0)
+ return -1;
+
+ if (!(json = virJSONValueFromString(buf)))
+ return -1;
+
+ /* Description and tags are not parsed. */
+ if (virJSONValueObjectRemoveKey(json, "description", NULL) < 0 ||
+ virJSONValueObjectRemoveKey(json, "tags", NULL) < 0)
+ return -1;
+
+ if (!(expected = virJSONValueToString(json, true)))
+ return -1;
+
+ if (!(actual = qemuVhostUserFormat(vu)))
+ return -1;
+
+ return virTestCompareToString(expected, actual);
+}
+
+
+static int
+testVUPrecedence(const void *opaque ATTRIBUTE_UNUSED)
+{
+ VIR_AUTOFREE(char *) fakehome = NULL;
+ VIR_AUTOSTRINGLIST vuList = NULL;
+ size_t nvuList;
+ size_t i;
+ const char *expected[] = {
+ PREFIX "/share/qemu/vhost-user/30-gpu.json",
+ SYSCONFDIR "/qemu/vhost-user/40-gpu.json",
+ PREFIX "/share/qemu/vhost-user/60-gpu.json",
+ };
+ const size_t nexpected = ARRAY_CARDINALITY(expected);
+
+ if (VIR_STRDUP(fakehome, abs_srcdir "/qemuvhostuserdata/home/user/.config")
< 0)
+ return -1;
+
+ setenv("XDG_CONFIG_HOME", fakehome, 1);
+
+ if (qemuVhostUserFetchConfigs(&vuList, false) < 0)
+ return -1;
+
+ if (!vuList) {
+ fprintf(stderr, "Expected a non-NULL result, but got a NULL
result\n");
+ return -1;
+ }
+
+ nvuList = virStringListLength((const char **)vuList);
+
+ for (i = 0; i < MAX(nvuList, nexpected); i++) {
+ const char *e = i < nexpected ? expected[i] : NULL;
+ const char *f = i < nvuList ? vuList[i] : NULL;
+
+ if (STRNEQ_NULLABLE(e, f)) {
+ fprintf(stderr,
+ "Unexpected path (i=%zu). Expected %s got %s \n",
+ i, NULLSTR(e), NULLSTR(f));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+mymain(void)
+{
+ int ret = 0;
+
+ virFileWrapperAddPrefix(SYSCONFDIR "/qemu/vhost-user",
+ abs_srcdir
"/qemuvhostuserdata/etc/qemu/vhost-user");
+ virFileWrapperAddPrefix(PREFIX "/share/qemu/vhost-user",
+ abs_srcdir
"/qemuvhostuserdata/usr/share/qemu/vhost-user");
+ virFileWrapperAddPrefix("/home/user/.config/qemu/vhost-user",
+ abs_srcdir
"/qemuvhostuserdata/home/user/.config/qemu/vhost-user");
+
+#define DO_PARSE_TEST(filename) \
+ do { \
+ if (virTestRun("QEMU vhost-user " filename, \
+ testParseFormatVU, filename) < 0) \
+ ret = -1; \
+ } while (0)
+
+ DO_PARSE_TEST("usr/share/qemu/vhost-user/50-gpu.json");
+
+ if (virTestRun("QEMU vhost-user precedence test", testVUPrecedence, NULL)
< 0)
+ ret = -1;
+
+ virFileWrapperClearPrefixes();
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+
+VIR_TEST_MAIN(mymain)
--
2.21.0