---
include/libvirt/virterror.h | 1 +
po/POTFILES | 1 +
src/qemu/meson.build | 1 +
src/qemu/qemu_block.c | 64 +-
src/qemu/qemu_block.h | 1 +
src/qemu/qemu_command.c | 26 +-
src/qemu/qemu_conf.c | 19 +
src/qemu/qemu_conf.h | 5 +
src/qemu/qemu_domain.c | 110 ++-
src/qemu/qemu_domain.h | 5 +
src/qemu/qemu_driver.c | 4 +-
src/qemu/qemu_extdevice.c | 25 +
src/qemu/qemu_nbdkit.c | 629 ++++++++++++++++++
src/qemu/qemu_nbdkit.h | 89 +++
src/qemu/qemu_validate.c | 22 +-
src/qemu/qemu_validate.h | 4 +-
src/util/virerror.c | 1 +
tests/qemublocktest.c | 8 +-
tests/qemustatusxml2xmldata/modern-in.xml | 1 -
...sk-cdrom-network-nbdkit.x86_64-latest.args | 42 ++
.../disk-cdrom-network-nbdkit.xml | 1 +
...isk-network-http-nbdkit.x86_64-latest.args | 45 ++
.../disk-network-http-nbdkit.xml | 1 +
...work-source-curl-nbdkit.x86_64-latest.args | 49 ++
.../disk-network-source-curl-nbdkit.xml | 1 +
...isk-network-source-curl.x86_64-latest.args | 53 ++
.../disk-network-source-curl.xml | 71 ++
tests/qemuxml2argvtest.c | 12 +
tests/testutilsqemu.c | 16 +
tests/testutilsqemu.h | 4 +
30 files changed, 1278 insertions(+), 33 deletions(-)
create mode 100644 src/qemu/qemu_nbdkit.c
create mode 100644 src/qemu/qemu_nbdkit.h
create mode 100644 tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args
create mode 120000 tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml
create mode 100644 tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args
create mode 120000 tests/qemuxml2argvdata/disk-network-http-nbdkit.xml
create mode 100644
tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args
create mode 120000 tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml
create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args
create mode 100644 tests/qemuxml2argvdata/disk-network-source-curl.xml
diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
index df13e4f11e..dd198dfd7d 100644
--- a/include/libvirt/virterror.h
+++ b/include/libvirt/virterror.h
@@ -141,6 +141,7 @@ typedef enum {
VIR_FROM_TPM = 70, /* Error from TPM (Since: 5.6.0) */
VIR_FROM_BPF = 71, /* Error from BPF code (Since: 5.10.0) */
VIR_FROM_CH = 72, /* Error from Cloud-Hypervisor driver (Since: 7.5.0) */
+ VIR_FROM_NBDKIT = 73, /* Error from Nbdkit code (Since: 8.5.0) */
# ifdef VIR_ENUM_SENTINELS
VIR_ERR_DOMAIN_LAST /* (Since: 0.9.13) */
diff --git a/po/POTFILES b/po/POTFILES
index faaba53c8f..99284b8173 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -177,6 +177,7 @@ src/qemu/qemu_monitor.c
src/qemu/qemu_monitor_json.c
src/qemu/qemu_monitor_text.c
src/qemu/qemu_namespace.c
+src/qemu/qemu_nbdkit.c
src/qemu/qemu_process.c
src/qemu/qemu_qapi.c
src/qemu/qemu_saveimage.c
diff --git a/src/qemu/meson.build b/src/qemu/meson.build
index 96952cc52d..101cf3591f 100644
--- a/src/qemu/meson.build
+++ b/src/qemu/meson.build
@@ -28,6 +28,7 @@ qemu_driver_sources = [
'qemu_monitor_json.c',
'qemu_monitor_text.c',
'qemu_namespace.c',
+ 'qemu_nbdkit.c',
'qemu_process.c',
'qemu_qapi.c',
'qemu_saveimage.c',
diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c
index 9fe22f18f2..91f17f133f 100644
--- a/src/qemu/qemu_block.c
+++ b/src/qemu/qemu_block.c
@@ -773,6 +773,35 @@ qemuBlockStorageSourceGetCURLProps(virStorageSource *src,
}
+static virJSONValue *
+qemuBlockStorageSourceGetNbdkitProps(virStorageSource *src)
+{
+ qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
+ virJSONValue *ret = NULL;
+ g_autoptr(virJSONValue) serverprops = NULL;
+ virStorageNetHostDef host = { NULL };
+
+ /* srcPriv->nbdkitProcess will already be initialized if we can use nbdkit
+ * to proxy this storage source */
+ if (!(srcPriv && srcPriv->nbdkitProcess))
+ return NULL;
+
+ host.transport = VIR_STORAGE_NET_HOST_TRANS_UNIX;
+ host.socket = srcPriv->nbdkitProcess->socketfile;
+ serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&host,
+ false);
+ if (!serverprops)
+ return NULL;
+
+ if (virJSONValueObjectAdd(&ret,
+ "a:server", &serverprops,
+ NULL) < 0)
+ return NULL;
+
+ return ret;
+}
+
+
static virJSONValue *
qemuBlockStorageSourceGetISCSIProps(virStorageSource *src,
bool onlytarget)
@@ -1207,6 +1236,14 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src,
case VIR_STORAGE_NET_PROTOCOL_FTP:
case VIR_STORAGE_NET_PROTOCOL_FTPS:
case VIR_STORAGE_NET_PROTOCOL_TFTP:
+ /* first try to use nbdkit for http/ftp sources */
+ if ((fileprops = qemuBlockStorageSourceGetNbdkitProps(src))) {
+ driver = "nbd";
+ break;
+ }
+
+ /* nbdkit is not supported for this host/source, fall back to old
+ * qemu storage plugins */
driver = virStorageNetProtocolTypeToString(src->protocol);
if (!(fileprops = qemuBlockStorageSourceGetCURLProps(src, onlytarget)))
return NULL;
@@ -1237,6 +1274,14 @@ qemuBlockStorageSourceGetBackendProps(virStorageSource *src,
break;
case VIR_STORAGE_NET_PROTOCOL_SSH:
+ /* first try to use nbdkit for ssh sources */
+ if ((fileprops = qemuBlockStorageSourceGetNbdkitProps(src))) {
+ driver = "nbd";
+ break;
+ }
+
+ /* nbdkit is not supported for this host/source. fallback to
+ * the old qemu storage plugins */
driver = "ssh";
if (!(fileprops = qemuBlockStorageSourceGetSshProps(src)))
return NULL;
@@ -1671,6 +1716,7 @@ qemuBlockStorageSourceAttachPrepareBlockdev(virStorageSource *src,
{
g_autoptr(qemuBlockStorageSourceAttachData) data = NULL;
unsigned int backendpropsflags = 0;
+ qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
if (autoreadonly)
backendpropsflags |= QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_AUTO_READONLY;
@@ -1685,6 +1731,7 @@ qemuBlockStorageSourceAttachPrepareBlockdev(virStorageSource *src,
data->storageNodeName = src->nodestorage;
data->formatNodeName = src->nodeformat;
+ data->useNbdkit = srcpriv && srcpriv->nbdkitProcess;
if (qemuBlockStorageSourceNeedsStorageSliceLayer(src)) {
if (!(data->storageSliceProps =
qemuBlockStorageSourceGetBlockdevStorageSliceProps(src)))
@@ -1701,6 +1748,10 @@ static int
qemuBlockStorageSourceAttachApplyStorageDeps(qemuMonitor *mon,
qemuBlockStorageSourceAttachData *data)
{
+ /* when using nbdkit, data is not passed via qemu secrets */
+ if (data->useNbdkit)
+ return 0;
+
if (data->prmgrProps &&
qemuMonitorAddObject(mon, &data->prmgrProps, &data->prmgrAlias)
< 0)
return -1;
@@ -2205,6 +2256,8 @@ qemuBlockGetBackingStoreString(virStorageSource *src,
virJSONValue *props = NULL;
g_autoptr(virURI) uri = NULL;
g_autofree char *backingJSON = NULL;
+ qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
+ bool useNbdkit = srcPriv && srcPriv->nbdkitProcess;
if (!src->sliceStorage) {
if (virStorageSourceIsLocalStorage(src)) {
@@ -2223,7 +2276,8 @@ qemuBlockGetBackingStoreString(virStorageSource *src,
src->ncookies == 0 &&
src->sslverify == VIR_TRISTATE_BOOL_ABSENT &&
src->timeout == 0 &&
- src->readahead == 0) {
+ src->readahead == 0 &&
+ !useNbdkit) {
switch ((virStorageNetProtocol) src->protocol) {
case VIR_STORAGE_NET_PROTOCOL_NBD:
@@ -2602,6 +2656,7 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src,
g_autoptr(virJSONValue) location = NULL;
const char *driver = NULL;
const char *filename = NULL;
+ qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
switch (actualType) {
case VIR_STORAGE_TYPE_FILE:
@@ -2630,6 +2685,13 @@ qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src,
break;
case VIR_STORAGE_NET_PROTOCOL_SSH:
+ if (srcPriv->nbdkitProcess) {
+ /* disk creation not yet supported with nbdkit, and even if it
+ * was supported, it would not be done with blockdev-create
+ * props */
+ return 0;
+ }
+
driver = "ssh";
if (!(location = qemuBlockStorageSourceGetSshProps(src)))
return -1;
diff --git a/src/qemu/qemu_block.h b/src/qemu/qemu_block.h
index 8641c8a2d2..a1f98f4452 100644
--- a/src/qemu/qemu_block.h
+++ b/src/qemu/qemu_block.h
@@ -113,6 +113,7 @@ struct qemuBlockStorageSourceAttachData {
char *tlsAlias;
virJSONValue *tlsKeySecretProps;
char *tlsKeySecretAlias;
+ bool useNbdkit;
};
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index b307d3139c..dd410362fb 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -1578,6 +1578,7 @@ qemuBuildNetworkDriveStr(virStorageSource *src,
qemuDomainSecretInfo *secinfo)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+ qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
size_t i;
char *ret = NULL;
@@ -1637,6 +1638,13 @@ qemuBuildNetworkDriveStr(virStorageSource *src,
case VIR_STORAGE_NET_PROTOCOL_FTP:
case VIR_STORAGE_NET_PROTOCOL_FTPS:
case VIR_STORAGE_NET_PROTOCOL_TFTP:
+ if (priv && priv->nbdkitProcess) {
+ virBufferAsprintf(&buf, "nbd:unix:%s",
priv->nbdkitProcess->socketfile);
+ ret = virBufferContentAndReset(&buf);
+ } else {
+ ret = qemuBuildNetworkDriveURI(src);
+ }
+ break;
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
ret = qemuBuildNetworkDriveURI(src);
@@ -2497,13 +2505,17 @@ qemuBuildBlockStorageSourceAttachDataCommandline(virCommand *cmd,
{
char *tmp;
- if (qemuBuildObjectCommandline(cmd, data->prmgrProps, qemuCaps) < 0 ||
- qemuBuildObjectCommandline(cmd, data->authsecretProps, qemuCaps) < 0 ||
- qemuBuildObjectCommandline(cmd, data->encryptsecretProps, qemuCaps) < 0 ||
- qemuBuildObjectCommandline(cmd, data->httpcookiesecretProps, qemuCaps) < 0
||
- qemuBuildObjectCommandline(cmd, data->tlsKeySecretProps, qemuCaps) < 0 ||
- qemuBuildObjectCommandline(cmd, data->tlsProps, qemuCaps) < 0)
- return -1;
+ /* disks that are backed by nbdkit do not send these secrets to qemu, but
+ * rather directly to nbdkit */
+ if (!data->useNbdkit) {
+ if (qemuBuildObjectCommandline(cmd, data->prmgrProps, qemuCaps) < 0 ||
+ qemuBuildObjectCommandline(cmd, data->authsecretProps, qemuCaps) < 0
||
+ qemuBuildObjectCommandline(cmd, data->encryptsecretProps, qemuCaps) < 0
||
+ qemuBuildObjectCommandline(cmd, data->httpcookiesecretProps, qemuCaps)
< 0 ||
+ qemuBuildObjectCommandline(cmd, data->tlsKeySecretProps, qemuCaps) < 0
||
+ qemuBuildObjectCommandline(cmd, data->tlsProps, qemuCaps) < 0)
+ return -1;
+ }
if (data->driveCmd)
virCommandAddArgList(cmd, "-drive", data->driveCmd, NULL);
diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
index 3b75cdeb95..22aeab0c49 100644
--- a/src/qemu/qemu_conf.c
+++ b/src/qemu/qemu_conf.c
@@ -1571,3 +1571,22 @@ qemuGetMemoryBackingPath(virQEMUDriver *driver,
*memPath = g_strdup_printf("%s/%s", domainPath, alias);
return 0;
}
+
+/*
+ * qemuGetNbdkitCaps:
+ * @driver: the qemu driver
+ *
+ * Gets the capabilities for Nbdkit for the specified driver. These can be used
+ * to determine whether a particular disk source can be served by nbdkit or
+ * not.
+ *
+ * Returns: a reference to qemuNbdkitCaps or NULL
+ */
+qemuNbdkitCaps*
+qemuGetNbdkitCaps(virQEMUDriver *driver)
+{
+ if (!QEMU_IS_NBDKIT_CAPS(driver->nbdkitCaps))
+ return NULL;
+
+ return g_object_ref(driver->nbdkitCaps);
+}
diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
index c40c452f58..f14f9fc4c1 100644
--- a/src/qemu/qemu_conf.h
+++ b/src/qemu/qemu_conf.h
@@ -36,6 +36,7 @@
#include "virthreadpool.h"
#include "locking/lock_manager.h"
#include "qemu_capabilities.h"
+#include "qemu_nbdkit.h"
#include "virclosecallbacks.h"
#include "virhostdev.h"
#include "virfile.h"
@@ -306,6 +307,8 @@ struct _virQEMUDriver {
/* Immutable pointer, self-locking APIs */
virHashAtomic *migrationErrors;
+
+ qemuNbdkitCaps *nbdkitCaps;
};
virQEMUDriverConfig *virQEMUDriverConfigNew(bool privileged,
@@ -359,3 +362,5 @@ int qemuGetMemoryBackingPath(virQEMUDriver *driver,
const virDomainDef *def,
const char *alias,
char **memPath);
+
+qemuNbdkitCaps * qemuGetNbdkitCaps(virQEMUDriver *driver);
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 9769e3bb92..faeb779b3f 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -21,6 +21,7 @@
#include <config.h>
+#include "qemu_conf.h"
#include "qemu_domain.h"
#include "qemu_alias.h"
#include "qemu_block.h"
@@ -818,6 +819,7 @@ qemuDomainStorageSourcePrivateDispose(void *obj)
g_clear_pointer(&priv->encinfo, qemuDomainSecretInfoFree);
g_clear_pointer(&priv->httpcookie, qemuDomainSecretInfoFree);
g_clear_pointer(&priv->tlsKeySecret, qemuDomainSecretInfoFree);
+ g_clear_pointer(&priv->nbdkitProcess, qemuNbdkitProcessFree);
}
@@ -1293,10 +1295,7 @@ qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivate *priv,
if (!src->auth && !hasEnc && src->ncookies == 0)
return 0;
- if (!(src->privateData = qemuDomainStorageSourcePrivateNew()))
- return -1;
-
- srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
+ srcPriv = qemuDomainStorageSourcePrivateFetch(src);
if (src->auth) {
virSecretUsageType usageType = VIR_SECRET_USAGE_TYPE_ISCSI;
@@ -1321,7 +1320,9 @@ qemuDomainSecretStorageSourcePrepare(qemuDomainObjPrivate *priv,
return -1;
}
- if (src->ncookies &&
+ /* when using nbdkit for http(s) sources, we don't need to pass cookies as
+ * qemu secrets */
+ if (!srcPriv->nbdkitProcess && src->ncookies &&
virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV) &&
!(srcPriv->httpcookie = qemuDomainSecretStorageSourcePrepareCookies(priv,
src,
@@ -1792,6 +1793,31 @@ qemuStorageSourcePrivateDataAssignSecinfo(qemuDomainSecretInfo
**secinfo,
}
+static int
+qemuStorageSourcePrivateDataParseNbdkit(xmlNodePtr node,
+ xmlXPathContextPtr ctxt,
+ virStorageSource *src)
+{
+ qemuDomainStorageSourcePrivate *srcpriv = qemuDomainStorageSourcePrivateFetch(src);
+ g_autofree char *pidfile = NULL;
+ g_autofree char *socketfile = NULL;
+ VIR_XPATH_NODE_AUTORESTORE(ctxt);
+
+ ctxt->node = node;
+
+ if (!(pidfile = virXPathString("string(./pidfile)", ctxt)))
+ return -1;
+
+ if (!(socketfile = virXPathString("string(./socketfile)", ctxt)))
+ return -1;
+
+ if (!srcpriv->nbdkitProcess)
+ srcpriv->nbdkitProcess = qemuNbdkitProcessLoad(src, pidfile, socketfile);
+
+ return 0;
+}
+
+
static int
qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt,
virStorageSource *src)
@@ -1802,6 +1828,7 @@ qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt,
g_autofree char *httpcookiealias = NULL;
g_autofree char *tlskeyalias = NULL;
g_autofree char *thresholdEventWithIndex = NULL;
+ xmlNodePtr nbdkitnode = NULL;
src->nodestorage =
virXPathString("string(./nodenames/nodename[@type='storage']/@name)",
ctxt);
src->nodeformat =
virXPathString("string(./nodenames/nodename[@type='format']/@name)",
ctxt);
@@ -1845,6 +1872,10 @@ qemuStorageSourcePrivateDataParse(xmlXPathContextPtr ctxt,
virTristateBoolTypeFromString(thresholdEventWithIndex) == VIR_TRISTATE_BOOL_YES)
src->thresholdEventWithIndex = true;
+ if ((nbdkitnode = virXPathNode("nbdkit", ctxt))) {
+ if (qemuStorageSourcePrivateDataParseNbdkit(nbdkitnode, ctxt, src) < 0)
+ return -1;
+ }
return 0;
}
@@ -1862,6 +1893,23 @@ qemuStorageSourcePrivateDataFormatSecinfo(virBuffer *buf,
}
+static void
+qemuStorageSourcePrivateDataFormatNbdkit(qemuNbdkitProcess *nbdkit,
+ virBuffer *buf)
+{
+ g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
+
+ if (!nbdkit)
+ return;
+
+ virBufferEscapeString(&childBuf,
"<pidfile>%s</pidfile>\n",
+ nbdkit->pidfile);
+ virBufferEscapeString(&childBuf,
"<socketfile>%s</socketfile>\n",
+ nbdkit->socketfile);
+ virXMLFormatElement(buf, "nbdkit", NULL, &childBuf);
+}
+
+
static int
qemuStorageSourcePrivateDataFormat(virStorageSource *src,
virBuffer *buf)
@@ -1900,6 +1948,9 @@ qemuStorageSourcePrivateDataFormat(virStorageSource *src,
if (src->thresholdEventWithIndex)
virBufferAddLit(buf, "<thresholdEvent
indexUsed='yes'/>\n");
+ if (srcPriv)
+ qemuStorageSourcePrivateDataFormatNbdkit(srcPriv->nbdkitProcess, buf);
+
return 0;
}
@@ -4804,6 +4855,7 @@ qemuDomainValidateActualNetDef(const virDomainNetDef *net,
int
qemuDomainValidateStorageSource(virStorageSource *src,
virQEMUCaps *qemuCaps,
+ qemuNbdkitCaps *nbdkitCaps,
bool maskBlockdev)
{
virStorageType actualType = virStorageSourceGetActualType(src);
@@ -4899,7 +4951,8 @@ qemuDomainValidateStorageSource(virStorageSource *src,
return -1;
}
- if (!src->detected && !blockdev) {
+ if (!src->detected && !blockdev &&
+ !qemuNbdkitCapsGet(nbdkitCaps, QEMU_NBDKIT_CAPS_PLUGIN_CURL)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("http cookies are not supported by this QEMU
binary"));
return -1;
@@ -4920,7 +4973,8 @@ qemuDomainValidateStorageSource(virStorageSource *src,
return -1;
}
- if (!src->detected && !blockdev) {
+ if (!src->detected && !blockdev &&
+ !qemuNbdkitCapsGet(nbdkitCaps, QEMU_NBDKIT_CAPS_FILTER_READAHEAD)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("readahead setting is not supported with this QEMU
binary"));
return -1;
@@ -4938,7 +4992,8 @@ qemuDomainValidateStorageSource(virStorageSource *src,
return -1;
}
- if (!src->detected && !blockdev) {
+ if (!src->detected && !blockdev &&
+ !qemuNbdkitCapsGet(nbdkitCaps, QEMU_NBDKIT_CAPS_PLUGIN_CURL)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("timeout setting is not supported with this QEMU
binary"));
return -1;
@@ -7705,6 +7760,7 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver,
bool isSD = qemuDiskBusIsSD(disk->bus);
uid_t uid;
gid_t gid;
+ g_autoptr(qemuNbdkitCaps) nbdkitcaps = qemuGetNbdkitCaps(priv->driver);
if (!disksrc)
disksrc = disk->src;
@@ -7787,7 +7843,8 @@ qemuDomainDetermineDiskChain(virQEMUDriver *driver,
n->format = VIR_STORAGE_FILE_RAW;
/* mask-out blockdev for 'sd' disks */
- if (qemuDomainValidateStorageSource(n, priv->qemuCaps, isSD) < 0)
+ if (qemuDomainValidateStorageSource(n, priv->qemuCaps,
+ nbdkitcaps, isSD) < 0)
return -1;
qemuDomainPrepareStorageSourceConfig(n, cfg, priv->qemuCaps);
@@ -10112,6 +10169,29 @@ qemuDomainPrepareStorageSourceNFS(virStorageSource *src)
}
+/* qemuPrepareStorageSourceNbdkit:
+ * @src: source for a disk
+ *
+ * If src is an network source that is managed by nbdkit, prepare data so that
+ * nbdkit can be launched before the domain is started
+ */
+static void
+qemuDomainPrepareStorageSourceNbdkit(virDomainDiskDef *disk,
+ virQEMUDriverConfig *cfg,
+ qemuDomainObjPrivate *priv)
+{
+ g_autoptr(qemuNbdkitCaps) nbdkit = qemuGetNbdkitCaps(priv->driver);
+ if (!nbdkit)
+ return;
+
+ if (virStorageSourceGetActualType(disk->src) != VIR_STORAGE_TYPE_NETWORK)
+ return;
+
+ qemuNbdkitInitStorageSource(nbdkit, disk->src, priv->libDir,
+ disk->info.alias, cfg->user, cfg->group);
+}
+
+
/* qemuProcessPrepareStorageSourceTLS:
* @source: source for a disk
* @cfg: driver configuration
@@ -10828,7 +10908,9 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDef *disk,
qemuDomainObjPrivate *priv,
virQEMUDriverConfig *cfg)
{
- if (qemuDomainValidateStorageSource(disk->src, priv->qemuCaps, true) < 0)
+ g_autoptr(qemuNbdkitCaps) nbdkitcaps = qemuGetNbdkitCaps(priv->driver);
+ if (qemuDomainValidateStorageSource(disk->src, priv->qemuCaps,
+ nbdkitcaps, true) < 0)
return -1;
qemuDomainPrepareStorageSourceConfig(disk->src, cfg, priv->qemuCaps);
@@ -10846,6 +10928,8 @@ qemuDomainPrepareDiskSourceLegacy(virDomainDiskDef *disk,
priv) < 0)
return -1;
+ qemuDomainPrepareStorageSourceNbdkit(disk, cfg, priv);
+
return 0;
}
@@ -10857,6 +10941,7 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef
*disk,
qemuDomainObjPrivate *priv,
virQEMUDriverConfig *cfg)
{
+ g_autoptr(qemuNbdkitCaps) nbdkitcaps = qemuGetNbdkitCaps(priv->driver);
src->nodestorage = g_strdup_printf("%s-storage", nodenameprefix);
src->nodeformat = g_strdup_printf("%s-format", nodenameprefix);
@@ -10866,7 +10951,8 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef
*disk,
if (src->encryption && src->encryption->engine ==
VIR_STORAGE_ENCRYPTION_ENGINE_DEFAULT)
src->encryption->engine = VIR_STORAGE_ENCRYPTION_ENGINE_QEMU;
- if (qemuDomainValidateStorageSource(src, priv->qemuCaps, false) < 0)
+ if (qemuDomainValidateStorageSource(src, priv->qemuCaps,
+ nbdkitcaps, false) < 0)
return -1;
qemuDomainPrepareStorageSourceConfig(src, cfg, priv->qemuCaps);
@@ -10887,6 +10973,8 @@ qemuDomainPrepareStorageSourceBlockdevNodename(virDomainDiskDef
*disk,
if (qemuDomainPrepareStorageSourceNFS(src) < 0)
return -1;
+ qemuDomainPrepareStorageSourceNbdkit(disk, cfg, priv);
+
return 0;
}
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
index a87dfff1bb..34e1bb6fba 100644
--- a/src/qemu/qemu_domain.h
+++ b/src/qemu/qemu_domain.h
@@ -33,6 +33,7 @@
#include "qemu_conf.h"
#include "qemu_capabilities.h"
#include "qemu_migration_params.h"
+#include "qemu_nbdkit.h"
#include "qemu_slirp.h"
#include "qemu_fd.h"
#include "virchrdev.h"
@@ -290,6 +291,9 @@ struct _qemuDomainStorageSourcePrivate {
/* key for decrypting TLS certificate */
qemuDomainSecretInfo *tlsKeySecret;
+
+ /* an nbdkit process for serving network storage sources */
+ qemuNbdkitProcess *nbdkitProcess;
};
virObject *qemuDomainStorageSourcePrivateNew(void);
@@ -995,6 +999,7 @@ qemuDomainPrepareDiskSourceData(virDomainDiskDef *disk,
int
qemuDomainValidateStorageSource(virStorageSource *src,
virQEMUCaps *qemuCaps,
+ qemuNbdkitCaps *nbdkitCaps,
bool maskBlockdev);
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 3b5c3db67c..d1a972eb37 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -831,6 +831,9 @@ qemuStateInitialize(bool privileged,
defsecmodel)))
goto error;
+ /* find whether nbdkit is available and query its capabilities */
+ qemu_driver->nbdkitCaps = qemuNbdkitCapsQuery();
+
/* If hugetlbfs is present, then we need to create a sub-directory within
* it, since we can't assume the root mount point has permissions that
* will let our spawned QEMU instances use it. */
@@ -14471,7 +14474,6 @@ qemuDomainBlockPivot(virQEMUDriver *driver,
if (reuse && shallow &&
virQEMUCapsGet(priv->qemuCaps,
QEMU_CAPS_BLOCKDEV_SNAPSHOT_ALLOW_WRITE_ONLY) &&
virStorageSourceHasBacking(disk->mirror)) {
-
if (!(chainattachdata =
qemuBuildStorageSourceChainAttachPrepareBlockdev(disk->mirror->backingStore)))
return -1;
diff --git a/src/qemu/qemu_extdevice.c b/src/qemu/qemu_extdevice.c
index b8e3c1000a..0f9361b294 100644
--- a/src/qemu/qemu_extdevice.c
+++ b/src/qemu/qemu_extdevice.c
@@ -218,6 +218,14 @@ qemuExtDevicesStart(virQEMUDriver *driver,
return -1;
}
+ for (i = 0; i < def->ndisks; i++) {
+ virDomainDiskDef *disk = def->disks[i];
+ qemuDomainStorageSourcePrivate *priv =
QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src);
+ if (priv && priv->nbdkitProcess &&
+ qemuNbdkitProcessStart(priv->nbdkitProcess, vm, driver) < 0)
+ return -1;
+ }
+
return 0;
}
@@ -262,6 +270,14 @@ qemuExtDevicesStop(virQEMUDriver *driver,
fs->fsdriver == VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS)
qemuVirtioFSStop(driver, vm, fs);
}
+
+ for (i = 0; i < def->ndisks; i++) {
+ virDomainDiskDef *disk = def->disks[i];
+ qemuDomainStorageSourcePrivate *priv =
QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk->src);
+
+ if (priv && priv->nbdkitProcess)
+ qemuNbdkitProcessStop(priv->nbdkitProcess);
+ }
}
@@ -324,6 +340,15 @@ qemuExtDevicesSetupCgroup(virQEMUDriver *driver,
return -1;
}
+ for (i = 0; i < def->ndisks; i++) {
+ virDomainDiskDef *disk = def->disks[i];
+ qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(disk);
+
+ if (priv && priv->nbdkitProcess &&
+ qemuNbdkitProcessSetupCgroup(priv->nbdkitProcess, cgroup) < 0)
+ return -1;
+ }
+
for (i = 0; i < def->nfss; i++) {
virDomainFSDef *fs = def->fss[i];
diff --git a/src/qemu/qemu_nbdkit.c b/src/qemu/qemu_nbdkit.c
new file mode 100644
index 0000000000..dd8759689c
--- /dev/null
+++ b/src/qemu/qemu_nbdkit.c
@@ -0,0 +1,629 @@
+/*
+ * qemu_nbdkit.c: helpers for using nbdkit
+ *
+ * Copyright (C) 2021 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 <glib.h>
+
+#include "vircommand.h"
+#include "virerror.h"
+#include "virlog.h"
+#include "virpidfile.h"
+#include "virsecureerase.h"
+#include "qemu_block.h"
+#include "qemu_conf.h"
+#include "qemu_domain.h"
+#include "qemu_driver.h"
+#include "qemu_extdevice.h"
+#include "qemu_nbdkit.h"
+#include "qemu_security.h"
+
+#include <fcntl.h>
+
+#define VIR_FROM_THIS VIR_FROM_NBDKIT
+
+VIR_LOG_INIT("qemu.nbdkit");
+
+struct _qemuNbdkitCaps {
+ GObject parent;
+
+ char *path;
+ char *version;
+
+ virBitmap *flags;
+};
+G_DEFINE_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, G_TYPE_OBJECT);
+
+
+static void
+qemuNbdkitCheckCommandCap(qemuNbdkitCaps *nbdkit,
+ virCommand *cmd,
+ qemuNbdkitCapsFlags cap)
+{
+ if (virCommandRun(cmd, NULL) != 0)
+ return;
+
+ VIR_DEBUG("Setting nbdkit capability %i", cap);
+ ignore_value(virBitmapSetBit(nbdkit->flags, cap));
+}
+
+
+static void
+qemuNbdkitQueryFilter(qemuNbdkitCaps *nbdkit,
+ const char *filter,
+ qemuNbdkitCapsFlags cap)
+{
+ g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path,
+ "--version",
+ NULL);
+
+ virCommandAddArgPair(cmd, "--filter", filter);
+
+ /* use null plugin to check for filter */
+ virCommandAddArg(cmd, "null");
+
+ qemuNbdkitCheckCommandCap(nbdkit, cmd, cap);
+}
+
+
+static void
+qemuNbdkitQueryPlugin(qemuNbdkitCaps *nbdkit,
+ const char *plugin,
+ qemuNbdkitCapsFlags cap)
+{
+ g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path,
+ plugin,
+ "--version",
+ NULL);
+
+ qemuNbdkitCheckCommandCap(nbdkit, cmd, cap);
+}
+
+
+static void
+qemuNbdkitQueryPlugins(qemuNbdkitCaps *nbdkit)
+{
+ qemuNbdkitQueryPlugin(nbdkit, "curl", QEMU_NBDKIT_CAPS_PLUGIN_CURL);
+ qemuNbdkitQueryPlugin(nbdkit, "ssh", QEMU_NBDKIT_CAPS_PLUGIN_SSH);
+}
+
+
+static void
+qemuNbdkitQueryFilters(qemuNbdkitCaps *nbdkit)
+{
+ qemuNbdkitQueryFilter(nbdkit, "readahead",
+ QEMU_NBDKIT_CAPS_FILTER_READAHEAD);
+}
+
+
+static int
+qemuNbdkitQueryVersion(qemuNbdkitCaps *nbdkit)
+{
+ g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path,
+ "--version",
+ NULL);
+
+ virCommandSetOutputBuffer(cmd, &nbdkit->version);
+
+ if (virCommandRun(cmd, NULL) != 0)
+ return -1;
+
+ VIR_DEBUG("Got nbdkit version %s", nbdkit->version);
+ return 0;
+}
+
+
+static void qemuNbdkitCapsFinalize(GObject *object)
+{
+ qemuNbdkitCaps *nbdkit = QEMU_NBDKIT_CAPS(object);
+
+ g_clear_pointer(&nbdkit->path, g_free);
+ g_clear_pointer(&nbdkit->version, g_free);
+ g_clear_pointer(&nbdkit->flags, virBitmapFree);
+
+ G_OBJECT_CLASS(qemu_nbdkit_caps_parent_class)->finalize(object);
+}
+
+
+void qemu_nbdkit_caps_init(qemuNbdkitCaps *caps)
+{
+ caps->flags = virBitmapNew(QEMU_NBDKIT_CAPS_LAST);
+ caps->version = NULL;
+}
+
+
+static void
+qemu_nbdkit_caps_class_init(qemuNbdkitCapsClass *klass)
+{
+ GObjectClass *obj = G_OBJECT_CLASS(klass);
+
+ obj->finalize = qemuNbdkitCapsFinalize;
+}
+
+
+qemuNbdkitCaps *
+qemuNbdkitCapsNew(const char *path)
+{
+ qemuNbdkitCaps *caps = g_object_new(QEMU_TYPE_NBDKIT_CAPS, NULL);
+ caps->path = g_strdup(path);
+
+ return caps;
+}
+
+
+qemuNbdkitCaps *
+qemuNbdkitCapsQuery(void)
+{
+ qemuNbdkitCaps *caps = NULL;
+ g_autofree char *path = virFindFileInPath("nbdkit");
+
+ if (!path)
+ return NULL;
+
+ // make sure it's executable
+ if (!virFileIsExecutable(path)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, _("nbdkit '%s' is not
executable"),
+ path);
+ return NULL;
+ }
+
+ VIR_DEBUG("found nbdkit executable '%s'", path);
+ caps = qemuNbdkitCapsNew(path);
+
+ qemuNbdkitQueryPlugins(caps);
+ qemuNbdkitQueryFilters(caps);
+ qemuNbdkitQueryVersion(caps);
+
+ return caps;
+}
+
+
+bool
+qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag)
+{
+ return virBitmapIsBitSet(nbdkitCaps->flags, flag);
+}
+
+
+void
+qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag)
+{
+ ignore_value(virBitmapSetBit(nbdkitCaps->flags, flag));
+}
+
+
+static int childProcess(int fd, uint8_t *output, size_t outputlen)
+{
+ if (safewrite(fd, output, outputlen) < 0) {
+ virReportSystemError(errno, "%s",
+ _("failed to write to socket for nbdkit"));
+ return -errno;
+ }
+ return 0;
+}
+
+
+/* Forks a process to write a password to a socket to the nbdkit process.
+ * Returns a file descriptor that can be passed to ndkit */
+static int qemuNbdkitForkHandler(uint8_t *output, size_t outputlen)
+{
+ enum {
+ PARENT_SOCKET = 0,
+ CHILD_SOCKET = 1
+ };
+ int pair[2] = { -1, -1 };
+ pid_t pid;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) {
+ virReportSystemError(errno, "%s",
+ _("failed to create socket for nbdkit"));
+ return -errno;
+ }
+
+ pid = virFork();
+
+ if (pid < 0)
+ return -errno;
+
+ /* child process */
+ if (pid == 0) {
+ int ret = childProcess(pair[CHILD_SOCKET], output, outputlen);
+ _exit(ret);
+ }
+
+ /* parent process */
+ VIR_FORCE_CLOSE(pair[CHILD_SOCKET]);
+
+ return pair[PARENT_SOCKET];
+}
+
+
+/* Forks a process to write cookies to a socket to the nbdkit process.
+ * Returns a file descriptor that can be passed to ndkit */
+static int qemuNbdkitProcessForkCookieHandler(qemuNbdkitProcess *proc)
+{
+ g_autofree char *cookies = qemuBlockStorageSourceGetCookieString(proc->source);
+
+ if (!cookies)
+ return 0;
+
+ if ((proc->cookiefd = qemuNbdkitForkHandler((uint8_t*)cookies, strlen(cookies)))
< 0)
+ return -1;
+
+ return 0;
+}
+
+
+/* Forks a process to write a password to a socket to the nbdkit process.
+ * Returns a file descriptor that can be passed to ndkit */
+static int qemuNbdkitProcessForkPasswordHandler(qemuNbdkitProcess *proc)
+{
+ g_autofree uint8_t *password = NULL;
+ size_t passwordlen = 0;
+ g_autoptr(virConnect) conn = virGetConnectSecret();
+
+ if (!proc->source->auth->username)
+ return 0;
+
+ if (virSecretGetSecretString(conn,
+ &proc->source->auth->seclookupdef,
+ /* FIXME: for some reason auth->authType is always
NONE... */
+ VIR_SECRET_USAGE_TYPE_ISCSI,
+ &password,
+ &passwordlen) < 0)
+ {
+ /* FIXME: message */
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to get auth secret for storage"));
+ return -1;
+ }
+
+ if ((proc->authfd = qemuNbdkitForkHandler(password, passwordlen)) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+qemuNbdkitProcess *
+qemuNbdkitProcessLoad(virStorageSource *source,
+ const char *pidfile,
+ const char *socketfile)
+{
+ int rc;
+ qemuNbdkitProcess *nbdkit = g_new0(qemuNbdkitProcess, 1);
+
+ nbdkit->pidfile = g_strdup(pidfile);
+ nbdkit->socketfile = g_strdup(socketfile);
+ nbdkit->source = virObjectRef(source);
+ nbdkit->user = -1;
+ nbdkit->group = -1;
+
+ if ((rc = virPidFileReadPath(nbdkit->pidfile, &nbdkit->pid)) < 0)
+ VIR_WARN("Failed to read pidfile %s", nbdkit->pidfile);
+
+ return nbdkit;
+}
+
+
+static qemuNbdkitProcess *
+qemuNbdkitProcessNew(qemuNbdkitCaps *caps,
+ virStorageSource *source,
+ char *statedir,
+ const char *alias,
+ uid_t user,
+ gid_t group
+ /*, char *selinux_label*/)
+{
+ qemuNbdkitProcess *proc = g_new0(qemuNbdkitProcess, 1);
+ g_autofree char *pidfile = g_strdup_printf("nbdkit-%s.pid", alias);
+ g_autofree char *socketfile = g_strdup_printf("nbdkit-%s.socket", alias);
+
+ proc->caps = g_object_ref(caps);
+ /* weak reference -- source owns this object, so it will always outlive us */
+ proc->source = source;
+ proc->user = user;
+ proc->group = group;
+ proc->pidfile = g_build_filename(statedir, pidfile, NULL);
+ proc->socketfile = g_build_filename(statedir, socketfile, NULL);
+
+ return proc;
+}
+
+
+void
+qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps,
+ virStorageSource *source,
+ char *statedir,
+ const char *alias,
+ uid_t user,
+ gid_t group
+ /*, char *selinux_label*/)
+{
+ /* FIXME: onlytarget ??? */
+ qemuDomainStorageSourcePrivate *srcPriv =
qemuDomainStorageSourcePrivateFetch(source);
+
+ if (srcPriv->nbdkitProcess)
+ return;
+
+ switch (source->protocol) {
+ case VIR_STORAGE_NET_PROTOCOL_HTTP:
+ case VIR_STORAGE_NET_PROTOCOL_HTTPS:
+ case VIR_STORAGE_NET_PROTOCOL_FTP:
+ case VIR_STORAGE_NET_PROTOCOL_FTPS:
+ case VIR_STORAGE_NET_PROTOCOL_TFTP:
+ if (!virBitmapIsBitSet(caps->flags, QEMU_NBDKIT_CAPS_PLUGIN_CURL))
+ return;
+ break;
+ case VIR_STORAGE_NET_PROTOCOL_SSH:
+ if (!virBitmapIsBitSet(caps->flags, QEMU_NBDKIT_CAPS_PLUGIN_SSH))
+ return;
+ break;
+ case VIR_STORAGE_NET_PROTOCOL_NONE:
+ case VIR_STORAGE_NET_PROTOCOL_NBD:
+ case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
+ case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
+ case VIR_STORAGE_NET_PROTOCOL_ISCSI:
+ case VIR_STORAGE_NET_PROTOCOL_VXHS:
+ case VIR_STORAGE_NET_PROTOCOL_NFS:
+ case VIR_STORAGE_NET_PROTOCOL_LAST:
+ return;
+ }
+ srcPriv->nbdkitProcess = qemuNbdkitProcessNew(caps, source, statedir, alias, user,
group);
+}
+
+
+static void
+qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc,
+ virCommand *cmd)
+{
+ g_autoptr(virURI) uri = qemuBlockStorageSourceGetURI(proc->source);
+ g_autofree char *uristring = virURIFormat(uri);
+
+ /* nbdkit plugin name */
+ virCommandAddArg(cmd, "curl");
+ virCommandAddArgPair(cmd, "protocols",
+
virStorageNetProtocolTypeToString(proc->source->protocol));
+ virCommandAddArgPair(cmd, "url", uristring);
+
+ if (proc->source->auth) {
+ virCommandAddArgPair(cmd, "user",
+ proc->source->auth->username);
+ }
+
+ // FIXME: if (srcPriv->secinfo)???
+ if (proc->authfd > 0) {
+ /* nbdkit auth parameter accepts a variation where nbdkit
+ * will read the cookies from a file descriptor: password=-FD */
+ g_autofree char *fdfmt = g_strdup_printf("-%i", proc->authfd);
+ virCommandAddArgPair(cmd, "password", fdfmt);
+ virCommandPassFD(cmd, proc->authfd, VIR_COMMAND_PASS_FD_CLOSE_PARENT);
+ }
+
+ if (proc->cookiefd > 0) {
+ /* nbdkit cookie parameter accepts a variation where nbdkit
+ * will read the cookies from a file descriptor: cookie=-FD */
+ g_autofree char *fdfmt = g_strdup_printf("-%i", proc->cookiefd);
+ virCommandAddArgPair(cmd, "cookie", fdfmt);
+ virCommandPassFD(cmd, proc->cookiefd, VIR_COMMAND_PASS_FD_CLOSE_PARENT);
+ }
+
+ if (proc->source->sslverify == VIR_TRISTATE_BOOL_NO) {
+ virCommandAddArgPair(cmd, "sslverify", "false");
+ }
+
+ if (proc->source->timeout > 0) {
+ g_autofree char *timeout = g_strdup_printf("%llu",
proc->source->timeout);
+ virCommandAddArgPair(cmd, "timeout", timeout);
+ }
+}
+
+
+static void
+qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc,
+ virCommand *cmd)
+{
+ char *user = NULL;
+ virStorageNetHostDef *host = &proc->source->hosts[0];
+
+ /* nbdkit plugin name */
+ virCommandAddArg(cmd, "ssh");
+
+ virCommandAddArgPair(cmd, "host", g_strdup(host->name));
+ virCommandAddArgPair(cmd, "port", g_strdup_printf("%u",
+ host->port));
+ virCommandAddArgPair(cmd, "path", g_strdup(proc->source->path));
+
+ if (proc->source->auth) {
+ user = g_strdup(proc->source->auth->username);
+ } else if (proc->source->ssh_user) {
+ user = g_strdup(proc->source->ssh_user);
+ }
+
+ if (user) {
+ virCommandAddArgPair(cmd, "user", user);
+ }
+
+ if (proc->source->ssh_host_key_check_disabled) {
+ virCommandAddArgPair(cmd, "verify-remote-host",
+ g_strdup("false"));
+ }
+}
+
+
+static virCommand *
+qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc)
+{
+ g_autoptr(virCommand) cmd = virCommandNewArgList(proc->caps->path,
+ "--exit-with-parent",
+ "--unix",
+ proc->socketfile,
+ "--foreground",
+ "--pidfile",
+ proc->pidfile,
+ "--verbose",
+ //"--selinux-label",
+ //selinux_label,
+ NULL);
+
+ if (proc->source->readonly)
+ virCommandAddArg(cmd, "--readonly");
+
+ if (qemuNbdkitCapsGet(proc->caps, QEMU_NBDKIT_CAPS_FILTER_READAHEAD) &&
+ proc->source->readahead > 0)
+ virCommandAddArgPair(cmd, "--filter", "readahead");
+
+ switch (proc->source->protocol) {
+ case VIR_STORAGE_NET_PROTOCOL_HTTP:
+ case VIR_STORAGE_NET_PROTOCOL_HTTPS:
+ case VIR_STORAGE_NET_PROTOCOL_FTP:
+ case VIR_STORAGE_NET_PROTOCOL_FTPS:
+ case VIR_STORAGE_NET_PROTOCOL_TFTP:
+ qemuNbdkitProcessBuildCommandCurl(proc, cmd);
+ break;
+ case VIR_STORAGE_NET_PROTOCOL_SSH:
+ qemuNbdkitProcessBuildCommandSSH(proc, cmd);
+ break;
+
+ case VIR_STORAGE_NET_PROTOCOL_NONE:
+ case VIR_STORAGE_NET_PROTOCOL_NBD:
+ case VIR_STORAGE_NET_PROTOCOL_RBD:
+ case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
+ case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
+ case VIR_STORAGE_NET_PROTOCOL_ISCSI:
+ case VIR_STORAGE_NET_PROTOCOL_VXHS:
+ case VIR_STORAGE_NET_PROTOCOL_NFS:
+ case VIR_STORAGE_NET_PROTOCOL_LAST:
+ virReportError(VIR_ERR_NO_SUPPORT,
+ _("protocol '%s' is not supported by
nbdkit"),
+
virStorageNetProtocolTypeToString(proc->source->protocol));
+ return NULL;
+ }
+
+ virCommandDaemonize(cmd);
+
+ return g_steal_pointer(&cmd);
+}
+
+
+void
+qemuNbdkitProcessFree(qemuNbdkitProcess *proc)
+{
+ if (virProcessKillPainfully(proc->pid, true) < 0)
+ VIR_WARN("Unable to kill nbdkit process");
+
+ unlink(proc->pidfile);
+ g_clear_pointer(&proc->pidfile, g_free);
+ unlink(proc->socketfile);
+ g_clear_pointer(&proc->socketfile, g_free);
+ VIR_FORCE_CLOSE(proc->cookiefd);
+ VIR_FORCE_CLOSE(proc->authfd);
+ g_clear_object(&proc->caps);
+ g_free(proc);
+}
+
+
+int
+qemuNbdkitProcessSetupCgroup(qemuNbdkitProcess *proc,
+ virCgroup *cgroup)
+{
+ return virCgroupAddProcess(cgroup, proc->pid);
+}
+
+
+/* FIXME: selinux permissions errors */
+int
+qemuNbdkitProcessStart(qemuNbdkitProcess *proc,
+ virDomainObj *vm,
+ virQEMUDriver *driver)
+{
+ g_autoptr(virCommand) cmd = NULL;
+ int rc;
+ int exitstatus = 0;
+ int cmdret = 0;
+ unsigned int loops = 0;
+ int errfd = -1;
+
+ if (qemuNbdkitProcessForkCookieHandler(proc) < 0)
+ return -1;
+
+ if (qemuNbdkitProcessForkPasswordHandler(proc) < 0)
+ return -1;
+
+ if (!(cmd = qemuNbdkitProcessBuildCommand(proc)))
+ return -1;
+
+ virCommandSetErrorFD(cmd, &errfd);
+
+ if (qemuExtDeviceLogCommand(driver, vm, cmd, "nbdkit") < 0)
+ goto error;
+
+ if (qemuSecurityCommandRun(driver, vm, cmd, proc->user, proc->group,
&exitstatus, &cmdret) < 0)
+ goto error;
+
+ if (cmdret < 0 || exitstatus != 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Could not start 'nbdkit'. exitstatus: %d"),
exitstatus);
+ goto error;
+ }
+
+ /* Wait for the pid file to appear. The file doesn't appear until nbdkit is
+ * ready to accept connections to the socket */
+ while (!virFileExists(proc->pidfile)) {
+ VIR_DEBUG("waiting for nbdkit pidfile %s: %u", proc->pidfile,
loops);
+ /* wait for 100ms before checking again, but don't do it for ever */
+ if (errno == ENOENT && loops < 10) {
+ g_usleep(100 * 1000);
+ loops++;
+ } else {
+ char errbuf[1024] = { 0 };
+ if (errfd && saferead(errfd, errbuf, sizeof(errbuf) - 1) > 0) {
+ virReportError(VIR_ERR_OPERATION_FAILED,
+ _("nbdkit failed to start: %s"), errbuf);
+ } else {
+ virReportSystemError(errno, "%s",
+ _("Gave up waiting for nbdkit to
start"));
+ }
+ goto error;
+ }
+ }
+
+ if ((rc = virPidFileReadPath(proc->pidfile, &proc->pid)) < 0) {
+ virReportSystemError(-rc,
+ _("Failed to read pidfile %s"),
+ proc->pidfile);
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ if (proc->pid)
+ virProcessKillPainfully(proc->pid, true);
+ return -1;
+}
+
+
+int
+qemuNbdkitProcessStop(qemuNbdkitProcess *proc)
+{
+ return virProcessKillPainfully(proc->pid, true);
+}
diff --git a/src/qemu/qemu_nbdkit.h b/src/qemu/qemu_nbdkit.h
new file mode 100644
index 0000000000..628b5010cc
--- /dev/null
+++ b/src/qemu/qemu_nbdkit.h
@@ -0,0 +1,89 @@
+/*
+ * qemu_nbdkit.h: helpers for using nbdkit
+ *
+ * Copyright (C) 2021 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 "internal.h"
+#include "virbitmap.h"
+#include "vircgroup.h"
+#include "vircommand.h"
+#include "virstorageobj.h"
+#include "viruri.h"
+
+typedef struct _qemuNbdkitCaps qemuNbdkitCaps;
+typedef struct _qemuNbdkitProcess qemuNbdkitProcess;
+
+typedef enum {
+ QEMU_NBDKIT_CAPS_PLUGIN_CURL,
+ QEMU_NBDKIT_CAPS_PLUGIN_SSH,
+ QEMU_NBDKIT_CAPS_FILTER_READAHEAD,
+ QEMU_NBDKIT_CAPS_LAST,
+} qemuNbdkitCapsFlags;
+
+qemuNbdkitCaps* qemuNbdkitCapsQuery(void);
+
+qemuNbdkitCaps* qemuNbdkitCapsNew(const char *path);
+
+void qemuNbdkitInitStorageSource(qemuNbdkitCaps *nbdkitCaps,
+ virStorageSource *source,
+ char *statedir,
+ const char *alias,
+ uid_t user,
+ gid_t group
+ /*, char *selinux_label*/);
+
+bool qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag);
+
+void qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps, qemuNbdkitCapsFlags flag);
+
+#define QEMU_TYPE_NBDKIT_CAPS qemu_nbdkit_caps_get_type()
+G_DECLARE_FINAL_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, QEMU, NBDKIT_CAPS, GObject);
+
+struct _qemuNbdkitProcess {
+ qemuNbdkitCaps *caps;
+ virStorageSource *source;
+
+ char *pidfile;
+ char *socketfile;
+ int cookiefd;
+ int authfd;
+ uid_t user;
+ gid_t group;
+ pid_t pid;
+};
+
+typedef struct _virQEMUDriver virQEMUDriver;
+
+int qemuNbdkitProcessStart(qemuNbdkitProcess *proc,
+ virDomainObj *vm,
+ virQEMUDriver *driver);
+
+qemuNbdkitProcess * qemuNbdkitProcessLoad(virStorageSource *source,
+ const char *pidfile,
+ const char *socketfile);
+
+int qemuNbdkitProcessSetupCgroup(qemuNbdkitProcess *proc, virCgroup *cgroup);
+
+int qemuNbdkitProcessStop(qemuNbdkitProcess *proc);
+
+void qemuNbdkitProcessFree(qemuNbdkitProcess *proc);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuNbdkitProcess, qemuNbdkitProcessFree);
diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
index 39210ba65b..b6cc75b651 100644
--- a/src/qemu/qemu_validate.c
+++ b/src/qemu/qemu_validate.c
@@ -602,7 +602,8 @@ qemuValidateDomainDefPM(const virDomainDef *def,
static int
qemuValidateDomainDefNvram(const virDomainDef *def,
- virQEMUCaps *qemuCaps)
+ virQEMUCaps *qemuCaps,
+ qemuNbdkitCaps *nbdkitCaps)
{
virStorageSource *src = def->os.loader->nvram;
@@ -655,7 +656,7 @@ qemuValidateDomainDefNvram(const virDomainDef *def,
return -1;
}
- if (qemuDomainValidateStorageSource(src, qemuCaps, false) < 0)
+ if (qemuDomainValidateStorageSource(src, qemuCaps, nbdkitCaps, false) < 0)
return -1;
return 0;
@@ -664,7 +665,8 @@ qemuValidateDomainDefNvram(const virDomainDef *def,
static int
qemuValidateDomainDefBoot(const virDomainDef *def,
- virQEMUCaps *qemuCaps)
+ virQEMUCaps *qemuCaps,
+ qemuNbdkitCaps *nbdkitCaps)
{
if (def->os.loader) {
if (def->os.loader->secure == VIR_TRISTATE_BOOL_YES) {
@@ -695,7 +697,7 @@ qemuValidateDomainDefBoot(const virDomainDef *def,
}
}
- if (qemuValidateDomainDefNvram(def, qemuCaps) < 0)
+ if (qemuValidateDomainDefNvram(def, qemuCaps, nbdkitCaps) < 0)
return -1;
}
@@ -1167,6 +1169,7 @@ qemuValidateDomainDef(const virDomainDef *def,
virQEMUDriver *driver = opaque;
g_autoptr(virQEMUCaps) qemuCapsLocal = NULL;
virQEMUCaps *qemuCaps = parseOpaque;
+ g_autoptr(qemuNbdkitCaps) nbdkitCaps = qemuGetNbdkitCaps(driver);
size_t i;
if (!qemuCaps) {
@@ -1277,7 +1280,7 @@ qemuValidateDomainDef(const virDomainDef *def,
if (qemuValidateDomainDefPM(def, qemuCaps) < 0)
return -1;
- if (qemuValidateDomainDefBoot(def, qemuCaps) < 0)
+ if (qemuValidateDomainDefBoot(def, qemuCaps, nbdkitCaps) < 0)
return -1;
if (qemuValidateDomainVCpuTopology(def, qemuCaps) < 0)
@@ -3240,7 +3243,8 @@ qemuValidateDomainDeviceDefDiskTransient(const virDomainDiskDef
*disk,
int
qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk,
const virDomainDef *def,
- virQEMUCaps *qemuCaps)
+ virQEMUCaps *qemuCaps,
+ qemuNbdkitCaps *nbdkitCaps)
{
const char *driverName = virDomainDiskGetDriver(disk);
bool isSD = qemuDiskBusIsSD(disk->bus);
@@ -3287,7 +3291,7 @@ qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk,
for (n = disk->src; virStorageSourceIsBacking(n); n = n->backingStore) {
/* blockdev support is masked out for 'sd' disks */
- if (qemuDomainValidateStorageSource(n, qemuCaps, isSD) < 0)
+ if (qemuDomainValidateStorageSource(n, qemuCaps, nbdkitCaps, isSD) < 0)
return -1;
}
@@ -5234,6 +5238,7 @@ qemuValidateDomainDeviceDef(const virDomainDeviceDef *dev,
virQEMUDriver *driver = opaque;
g_autoptr(virQEMUCaps) qemuCapsLocal = NULL;
virQEMUCaps *qemuCaps = parseOpaque;
+ g_autoptr(qemuNbdkitCaps) nbdkitcaps = qemuGetNbdkitCaps(driver);
if (!qemuCaps) {
if (!(qemuCapsLocal = virQEMUCapsCacheLookup(driver->qemuCapsCache,
@@ -5273,7 +5278,8 @@ qemuValidateDomainDeviceDef(const virDomainDeviceDef *dev,
return qemuValidateDomainDeviceDefVideo(dev->data.video, qemuCaps);
case VIR_DOMAIN_DEVICE_DISK:
- return qemuValidateDomainDeviceDefDisk(dev->data.disk, def, qemuCaps);
+ return qemuValidateDomainDeviceDefDisk(dev->data.disk, def, qemuCaps,
+ nbdkitcaps);
case VIR_DOMAIN_DEVICE_CONTROLLER:
return qemuValidateDomainDeviceDefController(dev->data.controller, def,
diff --git a/src/qemu/qemu_validate.h b/src/qemu/qemu_validate.h
index e06a43b8e3..1498a978ed 100644
--- a/src/qemu/qemu_validate.h
+++ b/src/qemu/qemu_validate.h
@@ -21,6 +21,7 @@
#pragma once
#include "qemu_capabilities.h"
+#include "qemu_nbdkit.h"
int
qemuValidateDomainDef(const virDomainDef *def,
@@ -30,7 +31,8 @@ qemuValidateDomainDef(const virDomainDef *def,
int
qemuValidateDomainDeviceDefDisk(const virDomainDiskDef *disk,
const virDomainDef *def,
- virQEMUCaps *qemuCaps);
+ virQEMUCaps *qemuCaps,
+ qemuNbdkitCaps *nbdkitCaps);
int
qemuValidateDomainDeviceDef(const virDomainDeviceDef *dev,
diff --git a/src/util/virerror.c b/src/util/virerror.c
index d114c0a346..5b665931ca 100644
--- a/src/util/virerror.c
+++ b/src/util/virerror.c
@@ -145,6 +145,7 @@ VIR_ENUM_IMPL(virErrorDomain,
"TPM", /* 70 */
"BPF",
"Cloud-Hypervisor Driver",
+ "Nbdkit",
);
diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c
index 57116c930b..18dd1f61fe 100644
--- a/tests/qemublocktest.c
+++ b/tests/qemublocktest.c
@@ -280,7 +280,8 @@ testQemuDiskXMLToProps(const void *opaque)
virDomainDiskInsert(vmdef, disk);
- if (qemuValidateDomainDeviceDefDisk(disk, vmdef, data->qemuCaps) < 0) {
+ /* FIXME: test with nbdkit */
+ if (qemuValidateDomainDeviceDefDisk(disk, vmdef, data->qemuCaps, NULL) < 0) {
VIR_TEST_VERBOSE("invalid configuration for disk");
return -1;
}
@@ -294,7 +295,8 @@ testQemuDiskXMLToProps(const void *opaque)
if (testQemuDiskXMLToJSONFakeSecrets(n) < 0)
return -1;
- if (qemuDomainValidateStorageSource(n, data->qemuCaps, false) < 0)
+ /* FIXME: test with nbdkit */
+ if (qemuDomainValidateStorageSource(n, data->qemuCaps, NULL, false) < 0)
return -1;
qemuDomainPrepareDiskSourceData(disk, n);
@@ -519,7 +521,7 @@ testQemuImageCreate(const void *opaque)
src->capacity = UINT_MAX * 2ULL;
src->physical = UINT_MAX + 1ULL;
- if (qemuDomainValidateStorageSource(src, data->qemuCaps, false) < 0)
+ if (qemuDomainValidateStorageSource(src, data->qemuCaps, NULL, false) < 0)
return -1;
if (qemuBlockStorageSourceCreateGetStorageProps(src, &protocolprops) < 0)
diff --git a/tests/qemustatusxml2xmldata/modern-in.xml
b/tests/qemustatusxml2xmldata/modern-in.xml
index 7759034f7a..49b005cfc7 100644
--- a/tests/qemustatusxml2xmldata/modern-in.xml
+++ b/tests/qemustatusxml2xmldata/modern-in.xml
@@ -337,7 +337,6 @@
<objects>
<secret type='auth' alias='test-auth-alias'/>
<secret type='encryption'
alias='test-encryption-alias'/>
- <secret type='httpcookie'
alias='http-cookie-alias'/>
<secret type='tlskey'
alias='tls-certificate-key-alias'/>
<TLSx509 alias='transport-alias'/>
</objects>
diff --git a/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args
b/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args
new file mode 100644
index 0000000000..c944a3ead4
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.x86_64-latest.args
@@ -0,0 +1,42 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/tmp/lib/domain--1-QEMUGuest1 \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \
+XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \
+XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \
+/usr/bin/qemu-system-x86_64 \
+-name guest=QEMUGuest1,debug-threads=on \
+-S \
+-object
'{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}'
\
+-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \
+-accel kvm \
+-cpu qemu64 \
+-m 1024 \
+-object
'{"qom-type":"memory-backend-ram","id":"pc.ram","size":1073741824}'
\
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-boot strict=on \
+-device
'{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-ide0-0-0.socket"},"node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-3-format","read-only":true,"driver":"raw","file":"libvirt-3-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"ide.0","unit":0,"drive":"libvirt-3-format","id":"ide0-0-0","bootindex":1}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-ide0-0-1.socket"},"node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-2-format","read-only":true,"driver":"raw","file":"libvirt-2-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"ide.0","unit":1,"drive":"libvirt-2-format","id":"ide0-0-1"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-ide0-1-0.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"ide.1","unit":0,"drive":"libvirt-1-format","id":"ide0-1-0"}'
\
+-audiodev
'{"id":"audio1","driver":"none"}' \
+-device
'{"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.0","addr":"0x2"}'
\
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml
b/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml
new file mode 120000
index 0000000000..55f677546f
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-cdrom-network-nbdkit.xml
@@ -0,0 +1 @@
+disk-cdrom-network.xml
\ No newline at end of file
diff --git a/tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args
b/tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args
new file mode 100644
index 0000000000..378dcc3e62
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-network-http-nbdkit.x86_64-latest.args
@@ -0,0 +1,45 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/tmp/lib/domain--1-QEMUGuest1 \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \
+XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \
+XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \
+/usr/bin/qemu-system-x86_64 \
+-name guest=QEMUGuest1,debug-threads=on \
+-S \
+-object
'{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}'
\
+-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \
+-accel kvm \
+-cpu qemu64 \
+-m 214 \
+-object
'{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}'
\
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-no-acpi \
+-boot strict=on \
+-device
'{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-virtio-disk0.socket"},"node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-4-format","read-only":false,"driver":"raw","file":"libvirt-4-storage"}'
\
+-device
'{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x2","drive":"libvirt-4-format","id":"virtio-disk0","bootindex":1}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-virtio-disk1.socket"},"node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-3-format","read-only":false,"driver":"raw","file":"libvirt-3-storage"}'
\
+-device
'{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x3","drive":"libvirt-3-format","id":"virtio-disk1"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-virtio-disk2.socket"},"node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-2-format","read-only":false,"driver":"raw","file":"libvirt-2-storage"}'
\
+-device
'{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-2-format","id":"virtio-disk2"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-virtio-disk3.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-1-format","read-only":false,"driver":"raw","file":"libvirt-1-storage"}'
\
+-device
'{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x5","drive":"libvirt-1-format","id":"virtio-disk3"}'
\
+-audiodev
'{"id":"audio1","driver":"none"}' \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxml2argvdata/disk-network-http-nbdkit.xml
b/tests/qemuxml2argvdata/disk-network-http-nbdkit.xml
new file mode 120000
index 0000000000..6a05204e8a
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-network-http-nbdkit.xml
@@ -0,0 +1 @@
+disk-network-http.xml
\ No newline at end of file
diff --git a/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args
b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args
new file mode 100644
index 0000000000..2410d8dfa5
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.x86_64-latest.args
@@ -0,0 +1,49 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/tmp/lib/domain--1-QEMUGuest1 \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \
+XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \
+XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \
+/usr/bin/qemu-system-x86_64 \
+-name guest=QEMUGuest1,debug-threads=on \
+-S \
+-object
'{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}'
\
+-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \
+-accel tcg \
+-cpu qemu64 \
+-m 214 \
+-object
'{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}'
\
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-no-acpi \
+-boot strict=on \
+-device
'{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}'
\
+-device
'{"driver":"ahci","id":"sata0","bus":"pci.0","addr":"0x2"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-virtio-disk0.socket"},"node-name":"libvirt-5-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-5-format","read-only":true,"driver":"raw","file":"libvirt-5-storage"}'
\
+-device
'{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x3","drive":"libvirt-5-format","id":"virtio-disk0","bootindex":1}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-virtio-disk4.socket"},"node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-4-format","read-only":false,"driver":"luks","key-secret":"libvirt-4-format-encryption-secret0","file":"libvirt-4-storage"}'
\
+-device
'{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-4-format","id":"virtio-disk4"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-sata0-0-1.socket"},"node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-3-format","read-only":true,"driver":"raw","file":"libvirt-3-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"sata0.1","drive":"libvirt-3-format","id":"sata0-0-1"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-sata0-0-2.socket"},"node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-2-format","read-only":true,"driver":"raw","file":"libvirt-2-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"sata0.2","drive":"libvirt-2-format","id":"sata0-0-2"}'
\
+-blockdev
'{"driver":"nbd","server":{"type":"unix","path":"/tmp/lib/domain--1-QEMUGuest1/nbdkit-sata0-0-3.socket"},"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"sata0.3","drive":"libvirt-1-format","id":"sata0-0-3"}'
\
+-audiodev
'{"id":"audio1","driver":"none"}' \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml
b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml
new file mode 120000
index 0000000000..4a1e40bd70
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-network-source-curl-nbdkit.xml
@@ -0,0 +1 @@
+disk-network-source-curl.xml
\ No newline at end of file
diff --git a/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args
b/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args
new file mode 100644
index 0000000000..ec6dd13f6c
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-network-source-curl.x86_64-latest.args
@@ -0,0 +1,53 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/tmp/lib/domain--1-QEMUGuest1 \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/tmp/lib/domain--1-QEMUGuest1/.local/share \
+XDG_CACHE_HOME=/tmp/lib/domain--1-QEMUGuest1/.cache \
+XDG_CONFIG_HOME=/tmp/lib/domain--1-QEMUGuest1/.config \
+/usr/bin/qemu-system-x86_64 \
+-name guest=QEMUGuest1,debug-threads=on \
+-S \
+-object
'{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/tmp/lib/domain--1-QEMUGuest1/master-key.aes"}'
\
+-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram \
+-accel tcg \
+-cpu qemu64 \
+-m 214 \
+-object
'{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}'
\
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-no-acpi \
+-boot strict=on \
+-device
'{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}'
\
+-device
'{"driver":"ahci","id":"sata0","bus":"pci.0","addr":"0x2"}'
\
+-object
'{"qom-type":"secret","id":"libvirt-5-storage-httpcookie-secret0","data":"BUU0KmnWfonHdjzhYhwVQZ5iTI1KweTJ22q8XWUVoBCVu1z70reDuczPBIabZtC3","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}'
\
+-blockdev
'{"driver":"https","url":"https://https.example.org:8443/path/to/disk1.iso","cookie-secret":"libvirt-5-storage-httpcookie-secret0","node-name":"libvirt-5-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-5-format","read-only":true,"driver":"raw","file":"libvirt-5-storage"}'
\
+-device
'{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x3","drive":"libvirt-5-format","id":"virtio-disk0","bootindex":1}'
\
+-object
'{"qom-type":"secret","id":"libvirt-4-format-encryption-secret0","data":"9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}'
\
+-object
'{"qom-type":"secret","id":"libvirt-4-storage-httpcookie-secret0","data":"BUU0KmnWfonHdjzhYhwVQZ5iTI1KweTJ22q8XWUVoBCVu1z70reDuczPBIabZtC3","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}'
\
+-blockdev
'{"driver":"https","url":"https://https.example.org:8443/path/to/disk5.iso?foo=bar","sslverify":false,"cookie-secret":"libvirt-4-storage-httpcookie-secret0","node-name":"libvirt-4-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-4-format","read-only":false,"driver":"luks","key-secret":"libvirt-4-format-encryption-secret0","file":"libvirt-4-storage"}'
\
+-device
'{"driver":"virtio-blk-pci","bus":"pci.0","addr":"0x4","drive":"libvirt-4-format","id":"virtio-disk4"}'
\
+-object
'{"qom-type":"secret","id":"libvirt-3-storage-httpcookie-secret0","data":"BUU0KmnWfonHdjzhYhwVQZ5iTI1KweTJ22q8XWUVoBBv7TuTgTkyAyOPpC2P5qLbOIypLoHpppjz+u5O+X8oT+jA1m7q/OJQ8dk2EFD5c0A=","keyid":"masterKey0","iv":"AAECAwQFBgcICQoLDA0ODw==","format":"base64"}'
\
+-blockdev
'{"driver":"http","url":"http://http.example.org:8080/path/to/disk2.iso","cookie-secret":"libvirt-3-storage-httpcookie-secret0","node-name":"libvirt-3-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-3-format","read-only":true,"driver":"raw","file":"libvirt-3-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"sata0.1","drive":"libvirt-3-format","id":"sata0-0-1"}'
\
+-blockdev
'{"driver":"ftp","url":"ftp://ftp.example.org:20/path/to/disk3.iso","node-name":"libvirt-2-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-2-format","read-only":true,"driver":"raw","file":"libvirt-2-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"sata0.2","drive":"libvirt-2-format","id":"sata0-0-2"}'
\
+-blockdev
'{"driver":"ftps","url":"ftps://ftps.example.org:22/path/to/disk4.iso","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}'
\
+-blockdev
'{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}'
\
+-device
'{"driver":"ide-cd","bus":"sata0.3","drive":"libvirt-1-format","id":"sata0-0-3"}'
\
+-audiodev
'{"id":"audio1","driver":"none"}' \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxml2argvdata/disk-network-source-curl.xml
b/tests/qemuxml2argvdata/disk-network-source-curl.xml
new file mode 100644
index 0000000000..1e50314abe
--- /dev/null
+++ b/tests/qemuxml2argvdata/disk-network-source-curl.xml
@@ -0,0 +1,71 @@
+<domain type='qemu'>
+ <name>QEMUGuest1</name>
+ <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+ <memory unit='KiB'>219136</memory>
+ <currentMemory unit='KiB'>219136</currentMemory>
+ <vcpu placement='static'>1</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <disk type='network' device='disk'>
+ <source protocol='https' name='path/to/disk1.iso'>
+ <host name='https.example.org' port='8443'/>
+ <cookies>
+ <cookie name='cookie1'>cookievalue1</cookie>
+ <cookie name='cookie2'>cookievalue2</cookie>
+ </cookies>
+ </source>
+ <target dev='vda' bus='virtio'/>
+ <readonly/>
+ </disk>
+ <disk type='network' device='cdrom'>
+ <source protocol='http' name='path/to/disk2.iso'>
+ <host name='http.example.org' port='8080'/>
+ <cookies>
+ <cookie name='cookie1'>cookievalue1</cookie>
+ <cookie name='cookie2'>cookievalue2</cookie>
+ <cookie name='cookie3'>cookievalue3</cookie>
+ </cookies>
+ </source>
+ <target dev='hdb' bus='sata'/>
+ </disk>
+ <disk type='network' device='cdrom'>
+ <source protocol='ftp' name='path/to/disk3.iso'>
+ <host name='ftp.example.org' port='20'/>
+ </source>
+ <target dev='hdc' bus='sata'/>
+ </disk>
+ <disk type='network' device='cdrom'>
+ <source protocol='ftps' name='path/to/disk4.iso'>
+ <host name='ftps.example.org' port='22'/>
+ </source>
+ <target dev='hdd' bus='sata'/>
+ </disk>
+ <disk type='network' device='disk'>
+ <source protocol='https' name='path/to/disk5.iso'
query='foo=bar'>
+ <host name='https.example.org' port='8443'/>
+ <ssl verify='no'/>
+ <cookies>
+ <cookie name='cookie1'>cookievalue1</cookie>
+ <cookie name='cookie2'>cookievalue2</cookie>
+ </cookies>
+ <encryption format='luks'>
+ <secret type='passphrase'
uuid='1148b693-0843-4cef-9f97-8feb4e1ae365'/>
+ </encryption>
+ </source>
+ <target dev='vde' bus='virtio'/>
+ </disk>
+ <controller type='usb' index='0'/>
+ <controller type='pci' index='0' model='pci-root'/>
+ <input type='mouse' bus='ps2'/>
+ <input type='keyboard' bus='ps2'/>
+ <memballoon model='none'/>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index 48dd20458e..379ea307f8 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -705,6 +705,11 @@ testCompareXMLToArgv(const void *data)
if (rc < 0)
goto cleanup;
+ if (info->nbdkitCaps) {
+ driver.nbdkitCaps = info->nbdkitCaps;
+ g_object_add_weak_pointer(G_OBJECT(info->nbdkitCaps),
(void**)&driver.nbdkitCaps);
+ }
+
if (info->migrateFrom &&
!(migrateURI = qemuMigrationDstGetURI(info->migrateFrom,
info->migrateFd)))
@@ -963,6 +968,9 @@ mymain(void)
# define DO_TEST_CAPS_ARCH_VER(name, arch, ver) \
DO_TEST_CAPS_ARCH_VER_FULL(name, arch, ver, ARG_END)
+# define DO_TEST_CAPS_LATEST_NBDKIT(name, ...) \
+ DO_TEST_CAPS_ARCH_LATEST_FULL(name, "x86_64", ARG_NBDKIT_CAPS, __VA_ARGS__,
QEMU_NBDKIT_CAPS_LAST, ARG_END)
+
# define DO_TEST_CAPS_LATEST(name) \
DO_TEST_CAPS_ARCH_LATEST(name, "x86_64")
@@ -1330,6 +1338,7 @@ mymain(void)
DO_TEST_CAPS_LATEST("disk-cdrom-bus-other");
DO_TEST_CAPS_VER("disk-cdrom-network", "4.1.0");
DO_TEST_CAPS_LATEST("disk-cdrom-network");
+ DO_TEST_CAPS_LATEST_NBDKIT("disk-cdrom-network-nbdkit",
QEMU_NBDKIT_CAPS_PLUGIN_CURL);
DO_TEST_CAPS_VER("disk-cdrom-tray", "4.1.0");
DO_TEST_CAPS_LATEST("disk-cdrom-tray");
DO_TEST_CAPS_VER("disk-floppy", "4.1.0");
@@ -1390,6 +1399,8 @@ mymain(void)
DO_TEST_CAPS_VER("disk-network-sheepdog", "6.0.0");
DO_TEST_CAPS_VER("disk-network-source-auth", "4.1.0");
DO_TEST_CAPS_LATEST("disk-network-source-auth");
+ DO_TEST_CAPS_LATEST("disk-network-source-curl");
+ DO_TEST_CAPS_LATEST_NBDKIT("disk-network-source-curl-nbdkit",
QEMU_NBDKIT_CAPS_PLUGIN_CURL);
DO_TEST_CAPS_LATEST("disk-network-nfs");
driver.config->vxhsTLS = 1;
driver.config->nbdTLSx509secretUUID =
g_strdup("6fd3f62d-9fe7-4a4e-a869-7acd6376d8ea");
@@ -1402,6 +1413,7 @@ mymain(void)
DO_TEST_CAPS_LATEST("disk-network-tlsx509-nbd-hostname");
DO_TEST_CAPS_VER("disk-network-tlsx509-vxhs", "5.0.0");
DO_TEST_CAPS_LATEST("disk-network-http");
+ DO_TEST_CAPS_LATEST_NBDKIT("disk-network-http-nbdkit",
QEMU_NBDKIT_CAPS_PLUGIN_CURL);
driver.config->vxhsTLS = 0;
VIR_FREE(driver.config->vxhsTLSx509certdir);
DO_TEST_CAPS_LATEST("disk-no-boot");
diff --git a/tests/testutilsqemu.c b/tests/testutilsqemu.c
index 6dabbaf36a..65b403c991 100644
--- a/tests/testutilsqemu.c
+++ b/tests/testutilsqemu.c
@@ -395,6 +395,7 @@ void qemuTestDriverFree(virQEMUDriver *driver)
virObjectUnref(driver->caps);
virObjectUnref(driver->config);
virObjectUnref(driver->securityManager);
+ g_clear_object(&driver->nbdkitCaps);
virCPUDefFree(cpuDefault);
virCPUDefFree(cpuHaswell);
@@ -632,6 +633,8 @@ int qemuTestDriverInit(virQEMUDriver *driver)
if (!driver->caps)
goto error;
+ driver->nbdkitCaps = NULL;
+
/* Using /dev/null for libDir and cacheDir automatically produces errors
* upon attempt to use any of them */
driver->qemuCapsCache = virQEMUCapsCacheNew("/dev/null",
"/dev/null", 0, 0);
@@ -858,6 +861,7 @@ testQemuInfoSetArgs(struct testQemuInfo *info,
info->conf = conf;
info->args.newargs = true;
+ info->args.fakeNbdkitCaps = qemuNbdkitCapsNew("/bin/true");
va_start(argptr, conf);
while ((argname = va_arg(argptr, testQemuInfoArgName)) != ARG_END) {
@@ -869,6 +873,13 @@ testQemuInfoSetArgs(struct testQemuInfo *info,
virQEMUCapsSet(info->args.fakeCaps, flag);
break;
+ case ARG_NBDKIT_CAPS:
+ info->args.fakeNbdkitCapsUsed = true;
+
+ while ((flag = va_arg(argptr, int)) < QEMU_NBDKIT_CAPS_LAST)
+ qemuNbdkitCapsSet(info->args.fakeNbdkitCaps, flag);
+ break;
+
case ARG_GIC:
info->args.gic = va_arg(argptr, int);
break;
@@ -993,6 +1004,9 @@ testQemuInfoInitArgs(struct testQemuInfo *info)
info->qemuCaps = g_steal_pointer(&info->args.fakeCaps);
}
+ if (info->args.fakeNbdkitCapsUsed)
+ info->nbdkitCaps = g_steal_pointer(&info->args.fakeNbdkitCaps);
+
if (info->args.gic != GIC_NONE &&
testQemuCapsSetGIC(info->qemuCaps, info->args.gic) < 0)
return -1;
@@ -1010,6 +1024,8 @@ testQemuInfoClear(struct testQemuInfo *info)
VIR_FREE(info->errfile);
virObjectUnref(info->qemuCaps);
g_clear_pointer(&info->args.fakeCaps, virObjectUnref);
+ g_clear_object(&info->nbdkitCaps);
+ g_clear_object(&info->args.fakeNbdkitCaps);
}
diff --git a/tests/testutilsqemu.h b/tests/testutilsqemu.h
index 7ce4c4ad8d..7d407004b4 100644
--- a/tests/testutilsqemu.h
+++ b/tests/testutilsqemu.h
@@ -49,6 +49,7 @@ typedef enum {
ARG_CAPS_VER,
ARG_CAPS_HOST_CPU_MODEL,
ARG_HOST_OS,
+ ARG_NBDKIT_CAPS,
ARG_END,
} testQemuInfoArgName;
@@ -79,6 +80,8 @@ struct testQemuArgs {
bool newargs;
virQEMUCaps *fakeCaps;
bool fakeCapsUsed;
+ qemuNbdkitCaps *fakeNbdkitCaps;
+ bool fakeNbdkitCapsUsed;
char *capsver;
char *capsarch;
qemuTestCPUDef capsHostCPUModel;
@@ -93,6 +96,7 @@ struct testQemuInfo {
char *outfile;
char *errfile;
virQEMUCaps *qemuCaps;
+ qemuNbdkitCaps *nbdkitCaps;
const char *migrateFrom;
int migrateFd;
unsigned int flags;
--
2.35.3