2017-03-13 12:46:18 +01:00
|
|
|
/*
|
|
|
|
* 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"
|
2018-07-03 14:13:29 +02:00
|
|
|
#include "qemu_command.h"
|
2017-03-13 12:46:18 +01:00
|
|
|
#include "qemu_domain.h"
|
2017-09-25 11:44:00 +02:00
|
|
|
#include "qemu_alias.h"
|
2019-11-19 08:43:58 +01:00
|
|
|
#include "qemu_security.h"
|
2023-10-26 15:44:34 +02:00
|
|
|
#include "qemu_process.h"
|
2024-09-13 10:33:47 +02:00
|
|
|
#include "qemu_chardev.h"
|
2017-03-13 12:46:18 +01:00
|
|
|
|
2021-01-22 10:36:21 +01:00
|
|
|
#include "storage_source.h"
|
2017-03-13 12:46:18 +01:00
|
|
|
#include "viralloc.h"
|
|
|
|
#include "virstring.h"
|
2018-05-16 13:39:22 +02:00
|
|
|
#include "virlog.h"
|
2017-03-13 12:46:18 +01:00
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
|
2018-05-16 13:39:22 +02:00
|
|
|
VIR_LOG_INIT("qemu.qemu_block");
|
|
|
|
|
2018-03-28 09:04:49 +02:00
|
|
|
/* qemu declares the buffer for node names as a 32 byte array */
|
|
|
|
static const size_t qemuBlockNodeNameBufSize = 32;
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuBlockNodeNameValidate(const char *nn)
|
|
|
|
{
|
|
|
|
if (!nn)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (strlen(nn) >= qemuBlockNodeNameBufSize) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("node-name '%1$s' too long for qemu"), nn);
|
2018-03-28 09:04:49 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-13 12:46:18 +01:00
|
|
|
|
2021-06-21 17:01:12 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceSetStorageNodename:
|
|
|
|
* @src: virStorageSource to set the storage nodename
|
|
|
|
* @nodename: The node name to set (stolen)
|
|
|
|
*
|
|
|
|
* Sets @nodename as the storage node name of @src. Using NULL @nodename clears
|
|
|
|
* the nodename. @src takes ownership of @nodename.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
qemuBlockStorageSourceSetStorageNodename(virStorageSource *src,
|
|
|
|
char *nodename)
|
|
|
|
{
|
2023-09-11 15:46:33 +02:00
|
|
|
g_free(src->nodenamestorage);
|
|
|
|
src->nodenamestorage = nodename;
|
2021-06-21 17:01:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-09-20 15:24:14 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceSetFormatNodename:
|
|
|
|
* @src: virStorageSource to set the format nodename
|
|
|
|
* @nodename: The node name to set (stolen)
|
|
|
|
*
|
|
|
|
* Sets @nodename as the format node name of @src. Using NULL @nodename clears
|
|
|
|
* the nodename. @src takes ownership of @nodename.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
qemuBlockStorageSourceSetFormatNodename(virStorageSource *src,
|
|
|
|
char *nodename)
|
|
|
|
{
|
2023-08-17 15:36:51 +02:00
|
|
|
g_free(src->nodenameformat);
|
|
|
|
src->nodenameformat = nodename;
|
2023-09-20 15:24:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-09-20 15:47:51 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetEffectiveNodename:
|
|
|
|
* @src: virStorageSource to get the effective nodename of
|
|
|
|
*
|
|
|
|
* Gets the nodename that exposes the guest visible data. This function always
|
|
|
|
* returns a name.
|
|
|
|
*/
|
|
|
|
const char *
|
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(virStorageSource *src)
|
|
|
|
{
|
2023-10-19 17:52:37 +02:00
|
|
|
if (src->nodenameformat)
|
|
|
|
return src->nodenameformat;
|
|
|
|
|
|
|
|
return qemuBlockStorageSourceGetEffectiveStorageNodename(src);
|
2023-09-20 15:47:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-11-24 10:14:52 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetSliceNodename:
|
|
|
|
*
|
|
|
|
* Gets the nodename corresponding to the storage slice layer. Returns NULL
|
|
|
|
* when there is no explicit storage slice layer.
|
|
|
|
*/
|
|
|
|
const char *
|
|
|
|
qemuBlockStorageSourceGetSliceNodename(virStorageSource *src)
|
|
|
|
{
|
|
|
|
if (!src->sliceStorage)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return src->sliceStorage->nodename;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-21 17:01:12 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetEffectiveStorageNodename:
|
|
|
|
* @src: virStorageSource to get the effective nodename of
|
|
|
|
*
|
|
|
|
* Gets the nodename that exposes the storage corresponding to @src, without
|
|
|
|
* the format driver applied. This function always returns a name.
|
|
|
|
*/
|
|
|
|
const char *
|
|
|
|
qemuBlockStorageSourceGetEffectiveStorageNodename(virStorageSource *src)
|
|
|
|
{
|
2023-11-24 10:14:52 +01:00
|
|
|
const char *slice = qemuBlockStorageSourceGetSliceNodename(src);
|
|
|
|
|
|
|
|
if (slice)
|
|
|
|
return slice;
|
2021-06-21 17:01:12 +02:00
|
|
|
|
2023-09-11 15:46:33 +02:00
|
|
|
return src->nodenamestorage;
|
2021-06-21 17:01:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetStorageNodename:
|
|
|
|
* @src: virStorageSource to get the effective nodename of
|
|
|
|
*
|
|
|
|
* Gets the nodename corresponding to the real backing storage format layer.
|
|
|
|
*/
|
|
|
|
const char *
|
|
|
|
qemuBlockStorageSourceGetStorageNodename(virStorageSource *src)
|
|
|
|
{
|
2023-09-11 15:46:33 +02:00
|
|
|
return src->nodenamestorage;
|
2021-06-21 17:01:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-09-20 15:24:14 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetFormatNodename:
|
|
|
|
* @src: virStorageSource to get the effective nodename of
|
|
|
|
*
|
|
|
|
* Gets the nodename corresponding to the format layer. Useful when accessing
|
|
|
|
* format specific features. Returns NULL if there is no format layer.
|
|
|
|
*/
|
|
|
|
const char *
|
|
|
|
qemuBlockStorageSourceGetFormatNodename(virStorageSource *src)
|
|
|
|
{
|
2023-08-17 15:36:51 +02:00
|
|
|
return src->nodenameformat;
|
2023-09-20 15:24:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-14 15:34:46 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceSupportsConcurrentAccess:
|
|
|
|
* @src: disk storage source
|
|
|
|
*
|
|
|
|
* Returns true if the given storage format supports concurrent access from two
|
|
|
|
* separate processes.
|
|
|
|
*/
|
|
|
|
bool
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceSupportsConcurrentAccess(virStorageSource *src)
|
2017-11-14 15:34:46 +01:00
|
|
|
{
|
|
|
|
/* no need to check in backing chain since only RAW storage supports this */
|
2023-12-12 17:16:25 +01:00
|
|
|
return qemuBlockStorageSourceIsRaw(src);
|
2017-11-14 15:34:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-13 15:38:50 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetURI:
|
|
|
|
* @src: disk storage source
|
|
|
|
*
|
|
|
|
* Formats a URI from a virStorageSource.
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
virURI *
|
|
|
|
qemuBlockStorageSourceGetURI(virStorageSource *src)
|
2017-07-13 15:38:50 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virURI) uri = NULL;
|
2017-07-13 15:38:50 +02:00
|
|
|
|
|
|
|
if (src->nhosts != 1) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("protocol '%1$s' accepts only one host"),
|
2017-07-13 15:38:50 +02:00
|
|
|
virStorageNetProtocolTypeToString(src->protocol));
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-07-13 15:38:50 +02:00
|
|
|
}
|
|
|
|
|
2020-10-05 12:28:26 +02:00
|
|
|
uri = g_new0(virURI, 1);
|
2017-07-13 15:38:50 +02:00
|
|
|
|
|
|
|
if (src->hosts->transport == VIR_STORAGE_NET_HOST_TRANS_TCP) {
|
|
|
|
uri->port = src->hosts->port;
|
|
|
|
|
2019-10-20 13:49:46 +02:00
|
|
|
uri->scheme = g_strdup(virStorageNetProtocolTypeToString(src->protocol));
|
2017-07-13 15:38:50 +02:00
|
|
|
} else {
|
2019-10-22 15:26:14 +02:00
|
|
|
uri->scheme = g_strdup_printf("%s+%s",
|
|
|
|
virStorageNetProtocolTypeToString(src->protocol),
|
|
|
|
virStorageNetHostTransportTypeToString(src->hosts->transport));
|
2017-07-13 15:38:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (src->path) {
|
|
|
|
if (src->volume) {
|
2019-10-22 15:26:14 +02:00
|
|
|
uri->path = g_strdup_printf("/%s/%s", src->volume, src->path);
|
2017-07-13 15:38:50 +02:00
|
|
|
} else {
|
2021-04-20 12:44:12 +08:00
|
|
|
uri->path = g_strdup_printf("%s%s",
|
|
|
|
g_path_is_absolute(src->path) ? "" : "/",
|
2019-10-22 15:26:14 +02:00
|
|
|
src->path);
|
2017-07-13 15:38:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-27 16:34:37 +01:00
|
|
|
uri->query = g_strdup(src->query);
|
|
|
|
|
2019-10-20 13:49:46 +02:00
|
|
|
uri->server = g_strdup(src->hosts->name);
|
2017-07-13 15:38:50 +02:00
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&uri);
|
2017-07-13 15:38:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-12 07:43:31 -04:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceBuildJSONSocketAddress
|
2021-03-11 08:16:13 +01:00
|
|
|
* @host: the virStorageNetHostDef * definition to build
|
2017-09-12 07:43:31 -04:00
|
|
|
*
|
|
|
|
* Formats @hosts into a json object conforming to the 'SocketAddress' type
|
|
|
|
* in qemu.
|
|
|
|
*
|
2021-03-11 08:16:13 +01:00
|
|
|
* Returns a virJSONValue * for a single server.
|
2017-09-12 07:43:31 -04:00
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
2022-07-19 15:45:22 +02:00
|
|
|
qemuBlockStorageSourceBuildJSONSocketAddress(virStorageNetHostDef *host)
|
2017-09-12 07:43:31 -04:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) server = NULL;
|
2019-10-15 15:16:31 +02:00
|
|
|
g_autofree char *port = NULL;
|
2017-09-12 07:43:31 -04:00
|
|
|
|
2023-09-13 18:21:21 +02:00
|
|
|
switch (host->transport) {
|
2017-09-12 07:43:31 -04:00
|
|
|
case VIR_STORAGE_NET_HOST_TRANS_TCP:
|
2019-10-22 15:26:14 +02:00
|
|
|
port = g_strdup_printf("%u", host->port);
|
2017-09-12 07:43:31 -04:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&server,
|
2022-07-19 15:45:22 +02:00
|
|
|
"s:type", "inet",
|
2021-11-08 17:24:50 +01:00
|
|
|
"s:host", host->name,
|
|
|
|
"s:port", port,
|
|
|
|
NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-09-12 07:43:31 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_HOST_TRANS_UNIX:
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&server,
|
|
|
|
"s:type", "unix",
|
2022-07-19 15:45:22 +02:00
|
|
|
"s:path", host->socket,
|
2021-11-08 17:24:50 +01:00
|
|
|
NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-09-12 07:43:31 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_HOST_TRANS_RDMA:
|
|
|
|
case VIR_STORAGE_NET_HOST_TRANS_LAST:
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("transport protocol '%1$s' is not yet supported"),
|
2017-09-12 07:43:31 -04:00
|
|
|
virStorageNetHostTransportTypeToString(host->transport));
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-09-12 07:43:31 -04:00
|
|
|
}
|
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&server);
|
2017-09-12 07:43:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-07 17:55:04 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceBuildHostsJSONSocketAddress:
|
|
|
|
* @src: disk storage source
|
|
|
|
*
|
|
|
|
* Formats src->hosts into a json object conforming to the 'SocketAddress' type
|
|
|
|
* in qemu.
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
2022-07-19 15:45:22 +02:00
|
|
|
qemuBlockStorageSourceBuildHostsJSONSocketAddress(virStorageSource *src)
|
2017-07-07 16:29:01 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) servers = NULL;
|
|
|
|
g_autoptr(virJSONValue) server = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageNetHostDef *host;
|
2017-07-07 16:29:01 +02:00
|
|
|
size_t i;
|
|
|
|
|
2020-01-31 08:18:36 +01:00
|
|
|
servers = virJSONValueNewArray();
|
2017-07-07 16:29:01 +02:00
|
|
|
|
|
|
|
for (i = 0; i < src->nhosts; i++) {
|
|
|
|
host = src->hosts + i;
|
|
|
|
|
2022-07-19 15:45:22 +02:00
|
|
|
if (!(server = qemuBlockStorageSourceBuildJSONSocketAddress(host)))
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-07-07 16:29:01 +02:00
|
|
|
|
2021-02-11 17:57:45 +01:00
|
|
|
if (virJSONValueArrayAppend(servers, &server) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-07-07 16:29:01 +02:00
|
|
|
}
|
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&servers);
|
2017-07-07 16:29:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-10-23 18:02:28 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceBuildJSONInetSocketAddress
|
2021-03-11 08:16:13 +01:00
|
|
|
* @host: the virStorageNetHostDef * definition to build
|
2017-10-23 18:02:28 +02:00
|
|
|
*
|
|
|
|
* Formats @hosts into a json object conforming to the 'InetSocketAddress' type
|
|
|
|
* in qemu.
|
|
|
|
*
|
2021-03-11 08:16:13 +01:00
|
|
|
* Returns a virJSONValue *for a single server.
|
2017-10-23 18:02:28 +02:00
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceBuildJSONInetSocketAddress(virStorageNetHostDef *host)
|
2017-10-23 18:02:28 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2019-10-15 15:16:31 +02:00
|
|
|
g_autofree char *port = NULL;
|
2017-10-23 18:02:28 +02:00
|
|
|
|
|
|
|
if (host->transport != VIR_STORAGE_NET_HOST_TRANS_TCP) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("only TCP protocol can be converted to InetSocketAddress"));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-10-22 15:26:14 +02:00
|
|
|
port = g_strdup_printf("%u", host->port);
|
2017-10-23 18:02:28 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
|
|
"s:host", host->name,
|
|
|
|
"s:port", port,
|
|
|
|
NULL));
|
2017-10-23 18:02:28 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-06 15:32:30 -06:00
|
|
|
/**
|
2021-03-11 08:16:13 +01:00
|
|
|
* qemuBlockStorageSourceBuildJSONNFSServer(virStorageNetHostDef *host)
|
|
|
|
* @host: the virStorageNetHostDef * definition to build
|
2021-01-06 15:32:30 -06:00
|
|
|
*
|
|
|
|
* Formats @hosts into a json object conforming to the 'NFSServer' type
|
|
|
|
* in qemu.
|
|
|
|
*
|
2021-03-11 08:16:13 +01:00
|
|
|
* Returns a virJSONValue *for a single server.
|
2021-01-06 15:32:30 -06:00
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceBuildJSONNFSServer(virStorageNetHostDef *host)
|
2021-01-06 15:32:30 -06:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2021-01-06 15:32:30 -06:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
|
|
"s:host", host->name,
|
|
|
|
"s:type", "inet",
|
|
|
|
NULL));
|
2021-01-06 15:32:30 -06:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-10-23 18:16:53 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceBuildHostsJSONInetSocketAddress:
|
|
|
|
* @src: disk storage source
|
|
|
|
*
|
|
|
|
* Formats src->hosts into a json object conforming to the 'InetSocketAddress'
|
|
|
|
* type in qemu.
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceBuildHostsJSONInetSocketAddress(virStorageSource *src)
|
2017-10-23 18:16:53 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) servers = NULL;
|
|
|
|
g_autoptr(virJSONValue) server = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageNetHostDef *host;
|
2017-10-23 18:16:53 +02:00
|
|
|
size_t i;
|
|
|
|
|
2020-01-31 08:18:36 +01:00
|
|
|
servers = virJSONValueNewArray();
|
2017-10-23 18:16:53 +02:00
|
|
|
|
|
|
|
for (i = 0; i < src->nhosts; i++) {
|
|
|
|
host = src->hosts + i;
|
|
|
|
|
|
|
|
if (!(server = qemuBlockStorageSourceBuildJSONInetSocketAddress(host)))
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 18:16:53 +02:00
|
|
|
|
2021-02-11 17:57:45 +01:00
|
|
|
if (virJSONValueArrayAppend(servers, &server) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 18:16:53 +02:00
|
|
|
}
|
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&servers);
|
2017-10-23 18:16:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetGlusterProps(virStorageSource *src,
|
2018-09-04 18:52:02 +02:00
|
|
|
bool onlytarget)
|
2017-07-07 16:29:01 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) servers = NULL;
|
|
|
|
g_autoptr(virJSONValue) props = NULL;
|
2017-07-07 16:29:01 +02:00
|
|
|
|
2022-07-19 15:45:22 +02:00
|
|
|
if (!(servers = qemuBlockStorageSourceBuildHostsJSONSocketAddress(src)))
|
2017-07-07 16:29:01 +02:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* { driver:"gluster",
|
|
|
|
* volume:"testvol",
|
|
|
|
* path:"/a.img",
|
|
|
|
* server :[{type:"tcp", host:"1.2.3.4", port:24007},
|
|
|
|
* {type:"unix", socket:"/tmp/glusterd.socket"}, ...]}
|
|
|
|
*/
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
|
|
|
"s:volume", src->volume,
|
|
|
|
"s:path", src->path,
|
|
|
|
"a:server", &servers, NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-11-23 16:07:47 +01:00
|
|
|
|
2018-09-04 18:52:02 +02:00
|
|
|
if (!onlytarget &&
|
|
|
|
src->debug &&
|
2021-11-08 17:18:59 +01:00
|
|
|
virJSONValueObjectAdd(&props, "u:debug", src->debugLevel, NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-11-23 16:07:47 +01:00
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&props);
|
2017-07-07 16:29:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetVxHSProps(virStorageSource *src,
|
2018-09-04 18:52:02 +02:00
|
|
|
bool onlytarget)
|
qemu: Add qemu command line generation for a VxHS block device
The VxHS block device will only use the newer formatting options and
avoid the legacy URI syntax.
An excerpt for a sample QEMU command line is:
-drive file.driver=vxhs,file.vdisk-id=eb90327c-8302-4725-9e1b-4e85ed4dc251,\
file.server.type=tcp,file.server.host=192.168.0.1,\
file.server.port=9999,format=raw,if=none,id=drive-virtio-disk0,cache=none \
-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\
id=virtio-disk0
Update qemuxml2argvtest with a simple test.
Signed-off-by: Ashish Mittal <Ashish.Mittal@veritas.com>
Signed-off-by: John Ferlan <jferlan@redhat.com>
2017-08-30 09:46:53 -04:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) server = NULL;
|
2018-09-04 18:52:02 +02:00
|
|
|
const char *tlsAlias = src->tlsAlias;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
qemu: Add qemu command line generation for a VxHS block device
The VxHS block device will only use the newer formatting options and
avoid the legacy URI syntax.
An excerpt for a sample QEMU command line is:
-drive file.driver=vxhs,file.vdisk-id=eb90327c-8302-4725-9e1b-4e85ed4dc251,\
file.server.type=tcp,file.server.host=192.168.0.1,\
file.server.port=9999,format=raw,if=none,id=drive-virtio-disk0,cache=none \
-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\
id=virtio-disk0
Update qemuxml2argvtest with a simple test.
Signed-off-by: Ashish Mittal <Ashish.Mittal@veritas.com>
Signed-off-by: John Ferlan <jferlan@redhat.com>
2017-08-30 09:46:53 -04:00
|
|
|
|
|
|
|
if (src->nhosts != 1) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("VxHS protocol accepts only one host"));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-10-23 18:02:28 +02:00
|
|
|
if (!(server = qemuBlockStorageSourceBuildJSONInetSocketAddress(&src->hosts[0])))
|
qemu: Add qemu command line generation for a VxHS block device
The VxHS block device will only use the newer formatting options and
avoid the legacy URI syntax.
An excerpt for a sample QEMU command line is:
-drive file.driver=vxhs,file.vdisk-id=eb90327c-8302-4725-9e1b-4e85ed4dc251,\
file.server.type=tcp,file.server.host=192.168.0.1,\
file.server.port=9999,format=raw,if=none,id=drive-virtio-disk0,cache=none \
-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\
id=virtio-disk0
Update qemuxml2argvtest with a simple test.
Signed-off-by: Ashish Mittal <Ashish.Mittal@veritas.com>
Signed-off-by: John Ferlan <jferlan@redhat.com>
2017-08-30 09:46:53 -04:00
|
|
|
return NULL;
|
|
|
|
|
2018-09-04 18:52:02 +02:00
|
|
|
if (onlytarget)
|
|
|
|
tlsAlias = NULL;
|
|
|
|
|
qemu: Add qemu command line generation for a VxHS block device
The VxHS block device will only use the newer formatting options and
avoid the legacy URI syntax.
An excerpt for a sample QEMU command line is:
-drive file.driver=vxhs,file.vdisk-id=eb90327c-8302-4725-9e1b-4e85ed4dc251,\
file.server.type=tcp,file.server.host=192.168.0.1,\
file.server.port=9999,format=raw,if=none,id=drive-virtio-disk0,cache=none \
-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\
id=virtio-disk0
Update qemuxml2argvtest with a simple test.
Signed-off-by: Ashish Mittal <Ashish.Mittal@veritas.com>
Signed-off-by: John Ferlan <jferlan@redhat.com>
2017-08-30 09:46:53 -04:00
|
|
|
/* VxHS disk specification example:
|
|
|
|
* { driver:"vxhs",
|
qemu: Add TLS support for Veritas HyperScale (VxHS)
Alter qemu command line generation in order to possibly add TLS for
a suitably configured domain.
Sample TLS args generated by libvirt -
-object tls-creds-x509,id=objvirtio-disk0_tls0,dir=/etc/pki/qemu,\
endpoint=client,verify-peer=yes \
-drive file.driver=vxhs,file.tls-creds=objvirtio-disk0_tls0,\
file.vdisk-id=eb90327c-8302-4725-9e1b-4e85ed4dc251,\
file.server.type=tcp,file.server.host=192.168.0.1,\
file.server.port=9999,format=raw,if=none,\
id=drive-virtio-disk0,cache=none \
-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\
id=virtio-disk0
Update the qemuxml2argvtest with a couple of examples. One for a
simple case and the other a bit more complex where multiple VxHS disks
are added where at least one uses a VxHS that doesn't require TLS
credentials and thus sets the domain disk source attribute "tls = 'no'".
Update the hotplug to be able to handle processing the tlsAlias whether
it's to add the TLS object when hotplugging a disk or to remove the TLS
object when hot unplugging a disk. The hot plug/unplug code is largely
generic, but the addition code does make the VXHS specific checks only
because it needs to grab the correct config directory and generate the
object as the command line would do.
Signed-off-by: Ashish Mittal <Ashish.Mittal@veritas.com>
Signed-off-by: John Ferlan <jferlan@redhat.com>
2017-08-30 11:06:00 -04:00
|
|
|
* tls-creds:"objvirtio-disk0_tls0",
|
qemu: Add qemu command line generation for a VxHS block device
The VxHS block device will only use the newer formatting options and
avoid the legacy URI syntax.
An excerpt for a sample QEMU command line is:
-drive file.driver=vxhs,file.vdisk-id=eb90327c-8302-4725-9e1b-4e85ed4dc251,\
file.server.type=tcp,file.server.host=192.168.0.1,\
file.server.port=9999,format=raw,if=none,id=drive-virtio-disk0,cache=none \
-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\
id=virtio-disk0
Update qemuxml2argvtest with a simple test.
Signed-off-by: Ashish Mittal <Ashish.Mittal@veritas.com>
Signed-off-by: John Ferlan <jferlan@redhat.com>
2017-08-30 09:46:53 -04:00
|
|
|
* vdisk-id:"eb90327c-8302-4725-4e85ed4dc251",
|
|
|
|
* server:{type:"tcp", host:"1.2.3.4", port:9999}}
|
|
|
|
*/
|
2021-11-08 17:24:50 +01:00
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
|
|
"S:tls-creds", tlsAlias,
|
|
|
|
"s:vdisk-id", src->path,
|
|
|
|
"a:server", &server, NULL));
|
qemu: Add qemu command line generation for a VxHS block device
The VxHS block device will only use the newer formatting options and
avoid the legacy URI syntax.
An excerpt for a sample QEMU command line is:
-drive file.driver=vxhs,file.vdisk-id=eb90327c-8302-4725-9e1b-4e85ed4dc251,\
file.server.type=tcp,file.server.host=192.168.0.1,\
file.server.port=9999,format=raw,if=none,id=drive-virtio-disk0,cache=none \
-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\
id=virtio-disk0
Update qemuxml2argvtest with a simple test.
Signed-off-by: Ashish Mittal <Ashish.Mittal@veritas.com>
Signed-off-by: John Ferlan <jferlan@redhat.com>
2017-08-30 09:46:53 -04:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetNFSProps(virStorageSource *src)
|
2021-01-06 15:32:30 -06:00
|
|
|
{
|
|
|
|
g_autoptr(virJSONValue) server = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2021-01-06 15:32:30 -06:00
|
|
|
|
|
|
|
if (!(server = qemuBlockStorageSourceBuildJSONNFSServer(&src->hosts[0])))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* NFS disk specification example:
|
|
|
|
* { driver:"nfs",
|
|
|
|
* user: "0",
|
|
|
|
* group: "0",
|
|
|
|
* path: "/foo/bar/baz",
|
|
|
|
* server: {type:"tcp", host:"1.2.3.4"}}
|
|
|
|
*/
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&ret,
|
|
|
|
"a:server", &server,
|
|
|
|
"S:path", src->path, NULL) < 0)
|
2021-01-06 15:32:30 -06:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (src->nfs_uid != -1 &&
|
2021-11-08 17:18:59 +01:00
|
|
|
virJSONValueObjectAdd(&ret, "i:user", src->nfs_uid, NULL) < 0)
|
2021-01-06 15:32:30 -06:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (src->nfs_gid != -1 &&
|
2021-11-08 17:18:59 +01:00
|
|
|
virJSONValueObjectAdd(&ret, "i:group", src->nfs_gid, NULL) < 0)
|
2021-01-06 15:32:30 -06:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetCURLProps(virStorageSource *src,
|
2018-09-04 18:52:02 +02:00
|
|
|
bool onlytarget)
|
2017-07-13 15:48:06 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
2017-07-13 15:48:06 +02:00
|
|
|
const char *passwordalias = NULL;
|
2017-05-04 12:55:49 +02:00
|
|
|
const char *cookiealias = NULL;
|
2017-07-13 15:48:06 +02:00
|
|
|
const char *username = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virURI) uri = NULL;
|
2019-10-15 15:16:31 +02:00
|
|
|
g_autofree char *uristr = NULL;
|
2020-03-23 16:36:22 +01:00
|
|
|
g_autofree char *cookiestr = NULL;
|
2017-07-13 15:48:06 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Common options:
|
|
|
|
* url, readahead, timeout, username, password-secret, proxy-username,
|
|
|
|
* proxy-password-secret
|
|
|
|
*
|
|
|
|
* Options for http transport:
|
|
|
|
* cookie, cookie-secret
|
|
|
|
*
|
|
|
|
* Options for secure transport (ftps, https):
|
|
|
|
* sslverify
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
if (!(uri = qemuBlockStorageSourceGetURI(src)))
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-07-13 15:48:06 +02:00
|
|
|
|
2023-02-03 15:38:03 +01:00
|
|
|
uristr = virURIFormat(uri);
|
2017-07-13 15:48:06 +02:00
|
|
|
|
2017-05-04 12:55:49 +02:00
|
|
|
if (!onlytarget) {
|
|
|
|
if (src->auth) {
|
|
|
|
username = src->auth->username;
|
2021-09-22 09:34:31 +02:00
|
|
|
passwordalias = srcPriv->secinfo->alias;
|
2017-05-04 12:55:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (srcPriv &&
|
|
|
|
srcPriv->httpcookie)
|
2021-09-22 09:34:31 +02:00
|
|
|
cookiealias = srcPriv->httpcookie->alias;
|
2020-03-23 16:36:22 +01:00
|
|
|
} else {
|
|
|
|
/* format target string along with cookies */
|
|
|
|
cookiestr = qemuBlockStorageSourceGetCookieString(src);
|
2017-07-13 15:48:06 +02:00
|
|
|
}
|
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
|
|
"s:url", uristr,
|
|
|
|
"S:username", username,
|
|
|
|
"S:password-secret", passwordalias,
|
|
|
|
"T:sslverify", src->sslverify,
|
|
|
|
"S:cookie", cookiestr,
|
|
|
|
"S:cookie-secret", cookiealias,
|
|
|
|
"P:timeout", src->timeout,
|
|
|
|
"P:readahead", src->readahead,
|
|
|
|
NULL));
|
2017-07-13 15:48:06 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-08 17:13:34 -05:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetNbdkitProps(virStorageSource *src)
|
|
|
|
{
|
|
|
|
qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
|
|
|
virJSONValue *ret = NULL;
|
|
|
|
g_autoptr(virJSONValue) serverprops = NULL;
|
|
|
|
virStorageNetHostDef host = { .transport = VIR_STORAGE_NET_HOST_TRANS_UNIX };
|
|
|
|
|
|
|
|
/* srcPriv->nbdkitProcess will already be initialized if we can use nbdkit
|
|
|
|
* to proxy this storage source */
|
|
|
|
if (!(srcPriv && srcPriv->nbdkitProcess))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
host.socket = srcPriv->nbdkitProcess->socketfile;
|
|
|
|
serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&host);
|
|
|
|
|
|
|
|
if (!serverprops)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (virJSONValueObjectAdd(&ret, "a:server", &serverprops, NULL) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetISCSIProps(virStorageSource *src,
|
2018-09-04 18:52:02 +02:00
|
|
|
bool onlytarget)
|
2017-10-19 17:44:57 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
2019-10-15 15:16:31 +02:00
|
|
|
g_autofree char *target = NULL;
|
2017-10-19 17:44:57 +02:00
|
|
|
char *lunStr = NULL;
|
|
|
|
char *username = NULL;
|
|
|
|
char *objalias = NULL;
|
2019-10-15 15:16:31 +02:00
|
|
|
g_autofree char *portal = NULL;
|
2017-10-19 17:44:57 +02:00
|
|
|
unsigned int lun = 0;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2017-10-19 17:44:57 +02:00
|
|
|
|
|
|
|
/* { driver:"iscsi",
|
|
|
|
* transport:"tcp", ("iser" also possible)
|
|
|
|
* portal:"example.com",
|
|
|
|
* target:"iqn.2017-04.com.example:iscsi-disks",
|
|
|
|
* lun:1,
|
|
|
|
* user:"username",
|
|
|
|
* password-secret:"secret-alias",
|
2018-08-07 14:57:43 +02:00
|
|
|
* initiator-name:"iqn.2017-04.com.example:client"
|
2017-10-19 17:44:57 +02:00
|
|
|
* }
|
|
|
|
*/
|
|
|
|
|
2024-06-06 19:57:51 +05:30
|
|
|
if (src->nhosts != 1) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("iSCSI protocol accepts only one host"));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-10-20 13:49:46 +02:00
|
|
|
target = g_strdup(src->path);
|
2017-10-19 17:44:57 +02:00
|
|
|
|
|
|
|
/* Separate the target and lun */
|
|
|
|
if ((lunStr = strchr(target, '/'))) {
|
|
|
|
*(lunStr++) = '\0';
|
|
|
|
if (virStrToLong_ui(lunStr, NULL, 10, &lun) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("cannot parse target for lunStr '%1$s'"),
|
2017-10-19 17:44:57 +02:00
|
|
|
target);
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-19 17:44:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* combine host and port into portal */
|
|
|
|
if (virSocketAddrNumericFamily(src->hosts[0].name) == AF_INET6) {
|
2019-10-22 15:26:14 +02:00
|
|
|
portal = g_strdup_printf("[%s]:%u", src->hosts[0].name,
|
|
|
|
src->hosts[0].port);
|
2017-10-19 17:44:57 +02:00
|
|
|
} else {
|
2019-10-22 15:26:14 +02:00
|
|
|
portal = g_strdup_printf("%s:%u", src->hosts[0].name, src->hosts[0].port);
|
2017-10-19 17:44:57 +02:00
|
|
|
}
|
|
|
|
|
2018-09-04 18:52:02 +02:00
|
|
|
if (!onlytarget && src->auth) {
|
2017-10-19 17:44:57 +02:00
|
|
|
username = src->auth->username;
|
2021-09-22 09:34:31 +02:00
|
|
|
objalias = srcPriv->secinfo->alias;
|
2017-10-19 17:44:57 +02:00
|
|
|
}
|
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
|
|
"s:portal", portal,
|
|
|
|
"s:target", target,
|
|
|
|
"u:lun", lun,
|
|
|
|
"s:transport", "tcp",
|
|
|
|
"S:user", username,
|
|
|
|
"S:password-secret", objalias,
|
|
|
|
"S:initiator-name", src->initiator.iqn,
|
|
|
|
NULL));
|
2017-10-19 17:44:57 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetNBDProps(virStorageSource *src,
|
2018-09-04 18:52:02 +02:00
|
|
|
bool onlytarget)
|
2017-07-13 15:48:06 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) serverprops = NULL;
|
2018-09-04 18:52:02 +02:00
|
|
|
const char *tlsAlias = src->tlsAlias;
|
2022-03-10 09:57:09 +01:00
|
|
|
const char *tlsHostname = src->tlsHostname;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2017-07-13 15:48:06 +02:00
|
|
|
|
|
|
|
if (src->nhosts != 1) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("nbd protocol accepts only one host"));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-07-19 15:45:22 +02:00
|
|
|
if (!(serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&src->hosts[0])))
|
2017-07-13 15:48:06 +02:00
|
|
|
return NULL;
|
|
|
|
|
2022-03-10 09:57:09 +01:00
|
|
|
if (onlytarget) {
|
2018-09-04 18:52:02 +02:00
|
|
|
tlsAlias = NULL;
|
2022-03-10 09:57:09 +01:00
|
|
|
tlsHostname = NULL;
|
|
|
|
}
|
2018-09-04 18:52:02 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&ret,
|
|
|
|
"a:server", &serverprops,
|
|
|
|
"S:export", src->path,
|
|
|
|
"S:tls-creds", tlsAlias,
|
2022-03-10 09:57:09 +01:00
|
|
|
"S:tls-hostname", tlsHostname,
|
2023-02-27 12:15:33 +01:00
|
|
|
"p:reconnect-delay", src->reconnectDelay,
|
2021-11-08 17:24:50 +01:00
|
|
|
NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-11-09 10:29:53 +01:00
|
|
|
|
2017-07-13 15:48:06 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetRBDProps(virStorageSource *src,
|
2018-09-04 18:52:02 +02:00
|
|
|
bool onlytarget)
|
2017-10-23 18:16:53 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) servers = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2021-10-24 04:51:29 -05:00
|
|
|
g_autoptr(virJSONValue) encrypt = NULL;
|
2022-04-22 16:16:18 +02:00
|
|
|
const char *encformat = NULL;
|
2017-10-23 18:16:53 +02:00
|
|
|
const char *username = NULL;
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) authmodes = NULL;
|
2018-07-09 16:05:16 +02:00
|
|
|
const char *keysecret = NULL;
|
2017-10-23 18:16:53 +02:00
|
|
|
|
|
|
|
if (src->nhosts > 0 &&
|
|
|
|
!(servers = qemuBlockStorageSourceBuildHostsJSONInetSocketAddress(src)))
|
|
|
|
return NULL;
|
|
|
|
|
2018-09-04 18:52:02 +02:00
|
|
|
if (!onlytarget && src->auth) {
|
2021-09-22 09:34:31 +02:00
|
|
|
username = srcPriv->secinfo->username;
|
|
|
|
keysecret = srcPriv->secinfo->alias;
|
2018-07-09 16:05:16 +02:00
|
|
|
/* the auth modes are modelled after our old command line generator */
|
2021-12-20 14:22:06 +01:00
|
|
|
if (!(authmodes = virJSONValueFromString("[\"cephx\",\"none\"]")))
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2018-07-09 16:05:16 +02:00
|
|
|
}
|
2017-10-23 18:16:53 +02:00
|
|
|
|
2021-10-24 04:51:29 -05:00
|
|
|
if (src->encryption &&
|
|
|
|
src->encryption->engine == VIR_STORAGE_ENCRYPTION_ENGINE_LIBRBD) {
|
2023-03-13 04:50:21 -05:00
|
|
|
size_t i;
|
|
|
|
|
2021-10-24 04:51:29 -05:00
|
|
|
switch ((virStorageEncryptionFormatType) src->encryption->format) {
|
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_LUKS:
|
|
|
|
encformat = "luks";
|
|
|
|
break;
|
|
|
|
|
2021-10-24 04:51:30 -05:00
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_LUKS2:
|
|
|
|
encformat = "luks2";
|
|
|
|
break;
|
|
|
|
|
2023-03-13 04:50:23 -05:00
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_LUKS_ANY:
|
|
|
|
encformat = "luks-any";
|
|
|
|
break;
|
|
|
|
|
2021-10-24 04:51:29 -05:00
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_QCOW:
|
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT:
|
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_LAST:
|
|
|
|
default:
|
2022-04-22 16:16:18 +02:00
|
|
|
break;
|
2021-10-24 04:51:29 -05:00
|
|
|
}
|
|
|
|
|
2023-03-13 04:50:21 -05:00
|
|
|
for (i = src->encryption->nsecrets; i > 0; --i) {
|
|
|
|
g_autoptr(virJSONValue) new = NULL;
|
|
|
|
|
|
|
|
/* we consume the lower layer 'encrypt' into a new object */
|
|
|
|
if (virJSONValueObjectAdd(&new,
|
|
|
|
"s:format", encformat,
|
|
|
|
"s:key-secret", srcPriv->encinfo[i-1]->alias,
|
|
|
|
"A:parent", &encrypt,
|
|
|
|
NULL) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
encrypt = g_steal_pointer(&new);
|
|
|
|
}
|
2021-10-24 04:51:29 -05:00
|
|
|
}
|
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&ret,
|
|
|
|
"s:pool", src->volume,
|
|
|
|
"s:image", src->path,
|
|
|
|
"S:snapshot", src->snapshot,
|
|
|
|
"S:conf", src->configFile,
|
|
|
|
"A:server", &servers,
|
|
|
|
"A:encrypt", &encrypt,
|
|
|
|
"S:user", username,
|
|
|
|
"A:auth-client-required", &authmodes,
|
|
|
|
"S:key-secret", keysecret,
|
|
|
|
NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 18:16:53 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetSheepdogProps(virStorageSource *src)
|
2017-10-23 18:44:35 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) serverprops = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2017-10-23 18:44:35 +02:00
|
|
|
|
|
|
|
if (src->nhosts != 1) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("sheepdog protocol accepts only one host"));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-07-19 15:45:22 +02:00
|
|
|
if (!(serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&src->hosts[0])))
|
2017-10-23 18:44:35 +02:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* libvirt does not support the 'snap-id' and 'tag' properties */
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&ret,
|
|
|
|
"a:server", &serverprops,
|
|
|
|
"s:vdi", src->path,
|
|
|
|
NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 18:44:35 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-10-23 18:54:12 +02:00
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetSshProps(virStorageSource *src)
|
2017-10-23 18:54:12 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) serverprops = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2017-10-23 18:54:12 +02:00
|
|
|
const char *username = NULL;
|
2020-03-09 15:05:58 +01:00
|
|
|
g_autoptr(virJSONValue) host_key_check = NULL;
|
2017-10-23 18:54:12 +02:00
|
|
|
|
|
|
|
if (src->nhosts != 1) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
2021-04-19 12:28:17 +02:00
|
|
|
_("ssh protocol accepts only one host"));
|
2017-10-23 18:54:12 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
serverprops = qemuBlockStorageSourceBuildJSONInetSocketAddress(&src->hosts[0]);
|
|
|
|
if (!serverprops)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (src->auth)
|
|
|
|
username = src->auth->username;
|
2020-03-09 15:05:58 +01:00
|
|
|
else if (src->ssh_user)
|
|
|
|
username = src->ssh_user;
|
|
|
|
|
|
|
|
if (src->ssh_host_key_check_disabled &&
|
2021-11-08 17:24:50 +01:00
|
|
|
virJSONValueObjectAdd(&host_key_check,
|
|
|
|
"s:mode", "none",
|
|
|
|
NULL) < 0)
|
2020-03-09 15:05:58 +01:00
|
|
|
return NULL;
|
2017-10-23 18:54:12 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&ret,
|
|
|
|
"s:path", src->path,
|
|
|
|
"a:server", &serverprops,
|
|
|
|
"S:user", username,
|
|
|
|
"A:host-key-check", &host_key_check,
|
|
|
|
NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-11-09 10:29:53 +01:00
|
|
|
|
2017-10-23 18:54:12 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetFileProps(virStorageSource *src,
|
2023-10-19 17:37:14 +02:00
|
|
|
bool onlytarget)
|
2017-12-05 17:02:28 +01:00
|
|
|
{
|
2022-05-02 18:42:40 +02:00
|
|
|
const char *path = src->path;
|
2017-12-05 17:05:12 +01:00
|
|
|
const char *iomode = NULL;
|
2018-05-30 15:47:13 +02:00
|
|
|
const char *prManagerAlias = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2017-12-05 17:02:28 +01:00
|
|
|
|
2018-09-04 18:52:02 +02:00
|
|
|
if (!onlytarget) {
|
2022-05-02 18:42:40 +02:00
|
|
|
qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
|
|
|
|
2018-09-04 18:52:02 +02:00
|
|
|
if (src->pr)
|
|
|
|
prManagerAlias = src->pr->mgralias;
|
|
|
|
|
|
|
|
if (src->iomode != VIR_DOMAIN_DISK_IO_DEFAULT)
|
|
|
|
iomode = virDomainDiskIoTypeToString(src->iomode);
|
2022-05-02 18:42:40 +02:00
|
|
|
|
2023-10-19 17:37:14 +02:00
|
|
|
if (srcpriv && srcpriv->fdpass)
|
2022-05-02 18:42:40 +02:00
|
|
|
path = qemuFDPassGetPath(srcpriv->fdpass);
|
2018-09-04 18:52:02 +02:00
|
|
|
}
|
2018-05-30 15:47:13 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
2022-05-02 18:42:40 +02:00
|
|
|
"s:filename", path,
|
2021-11-08 17:24:50 +01:00
|
|
|
"S:aio", iomode,
|
|
|
|
"S:pr-manager", prManagerAlias,
|
|
|
|
NULL) < 0);
|
2017-12-05 17:02:28 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetVvfatProps(virStorageSource *src,
|
2018-09-04 18:52:02 +02:00
|
|
|
bool onlytarget)
|
2018-03-29 08:44:02 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) ret = NULL;
|
2018-03-29 08:44:02 +02:00
|
|
|
|
|
|
|
/* libvirt currently does not handle the following attributes:
|
|
|
|
* '*fat-type': 'int'
|
|
|
|
* '*label': 'str'
|
|
|
|
*/
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&ret,
|
|
|
|
"s:driver", "vvfat",
|
|
|
|
"s:dir", src->path,
|
|
|
|
"b:floppy", src->floppyimg, NULL) < 0)
|
2018-09-04 18:52:02 +02:00
|
|
|
return NULL;
|
2018-03-29 08:44:02 +02:00
|
|
|
|
2018-09-04 18:52:02 +02:00
|
|
|
if (!onlytarget &&
|
2021-11-08 17:18:59 +01:00
|
|
|
virJSONValueObjectAdd(&ret, "b:rw", !src->readonly, NULL) < 0)
|
2018-09-04 18:52:02 +02:00
|
|
|
return NULL;
|
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&ret);
|
2018-03-29 08:44:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetNVMeProps(virStorageSource *src)
|
2019-06-21 15:07:22 +02:00
|
|
|
{
|
|
|
|
const virStorageSourceNVMeDef *nvme = src->nvme;
|
|
|
|
g_autofree char *pciAddr = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2019-06-21 15:07:22 +02:00
|
|
|
|
|
|
|
if (!(pciAddr = virPCIDeviceAddressAsString(&nvme->pciAddr)))
|
|
|
|
return NULL;
|
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
|
|
"s:driver", "nvme",
|
|
|
|
"s:device", pciAddr,
|
|
|
|
"U:namespace", nvme->namespc,
|
|
|
|
NULL));
|
2019-06-21 15:07:22 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-17 09:58:36 -05:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetVhostVdpaProps(virStorageSource *src)
|
|
|
|
{
|
|
|
|
virJSONValue *ret = NULL;
|
|
|
|
qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
|
|
|
|
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
|
|
"s:driver", "virtio-blk-vhost-vdpa",
|
|
|
|
"s:path", qemuFDPassGetPath(srcpriv->fdpass),
|
|
|
|
NULL));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-04-25 17:04:35 +02:00
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceGetBlockdevGetCacheProps(virStorageSource *src,
|
2023-10-18 13:51:09 +02:00
|
|
|
virJSONValue **cache)
|
2018-04-25 17:04:35 +02:00
|
|
|
{
|
|
|
|
bool direct = false;
|
|
|
|
bool noflush = false;
|
|
|
|
|
2023-10-18 13:19:08 +02:00
|
|
|
if (!qemuDomainDiskCachemodeFlags(src->cachemode, NULL, &direct, &noflush))
|
2018-04-25 17:04:35 +02:00
|
|
|
return 0;
|
|
|
|
|
2023-10-18 13:51:09 +02:00
|
|
|
if (virJSONValueObjectAdd(cache,
|
2021-11-08 17:24:50 +01:00
|
|
|
"b:direct", direct,
|
|
|
|
"b:no-flush", noflush,
|
|
|
|
NULL) < 0)
|
2018-04-25 17:04:35 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-10-19 16:04:11 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceAddBlockdevCommonProps:
|
|
|
|
* @props: JSON object to append the props to
|
|
|
|
* @src: storage source
|
|
|
|
* @nodename: nodename to use for @src
|
|
|
|
* @effective: Whether props for the effective(topmost) layer are to be formatted
|
|
|
|
*
|
|
|
|
* Add the common props (node name, read-only state, cache configuration, discard)
|
|
|
|
* to a JSON object for a -blockdev definition. If @effective is true,
|
|
|
|
* the props are configured for the topmost layer used to access the data,
|
|
|
|
* otherwise the props are configured for the storage protocol backing a format
|
|
|
|
* layer.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
qemuBlockStorageSourceAddBlockdevCommonProps(virJSONValue **props,
|
|
|
|
virStorageSource *src,
|
|
|
|
const char *nodename,
|
|
|
|
bool effective)
|
|
|
|
{
|
|
|
|
virStorageType actualType = virStorageSourceGetActualType(src);
|
|
|
|
g_autoptr(virJSONValue) cache = NULL;
|
|
|
|
const char *detectZeroes = NULL;
|
|
|
|
const char *discard = NULL;
|
|
|
|
virTristateBool autoReadOnly = VIR_TRISTATE_BOOL_ABSENT;
|
|
|
|
virTristateBool readOnly = VIR_TRISTATE_BOOL_ABSENT;
|
|
|
|
|
|
|
|
if (qemuBlockNodeNameValidate(nodename) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceGetBlockdevGetCacheProps(src, &cache) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (effective) {
|
|
|
|
virDomainDiskDetectZeroes dz = virDomainDiskGetDetectZeroesMode(src->discard,
|
|
|
|
src->detect_zeroes);
|
|
|
|
|
|
|
|
if (src->discard != VIR_DOMAIN_DISK_DISCARD_DEFAULT)
|
|
|
|
discard = virDomainDiskDiscardTypeToString(src->discard);
|
|
|
|
|
|
|
|
if (dz != VIR_DOMAIN_DISK_DETECT_ZEROES_DEFAULT)
|
|
|
|
detectZeroes = virDomainDiskDetectZeroesTypeToString(dz);
|
|
|
|
|
|
|
|
autoReadOnly = VIR_TRISTATE_BOOL_ABSENT;
|
|
|
|
readOnly = virTristateBoolFromBool(src->readonly);
|
|
|
|
} else {
|
|
|
|
/* when passing a FD to qemu via the /dev/fdset mechanism qemu
|
|
|
|
* fetches the appropriate FD from the fdset by checking that it has
|
|
|
|
* the correct accessmode. Now with 'auto-read-only' in effect qemu
|
|
|
|
* wants to use a read-only FD first. If the user didn't pass multiple
|
|
|
|
* FDs the feature will not work regardless, so we'll not enable it. */
|
|
|
|
if ((actualType == VIR_STORAGE_TYPE_FILE || actualType == VIR_STORAGE_TYPE_BLOCK) &&
|
|
|
|
src->fdtuple && src->fdtuple->nfds == 1) {
|
|
|
|
autoReadOnly = VIR_TRISTATE_BOOL_ABSENT;
|
|
|
|
|
|
|
|
/* now we setup the normal readonly flag. If user requested write access honour it */
|
|
|
|
if (src->fdtuple->writable)
|
|
|
|
readOnly = VIR_TRISTATE_BOOL_NO;
|
|
|
|
else
|
|
|
|
readOnly = virTristateBoolFromBool(src->readonly);
|
|
|
|
} else {
|
|
|
|
autoReadOnly = VIR_TRISTATE_BOOL_YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
discard = "unmap";
|
|
|
|
}
|
|
|
|
|
|
|
|
/* currently unhandled global properties:
|
|
|
|
* '*force-share': 'bool'
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (virJSONValueObjectAdd(props,
|
|
|
|
"s:node-name", nodename,
|
|
|
|
"T:read-only", readOnly,
|
|
|
|
"T:auto-read-only", autoReadOnly,
|
|
|
|
"S:discard", discard,
|
|
|
|
"S:detect-zeroes", detectZeroes,
|
|
|
|
"A:cache", &cache,
|
|
|
|
NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-07 17:37:42 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetBackendProps:
|
|
|
|
* @src: disk source
|
2020-06-19 16:28:12 +02:00
|
|
|
* @flags: bitwise-or of qemuBlockStorageSourceBackendPropsFlags
|
|
|
|
*
|
|
|
|
* Flags:
|
|
|
|
* QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_LEGACY:
|
|
|
|
* use legacy formatting of attributes (for -drive / old qemus)
|
|
|
|
* QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_TARGET_ONLY:
|
|
|
|
* omit any data which does not identify the image itself
|
2023-10-19 16:12:38 +02:00
|
|
|
* QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_EFFECTIVE_NODE:
|
|
|
|
* the 'protocol' node is used as the effective/top node of a virStorageSource
|
2017-07-07 17:37:42 +02:00
|
|
|
*
|
|
|
|
* Creates a JSON object describing the underlying storage or protocol of a
|
|
|
|
* storage source. Returns NULL on error and reports an appropriate error message.
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetBackendProps(virStorageSource *src,
|
2020-06-19 16:28:12 +02:00
|
|
|
unsigned int flags)
|
2017-07-07 16:29:01 +02:00
|
|
|
{
|
2022-01-23 12:10:35 +01:00
|
|
|
virStorageType actualType = virStorageSourceGetActualType(src);
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) fileprops = NULL;
|
2019-02-05 16:03:39 +01:00
|
|
|
const char *driver = NULL;
|
2020-06-19 16:28:12 +02:00
|
|
|
bool onlytarget = flags & QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_TARGET_ONLY;
|
|
|
|
bool legacy = flags & QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_LEGACY;
|
2018-09-04 19:29:10 +02:00
|
|
|
|
2022-01-23 12:10:35 +01:00
|
|
|
switch (actualType) {
|
2017-07-07 16:29:01 +02:00
|
|
|
case VIR_STORAGE_TYPE_BLOCK:
|
|
|
|
case VIR_STORAGE_TYPE_FILE:
|
2019-02-05 16:03:39 +01:00
|
|
|
if (virStorageSourceIsBlockLocal(src)) {
|
|
|
|
if (src->hostcdrom)
|
|
|
|
driver = "host_cdrom";
|
|
|
|
else
|
|
|
|
driver = "host_device";
|
|
|
|
} else {
|
|
|
|
driver = "file";
|
|
|
|
}
|
|
|
|
|
2023-10-19 17:37:14 +02:00
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetFileProps(src, onlytarget)))
|
2017-07-13 09:48:14 +02:00
|
|
|
return NULL;
|
|
|
|
break;
|
2018-03-29 08:44:02 +02:00
|
|
|
|
|
|
|
case VIR_STORAGE_TYPE_DIR:
|
|
|
|
/* qemu handles directories by exposing them as a device with emulated
|
|
|
|
* FAT filesystem */
|
2018-09-04 18:52:02 +02:00
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetVvfatProps(src, onlytarget)))
|
2018-03-29 08:44:02 +02:00
|
|
|
return NULL;
|
|
|
|
break;
|
2017-07-13 09:48:14 +02:00
|
|
|
|
2019-06-03 17:31:13 +02:00
|
|
|
case VIR_STORAGE_TYPE_NVME:
|
2019-06-21 15:07:22 +02:00
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetNVMeProps(src)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
2023-02-06 14:20:01 -06:00
|
|
|
case VIR_STORAGE_TYPE_VHOST_VDPA:
|
2023-03-17 09:58:36 -05:00
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetVhostVdpaProps(src)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
2023-02-06 14:20:01 -06:00
|
|
|
|
2021-01-25 18:13:29 +01:00
|
|
|
case VIR_STORAGE_TYPE_VHOST_USER:
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("unable to create blockdev props for vhostuser disk type"));
|
|
|
|
return NULL;
|
|
|
|
|
2019-06-21 15:07:22 +02:00
|
|
|
case VIR_STORAGE_TYPE_VOLUME:
|
2020-02-06 10:07:44 +01:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("storage source pool '%1$s' volume '%2$s' is not translated"),
|
2020-02-06 10:07:44 +01:00
|
|
|
src->srcpool->pool, src->srcpool->volume);
|
|
|
|
return NULL;
|
|
|
|
|
2017-07-07 16:29:01 +02:00
|
|
|
case VIR_STORAGE_TYPE_NONE:
|
|
|
|
case VIR_STORAGE_TYPE_LAST:
|
2020-02-06 10:07:44 +01:00
|
|
|
virReportEnumRangeError(virStorageType, actualType);
|
2017-11-09 10:13:26 +01:00
|
|
|
return NULL;
|
2017-07-07 16:29:01 +02:00
|
|
|
|
|
|
|
case VIR_STORAGE_TYPE_NETWORK:
|
2023-10-18 09:38:56 +02:00
|
|
|
/* prefer using nbdkit if configured for sources that are supported */
|
2022-07-08 17:13:34 -05:00
|
|
|
if ((fileprops = qemuBlockStorageSourceGetNbdkitProps(src))) {
|
2019-02-05 16:03:39 +01:00
|
|
|
driver = "nbd";
|
2021-01-06 15:32:30 -06:00
|
|
|
break;
|
2023-10-18 09:38:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
switch ((virStorageNetProtocol) src->protocol) {
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
|
|
|
driver = "gluster";
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetGlusterProps(src, onlytarget)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
|
|
|
driver = "vxhs";
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetVxHSProps(src, onlytarget)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
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:
|
|
|
|
driver = virStorageNetProtocolTypeToString(src->protocol);
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetCURLProps(src, onlytarget)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
|
|
|
driver = "iscsi";
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetISCSIProps(src, onlytarget)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
|
|
|
driver = "nbd";
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetNBDProps(src, onlytarget)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
|
|
|
driver = "rbd";
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetRBDProps(src, onlytarget)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
|
|
|
driver = "sheepdog";
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetSheepdogProps(src)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
|
|
|
driver = "ssh";
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetSshProps(src)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
|
|
|
driver = "nfs";
|
|
|
|
if (!(fileprops = qemuBlockStorageSourceGetNFSProps(src)))
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
|
|
|
virReportEnumRangeError(virStorageNetProtocol, src->protocol);
|
|
|
|
return NULL;
|
2017-07-07 16:29:01 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-02-05 16:03:39 +01:00
|
|
|
if (driver && virJSONValueObjectPrependString(fileprops, "driver", driver) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
2023-10-18 13:58:29 +02:00
|
|
|
if (!onlytarget && !legacy) {
|
2023-10-19 16:23:23 +02:00
|
|
|
if (qemuBlockStorageSourceAddBlockdevCommonProps(&fileprops, src,
|
|
|
|
qemuBlockStorageSourceGetStorageNodename(src),
|
|
|
|
!!(flags & QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_EFFECTIVE_NODE)) < 0)
|
2023-10-19 16:12:38 +02:00
|
|
|
return NULL;
|
2017-10-09 08:55:15 +02:00
|
|
|
}
|
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&fileprops);
|
2017-07-07 16:29:01 +02:00
|
|
|
}
|
2017-10-23 12:26:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceGetFormatLUKSProps(virStorageSource *src,
|
|
|
|
virJSONValue *props)
|
2017-10-23 12:26:10 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
2020-03-19 17:23:33 +01:00
|
|
|
|
2023-03-13 04:50:20 -05:00
|
|
|
/* validation ensures that the qemu encryption engine accepts only a single secret */
|
|
|
|
if (!srcPriv || !srcPriv->encinfo || !srcPriv->encinfo[0]->alias) {
|
2020-03-19 17:23:33 +01:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("missing secret info for 'luks' driver"));
|
|
|
|
return -1;
|
2017-10-23 12:26:10 +02:00
|
|
|
}
|
|
|
|
|
2021-11-08 17:18:59 +01:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
2020-03-19 17:23:33 +01:00
|
|
|
"s:driver", "luks",
|
2023-03-13 04:50:20 -05:00
|
|
|
"s:key-secret", srcPriv->encinfo[0]->alias,
|
2020-03-19 17:23:33 +01:00
|
|
|
NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-10-23 12:26:10 +02:00
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceGetCryptoProps(virStorageSource *src,
|
|
|
|
virJSONValue **encprops)
|
2017-10-23 12:26:10 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
2017-10-23 12:26:10 +02:00
|
|
|
const char *encformat = NULL;
|
|
|
|
|
|
|
|
*encprops = NULL;
|
|
|
|
|
|
|
|
if (!src->encryption ||
|
2021-10-24 04:51:28 -05:00
|
|
|
src->encryption->engine != VIR_STORAGE_ENCRYPTION_ENGINE_QEMU ||
|
2017-10-23 12:26:10 +02:00
|
|
|
!srcpriv ||
|
2021-09-22 09:34:31 +02:00
|
|
|
!srcpriv->encinfo)
|
2017-10-23 12:26:10 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
switch ((virStorageEncryptionFormatType) src->encryption->format) {
|
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_QCOW:
|
|
|
|
encformat = "aes";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_LUKS:
|
|
|
|
encformat = "luks";
|
|
|
|
break;
|
|
|
|
|
2021-10-24 04:51:30 -05:00
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_LUKS2:
|
2023-03-13 04:50:23 -05:00
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_LUKS_ANY:
|
|
|
|
/* validation code asserts the above cases are impossible */
|
2017-10-23 12:26:10 +02:00
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT:
|
|
|
|
case VIR_STORAGE_ENCRYPTION_FORMAT_LAST:
|
|
|
|
default:
|
|
|
|
virReportEnumRangeError(virStorageEncryptionFormatType,
|
|
|
|
src->encryption->format);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-03-13 04:50:20 -05:00
|
|
|
/* validation ensures that the qemu encryption engine accepts only a single secret */
|
2021-11-08 17:24:50 +01:00
|
|
|
return virJSONValueObjectAdd(encprops,
|
|
|
|
"s:format", encformat,
|
2023-03-13 04:50:20 -05:00
|
|
|
"s:key-secret", srcpriv->encinfo[0]->alias,
|
2021-11-08 17:24:50 +01:00
|
|
|
NULL);
|
2017-10-23 12:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceGetFormatQcowGenericProps(virStorageSource *src,
|
2017-10-23 12:26:10 +02:00
|
|
|
const char *format,
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *props)
|
2017-10-23 12:26:10 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) encprops = NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
|
|
|
|
if (qemuBlockStorageSourceGetCryptoProps(src, &encprops) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2021-11-08 17:18:59 +01:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
2017-10-23 12:26:10 +02:00
|
|
|
"s:driver", format,
|
|
|
|
"A:encrypt", &encprops, NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return -1;
|
2017-10-23 12:26:10 +02:00
|
|
|
|
2019-04-04 11:31:05 +02:00
|
|
|
return 0;
|
2017-10-23 12:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceGetFormatQcow2Props(virStorageSource *src,
|
|
|
|
virJSONValue *props)
|
2017-10-23 12:26:10 +02:00
|
|
|
{
|
|
|
|
/* currently unhandled qcow2 props:
|
|
|
|
*
|
|
|
|
* 'lazy-refcounts'
|
|
|
|
* 'pass-discard-request'
|
|
|
|
* 'pass-discard-snapshot'
|
|
|
|
* 'pass-discard-other'
|
|
|
|
* 'overlap-check'
|
|
|
|
* 'l2-cache-size'
|
|
|
|
* 'l2-cache-entry-size'
|
|
|
|
* 'refcount-cache-size'
|
|
|
|
* 'cache-clean-interval'
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceGetFormatQcowGenericProps(src, "qcow2", props) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2021-01-06 18:20:29 +01:00
|
|
|
/* 'cache-size' controls the maximum size of L2 and refcount caches.
|
|
|
|
* see: qemu.git/docs/qcow2-cache.txt
|
|
|
|
* https://git.qemu.org/?p=qemu.git;a=blob;f=docs/qcow2-cache.txt
|
|
|
|
*/
|
2023-06-09 12:47:36 +02:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
|
|
|
"P:cache-size", src->metadataCacheMaxSize,
|
|
|
|
"T:discard-no-unref", src->discard_no_unref,
|
|
|
|
NULL) < 0)
|
|
|
|
return -1;
|
2021-01-06 18:20:29 +01:00
|
|
|
|
2024-11-20 18:48:48 +03:00
|
|
|
if (src->dataFileStore) {
|
|
|
|
if (virJSONValueObjectAdd(&props,
|
|
|
|
"s:data-file", qemuBlockStorageSourceGetEffectiveNodename(src->dataFileStore),
|
|
|
|
NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-10-23 12:26:10 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
|
|
|
qemuBlockStorageSourceGetBlockdevFormatProps(virStorageSource *src)
|
2017-10-23 12:26:10 +02:00
|
|
|
{
|
|
|
|
const char *driver = NULL;
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) props = NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
|
2023-10-19 16:04:11 +02:00
|
|
|
if (qemuBlockStorageSourceAddBlockdevCommonProps(&props,
|
|
|
|
src,
|
|
|
|
qemuBlockStorageSourceGetFormatNodename(src),
|
|
|
|
true) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
|
|
|
|
switch ((virStorageFileFormat) src->format) {
|
|
|
|
case VIR_STORAGE_FILE_FAT:
|
|
|
|
/* The fat layer is emulated by the storage access layer, so we need to
|
|
|
|
* put a raw layer on top */
|
2023-10-23 15:46:26 +02:00
|
|
|
driver = "raw";
|
2023-12-12 17:16:25 +01:00
|
|
|
break;
|
|
|
|
|
2017-10-23 12:26:10 +02:00
|
|
|
case VIR_STORAGE_FILE_RAW:
|
2023-12-12 17:16:25 +01:00
|
|
|
if (qemuBlockStorageSourceIsLUKS(src)) {
|
2020-03-19 17:23:33 +01:00
|
|
|
if (qemuBlockStorageSourceGetFormatLUKSProps(src, props) < 0)
|
|
|
|
return NULL;
|
|
|
|
} else {
|
2023-10-23 15:46:26 +02:00
|
|
|
driver = "raw";
|
2020-03-19 17:23:33 +01:00
|
|
|
}
|
2017-10-23 12:26:10 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_QCOW2:
|
|
|
|
if (qemuBlockStorageSourceGetFormatQcow2Props(src, props) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_QCOW:
|
|
|
|
if (qemuBlockStorageSourceGetFormatQcowGenericProps(src, "qcow", props) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
/* formats without any special parameters */
|
|
|
|
case VIR_STORAGE_FILE_PLOOP:
|
|
|
|
driver = "parallels";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_VHD:
|
|
|
|
driver = "vhdx";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_BOCHS:
|
|
|
|
case VIR_STORAGE_FILE_CLOOP:
|
|
|
|
case VIR_STORAGE_FILE_DMG:
|
|
|
|
case VIR_STORAGE_FILE_VDI:
|
|
|
|
case VIR_STORAGE_FILE_VPC:
|
|
|
|
case VIR_STORAGE_FILE_QED:
|
|
|
|
case VIR_STORAGE_FILE_VMDK:
|
|
|
|
driver = virStorageFileFormatTypeToString(src->format);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_AUTO_SAFE:
|
|
|
|
case VIR_STORAGE_FILE_AUTO:
|
|
|
|
case VIR_STORAGE_FILE_NONE:
|
|
|
|
case VIR_STORAGE_FILE_COW:
|
|
|
|
case VIR_STORAGE_FILE_ISO:
|
|
|
|
case VIR_STORAGE_FILE_DIR:
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("mishandled storage format '%1$s'"),
|
2017-10-23 12:26:10 +02:00
|
|
|
virStorageFileFormatTypeToString(src->format));
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_LAST:
|
|
|
|
default:
|
|
|
|
virReportEnumRangeError(virStorageFileFormat, src->format);
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (driver &&
|
2021-11-08 17:18:59 +01:00
|
|
|
virJSONValueObjectAdd(&props, "s:driver", driver, NULL) < 0)
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&props);
|
2017-10-23 12:26:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-09-12 16:43:53 +02:00
|
|
|
* qemuBlockStorageSourceGetFormatProps:
|
2017-10-23 12:26:10 +02:00
|
|
|
*
|
|
|
|
* @src: storage source to format
|
2019-09-03 13:27:52 +02:00
|
|
|
* @backingStore: a storage source to use as backing of @src
|
2017-10-23 12:26:10 +02:00
|
|
|
*
|
2023-09-12 16:43:53 +02:00
|
|
|
* Formats properties of @src related to the format blockdev driver in qemu
|
|
|
|
* into a JSON object which can be used with blockdev-add or -blockdev.
|
2017-10-23 12:26:10 +02:00
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *
|
2023-09-12 16:43:53 +02:00
|
|
|
qemuBlockStorageSourceGetFormatProps(virStorageSource *src,
|
|
|
|
virStorageSource *backingStore)
|
2017-10-23 12:26:10 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) props = NULL;
|
2023-09-12 16:36:31 +02:00
|
|
|
const char *backingFormatterStr = NULL;
|
|
|
|
const char *backingNodename = NULL;
|
2017-10-23 12:26:10 +02:00
|
|
|
|
2023-09-12 16:36:31 +02:00
|
|
|
if (virStorageSourceIsBacking(backingStore) &&
|
|
|
|
src->format < VIR_STORAGE_FILE_BACKING) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("storage format '%1$s' does not support backing store"),
|
|
|
|
virStorageFileFormatTypeToString(src->format));
|
2019-04-04 11:31:05 +02:00
|
|
|
return NULL;
|
2023-09-12 16:36:31 +02:00
|
|
|
}
|
2017-10-23 12:26:10 +02:00
|
|
|
|
2023-09-12 16:36:31 +02:00
|
|
|
if (backingStore &&
|
|
|
|
src->format >= VIR_STORAGE_FILE_BACKING) {
|
|
|
|
if (virStorageSourceIsBacking(backingStore)) {
|
|
|
|
backingFormatterStr = "s:backing";
|
2023-09-12 16:53:01 +02:00
|
|
|
backingNodename = qemuBlockStorageSourceGetEffectiveNodename(backingStore);
|
2017-10-23 12:26:10 +02:00
|
|
|
} else {
|
2023-09-12 16:36:31 +02:00
|
|
|
/* chain is terminated, indicate that no detection should happen in qemu */
|
|
|
|
backingFormatterStr = "n:backing";
|
2017-10-23 12:26:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-12 16:36:31 +02:00
|
|
|
if (!(props = qemuBlockStorageSourceGetBlockdevFormatProps(src)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (virJSONValueObjectAdd(&props,
|
2023-09-25 16:44:26 +02:00
|
|
|
"s:file", qemuBlockStorageSourceGetEffectiveStorageNodename(src),
|
2023-09-12 16:36:31 +02:00
|
|
|
backingFormatterStr, backingNodename,
|
|
|
|
NULL) < 0)
|
|
|
|
return 0;
|
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&props);
|
2017-10-23 12:26:10 +02:00
|
|
|
}
|
2018-02-23 12:59:13 +01:00
|
|
|
|
|
|
|
|
2023-11-03 14:21:11 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetBlockdevStorageSliceProps:
|
|
|
|
* @src: storage source object
|
|
|
|
* @effective: Whether this blockdev will be the 'effective' layer of @src
|
2023-12-11 17:13:39 +01:00
|
|
|
* @resize: If true, the 'size' and 'offset' parameters are not formatted
|
2023-11-03 14:21:11 +01:00
|
|
|
*
|
|
|
|
* Formats the JSON object representing -blockdev configuration required to
|
|
|
|
* configure a 'slice' of @src. If @effective is true, the slice layer is the
|
2023-12-11 17:13:39 +01:00
|
|
|
* topmost/effective blockdev layer of @src. If @resize is true the 'size' and
|
|
|
|
* 'offset' are not formatted, which is used to remove a slice restriction
|
|
|
|
* to resize the image.
|
2023-11-03 14:21:11 +01:00
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
static virJSONValue *
|
2023-11-03 14:21:11 +01:00
|
|
|
qemuBlockStorageSourceGetBlockdevStorageSliceProps(virStorageSource *src,
|
2023-12-11 17:13:39 +01:00
|
|
|
bool effective,
|
|
|
|
bool resize)
|
2020-02-10 14:37:14 +01:00
|
|
|
{
|
|
|
|
g_autoptr(virJSONValue) props = NULL;
|
2023-10-18 13:51:09 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
|
|
|
"s:driver", "raw",
|
2023-12-11 17:13:39 +01:00
|
|
|
"s:file", qemuBlockStorageSourceGetStorageNodename(src),
|
|
|
|
NULL) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!resize &&
|
|
|
|
virJSONValueObjectAdd(&props,
|
2021-11-08 17:24:50 +01:00
|
|
|
"U:offset", src->sliceStorage->offset,
|
|
|
|
"U:size", src->sliceStorage->size,
|
|
|
|
NULL) < 0)
|
2020-02-10 14:37:14 +01:00
|
|
|
return NULL;
|
2023-10-24 12:38:57 +02:00
|
|
|
|
|
|
|
if (qemuBlockStorageSourceAddBlockdevCommonProps(&props,
|
|
|
|
src,
|
|
|
|
src->sliceStorage->nodename,
|
2023-11-03 14:21:11 +01:00
|
|
|
effective) < 0)
|
2023-10-24 12:38:57 +02:00
|
|
|
return NULL;
|
2020-02-10 14:37:14 +01:00
|
|
|
|
|
|
|
return g_steal_pointer(&props);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-23 12:59:13 +01:00
|
|
|
void
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachDataFree(qemuBlockStorageSourceAttachData *data)
|
2018-02-23 12:59:13 +01:00
|
|
|
{
|
2023-03-13 04:50:19 -05:00
|
|
|
size_t i;
|
2018-02-23 12:59:13 +01:00
|
|
|
if (!data)
|
|
|
|
return;
|
|
|
|
|
|
|
|
virJSONValueFree(data->storageProps);
|
2020-02-10 14:37:14 +01:00
|
|
|
virJSONValueFree(data->storageSliceProps);
|
2018-02-23 12:59:13 +01:00
|
|
|
virJSONValueFree(data->formatProps);
|
2018-06-01 15:56:47 +02:00
|
|
|
virJSONValueFree(data->prmgrProps);
|
2018-05-17 10:32:32 +02:00
|
|
|
virJSONValueFree(data->authsecretProps);
|
2020-03-09 09:14:07 +01:00
|
|
|
virJSONValueFree(data->httpcookiesecretProps);
|
2023-03-13 04:50:19 -05:00
|
|
|
for (i = 0; i < data->encryptsecretCount; ++i) {
|
|
|
|
virJSONValueFree(data->encryptsecretProps[i]);
|
|
|
|
g_free(data->encryptsecretAlias[i]);
|
|
|
|
}
|
2018-06-01 12:11:06 +02:00
|
|
|
virJSONValueFree(data->tlsProps);
|
2020-06-29 15:11:00 +02:00
|
|
|
virJSONValueFree(data->tlsKeySecretProps);
|
2021-02-03 14:36:01 -05:00
|
|
|
g_free(data->tlsAlias);
|
|
|
|
g_free(data->tlsKeySecretAlias);
|
|
|
|
g_free(data->authsecretAlias);
|
2023-03-13 04:50:19 -05:00
|
|
|
g_free(data->encryptsecretProps);
|
2021-02-03 14:36:01 -05:00
|
|
|
g_free(data->encryptsecretAlias);
|
|
|
|
g_free(data->httpcookiesecretAlias);
|
|
|
|
g_free(data->driveCmd);
|
|
|
|
g_free(data->chardevAlias);
|
|
|
|
g_free(data);
|
2018-02-23 12:59:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceAttachPrepareBlockdev:
|
|
|
|
* @src: storage source to prepare data from
|
2019-09-03 13:58:34 +02:00
|
|
|
* @backingStore: storage source to use as backing of @src
|
2018-09-04 19:29:10 +02:00
|
|
|
* @autoreadonly: use 'auto-read-only' feature of qemu
|
2018-02-23 12:59:13 +01:00
|
|
|
*
|
|
|
|
* Creates a qemuBlockStorageSourceAttachData structure containing data to attach
|
|
|
|
* @src to a VM using the blockdev-add approach. Note that this function only
|
|
|
|
* creates the data for the storage source itself, any other related
|
|
|
|
* authentication/encryption/... objects need to be prepared separately.
|
|
|
|
*
|
|
|
|
* The changes are then applied using qemuBlockStorageSourceAttachApply.
|
|
|
|
*
|
|
|
|
* Returns the filled data structure on success or NULL on error and a libvirt
|
|
|
|
* error is reported
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachData *
|
|
|
|
qemuBlockStorageSourceAttachPrepareBlockdev(virStorageSource *src,
|
2023-10-17 16:10:47 +02:00
|
|
|
virStorageSource *backingStore)
|
2018-02-23 12:59:13 +01:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(qemuBlockStorageSourceAttachData) data = NULL;
|
2023-11-03 14:26:35 +01:00
|
|
|
bool effective = true;
|
2023-10-19 17:39:15 +02:00
|
|
|
unsigned int backendpropsflags = 0;
|
2018-02-23 12:59:13 +01:00
|
|
|
|
2020-10-05 12:28:26 +02:00
|
|
|
data = g_new0(qemuBlockStorageSourceAttachData, 1);
|
2018-02-23 12:59:13 +01:00
|
|
|
|
2023-11-03 14:26:35 +01:00
|
|
|
if (qemuBlockStorageSourceGetFormatNodename(src)) {
|
|
|
|
if (!(data->formatProps = qemuBlockStorageSourceGetFormatProps(src, backingStore)))
|
|
|
|
return NULL;
|
2018-02-23 12:59:13 +01:00
|
|
|
|
2023-11-03 14:26:35 +01:00
|
|
|
data->formatNodeName = qemuBlockStorageSourceGetFormatNodename(src);
|
|
|
|
|
|
|
|
effective = false;
|
|
|
|
}
|
2018-02-23 12:59:13 +01:00
|
|
|
|
2023-11-24 10:18:56 +01:00
|
|
|
if ((data->storageSliceNodeName = qemuBlockStorageSourceGetSliceNodename(src))) {
|
2023-12-11 17:13:39 +01:00
|
|
|
if (!(data->storageSliceProps = qemuBlockStorageSourceGetBlockdevStorageSliceProps(src, effective, false)))
|
2020-02-10 14:37:14 +01:00
|
|
|
return NULL;
|
2023-11-03 14:26:35 +01:00
|
|
|
|
|
|
|
effective = false;
|
2020-02-10 14:37:14 +01:00
|
|
|
}
|
|
|
|
|
2023-11-03 14:26:35 +01:00
|
|
|
if (effective)
|
|
|
|
backendpropsflags = QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_EFFECTIVE_NODE;
|
|
|
|
|
|
|
|
if (!(data->storageProps = qemuBlockStorageSourceGetBackendProps(src, backendpropsflags)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
data->storageNodeName = qemuBlockStorageSourceGetStorageNodename(src);
|
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&data);
|
2018-02-23 12:59:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-27 17:30:12 +02:00
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachApplyStorageDeps(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceAttachData *data)
|
2018-02-23 12:59:13 +01:00
|
|
|
{
|
2018-06-01 15:56:47 +02:00
|
|
|
if (data->prmgrProps &&
|
|
|
|
qemuMonitorAddObject(mon, &data->prmgrProps, &data->prmgrAlias) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2018-05-17 10:32:32 +02:00
|
|
|
if (data->authsecretProps &&
|
|
|
|
qemuMonitorAddObject(mon, &data->authsecretProps,
|
|
|
|
&data->authsecretAlias) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2020-03-09 09:14:07 +01:00
|
|
|
if (data->httpcookiesecretProps &&
|
|
|
|
qemuMonitorAddObject(mon, &data->httpcookiesecretProps,
|
|
|
|
&data->httpcookiesecretAlias) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2020-06-29 15:11:00 +02:00
|
|
|
if (data->tlsKeySecretProps &&
|
|
|
|
qemuMonitorAddObject(mon, &data->tlsKeySecretProps,
|
|
|
|
&data->tlsKeySecretAlias) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2018-06-01 12:11:06 +02:00
|
|
|
if (data->tlsProps &&
|
|
|
|
qemuMonitorAddObject(mon, &data->tlsProps, &data->tlsAlias) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2023-01-31 15:35:05 +01:00
|
|
|
if (qemuFDPassTransferMonitor(data->fdpass, mon) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2019-05-27 17:30:12 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachApplyStorage(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceAttachData *data)
|
2019-05-27 17:30:12 +02:00
|
|
|
{
|
2018-02-23 12:59:13 +01:00
|
|
|
if (data->storageProps) {
|
2020-02-06 13:13:32 +01:00
|
|
|
if (qemuMonitorBlockdevAdd(mon, &data->storageProps) < 0)
|
2018-02-23 12:59:13 +01:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
data->storageAttached = true;
|
|
|
|
}
|
|
|
|
|
2019-05-27 17:30:12 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachApplyFormatDeps(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceAttachData *data)
|
2019-05-27 17:30:12 +02:00
|
|
|
{
|
2023-03-13 04:50:19 -05:00
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < data->encryptsecretCount; ++i) {
|
|
|
|
if (qemuMonitorAddObject(mon, &data->encryptsecretProps[i],
|
|
|
|
&data->encryptsecretAlias[i]) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
2019-05-27 17:30:12 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachApplyFormat(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceAttachData *data)
|
2019-05-27 17:30:12 +02:00
|
|
|
{
|
2018-02-23 12:59:13 +01:00
|
|
|
if (data->formatProps) {
|
2020-02-06 13:13:32 +01:00
|
|
|
if (qemuMonitorBlockdevAdd(mon, &data->formatProps) < 0)
|
2018-02-23 12:59:13 +01:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
data->formatAttached = true;
|
|
|
|
}
|
|
|
|
|
2019-05-27 17:30:12 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-10 14:37:14 +01:00
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachApplyStorageSlice(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceAttachData *data)
|
2020-02-10 14:37:14 +01:00
|
|
|
{
|
|
|
|
if (data->storageSliceProps) {
|
|
|
|
if (qemuMonitorBlockdevAdd(mon, &data->storageSliceProps) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
data->storageSliceAttached = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-05-27 17:30:12 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceAttachApply:
|
|
|
|
* @mon: monitor object
|
|
|
|
* @data: structure holding data of block device to apply
|
|
|
|
*
|
|
|
|
* Attaches a virStorageSource definition converted to
|
|
|
|
* qemuBlockStorageSourceAttachData to a running VM. This function expects being
|
|
|
|
* called after the monitor was entered.
|
|
|
|
*
|
|
|
|
* Returns 0 on success and -1 on error with a libvirt error reported. If an
|
|
|
|
* error occurred, changes which were already applied need to be rolled back by
|
|
|
|
* calling qemuBlockStorageSourceAttachRollback.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachApply(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceAttachData *data)
|
2019-05-27 17:30:12 +02:00
|
|
|
{
|
|
|
|
if (qemuBlockStorageSourceAttachApplyStorageDeps(mon, data) < 0 ||
|
2023-01-12 03:04:01 -06:00
|
|
|
qemuBlockStorageSourceAttachApplyFormatDeps(mon, data) < 0 ||
|
2019-05-27 17:30:12 +02:00
|
|
|
qemuBlockStorageSourceAttachApplyStorage(mon, data) < 0 ||
|
2020-02-10 14:37:14 +01:00
|
|
|
qemuBlockStorageSourceAttachApplyStorageSlice(mon, data) < 0 ||
|
2019-05-27 17:30:12 +02:00
|
|
|
qemuBlockStorageSourceAttachApplyFormat(mon, data) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2021-02-01 12:00:35 +01:00
|
|
|
if (data->chardevDef) {
|
2024-09-13 10:33:47 +02:00
|
|
|
g_autoptr(virJSONValue) props = NULL;
|
|
|
|
|
2024-10-11 14:20:32 +02:00
|
|
|
if (qemuChardevGetBackendProps(data->chardevDef, false, data->qemuCaps,
|
2024-09-13 10:33:47 +02:00
|
|
|
data->chardevAlias, NULL, &props) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (qemuMonitorAttachCharDev(mon, &props, NULL) < 0)
|
2021-02-01 12:00:35 +01:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
data->chardevAdded = true;
|
|
|
|
}
|
|
|
|
|
2018-02-23 12:59:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceAttachRollback:
|
|
|
|
* @mon: monitor object
|
|
|
|
* @data: structure holding data of block device to roll back
|
|
|
|
*
|
|
|
|
* Attempts a best effort rollback of changes which were made to a running VM by
|
|
|
|
* qemuBlockStorageSourceAttachApply. Preserves any existing errors.
|
|
|
|
*
|
|
|
|
* This function expects being called after the monitor was entered.
|
|
|
|
*/
|
|
|
|
void
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachRollback(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceAttachData *data)
|
2018-02-23 12:59:13 +01:00
|
|
|
{
|
|
|
|
virErrorPtr orig_err;
|
2023-03-13 04:50:19 -05:00
|
|
|
size_t i;
|
2018-02-23 12:59:13 +01:00
|
|
|
|
|
|
|
virErrorPreserveLast(&orig_err);
|
|
|
|
|
2021-02-01 12:00:35 +01:00
|
|
|
if (data->chardevAdded) {
|
|
|
|
if (qemuMonitorDetachCharDev(mon, data->chardevAlias) < 0) {
|
2021-10-01 13:46:53 +02:00
|
|
|
VIR_WARN("Unable to remove chardev %s after failed 'device_add'",
|
2021-02-01 12:00:35 +01:00
|
|
|
data->chardevAlias);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 12:59:13 +01:00
|
|
|
if (data->formatAttached)
|
|
|
|
ignore_value(qemuMonitorBlockdevDel(mon, data->formatNodeName));
|
|
|
|
|
2020-02-10 14:37:14 +01:00
|
|
|
if (data->storageSliceAttached)
|
|
|
|
ignore_value(qemuMonitorBlockdevDel(mon, data->storageSliceNodeName));
|
|
|
|
|
2018-02-23 12:59:13 +01:00
|
|
|
if (data->storageAttached)
|
|
|
|
ignore_value(qemuMonitorBlockdevDel(mon, data->storageNodeName));
|
|
|
|
|
2018-06-01 15:56:47 +02:00
|
|
|
if (data->prmgrAlias)
|
2020-03-18 12:24:40 +01:00
|
|
|
ignore_value(qemuMonitorDelObject(mon, data->prmgrAlias, false));
|
2018-06-01 15:56:47 +02:00
|
|
|
|
2018-05-17 10:32:32 +02:00
|
|
|
if (data->authsecretAlias)
|
2020-03-18 12:24:40 +01:00
|
|
|
ignore_value(qemuMonitorDelObject(mon, data->authsecretAlias, false));
|
2018-05-17 10:32:32 +02:00
|
|
|
|
2023-03-13 04:50:19 -05:00
|
|
|
for (i = 0; i < data->encryptsecretCount; ++i) {
|
|
|
|
if (data->encryptsecretAlias[i])
|
|
|
|
ignore_value(qemuMonitorDelObject(mon, data->encryptsecretAlias[i], false));
|
|
|
|
}
|
2018-05-17 10:32:32 +02:00
|
|
|
|
2020-03-09 09:14:07 +01:00
|
|
|
if (data->httpcookiesecretAlias)
|
2020-03-18 12:24:40 +01:00
|
|
|
ignore_value(qemuMonitorDelObject(mon, data->httpcookiesecretAlias, false));
|
2020-03-09 09:14:07 +01:00
|
|
|
|
2018-06-01 12:11:06 +02:00
|
|
|
if (data->tlsAlias)
|
2020-03-18 12:24:40 +01:00
|
|
|
ignore_value(qemuMonitorDelObject(mon, data->tlsAlias, false));
|
2018-06-01 12:11:06 +02:00
|
|
|
|
2020-06-29 15:11:00 +02:00
|
|
|
if (data->tlsKeySecretAlias)
|
|
|
|
ignore_value(qemuMonitorDelObject(mon, data->tlsKeySecretAlias, false));
|
2018-05-17 10:32:32 +02:00
|
|
|
|
2023-01-31 15:35:05 +01:00
|
|
|
qemuFDPassTransferMonitorRollback(data->fdpass, mon);
|
|
|
|
|
2018-02-23 12:59:13 +01:00
|
|
|
virErrorRestore(&orig_err);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-10-10 15:30:24 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceDetachPrepare:
|
|
|
|
* @src: disk source structure
|
|
|
|
*
|
2021-03-11 08:16:13 +01:00
|
|
|
* Prepare qemuBlockStorageSourceAttachData *for detaching a single source
|
2022-07-21 14:48:11 +02:00
|
|
|
* from a VM.
|
2018-10-10 15:30:24 +02:00
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceAttachData *
|
2022-07-21 14:48:11 +02:00
|
|
|
qemuBlockStorageSourceDetachPrepare(virStorageSource *src)
|
2018-10-10 15:30:24 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(qemuBlockStorageSourceAttachData) data = NULL;
|
2018-10-10 15:30:24 +02:00
|
|
|
|
2020-03-06 09:45:45 +01:00
|
|
|
data = g_new0(qemuBlockStorageSourceAttachData, 1);
|
2018-10-10 15:30:24 +02:00
|
|
|
|
2023-11-03 14:29:00 +01:00
|
|
|
if ((data->formatNodeName = qemuBlockStorageSourceGetFormatNodename(src)))
|
|
|
|
data->formatAttached = true;
|
2020-02-10 14:37:14 +01:00
|
|
|
|
2023-11-24 10:18:56 +01:00
|
|
|
if ((data->storageSliceNodeName = qemuBlockStorageSourceGetSliceNodename(src)))
|
2022-07-21 14:48:11 +02:00
|
|
|
data->storageSliceAttached = true;
|
2018-10-10 15:30:24 +02:00
|
|
|
|
2023-11-03 14:29:00 +01:00
|
|
|
data->storageNodeName = qemuBlockStorageSourceGetStorageNodename(src);
|
|
|
|
data->storageAttached = true;
|
|
|
|
|
2018-10-10 15:30:24 +02:00
|
|
|
if (src->pr &&
|
2019-10-20 13:49:46 +02:00
|
|
|
!virStoragePRDefIsManaged(src->pr))
|
|
|
|
data->prmgrAlias = g_strdup(src->pr->mgralias);
|
2018-10-10 15:30:24 +02:00
|
|
|
|
2019-10-20 13:49:46 +02:00
|
|
|
data->tlsAlias = g_strdup(src->tlsAlias);
|
2018-10-10 15:30:24 +02:00
|
|
|
|
|
|
|
if (srcpriv) {
|
2021-09-22 09:34:31 +02:00
|
|
|
if (srcpriv->secinfo)
|
|
|
|
data->authsecretAlias = g_strdup(srcpriv->secinfo->alias);
|
2019-10-20 13:49:46 +02:00
|
|
|
|
2023-03-13 04:50:19 -05:00
|
|
|
if (srcpriv->encinfo) {
|
2023-03-13 04:50:20 -05:00
|
|
|
size_t i;
|
|
|
|
|
|
|
|
data->encryptsecretCount = srcpriv->enccount;
|
|
|
|
data->encryptsecretProps = g_new0(virJSONValue *, srcpriv->enccount);
|
|
|
|
data->encryptsecretAlias = g_new0(char *, srcpriv->enccount);
|
|
|
|
|
|
|
|
for (i = 0; i < srcpriv->enccount; ++i) {
|
|
|
|
data->encryptsecretAlias[i] = g_strdup(srcpriv->encinfo[i]->alias);
|
|
|
|
}
|
2023-03-13 04:50:19 -05:00
|
|
|
}
|
2020-03-09 09:14:07 +01:00
|
|
|
|
|
|
|
if (srcpriv->httpcookie)
|
2021-09-22 09:34:31 +02:00
|
|
|
data->httpcookiesecretAlias = g_strdup(srcpriv->httpcookie->alias);
|
2020-06-29 15:11:00 +02:00
|
|
|
|
|
|
|
if (srcpriv->tlsKeySecret)
|
2021-09-22 09:34:31 +02:00
|
|
|
data->tlsKeySecretAlias = g_strdup(srcpriv->tlsKeySecret->alias);
|
2023-01-31 15:35:05 +01:00
|
|
|
|
|
|
|
data->fdpass = srcpriv->fdpass;
|
2018-10-10 15:30:24 +02:00
|
|
|
}
|
|
|
|
|
2020-03-06 09:45:45 +01:00
|
|
|
return g_steal_pointer(&data);
|
2018-10-10 15:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-04 16:50:49 +02:00
|
|
|
void
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceChainDataFree(qemuBlockStorageSourceChainData *data)
|
2019-04-04 16:50:49 +02:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (!data)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; i < data->nsrcdata; i++)
|
|
|
|
qemuBlockStorageSourceAttachDataFree(data->srcdata[i]);
|
|
|
|
|
2021-05-07 14:48:02 +02:00
|
|
|
virJSONValueFree(data->copyOnReadProps);
|
|
|
|
g_free(data->copyOnReadNodename);
|
|
|
|
|
2021-02-03 14:36:01 -05:00
|
|
|
g_free(data->srcdata);
|
|
|
|
g_free(data);
|
2019-04-04 16:50:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceChainDetachPrepareBlockdev
|
|
|
|
* @src: storage source chain to remove
|
|
|
|
*
|
2021-03-11 08:16:13 +01:00
|
|
|
* Prepares qemuBlockStorageSourceChainData *for detaching @src and its
|
2019-04-04 16:50:49 +02:00
|
|
|
* backingStore if -blockdev was used.
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceChainData *
|
|
|
|
qemuBlockStorageSourceChainDetachPrepareBlockdev(virStorageSource *src)
|
2019-04-04 16:50:49 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(qemuBlockStorageSourceAttachData) backend = NULL;
|
|
|
|
g_autoptr(qemuBlockStorageSourceChainData) data = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *n;
|
2019-04-04 16:50:49 +02:00
|
|
|
|
2020-10-05 12:28:26 +02:00
|
|
|
data = g_new0(qemuBlockStorageSourceChainData, 1);
|
2019-04-04 16:50:49 +02:00
|
|
|
|
|
|
|
for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
2022-07-21 14:48:11 +02:00
|
|
|
if (!(backend = qemuBlockStorageSourceDetachPrepare(n)))
|
2019-04-04 16:50:49 +02:00
|
|
|
return NULL;
|
|
|
|
|
2021-08-03 14:14:20 +02:00
|
|
|
VIR_APPEND_ELEMENT(data->srcdata, data->nsrcdata, backend);
|
2024-11-20 18:48:48 +03:00
|
|
|
|
|
|
|
if (n->dataFileStore) {
|
|
|
|
if (!(backend = qemuBlockStorageSourceDetachPrepare(n->dataFileStore)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
VIR_APPEND_ELEMENT(data->srcdata, data->nsrcdata, backend);
|
|
|
|
}
|
2019-04-04 16:50:49 +02:00
|
|
|
}
|
|
|
|
|
2019-10-16 13:35:54 +02:00
|
|
|
return g_steal_pointer(&data);
|
2019-04-04 16:50:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-01 12:00:35 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceChainDetachPrepareChardev
|
|
|
|
* @src: storage source chain to remove
|
|
|
|
*
|
2021-03-11 08:16:13 +01:00
|
|
|
* Prepares qemuBlockStorageSourceChainData *for detaching @src and its
|
2021-02-01 12:00:35 +01:00
|
|
|
* backingStore if -chardev was used.
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceChainData *
|
2021-02-01 12:00:35 +01:00
|
|
|
qemuBlockStorageSourceChainDetachPrepareChardev(char *chardevAlias)
|
|
|
|
{
|
|
|
|
g_autoptr(qemuBlockStorageSourceAttachData) backend = NULL;
|
|
|
|
g_autoptr(qemuBlockStorageSourceChainData) data = NULL;
|
|
|
|
|
|
|
|
data = g_new0(qemuBlockStorageSourceChainData, 1);
|
|
|
|
backend = g_new0(qemuBlockStorageSourceAttachData, 1);
|
|
|
|
|
|
|
|
backend->chardevAlias = chardevAlias;
|
|
|
|
backend->chardevAdded = true;
|
|
|
|
|
2021-08-03 14:14:20 +02:00
|
|
|
VIR_APPEND_ELEMENT(data->srcdata, data->nsrcdata, backend);
|
2021-02-01 12:00:35 +01:00
|
|
|
|
|
|
|
return g_steal_pointer(&data);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-04 16:50:49 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceChainAttach:
|
|
|
|
* @mon: monitor object
|
|
|
|
* @data: storage source chain data
|
|
|
|
*
|
|
|
|
* Attach a storage source including its backing chain and supporting objects.
|
|
|
|
* Caller must enter @mon prior calling this function. In case of error this
|
|
|
|
* function returns -1. @data is updated so that qemuBlockStorageSourceChainDetach
|
|
|
|
* can be used to roll-back the changes.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceChainAttach(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceChainData *data)
|
2019-04-04 16:50:49 +02:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = data->nsrcdata; i > 0; i--) {
|
|
|
|
if (qemuBlockStorageSourceAttachApply(mon, data->srcdata[i - 1]) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-05-07 14:48:02 +02:00
|
|
|
if (data->copyOnReadProps) {
|
|
|
|
if (qemuMonitorBlockdevAdd(mon, &data->copyOnReadProps) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-04-04 16:50:49 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceChainDetach:
|
|
|
|
* @mon: monitor object
|
|
|
|
* @data: storage source chain data
|
|
|
|
*
|
|
|
|
* Detach a unused storage source including all its backing chain and related
|
|
|
|
* objects described by @data.
|
|
|
|
*/
|
|
|
|
void
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceChainDetach(qemuMonitor *mon,
|
|
|
|
qemuBlockStorageSourceChainData *data)
|
2019-04-04 16:50:49 +02:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
2024-08-16 16:08:53 +02:00
|
|
|
if (!data)
|
|
|
|
return;
|
|
|
|
|
2021-05-07 14:48:02 +02:00
|
|
|
if (data->copyOnReadAttached)
|
|
|
|
ignore_value(qemuMonitorBlockdevDel(mon, data->copyOnReadNodename));
|
|
|
|
|
|
|
|
|
2019-04-04 16:50:49 +02:00
|
|
|
for (i = 0; i < data->nsrcdata; i++)
|
|
|
|
qemuBlockStorageSourceAttachRollback(mon, data->srcdata[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-09-07 11:33:54 +02:00
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockSnapshotAddBlockdev(virJSONValue *actions,
|
|
|
|
virDomainDiskDef *disk,
|
|
|
|
virStorageSource *newsrc)
|
2018-09-07 11:33:54 +02:00
|
|
|
{
|
2019-09-26 16:33:43 +02:00
|
|
|
return qemuMonitorTransactionSnapshotBlockdev(actions,
|
2021-03-04 14:54:00 +01:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(disk->src),
|
|
|
|
qemuBlockStorageSourceGetFormatNodename(newsrc));
|
2018-09-07 11:33:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-06-11 13:25:20 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageGetCopyOnReadProps:
|
|
|
|
* @disk: disk with copy-on-read enabled
|
|
|
|
*
|
|
|
|
* Creates blockdev properties for a disk copy-on-read layer.
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *
|
|
|
|
qemuBlockStorageGetCopyOnReadProps(virDomainDiskDef *disk)
|
2018-06-11 13:25:20 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainDiskPrivate *priv = QEMU_DOMAIN_DISK_PRIVATE(disk);
|
|
|
|
virJSONValue *ret = NULL;
|
2018-06-11 13:25:20 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
ignore_value(virJSONValueObjectAdd(&ret,
|
|
|
|
"s:driver", "copy-on-read",
|
|
|
|
"s:node-name", priv->nodeCopyOnRead,
|
2023-10-16 16:09:55 +02:00
|
|
|
"s:file", qemuBlockStorageSourceGetEffectiveNodename(disk->src),
|
2021-11-08 17:24:50 +01:00
|
|
|
"s:discard", "unmap",
|
|
|
|
NULL));
|
2018-06-11 13:25:20 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2018-09-03 20:08:07 +02:00
|
|
|
|
|
|
|
|
2019-07-22 17:36:05 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockGetBackingStoreString:
|
|
|
|
* @src: storage source to get the string for
|
2020-03-23 11:48:10 +01:00
|
|
|
* @pretty: pretty-print the JSON (if applicable, used by tests)
|
2019-07-22 17:36:05 +02:00
|
|
|
*
|
|
|
|
* Formats a string used in the backing store field of a disk image which
|
|
|
|
* supports backing store. Non-local storage may result in use of the json:
|
|
|
|
* pseudo protocol for any complex configuration.
|
|
|
|
*/
|
|
|
|
char *
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockGetBackingStoreString(virStorageSource *src,
|
2020-03-23 11:48:10 +01:00
|
|
|
bool pretty)
|
2019-07-22 17:36:05 +02:00
|
|
|
{
|
2022-01-23 12:10:35 +01:00
|
|
|
virStorageType actualType = virStorageSourceGetActualType(src);
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) backingProps = NULL;
|
2020-02-12 17:25:12 +01:00
|
|
|
g_autoptr(virJSONValue) sliceProps = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *props = NULL;
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virURI) uri = NULL;
|
2019-10-15 15:16:31 +02:00
|
|
|
g_autofree char *backingJSON = NULL;
|
2019-07-22 17:36:05 +02:00
|
|
|
|
2020-02-12 17:25:12 +01:00
|
|
|
if (!src->sliceStorage) {
|
2020-03-23 16:04:19 +01:00
|
|
|
if (virStorageSourceIsLocalStorage(src)) {
|
|
|
|
if (src->type == VIR_STORAGE_TYPE_DIR &&
|
|
|
|
src->format == VIR_STORAGE_FILE_FAT)
|
|
|
|
return g_strdup_printf("fat:%s", src->path);
|
|
|
|
|
2020-03-23 15:29:56 +01:00
|
|
|
return g_strdup(src->path);
|
2020-03-23 16:04:19 +01:00
|
|
|
}
|
2019-07-22 17:36:05 +02:00
|
|
|
|
2020-02-12 17:25:12 +01:00
|
|
|
/* generate simplified URIs for the easy cases */
|
|
|
|
if (actualType == VIR_STORAGE_TYPE_NETWORK &&
|
|
|
|
src->nhosts == 1 &&
|
2020-03-23 16:36:22 +01:00
|
|
|
src->hosts->transport == VIR_STORAGE_NET_HOST_TRANS_TCP &&
|
|
|
|
src->timeout == 0 &&
|
|
|
|
src->ncookies == 0 &&
|
|
|
|
src->sslverify == VIR_TRISTATE_BOOL_ABSENT &&
|
|
|
|
src->timeout == 0 &&
|
2023-02-27 12:15:33 +01:00
|
|
|
src->readahead == 0 &&
|
|
|
|
src->reconnectDelay == 0) {
|
2020-02-12 17:25:12 +01:00
|
|
|
|
|
|
|
switch ((virStorageNetProtocol) src->protocol) {
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
|
|
|
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:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
|
|
|
if (!(uri = qemuBlockStorageSourceGetURI(src)))
|
|
|
|
return NULL;
|
2019-07-22 17:36:05 +02:00
|
|
|
|
2020-03-23 15:29:56 +01:00
|
|
|
return virURIFormat(uri);
|
2019-07-22 17:36:05 +02:00
|
|
|
|
2020-02-12 17:25:12 +01:00
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
2021-01-06 15:32:26 -06:00
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
2020-02-12 17:25:12 +01:00
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
|
|
|
break;
|
|
|
|
}
|
2019-07-22 17:36:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* use json: pseudo protocol otherwise */
|
2020-06-19 16:28:12 +02:00
|
|
|
if (!(backingProps = qemuBlockStorageSourceGetBackendProps(src,
|
|
|
|
QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_TARGET_ONLY)))
|
2019-07-22 17:36:05 +02:00
|
|
|
return NULL;
|
|
|
|
|
2020-02-12 17:25:12 +01:00
|
|
|
props = backingProps;
|
|
|
|
|
|
|
|
if (src->sliceStorage) {
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&sliceProps,
|
|
|
|
"s:driver", "raw",
|
|
|
|
"U:offset", src->sliceStorage->offset,
|
|
|
|
"U:size", src->sliceStorage->size,
|
|
|
|
"a:file", &backingProps,
|
|
|
|
NULL) < 0)
|
2020-02-12 17:25:12 +01:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
props = sliceProps;
|
|
|
|
}
|
|
|
|
|
2020-03-23 11:48:10 +01:00
|
|
|
if (!(backingJSON = virJSONValueToString(props, pretty)))
|
2019-07-22 17:36:05 +02:00
|
|
|
return NULL;
|
|
|
|
|
2020-03-23 16:09:31 +01:00
|
|
|
return g_strdup_printf("json:{\"file\":%s}", backingJSON);
|
2019-07-22 17:36:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-09-03 20:08:07 +02:00
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateAddBacking(virStorageSource *backing,
|
|
|
|
virJSONValue *props,
|
2018-09-03 20:08:07 +02:00
|
|
|
bool format)
|
|
|
|
{
|
2019-10-15 15:16:31 +02:00
|
|
|
g_autofree char *backingFileStr = NULL;
|
2018-09-03 20:08:07 +02:00
|
|
|
const char *backingFormatStr = NULL;
|
|
|
|
|
|
|
|
if (!virStorageSourceIsBacking(backing))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (format) {
|
2023-12-12 17:16:25 +01:00
|
|
|
if (qemuBlockStorageSourceIsLUKS(backing))
|
2018-09-03 20:08:07 +02:00
|
|
|
backingFormatStr = "luks";
|
|
|
|
else
|
|
|
|
backingFormatStr = virStorageFileFormatTypeToString(backing->format);
|
|
|
|
}
|
|
|
|
|
2020-03-23 11:48:10 +01:00
|
|
|
if (!(backingFileStr = qemuBlockGetBackingStoreString(backing, false)))
|
2019-07-22 17:36:05 +02:00
|
|
|
return -1;
|
2018-09-03 20:08:07 +02:00
|
|
|
|
2021-11-08 17:18:59 +01:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
2018-09-03 20:08:07 +02:00
|
|
|
"S:backing-file", backingFileStr,
|
|
|
|
"S:backing-fmt", backingFormatStr,
|
|
|
|
NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGetFormatPropsGeneric(virStorageSource *src,
|
2018-09-03 20:08:07 +02:00
|
|
|
const char *driver,
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue **retprops,
|
|
|
|
virStorageSource *backing)
|
2018-09-03 20:08:07 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) props = NULL;
|
2018-09-03 20:08:07 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
|
|
|
"s:driver", driver,
|
2023-09-25 16:24:07 +02:00
|
|
|
"s:file", qemuBlockStorageSourceGetEffectiveStorageNodename(src),
|
2021-11-08 17:24:50 +01:00
|
|
|
"U:size", src->capacity,
|
|
|
|
NULL) < 0)
|
2018-09-03 20:08:07 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (backing &&
|
|
|
|
qemuBlockStorageSourceCreateAddBacking(backing, props, false) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2019-10-16 13:43:18 +02:00
|
|
|
*retprops = g_steal_pointer(&props);
|
2018-09-03 20:08:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGetEncryptionLUKS(virStorageSource *src,
|
|
|
|
virJSONValue **luksProps)
|
2018-09-03 20:08:07 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) props = NULL;
|
2019-10-15 15:16:31 +02:00
|
|
|
g_autofree char *cipheralg = NULL;
|
2018-09-03 20:08:07 +02:00
|
|
|
const char *keysecret = NULL;
|
|
|
|
|
|
|
|
if (srcpriv &&
|
2021-09-22 09:34:31 +02:00
|
|
|
srcpriv->encinfo)
|
2023-03-13 04:50:20 -05:00
|
|
|
keysecret = srcpriv->encinfo[0]->alias;
|
2018-09-03 20:08:07 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
|
|
|
"s:key-secret", keysecret,
|
|
|
|
NULL) < 0)
|
2018-09-03 20:08:07 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (src->encryption) {
|
2019-10-22 15:26:14 +02:00
|
|
|
if (src->encryption->encinfo.cipher_name) {
|
|
|
|
cipheralg = g_strdup_printf("%s-%u",
|
|
|
|
src->encryption->encinfo.cipher_name,
|
|
|
|
src->encryption->encinfo.cipher_size);
|
|
|
|
}
|
2018-09-03 20:08:07 +02:00
|
|
|
|
2021-11-08 17:18:59 +01:00
|
|
|
if (virJSONValueObjectAdd(&props,
|
2018-09-03 20:08:07 +02:00
|
|
|
"S:cipher-alg", cipheralg,
|
|
|
|
"S:cipher-mode", src->encryption->encinfo.cipher_mode,
|
|
|
|
"S:hash-alg", src->encryption->encinfo.cipher_hash,
|
|
|
|
"S:ivgen-alg", src->encryption->encinfo.ivgen_name,
|
|
|
|
"S:ivgen-hash-alg", src->encryption->encinfo.ivgen_hash,
|
|
|
|
NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-10-16 13:43:18 +02:00
|
|
|
*luksProps = g_steal_pointer(&props);
|
2018-09-03 20:08:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGetFormatPropsLUKS(virStorageSource *src,
|
|
|
|
virJSONValue **props)
|
2018-09-03 20:08:07 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) luksprops = NULL;
|
2018-09-03 20:08:07 +02:00
|
|
|
|
|
|
|
if (qemuBlockStorageSourceCreateGetEncryptionLUKS(src, &luksprops) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2021-11-08 17:18:59 +01:00
|
|
|
if (virJSONValueObjectAdd(&luksprops,
|
2018-09-03 20:08:07 +02:00
|
|
|
"s:driver", "luks",
|
2023-09-25 16:24:07 +02:00
|
|
|
"s:file", qemuBlockStorageSourceGetEffectiveStorageNodename(src),
|
2019-08-30 16:33:48 +02:00
|
|
|
"U:size", src->capacity,
|
2018-09-03 20:08:07 +02:00
|
|
|
NULL) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2019-10-16 13:43:18 +02:00
|
|
|
*props = g_steal_pointer(&luksprops);
|
2018-09-03 20:08:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateAddEncryptionQcow(virStorageSource *src,
|
|
|
|
virJSONValue *props)
|
2018-09-03 20:08:07 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) encryptProps = NULL;
|
2018-09-03 20:08:07 +02:00
|
|
|
|
|
|
|
if (!src->encryption)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (src->encryption->format != VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
|
|
_("creation of qcow/qcow2 files supports only 'luks' encryption"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceCreateGetEncryptionLUKS(src, &encryptProps) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2021-11-08 17:18:59 +01:00
|
|
|
if (virJSONValueObjectAdd(&encryptProps, "s:format", "luks", NULL) < 0)
|
2018-09-03 20:08:07 +02:00
|
|
|
return -1;
|
|
|
|
|
2021-11-08 17:18:59 +01:00
|
|
|
if (virJSONValueObjectAdd(&props, "a:encrypt", &encryptProps, NULL) < 0)
|
2018-09-03 20:08:07 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGetFormatPropsQcow2(virStorageSource *src,
|
|
|
|
virStorageSource *backing,
|
|
|
|
virJSONValue **props)
|
2018-09-03 20:08:07 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) qcow2props = NULL;
|
2018-09-03 20:08:07 +02:00
|
|
|
const char *qcow2version = NULL;
|
2021-12-16 12:46:51 +01:00
|
|
|
bool extendedL2 = false;
|
2018-09-03 20:08:07 +02:00
|
|
|
|
|
|
|
if (STREQ_NULLABLE(src->compat, "0.10"))
|
|
|
|
qcow2version = "v2";
|
|
|
|
else if (STREQ_NULLABLE(src->compat, "1.1"))
|
|
|
|
qcow2version = "v3";
|
|
|
|
|
2021-12-16 12:46:51 +01:00
|
|
|
if (src->features)
|
|
|
|
extendedL2 = virBitmapIsBitSet(src->features, VIR_STORAGE_FILE_FEATURE_EXTENDED_L2);
|
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&qcow2props,
|
|
|
|
"s:driver", "qcow2",
|
2023-09-25 16:24:07 +02:00
|
|
|
"s:file", qemuBlockStorageSourceGetEffectiveStorageNodename(src),
|
2021-11-08 17:24:50 +01:00
|
|
|
"U:size", src->capacity,
|
|
|
|
"S:version", qcow2version,
|
|
|
|
"P:cluster-size", src->clusterSize,
|
2021-12-16 12:46:51 +01:00
|
|
|
"B:extended-l2", extendedL2,
|
2021-11-08 17:24:50 +01:00
|
|
|
NULL) < 0)
|
2018-09-03 20:08:07 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceCreateAddBacking(backing, qcow2props, true) < 0 ||
|
|
|
|
qemuBlockStorageSourceCreateAddEncryptionQcow(src, qcow2props) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2019-10-16 13:43:18 +02:00
|
|
|
*props = g_steal_pointer(&qcow2props);
|
2018-09-03 20:08:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGetFormatPropsQcow(virStorageSource *src,
|
|
|
|
virStorageSource *backing,
|
|
|
|
virJSONValue **props)
|
2018-09-03 20:08:07 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) qcowprops = NULL;
|
2018-09-03 20:08:07 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&qcowprops,
|
|
|
|
"s:driver", "qcow",
|
2023-09-25 16:24:07 +02:00
|
|
|
"s:file", qemuBlockStorageSourceGetEffectiveStorageNodename(src),
|
2021-11-08 17:24:50 +01:00
|
|
|
"U:size", src->capacity,
|
|
|
|
NULL) < 0)
|
2018-09-03 20:08:07 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceCreateAddBacking(backing, qcowprops, false) < 0 ||
|
|
|
|
qemuBlockStorageSourceCreateAddEncryptionQcow(src, qcowprops) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2019-10-16 13:43:18 +02:00
|
|
|
*props = g_steal_pointer(&qcowprops);
|
2018-09-03 20:08:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGetFormatPropsQed(virStorageSource *src,
|
|
|
|
virStorageSource *backing,
|
|
|
|
virJSONValue **props)
|
2018-09-03 20:08:07 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) qedprops = NULL;
|
2018-09-03 20:08:07 +02:00
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&qedprops,
|
|
|
|
"s:driver", "qed",
|
2023-09-25 16:24:07 +02:00
|
|
|
"s:file", qemuBlockStorageSourceGetEffectiveStorageNodename(src),
|
2021-11-08 17:24:50 +01:00
|
|
|
"U:size", src->capacity,
|
|
|
|
NULL) < 0)
|
2018-09-03 20:08:07 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceCreateAddBacking(backing, qedprops, true) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2019-10-16 13:43:18 +02:00
|
|
|
*props = g_steal_pointer(&qedprops);
|
2018-09-03 20:08:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceCreateGetFormatProps:
|
|
|
|
* @src: storage source to format
|
|
|
|
* @backing: storage source describing backing image of @src (if necessary)
|
|
|
|
* @props: filled with props to be used with 'blockdev-create' to format @src
|
|
|
|
*
|
|
|
|
* @src must be properly initialized to contain node-names of the protocol layer
|
|
|
|
* which should be formatted. @props may be NULL with success returned in which
|
|
|
|
* case creation of given storage format is not supported. Note that creation
|
|
|
|
* of 'raw' storage is also returns NULL as there is nothing to do.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGetFormatProps(virStorageSource *src,
|
|
|
|
virStorageSource *backing,
|
|
|
|
virJSONValue **props)
|
2018-09-03 20:08:07 +02:00
|
|
|
{
|
|
|
|
switch ((virStorageFileFormat) src->format) {
|
|
|
|
case VIR_STORAGE_FILE_RAW:
|
2023-12-12 17:16:25 +01:00
|
|
|
if (!qemuBlockStorageSourceIsLUKS(src))
|
2018-09-03 20:08:07 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsLUKS(src, props);
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_QCOW2:
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsQcow2(src, backing, props);
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_QCOW:
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsQcow(src, backing, props);
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_QED:
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsQed(src, backing, props);
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_VPC:
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsGeneric(src, "vpc",
|
|
|
|
props, NULL);
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_PLOOP:
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsGeneric(src, "parallels",
|
|
|
|
props, NULL);
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_VDI:
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsGeneric(src, "vdi",
|
|
|
|
props, NULL);
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_VHD:
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsGeneric(src, "vhdx",
|
|
|
|
props, NULL);
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_VMDK:
|
|
|
|
return qemuBlockStorageSourceCreateGetFormatPropsGeneric(src, "vmdk",
|
|
|
|
props, backing);
|
|
|
|
|
|
|
|
/* unsupported by qemu / impossible */
|
|
|
|
case VIR_STORAGE_FILE_FAT:
|
|
|
|
case VIR_STORAGE_FILE_BOCHS:
|
|
|
|
case VIR_STORAGE_FILE_CLOOP:
|
|
|
|
case VIR_STORAGE_FILE_DMG:
|
|
|
|
case VIR_STORAGE_FILE_COW:
|
|
|
|
case VIR_STORAGE_FILE_ISO:
|
|
|
|
case VIR_STORAGE_FILE_DIR:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_AUTO_SAFE:
|
|
|
|
case VIR_STORAGE_FILE_AUTO:
|
|
|
|
case VIR_STORAGE_FILE_NONE:
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("mishandled storage format '%1$s'"),
|
2018-09-03 20:08:07 +02:00
|
|
|
virStorageFileFormatTypeToString(src->format));
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
case VIR_STORAGE_FILE_LAST:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
virReportEnumRangeError(virStorageFileFormat, src->format);
|
|
|
|
return -1;
|
|
|
|
}
|
2018-09-06 09:09:55 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceCreateGetStorageProps:
|
|
|
|
* @src: storage source to create
|
|
|
|
* @props: filled with props to be used with 'blockdev-create' to create @src
|
|
|
|
*
|
|
|
|
* This function should be used only if @src->type is VIR_STORAGE_TYPE_NETWORK.
|
|
|
|
* Note that @props may be NULL if qemu does not support creation storage
|
|
|
|
* on given protocol. @src->physical is used as size for the storage.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGetStorageProps(virStorageSource *src,
|
|
|
|
virJSONValue **props)
|
2018-09-06 09:09:55 +02:00
|
|
|
{
|
2022-01-23 12:10:35 +01:00
|
|
|
virStorageType actualType = virStorageSourceGetActualType(src);
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) location = NULL;
|
2018-09-06 09:09:55 +02:00
|
|
|
const char *driver = NULL;
|
|
|
|
const char *filename = NULL;
|
2022-07-08 17:13:34 -05:00
|
|
|
qemuDomainStorageSourcePrivate *srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
2018-09-06 09:09:55 +02:00
|
|
|
|
2022-01-23 12:10:35 +01:00
|
|
|
switch (actualType) {
|
2018-09-06 09:09:55 +02:00
|
|
|
case VIR_STORAGE_TYPE_FILE:
|
|
|
|
driver = "file";
|
|
|
|
filename = src->path;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_TYPE_NETWORK:
|
|
|
|
switch ((virStorageNetProtocol) src->protocol) {
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
|
|
|
driver = "gluster";
|
2022-07-19 15:45:22 +02:00
|
|
|
if (!(location = qemuBlockStorageSourceGetGlusterProps(src, false)))
|
2018-09-06 09:09:55 +02:00
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
|
|
|
driver = "rbd";
|
|
|
|
if (!(location = qemuBlockStorageSourceGetRBDProps(src, false)))
|
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
|
|
|
driver = "sheepdog";
|
|
|
|
if (!(location = qemuBlockStorageSourceGetSheepdogProps(src)))
|
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
2022-07-08 17:13:34 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-09-06 09:09:55 +02:00
|
|
|
driver = "ssh";
|
|
|
|
if (!(location = qemuBlockStorageSourceGetSshProps(src)))
|
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
|
2021-01-06 15:32:30 -06:00
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
|
|
|
driver = "nfs";
|
|
|
|
if (!(location = qemuBlockStorageSourceGetNFSProps(src)))
|
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
|
2018-09-06 09:09:55 +02:00
|
|
|
/* unsupported/impossible */
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
|
|
|
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:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_TYPE_BLOCK:
|
|
|
|
case VIR_STORAGE_TYPE_DIR:
|
|
|
|
case VIR_STORAGE_TYPE_VOLUME:
|
2019-06-03 17:31:13 +02:00
|
|
|
case VIR_STORAGE_TYPE_NVME:
|
2021-01-25 18:13:29 +01:00
|
|
|
case VIR_STORAGE_TYPE_VHOST_USER:
|
2023-02-06 14:20:01 -06:00
|
|
|
case VIR_STORAGE_TYPE_VHOST_VDPA:
|
2018-09-06 09:09:55 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
case VIR_STORAGE_TYPE_NONE:
|
|
|
|
case VIR_STORAGE_TYPE_LAST:
|
|
|
|
virReportEnumRangeError(virStorageType, actualType);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(props,
|
|
|
|
"s:driver", driver,
|
|
|
|
"S:filename", filename,
|
|
|
|
"A:location", &location,
|
|
|
|
"U:size", src->physical,
|
|
|
|
NULL) < 0)
|
2018-09-06 09:09:55 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2019-06-10 18:13:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateGeneric(virDomainObj *vm,
|
|
|
|
virJSONValue *createProps,
|
|
|
|
virStorageSource *src,
|
|
|
|
virStorageSource *chain,
|
2019-06-10 18:13:09 +02:00
|
|
|
bool storageCreate,
|
2022-03-24 16:32:42 +01:00
|
|
|
virDomainAsyncJob asyncJob)
|
2019-06-10 18:13:09 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) props = createProps;
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainObjPrivate *priv = vm->privateData;
|
|
|
|
qemuBlockJobData *job = NULL;
|
2019-06-10 18:13:09 +02:00
|
|
|
int ret = -1;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!(job = qemuBlockJobNewCreate(vm, src, chain, storageCreate)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
qemuBlockJobSyncBegin(job);
|
|
|
|
|
2022-08-10 14:57:02 +02:00
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
2021-12-01 09:10:40 +01:00
|
|
|
rc = qemuMonitorBlockdevCreate(priv->mon, job->name, &props);
|
2019-06-10 18:13:09 +02:00
|
|
|
|
2022-03-18 11:17:28 +01:00
|
|
|
qemuDomainObjExitMonitor(vm);
|
2021-11-24 13:11:52 +01:00
|
|
|
if (rc < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
qemuBlockJobStarted(job, vm);
|
|
|
|
|
2019-12-20 09:30:28 +01:00
|
|
|
qemuBlockJobUpdate(vm, job, asyncJob);
|
2019-06-10 18:13:09 +02:00
|
|
|
while (qemuBlockJobIsRunning(job)) {
|
2022-08-10 15:35:54 +02:00
|
|
|
if (qemuDomainObjWait(vm) < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
2019-12-20 09:30:28 +01:00
|
|
|
qemuBlockJobUpdate(vm, job, asyncJob);
|
2019-06-10 18:13:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (job->state == QEMU_BLOCKJOB_STATE_FAILED ||
|
|
|
|
job->state == QEMU_BLOCKJOB_STATE_CANCELLED) {
|
|
|
|
if (job->state == QEMU_BLOCKJOB_STATE_CANCELLED && !job->errmsg) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
|
|
|
|
_("blockdev-create job was cancelled"));
|
|
|
|
} else {
|
|
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("failed to format image: '%1$s'"), NULLSTR(job->errmsg));
|
2019-06-10 18:13:09 +02:00
|
|
|
}
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
qemuBlockJobStartupFinalize(vm, job);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateStorage(virDomainObj *vm,
|
|
|
|
virStorageSource *src,
|
|
|
|
virStorageSource *chain,
|
2022-03-24 16:32:42 +01:00
|
|
|
virDomainAsyncJob asyncJob)
|
2019-06-10 18:13:09 +02:00
|
|
|
{
|
2022-01-23 12:10:35 +01:00
|
|
|
virStorageType actualType = virStorageSourceGetActualType(src);
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) createstorageprops = NULL;
|
2019-06-10 18:13:09 +02:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* We create local files directly to be able to apply security labels
|
|
|
|
* properly. This is enough for formats which store the capacity of the image
|
|
|
|
* in the metadata as they will grow. We must create a correctly sized
|
|
|
|
* image for 'raw' and 'luks' though as the image size influences the
|
|
|
|
* capacity.
|
|
|
|
*/
|
|
|
|
if (actualType != VIR_STORAGE_TYPE_NETWORK &&
|
|
|
|
!(actualType == VIR_STORAGE_TYPE_FILE && src->format == VIR_STORAGE_FILE_RAW))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceCreateGetStorageProps(src, &createstorageprops) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!createstorageprops) {
|
|
|
|
/* we can always try opening it to see whether it was existing */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = qemuBlockStorageSourceCreateGeneric(vm, createstorageprops, src, chain,
|
|
|
|
true, asyncJob);
|
|
|
|
createstorageprops = NULL;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreateFormat(virDomainObj *vm,
|
|
|
|
virStorageSource *src,
|
|
|
|
virStorageSource *backingStore,
|
|
|
|
virStorageSource *chain,
|
2022-03-24 16:32:42 +01:00
|
|
|
virDomainAsyncJob asyncJob)
|
2019-06-10 18:13:09 +02:00
|
|
|
{
|
2019-10-15 14:47:50 +02:00
|
|
|
g_autoptr(virJSONValue) createformatprops = NULL;
|
2019-06-10 18:13:09 +02:00
|
|
|
int ret;
|
|
|
|
|
2023-12-12 17:16:25 +01:00
|
|
|
/* we don't bother creating only a true 'raw' image */
|
|
|
|
if (qemuBlockStorageSourceIsRaw(src))
|
2019-06-10 18:13:09 +02:00
|
|
|
return 0;
|
|
|
|
|
2024-11-20 18:48:48 +03:00
|
|
|
if (src->dataFileStore) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
|
|
_("creation of storage images with <dataStore> feature is not supported"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-06-10 18:13:09 +02:00
|
|
|
if (qemuBlockStorageSourceCreateGetFormatProps(src, backingStore,
|
|
|
|
&createformatprops) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!createformatprops) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("can't create storage format '%1$s'"),
|
2019-06-10 18:13:09 +02:00
|
|
|
virStorageFileFormatTypeToString(src->format));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = qemuBlockStorageSourceCreateGeneric(vm, createformatprops, src, chain,
|
|
|
|
false, asyncJob);
|
|
|
|
createformatprops = NULL;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceCreate:
|
|
|
|
* @vm: domain object
|
|
|
|
* @src: storage source definition to create
|
|
|
|
* @backingStore: backingStore of the new image (used only in image metadata)
|
|
|
|
* @chain: backing chain to unplug in case of a long-running job failure
|
|
|
|
* @data: qemuBlockStorageSourceAttachData for @src so that it can be attached
|
|
|
|
* @asyncJob: qemu asynchronous job type
|
|
|
|
*
|
|
|
|
* Creates and formats a storage volume according to @src and attaches it to @vm.
|
|
|
|
* @data must provide attachment data as if @src was existing. @src is attached
|
|
|
|
* after successful return of this function. If libvirtd is restarted during
|
|
|
|
* the create job @chain is unplugged, otherwise it's left for the caller.
|
|
|
|
* If @backingStore is provided, the new image will refer to it as its backing
|
|
|
|
* store.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceCreate(virDomainObj *vm,
|
|
|
|
virStorageSource *src,
|
|
|
|
virStorageSource *backingStore,
|
|
|
|
virStorageSource *chain,
|
|
|
|
qemuBlockStorageSourceAttachData *data,
|
2022-03-24 16:32:42 +01:00
|
|
|
virDomainAsyncJob asyncJob)
|
2019-06-10 18:13:09 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainObjPrivate *priv = vm->privateData;
|
2019-06-10 18:13:09 +02:00
|
|
|
int ret = -1;
|
|
|
|
int rc;
|
|
|
|
|
2020-02-12 17:26:47 +01:00
|
|
|
if (src->sliceStorage) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
|
|
_("creation of images with slice type='storage' is not supported"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-05-11 14:23:13 +02:00
|
|
|
/* grant write access to read-only images during formatting */
|
|
|
|
if (src->readonly &&
|
|
|
|
qemuDomainStorageSourceAccessAllow(priv->driver, vm, src, false,
|
|
|
|
false, true) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2022-08-10 14:57:02 +02:00
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
rc = qemuBlockStorageSourceAttachApplyStorageDeps(priv->mon, data);
|
2023-01-12 03:04:01 -06:00
|
|
|
if (rc == 0)
|
|
|
|
rc = qemuBlockStorageSourceAttachApplyFormatDeps(priv->mon, data);
|
2019-06-10 18:13:09 +02:00
|
|
|
|
2022-03-18 11:17:28 +01:00
|
|
|
qemuDomainObjExitMonitor(vm);
|
2021-11-24 13:11:52 +01:00
|
|
|
if (rc < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceCreateStorage(vm, src, chain, asyncJob) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2022-08-10 14:57:02 +02:00
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
rc = qemuBlockStorageSourceAttachApplyStorage(priv->mon, data);
|
|
|
|
|
2022-03-18 11:17:28 +01:00
|
|
|
qemuDomainObjExitMonitor(vm);
|
2021-11-24 13:11:52 +01:00
|
|
|
if (rc < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceCreateFormat(vm, src, backingStore, chain,
|
|
|
|
asyncJob) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2020-05-11 14:23:13 +02:00
|
|
|
/* revoke write access to read-only images during formatting */
|
|
|
|
if (src->readonly &&
|
|
|
|
qemuDomainStorageSourceAccessAllow(priv->driver, vm, src, true,
|
|
|
|
false, true) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2022-08-10 14:57:02 +02:00
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
rc = qemuBlockStorageSourceAttachApplyFormat(priv->mon, data);
|
|
|
|
|
2022-03-18 11:17:28 +01:00
|
|
|
qemuDomainObjExitMonitor(vm);
|
2021-11-24 13:11:52 +01:00
|
|
|
if (rc < 0)
|
2019-06-10 18:13:09 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if (ret < 0 &&
|
|
|
|
virDomainObjIsActive(vm) &&
|
2022-08-10 14:57:02 +02:00
|
|
|
qemuDomainObjEnterMonitorAsync(vm, asyncJob) == 0) {
|
2019-06-10 18:13:09 +02:00
|
|
|
|
|
|
|
qemuBlockStorageSourceAttachRollback(priv->mon, data);
|
2022-03-18 11:17:28 +01:00
|
|
|
qemuDomainObjExitMonitor(vm);
|
2019-06-10 18:13:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceCreateDetectSize:
|
2019-10-10 15:02:00 +02:00
|
|
|
* @blockNamedNodeData: hash table filled with qemuBlockNamedNodeData
|
2019-06-10 18:13:09 +02:00
|
|
|
* @src: storage source to update size/capacity on
|
|
|
|
* @templ: storage source template
|
|
|
|
*
|
|
|
|
* When creating a storage source via blockdev-create we need to know the size
|
|
|
|
* and capacity of the original volume (e.g. when creating a snapshot or copy).
|
|
|
|
* This function updates @src's 'capacity' and 'physical' attributes according
|
|
|
|
* to the detected sizes from @templ.
|
|
|
|
*/
|
|
|
|
int
|
2020-10-22 19:04:18 +02:00
|
|
|
qemuBlockStorageSourceCreateDetectSize(GHashTable *blockNamedNodeData,
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *src,
|
|
|
|
virStorageSource *templ)
|
2019-06-10 18:13:09 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockNamedNodeData *entry;
|
2019-06-10 18:13:09 +02:00
|
|
|
|
2023-10-16 16:09:55 +02:00
|
|
|
if (!(entry = virHashLookup(blockNamedNodeData, qemuBlockStorageSourceGetEffectiveNodename(templ)))) {
|
2019-06-10 18:13:09 +02:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("failed to update capacity data for block node '%1$s'"),
|
2023-10-16 16:09:55 +02:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(templ));
|
2019-06-10 18:13:09 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-12-16 18:35:34 +01:00
|
|
|
/* propagate properties of qcow2 images if possible*/
|
2020-08-26 16:45:51 +02:00
|
|
|
if (templ->format == VIR_STORAGE_FILE_QCOW2 &&
|
2021-12-16 18:35:34 +01:00
|
|
|
src->format == VIR_STORAGE_FILE_QCOW2) {
|
|
|
|
if (src->clusterSize == 0)
|
|
|
|
src->clusterSize = entry->clusterSize;
|
|
|
|
|
|
|
|
if (entry->qcow2extendedL2) {
|
|
|
|
if (!src->features)
|
|
|
|
src->features = virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST);
|
|
|
|
ignore_value(virBitmapSetBit(src->features, VIR_STORAGE_FILE_FEATURE_EXTENDED_L2));
|
|
|
|
}
|
|
|
|
}
|
2020-08-26 16:45:51 +02:00
|
|
|
|
2023-12-12 17:16:25 +01:00
|
|
|
if (qemuBlockStorageSourceIsRaw(src)) {
|
2019-06-10 18:13:09 +02:00
|
|
|
src->physical = entry->capacity;
|
|
|
|
} else {
|
|
|
|
src->physical = entry->physical;
|
|
|
|
}
|
|
|
|
|
|
|
|
src->capacity = entry->capacity;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2019-11-19 08:43:58 +01:00
|
|
|
|
|
|
|
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockRemoveImageMetadata(virQEMUDriver *driver,
|
|
|
|
virDomainObj *vm,
|
2019-11-19 08:43:58 +01:00
|
|
|
const char *diskTarget,
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *src)
|
2019-11-19 08:43:58 +01:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *n;
|
2019-11-19 08:43:58 +01:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
|
|
if (qemuSecurityMoveImageMetadata(driver, vm, n, NULL) < 0) {
|
|
|
|
VIR_WARN("Unable to remove disk metadata on "
|
|
|
|
"vm %s from %s (disk target %s)",
|
|
|
|
vm->def->name,
|
|
|
|
NULLSTR(n->path),
|
|
|
|
diskTarget);
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2019-12-05 15:57:30 +01:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockNamedNodeDataGetBitmapByName:
|
|
|
|
* @blockNamedNodeData: hash table returned by qemuMonitorBlockGetNamedNodeData
|
|
|
|
* @src: disk source to find the bitmap for
|
|
|
|
* @bitmap: name of the bitmap to find
|
|
|
|
*
|
|
|
|
* Looks up a bitmap named @bitmap of the @src image.
|
|
|
|
*/
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockNamedNodeDataBitmap *
|
2020-10-22 19:04:18 +02:00
|
|
|
qemuBlockNamedNodeDataGetBitmapByName(GHashTable *blockNamedNodeData,
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *src,
|
2019-12-05 15:57:30 +01:00
|
|
|
const char *bitmap)
|
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockNamedNodeData *nodedata;
|
2019-12-05 15:57:30 +01:00
|
|
|
size_t i;
|
|
|
|
|
2023-10-16 16:20:27 +02:00
|
|
|
if (!(nodedata = virHashLookup(blockNamedNodeData,
|
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(src))))
|
2019-12-05 15:57:30 +01:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < nodedata->nbitmaps; i++) {
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockNamedNodeDataBitmap *bitmapdata = nodedata->bitmaps[i];
|
2019-12-05 15:57:30 +01:00
|
|
|
|
|
|
|
if (STRNEQ(bitmapdata->name, bitmap))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
return bitmapdata;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2020-01-21 15:10:27 +01:00
|
|
|
|
|
|
|
|
2020-10-22 19:04:18 +02:00
|
|
|
GHashTable *
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockGetNamedNodeData(virDomainObj *vm,
|
2022-03-24 16:32:42 +01:00
|
|
|
virDomainAsyncJob asyncJob)
|
2020-01-21 15:10:27 +01:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainObjPrivate *priv = vm->privateData;
|
2022-11-09 10:45:27 +01:00
|
|
|
GHashTable *blockNamedNodeData = NULL;
|
2020-01-21 15:10:27 +01:00
|
|
|
|
2022-08-10 14:57:02 +02:00
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
2020-01-21 15:10:27 +01:00
|
|
|
return NULL;
|
|
|
|
|
2022-11-09 11:12:48 +01:00
|
|
|
blockNamedNodeData = qemuMonitorBlockGetNamedNodeData(priv->mon);
|
2020-01-21 15:10:27 +01:00
|
|
|
|
2022-03-18 11:17:28 +01:00
|
|
|
qemuDomainObjExitMonitor(vm);
|
2021-11-24 13:11:52 +01:00
|
|
|
|
2022-11-09 10:45:27 +01:00
|
|
|
return blockNamedNodeData;
|
2020-01-21 15:10:27 +01:00
|
|
|
}
|
2020-01-29 12:08:50 +01:00
|
|
|
|
|
|
|
|
2020-06-01 12:05:49 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockGetBitmapMergeActionsGetBitmaps:
|
|
|
|
*
|
|
|
|
* Collect a list of bitmaps which need to be handled in
|
|
|
|
* qemuBlockGetBitmapMergeActions. The list contains only valid bitmaps in the
|
|
|
|
* sub-chain which is being processed.
|
|
|
|
*
|
|
|
|
* Note that the returned GSList contains bitmap names string pointers borrowed
|
|
|
|
* from @blockNamedNodeData so they must not be freed.
|
|
|
|
*/
|
|
|
|
static GSList *
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockGetBitmapMergeActionsGetBitmaps(virStorageSource *topsrc,
|
2020-06-01 12:05:49 +02:00
|
|
|
const char *bitmapname,
|
2020-10-22 19:04:18 +02:00
|
|
|
GHashTable *blockNamedNodeData)
|
2020-06-01 12:05:49 +02:00
|
|
|
{
|
|
|
|
g_autoptr(GSList) ret = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockNamedNodeData *entry;
|
2020-06-01 12:05:49 +02:00
|
|
|
size_t i;
|
|
|
|
|
|
|
|
/* for now it doesn't make sense to consider bitmaps which are not present
|
|
|
|
* in @topsrc as we can't recreate a bitmap for a layer if it's missing */
|
|
|
|
|
2023-10-16 16:20:27 +02:00
|
|
|
if (!(entry = virHashLookup(blockNamedNodeData, qemuBlockStorageSourceGetEffectiveNodename(topsrc))))
|
2020-06-01 12:05:49 +02:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < entry->nbitmaps; i++) {
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockNamedNodeDataBitmap *bitmap = entry->bitmaps[i];
|
2020-06-01 12:05:49 +02:00
|
|
|
|
|
|
|
if (bitmapname &&
|
|
|
|
STRNEQ(bitmapname, bitmap->name))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!qemuBlockBitmapChainIsValid(topsrc, bitmap->name, blockNamedNodeData))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = g_slist_prepend(ret, bitmap->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return g_steal_pointer(&ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockGetBitmapMergeActions:
|
|
|
|
* @topsrc: top of the chain to merge bitmaps in
|
|
|
|
* @basesrc: bottom of the chain to merge bitmaps in (NULL for full chain)
|
|
|
|
* @target: destination storage source of the merge (may be part of original chain)
|
|
|
|
* @bitmapname: name of bitmap to perform the merge (NULL for all bitmaps)
|
|
|
|
* @dstbitmapname: name of destination bitmap of the merge (see below for caveats)
|
|
|
|
* @writebitmapsrc: storage source corresponding to the node containing the write temporary bitmap
|
|
|
|
* @actions: returns actions for a 'transaction' QMP command for executing the merge
|
|
|
|
* @blockNamedNodeData: hash table filled with qemuBlockNamedNodeData
|
|
|
|
*
|
|
|
|
* Calculate handling of dirty block bitmaps between @topsrc and @basesrc. If
|
|
|
|
* @basesrc is NULL the end of the chain is considered. @target is the destination
|
|
|
|
* storage source definition of the merge and may or may not be part of the
|
|
|
|
* merged chain.
|
|
|
|
*
|
|
|
|
* Specifically the merging algorithm ensures that each considered bitmap is
|
|
|
|
* merged with the appropriate bitmaps so that it properly describes
|
|
|
|
* the state of dirty blocks when looked at from @topsrc based on the depth
|
|
|
|
* of the backing chain where the bitmap is placed.
|
|
|
|
*
|
|
|
|
* If @bitmapname is non-NULL only bitmaps with that name are handled, otherwise
|
|
|
|
* all bitmaps are considered.
|
|
|
|
*
|
|
|
|
* If @dstbitmap is non-NULL everything is merged into a bitmap with that name,
|
|
|
|
* otherwise each bitmap is merged into a bitmap with the same name into @target.
|
|
|
|
* Additionally if @dstbitmap is non-NULL the target bitmap is created as 'inactive'
|
|
|
|
* and 'transient' as a special case for the backup operation.
|
|
|
|
*
|
|
|
|
* If @writebitmapsrc is non-NULL, the 'libvirt-tmp-activewrite' bitmap from
|
|
|
|
* given node is merged along with others. This bitmap corresponds to the writes
|
|
|
|
* which occurred between an active layer job finished and the rest of the bitmap
|
|
|
|
* merging.
|
|
|
|
*
|
|
|
|
* If the bitmap is not valid somehow (see qemuBlockBitmapChainIsValid) given
|
|
|
|
* bitmap is silently skipped, so callers must ensure that given bitmap is valid
|
|
|
|
* if they care about it.
|
|
|
|
*
|
|
|
|
* The resulting 'transaction' QMP command actions are filled in and returned via
|
|
|
|
* @actions.
|
|
|
|
*
|
|
|
|
* Note that @actions may be NULL if no merging is required.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockGetBitmapMergeActions(virStorageSource *topsrc,
|
|
|
|
virStorageSource *basesrc,
|
|
|
|
virStorageSource *target,
|
2020-06-01 12:05:49 +02:00
|
|
|
const char *bitmapname,
|
|
|
|
const char *dstbitmapname,
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *writebitmapsrc,
|
|
|
|
virJSONValue **actions,
|
2020-10-22 19:04:18 +02:00
|
|
|
GHashTable *blockNamedNodeData)
|
2020-06-01 12:05:49 +02:00
|
|
|
{
|
|
|
|
g_autoptr(virJSONValue) act = virJSONValueNewArray();
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *n;
|
2020-06-01 12:05:49 +02:00
|
|
|
|
|
|
|
g_autoptr(GSList) bitmaps = NULL;
|
|
|
|
GSList *next;
|
|
|
|
|
|
|
|
if (!(bitmaps = qemuBlockGetBitmapMergeActionsGetBitmaps(topsrc, bitmapname,
|
|
|
|
blockNamedNodeData)))
|
2020-07-16 14:46:43 +02:00
|
|
|
goto done;
|
2020-06-01 12:05:49 +02:00
|
|
|
|
|
|
|
for (next = bitmaps; next; next = next->next) {
|
|
|
|
const char *curbitmap = next->data;
|
|
|
|
const char *mergebitmapname = dstbitmapname;
|
|
|
|
bool mergebitmappersistent = false;
|
|
|
|
bool mergebitmapdisabled = true;
|
|
|
|
g_autoptr(virJSONValue) merge = virJSONValueNewArray();
|
|
|
|
unsigned long long granularity = 0;
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockNamedNodeDataBitmap *bitmap;
|
2020-06-01 12:05:49 +02:00
|
|
|
|
|
|
|
/* explicitly named destinations mean that we want a temporary
|
|
|
|
* disabled bitmap only, so undo the default for non-explicit cases */
|
|
|
|
if (!mergebitmapname) {
|
|
|
|
mergebitmapname = curbitmap;
|
|
|
|
mergebitmappersistent = true;
|
|
|
|
mergebitmapdisabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (n = topsrc; virStorageSourceIsBacking(n) && n != basesrc; n = n->backingStore) {
|
|
|
|
if (!(bitmap = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
|
|
|
|
n, curbitmap)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (granularity == 0)
|
|
|
|
granularity = bitmap->granularity;
|
|
|
|
|
|
|
|
if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(merge,
|
2023-10-16 16:20:27 +02:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(n),
|
2020-06-01 12:05:49 +02:00
|
|
|
bitmap->name) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dstbitmapname ||
|
|
|
|
!(bitmap = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
|
|
|
|
target, curbitmap))) {
|
|
|
|
|
|
|
|
if (qemuMonitorTransactionBitmapAdd(act,
|
2023-10-16 16:20:27 +02:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(target),
|
2020-06-01 12:05:49 +02:00
|
|
|
mergebitmapname,
|
|
|
|
mergebitmappersistent,
|
|
|
|
mergebitmapdisabled,
|
|
|
|
granularity) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (writebitmapsrc &&
|
|
|
|
qemuMonitorTransactionBitmapMergeSourceAddBitmap(merge,
|
2023-10-16 16:20:27 +02:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(writebitmapsrc),
|
2020-06-01 12:05:49 +02:00
|
|
|
"libvirt-tmp-activewrite") < 0)
|
|
|
|
return -1;
|
|
|
|
|
2023-10-16 16:20:27 +02:00
|
|
|
if (qemuMonitorTransactionBitmapMerge(act, qemuBlockStorageSourceGetEffectiveNodename(target),
|
2020-06-01 12:05:49 +02:00
|
|
|
mergebitmapname, &merge) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-07-16 14:46:43 +02:00
|
|
|
done:
|
2020-06-01 12:05:49 +02:00
|
|
|
if (writebitmapsrc &&
|
2023-10-16 16:20:27 +02:00
|
|
|
qemuMonitorTransactionBitmapRemove(act, qemuBlockStorageSourceGetEffectiveNodename(writebitmapsrc),
|
2020-06-01 12:05:49 +02:00
|
|
|
"libvirt-tmp-activewrite") < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virJSONValueArraySize(act) > 0)
|
|
|
|
*actions = g_steal_pointer(&act);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-29 12:08:50 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockBitmapChainIsValid:
|
|
|
|
*
|
2020-04-21 10:25:09 +02:00
|
|
|
* Validates that the backing chain of @src contains bitmaps which libvirt will
|
|
|
|
* consider as properly corresponding to a checkpoint named @bitmapname.
|
2020-01-29 12:08:50 +01:00
|
|
|
*
|
2020-04-21 10:25:09 +02:00
|
|
|
* The bitmaps need to:
|
|
|
|
* 1) start from the top image @src
|
|
|
|
* 2) must be present in consecutive layers
|
|
|
|
* 3) all must be active, persistent and not inconsistent
|
2020-01-29 12:08:50 +01:00
|
|
|
*/
|
|
|
|
bool
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockBitmapChainIsValid(virStorageSource *src,
|
2020-01-29 12:08:50 +01:00
|
|
|
const char *bitmapname,
|
2020-10-22 19:04:18 +02:00
|
|
|
GHashTable *blockNamedNodeData)
|
2020-01-29 12:08:50 +01:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *n;
|
2020-04-21 10:25:09 +02:00
|
|
|
bool found = false;
|
2020-01-29 12:08:50 +01:00
|
|
|
bool chain_ended = false;
|
|
|
|
|
2020-04-21 10:25:09 +02:00
|
|
|
for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockNamedNodeDataBitmap *bitmap;
|
2020-04-21 10:25:09 +02:00
|
|
|
|
|
|
|
if (!(bitmap = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
|
|
|
|
n, bitmapname))) {
|
|
|
|
/* rule 1, must start from top */
|
|
|
|
if (!found)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
chain_ended = true;
|
2020-01-29 12:08:50 +01:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-04-21 10:25:09 +02:00
|
|
|
/* rule 2, no-gaps */
|
2020-01-29 12:08:50 +01:00
|
|
|
if (chain_ended)
|
|
|
|
return false;
|
|
|
|
|
2020-04-21 10:25:09 +02:00
|
|
|
/* rule 3 */
|
|
|
|
if (bitmap->inconsistent || !bitmap->persistent || !bitmap->recording)
|
2020-01-29 12:08:50 +01:00
|
|
|
return false;
|
2020-04-21 10:25:09 +02:00
|
|
|
|
|
|
|
found = true;
|
2020-01-29 12:08:50 +01:00
|
|
|
}
|
|
|
|
|
2020-04-21 10:25:09 +02:00
|
|
|
return found;
|
2020-01-29 12:08:50 +01:00
|
|
|
}
|
2020-01-29 12:16:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockBitmapsHandleBlockcopy:
|
|
|
|
* @src: disk source
|
|
|
|
* @mirror: mirror source
|
|
|
|
* @blockNamedNodeData: hash table containing data about bitmaps
|
|
|
|
* @shallow: whether shallow copy is requested
|
|
|
|
* @actions: filled with arguments for a 'transaction' command
|
|
|
|
*
|
|
|
|
* Calculates which bitmaps to copy and merge during a virDomainBlockCopy job.
|
|
|
|
* This is designed to be called when the job is already synchronised as it
|
|
|
|
* may result in active bitmaps being created.
|
|
|
|
*
|
|
|
|
* Returns 0 on success and -1 on error. If @actions is NULL when 0 is returned
|
|
|
|
* there are no actions to perform for the given job.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockBitmapsHandleBlockcopy(virStorageSource *src,
|
|
|
|
virStorageSource *mirror,
|
2020-10-22 19:04:18 +02:00
|
|
|
GHashTable *blockNamedNodeData,
|
2020-01-29 12:16:12 +01:00
|
|
|
bool shallow,
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue **actions)
|
2020-01-29 12:16:12 +01:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *base = NULL;
|
2020-01-29 12:16:12 +01:00
|
|
|
|
2023-11-23 13:43:36 +01:00
|
|
|
/* if copy destination is a 'raw' image there's no point in attempting to
|
|
|
|
* merge the bitmaps into it */
|
|
|
|
if (mirror->format == VIR_STORAGE_FILE_RAW)
|
|
|
|
return 0;
|
|
|
|
|
2020-05-29 10:34:11 +02:00
|
|
|
if (shallow)
|
|
|
|
base = src->backingStore;
|
2020-01-29 12:16:12 +01:00
|
|
|
|
2020-05-29 10:34:11 +02:00
|
|
|
if (qemuBlockGetBitmapMergeActions(src, base, mirror, NULL, NULL, mirror, actions,
|
|
|
|
blockNamedNodeData) < 0)
|
2020-01-29 12:16:12 +01:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-02-13 09:24:36 +01:00
|
|
|
|
|
|
|
|
2020-03-02 14:40:18 +01:00
|
|
|
/**
|
|
|
|
* @topsrc: virStorageSource representing 'top' of the job
|
|
|
|
* @basesrc: virStorageSource representing 'base' of the job
|
2020-04-17 09:51:40 +02:00
|
|
|
* @active: commit job is an active layer block-commit
|
2020-03-02 14:40:18 +01:00
|
|
|
* @blockNamedNodeData: hash table containing data about bitmaps
|
|
|
|
* @actions: filled with arguments for a 'transaction' command
|
|
|
|
* @disabledBitmapsBase: bitmap names which were disabled
|
|
|
|
*
|
|
|
|
* Calculates the necessary bitmap merges/additions/enablements to properly
|
|
|
|
* handle commit of images from 'top' into 'base'. The necessary operations
|
|
|
|
* in the form of arguments of the 'transaction' command are filled into
|
|
|
|
* 'actions' if there is anything to do. Otherwise NULL is returned.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockBitmapsHandleCommitFinish(virStorageSource *topsrc,
|
|
|
|
virStorageSource *basesrc,
|
2020-04-17 09:51:40 +02:00
|
|
|
bool active,
|
2020-10-22 19:04:18 +02:00
|
|
|
GHashTable *blockNamedNodeData,
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue **actions)
|
2020-03-02 14:40:18 +01:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *writebitmapsrc = NULL;
|
2020-03-02 14:40:18 +01:00
|
|
|
|
2023-11-23 13:43:36 +01:00
|
|
|
/* if base is a 'raw' image there's no point in attempting to merge the
|
|
|
|
* bitmaps into it */
|
|
|
|
if (basesrc->format == VIR_STORAGE_FILE_RAW)
|
|
|
|
return 0;
|
|
|
|
|
2020-04-17 09:51:40 +02:00
|
|
|
if (active)
|
|
|
|
writebitmapsrc = basesrc;
|
2020-03-02 14:40:18 +01:00
|
|
|
|
2020-04-17 09:51:40 +02:00
|
|
|
if (qemuBlockGetBitmapMergeActions(topsrc, basesrc, basesrc, NULL, NULL,
|
|
|
|
writebitmapsrc, actions,
|
|
|
|
blockNamedNodeData) < 0)
|
2020-03-02 14:40:18 +01:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-13 09:24:36 +01:00
|
|
|
/**
|
2023-11-22 14:32:48 +01:00
|
|
|
* qemuBlockReopenAccess:
|
2020-02-13 09:24:36 +01:00
|
|
|
* @vm: domain object
|
|
|
|
* @src: storage source to reopen
|
2023-11-22 14:32:48 +01:00
|
|
|
* @readonly: requested readonly mode
|
2020-02-13 09:24:36 +01:00
|
|
|
* @asyncJob: qemu async job type
|
|
|
|
*
|
2023-11-22 14:32:48 +01:00
|
|
|
* Reopen @src image to ensure that it is in @readonly. Does nothing if it is
|
|
|
|
* already in the requested state.
|
|
|
|
*
|
|
|
|
* Callers must use qemuBlockReopenReadWrite/qemuBlockReopenReadOnly functions.
|
2020-02-13 09:24:36 +01:00
|
|
|
*/
|
|
|
|
static int
|
2023-11-22 14:32:48 +01:00
|
|
|
qemuBlockReopenAccess(virDomainObj *vm,
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *src,
|
2023-11-22 14:32:48 +01:00
|
|
|
bool readonly,
|
2022-03-24 16:32:42 +01:00
|
|
|
virDomainAsyncJob asyncJob)
|
2020-02-13 09:24:36 +01:00
|
|
|
{
|
2023-11-22 16:51:31 +01:00
|
|
|
g_autoptr(virJSONValue) reopenoptions = virJSONValueNewArray();
|
|
|
|
g_autoptr(virJSONValue) srcprops = NULL;
|
2020-02-13 09:24:36 +01:00
|
|
|
int rc;
|
2023-11-22 14:32:48 +01:00
|
|
|
|
2024-11-26 13:08:34 +01:00
|
|
|
VIR_DEBUG("nodename:'%s' current-ro:'%d requested-ro='%d'",
|
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(src),
|
|
|
|
src->readonly, readonly);
|
|
|
|
|
2023-11-22 14:32:48 +01:00
|
|
|
if (src->readonly == readonly)
|
|
|
|
return 0;
|
2020-02-13 09:24:36 +01:00
|
|
|
|
|
|
|
/* If we are lacking the object here, qemu might have opened an image with
|
|
|
|
* a node name unknown to us */
|
2024-11-26 09:10:10 +01:00
|
|
|
if (src->format >= VIR_STORAGE_FILE_BACKING && !src->backingStore) {
|
2020-02-13 09:24:36 +01:00
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
|
|
_("can't reopen image with unknown presence of backing store"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-11-22 14:32:48 +01:00
|
|
|
src->readonly = readonly;
|
2024-11-26 13:20:56 +01:00
|
|
|
/* from now on all error paths must use 'goto error' which restores the original state */
|
2023-11-22 14:32:48 +01:00
|
|
|
|
2023-11-22 16:58:12 +01:00
|
|
|
/* based on which is the current 'effecitve' layer we must reopen the
|
|
|
|
* appropriate blockdev */
|
|
|
|
if (qemuBlockStorageSourceGetFormatNodename(src)) {
|
|
|
|
if (!(srcprops = qemuBlockStorageSourceGetFormatProps(src, src->backingStore)))
|
2024-11-26 13:20:56 +01:00
|
|
|
goto error;
|
2023-11-22 16:58:12 +01:00
|
|
|
} else if (qemuBlockStorageSourceGetSliceNodename(src)) {
|
2023-12-11 17:13:39 +01:00
|
|
|
if (!(srcprops = qemuBlockStorageSourceGetBlockdevStorageSliceProps(src, true, false)))
|
2024-11-26 13:20:56 +01:00
|
|
|
goto error;
|
2023-11-22 16:58:12 +01:00
|
|
|
} else {
|
|
|
|
if (!(srcprops = qemuBlockStorageSourceGetBackendProps(src,
|
|
|
|
QEMU_BLOCK_STORAGE_SOURCE_BACKEND_PROPS_EFFECTIVE_NODE)))
|
2024-11-26 13:20:56 +01:00
|
|
|
goto error;
|
2023-11-22 16:58:12 +01:00
|
|
|
}
|
2023-11-22 16:51:31 +01:00
|
|
|
|
|
|
|
if (virJSONValueArrayAppend(reopenoptions, &srcprops) < 0)
|
2024-11-26 13:20:56 +01:00
|
|
|
goto error;
|
2023-11-22 16:51:31 +01:00
|
|
|
|
2022-08-10 14:57:02 +02:00
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
2024-11-26 13:20:56 +01:00
|
|
|
goto error;
|
2020-02-13 09:24:36 +01:00
|
|
|
|
2023-11-22 16:51:31 +01:00
|
|
|
rc = qemuMonitorBlockdevReopen(qemuDomainGetMonitor(vm), &reopenoptions);
|
2020-02-13 09:24:36 +01:00
|
|
|
|
2022-03-18 11:17:28 +01:00
|
|
|
qemuDomainObjExitMonitor(vm);
|
2021-11-24 13:11:52 +01:00
|
|
|
if (rc < 0)
|
2024-11-26 13:20:56 +01:00
|
|
|
goto error;
|
2020-02-13 09:24:36 +01:00
|
|
|
|
2024-11-26 13:20:56 +01:00
|
|
|
return 0;
|
2020-02-13 09:24:36 +01:00
|
|
|
|
2024-11-26 13:20:56 +01:00
|
|
|
error:
|
2023-11-22 14:32:48 +01:00
|
|
|
src->readonly = !readonly;
|
2024-11-26 13:20:56 +01:00
|
|
|
return -1;
|
2020-02-13 09:24:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-11-22 14:27:19 +01:00
|
|
|
* qemuBlockReopenReadWrite:
|
2020-02-13 09:24:36 +01:00
|
|
|
* @vm: domain object
|
|
|
|
* @src: storage source to reopen
|
|
|
|
* @asyncJob: qemu async job type
|
|
|
|
*
|
2023-11-22 14:27:19 +01:00
|
|
|
* Semantic wrapper that reopens @src read-write. After successful reopen @src's
|
|
|
|
* 'readonly' flag is modified. Does nothing if @src is already read-write.
|
2020-02-13 09:24:36 +01:00
|
|
|
*/
|
|
|
|
int
|
2023-11-22 14:27:19 +01:00
|
|
|
qemuBlockReopenReadWrite(virDomainObj *vm,
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageSource *src,
|
2022-03-24 16:32:42 +01:00
|
|
|
virDomainAsyncJob asyncJob)
|
2020-02-13 09:24:36 +01:00
|
|
|
{
|
2023-11-22 14:27:19 +01:00
|
|
|
return qemuBlockReopenAccess(vm, src, false, asyncJob);
|
|
|
|
}
|
2020-02-13 09:24:36 +01:00
|
|
|
|
|
|
|
|
2023-11-22 14:27:19 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockReopenReadOnly:
|
|
|
|
* @vm: domain object
|
|
|
|
* @src: storage source to reopen
|
|
|
|
* @asyncJob: qemu async job type
|
|
|
|
*
|
|
|
|
* Semantic wrapper that reopens @src read-only. After successful reopen @src's
|
|
|
|
* 'readonly' flag is modified. Does nothing if @src is already read-only.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuBlockReopenReadOnly(virDomainObj *vm,
|
|
|
|
virStorageSource *src,
|
|
|
|
virDomainAsyncJob asyncJob)
|
|
|
|
{
|
|
|
|
return qemuBlockReopenAccess(vm, src, true, asyncJob);
|
2020-02-13 09:24:36 +01:00
|
|
|
}
|
2020-03-19 16:43:49 +01:00
|
|
|
|
2023-12-12 17:11:45 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceIsLUKS:
|
|
|
|
* @src: storage source object
|
|
|
|
*
|
|
|
|
* Returns true if @src is an image in 'luks' format, which is to be decrypted
|
|
|
|
* in qemu (rather than transparently by the transport layer or host's kernel).
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
qemuBlockStorageSourceIsLUKS(const virStorageSource *src)
|
|
|
|
{
|
|
|
|
if (src->format != VIR_STORAGE_FILE_RAW)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (src->encryption &&
|
|
|
|
src->encryption->engine == VIR_STORAGE_ENCRYPTION_ENGINE_QEMU &&
|
|
|
|
src->encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceIsRaw:
|
|
|
|
* @src: storage source object
|
|
|
|
*
|
|
|
|
* Returns true if @src is a true 'raw' image. This specifically excludes
|
|
|
|
* LUKS encrypted images to be decrypted by qemu.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
qemuBlockStorageSourceIsRaw(const virStorageSource *src)
|
|
|
|
{
|
|
|
|
if (src->format != VIR_STORAGE_FILE_RAW)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (qemuBlockStorageSourceIsLUKS(src))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-12-11 15:37:51 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockReopenSliceExpand:
|
|
|
|
* @vm: domain object
|
|
|
|
* @src: storage source to reopen
|
|
|
|
*
|
|
|
|
* Reopen @src image to remove its storage slice. Note that this currently
|
|
|
|
* works only for 'raw' disks.
|
|
|
|
*
|
|
|
|
* Note: This changes transforms the definition such that the 'raw' driver
|
|
|
|
* becomes the 'format' layer rather than the 'slice' layer, to be able
|
|
|
|
* to free the slice definition.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuBlockReopenSliceExpand(virDomainObj *vm,
|
|
|
|
virStorageSource *src)
|
|
|
|
{
|
|
|
|
g_autoptr(virJSONValue) reopenoptions = virJSONValueNewArray();
|
|
|
|
g_autoptr(virJSONValue) srcprops = NULL;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* If we are lacking the object here, qemu might have opened an image with
|
|
|
|
* a node name unknown to us */
|
|
|
|
/* Note: This is currently dead code, as only 'raw' images are supported */
|
|
|
|
if (!src->backingStore) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
|
|
_("can't reopen image with unknown presence of backing store"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If there is an explicit storage slice 'raw' driver layer we need to modify that */
|
|
|
|
if (qemuBlockStorageSourceGetSliceNodename(src)) {
|
|
|
|
/* we need to know whether the slice layer is the "effective" layer */
|
|
|
|
bool isEffective = !qemuBlockStorageSourceGetSliceNodename(src);
|
|
|
|
|
|
|
|
if (!(srcprops = qemuBlockStorageSourceGetBlockdevStorageSliceProps(src, isEffective, true)))
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
if (!(srcprops = qemuBlockStorageSourceGetFormatProps(src, src->backingStore)))
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virJSONValueArrayAppend(reopenoptions, &srcprops) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, VIR_ASYNC_JOB_NONE) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
rc = qemuMonitorBlockdevReopen(qemuDomainGetMonitor(vm), &reopenoptions);
|
|
|
|
|
|
|
|
qemuDomainObjExitMonitor(vm);
|
|
|
|
if (rc < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* transform the 'slice' raw driver into a 'format' driver so that we don't
|
|
|
|
* have to add extra code */
|
|
|
|
if (qemuBlockStorageSourceGetSliceNodename(src))
|
|
|
|
qemuBlockStorageSourceSetFormatNodename(src, g_strdup(qemuBlockStorageSourceGetSliceNodename(src)));
|
|
|
|
|
|
|
|
/* get rid of the slice */
|
|
|
|
g_clear_pointer(&src->sliceStorage, virStorageSourceSliceFree);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-03-19 16:43:49 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceNeedSliceLayer:
|
|
|
|
* @src: source to inspect
|
|
|
|
*
|
|
|
|
* Returns true if @src requires an extra 'raw' layer for handling of the storage
|
|
|
|
* slice.
|
2023-11-24 10:18:56 +01:00
|
|
|
*
|
|
|
|
* Important: This helper must be used only for decisions when setting up a
|
|
|
|
* '-blockdev' backend in which case the storage slice layer node name will be
|
|
|
|
* populated.
|
|
|
|
* Any cases when the backend can be already in use must decide based on the
|
|
|
|
* existence of the storage slice layer nodename.
|
2020-03-19 16:43:49 +01:00
|
|
|
*/
|
|
|
|
bool
|
|
|
|
qemuBlockStorageSourceNeedsStorageSliceLayer(const virStorageSource *src)
|
|
|
|
{
|
2023-10-23 15:46:26 +02:00
|
|
|
return !!src->sliceStorage;
|
2020-03-19 16:43:49 +01:00
|
|
|
}
|
2020-03-23 16:31:19 +01:00
|
|
|
|
|
|
|
|
2023-10-19 22:07:49 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceNeedsFormatLayer:
|
|
|
|
* @src: storage source
|
|
|
|
*
|
|
|
|
* Returns true if configuration of @src requires a 'format' layer -blockdev.
|
|
|
|
*
|
|
|
|
* Important: This helper must be used only for decisions when setting up a
|
|
|
|
* '-blockdev' backend in which case the format layer node name will be populated.
|
|
|
|
* Any cases when the backend can be already in use must decide based on the
|
|
|
|
* existence of the format layer nodename.
|
|
|
|
*/
|
|
|
|
bool
|
2023-10-19 22:07:49 +02:00
|
|
|
qemuBlockStorageSourceNeedsFormatLayer(const virStorageSource *src,
|
|
|
|
virQEMUCaps *qemuCaps)
|
2023-10-19 22:07:49 +02:00
|
|
|
{
|
2023-10-19 22:07:49 +02:00
|
|
|
virStorageType actualType = virStorageSourceGetActualType(src);
|
2023-10-23 15:46:26 +02:00
|
|
|
|
2023-10-19 22:07:49 +02:00
|
|
|
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_BLOCKJOB_BACKING_MASK_PROTOCOL))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (!qemuBlockStorageSourceIsRaw(src))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/* when passing a FD to qemu via the /dev/fdset mechanism qemu
|
|
|
|
* fetches the appropriate FD from the fdset by checking that it has
|
|
|
|
* the correct accessmode. Now if a user passes an explicitly read-write fd
|
|
|
|
* but intends to use the disk in read-only mode we need to install a
|
|
|
|
* read-only raw driver on top as qemu wouldn't be able to pick the correct
|
|
|
|
* fd. */
|
|
|
|
if ((actualType == VIR_STORAGE_TYPE_FILE || actualType == VIR_STORAGE_TYPE_BLOCK) &&
|
|
|
|
src->fdtuple &&
|
|
|
|
src->fdtuple->nfds == 1 &&
|
|
|
|
src->fdtuple->writable)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
2023-10-19 22:07:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-03-23 16:31:19 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockStorageSourceGetCookieString:
|
|
|
|
* @src: storage source
|
|
|
|
*
|
|
|
|
* Returns a properly formatted string representing cookies of @src in format
|
|
|
|
* accepted by qemu.
|
|
|
|
*/
|
|
|
|
char *
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockStorageSourceGetCookieString(virStorageSource *src)
|
2020-03-23 16:31:19 +01:00
|
|
|
{
|
2020-07-02 18:26:41 -04:00
|
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
2020-03-23 16:31:19 +01:00
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < src->ncookies; i++) {
|
2021-03-11 08:16:13 +01:00
|
|
|
virStorageNetCookieDef *cookie = src->cookies[i];
|
2020-03-23 16:31:19 +01:00
|
|
|
|
|
|
|
virBufferAsprintf(&buf, "%s=%s; ", cookie->name, cookie->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
virBufferTrim(&buf, "; ");
|
|
|
|
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
|
|
}
|
2020-03-30 11:18:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockUpdateRelativeBacking:
|
|
|
|
* @vm: domain object
|
|
|
|
* @src: starting point of the update
|
|
|
|
* @topsrc: top level image in the backing chain (used to get security label)
|
|
|
|
*
|
|
|
|
* Reload data necessary for keeping backing store links starting from @src
|
|
|
|
* relative.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockUpdateRelativeBacking(virDomainObj *vm,
|
|
|
|
virStorageSource *src,
|
|
|
|
virStorageSource *topsrc)
|
2020-03-30 11:18:32 +02:00
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainObjPrivate *priv = vm->privateData;
|
|
|
|
virQEMUDriver *driver = priv->driver;
|
|
|
|
virStorageSource *n;
|
2020-03-30 11:18:32 +02:00
|
|
|
|
|
|
|
for (n = src; virStorageSourceHasBacking(n); n = n->backingStore) {
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (n->backingStore->relPath)
|
|
|
|
break;
|
|
|
|
|
2021-01-21 16:46:14 +01:00
|
|
|
if (!virStorageSourceSupportsBackingChainTraversal(n))
|
2020-03-30 11:18:32 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (qemuDomainStorageFileInit(driver, vm, n, topsrc) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2021-01-22 15:08:23 +01:00
|
|
|
rc = virStorageSourceFetchRelativeBackingPath(n, &n->backingStore->relPath);
|
2020-03-30 11:18:32 +02:00
|
|
|
|
2021-01-21 16:46:14 +01:00
|
|
|
virStorageSourceDeinit(n);
|
2020-03-30 11:18:32 +02:00
|
|
|
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-10-14 11:12:19 +02:00
|
|
|
|
|
|
|
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *
|
2020-10-14 11:33:06 +02:00
|
|
|
qemuBlockExportGetNBDProps(const char *nodename,
|
|
|
|
const char *exportname,
|
|
|
|
bool writable,
|
2020-10-26 13:06:17 +01:00
|
|
|
const char **bitmaps)
|
2020-10-14 11:33:06 +02:00
|
|
|
{
|
|
|
|
g_autofree char *exportid = NULL;
|
2020-10-26 13:06:17 +01:00
|
|
|
g_autoptr(virJSONValue) bitmapsarr = NULL;
|
2021-03-11 08:16:13 +01:00
|
|
|
virJSONValue *ret = NULL;
|
2020-10-14 11:33:06 +02:00
|
|
|
|
|
|
|
exportid = g_strdup_printf("libvirt-nbd-%s", nodename);
|
|
|
|
|
2020-10-26 13:06:17 +01:00
|
|
|
if (bitmaps && *bitmaps) {
|
|
|
|
bitmapsarr = virJSONValueNewArray();
|
|
|
|
|
|
|
|
while (*bitmaps) {
|
|
|
|
if (virJSONValueArrayAppendString(bitmapsarr, *(bitmaps++)) < 0)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-08 17:24:50 +01:00
|
|
|
if (virJSONValueObjectAdd(&ret,
|
|
|
|
"s:type", "nbd",
|
|
|
|
"s:id", exportid,
|
|
|
|
"s:node-name", nodename,
|
|
|
|
"b:writable", writable,
|
|
|
|
"s:name", exportname,
|
|
|
|
"A:bitmaps", &bitmapsarr,
|
|
|
|
NULL) < 0)
|
2020-10-14 11:33:06 +02:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-14 11:12:19 +02:00
|
|
|
/**
|
|
|
|
* qemuBlockExportAddNBD:
|
|
|
|
* @vm: domain object
|
|
|
|
* @src: disk source to export
|
|
|
|
* @exportname: name for the export
|
|
|
|
* @writable: whether the NBD export allows writes
|
|
|
|
* @bitmap: (optional) block dirty bitmap to export along
|
|
|
|
*
|
|
|
|
* This function automatically selects the proper invocation of exporting a
|
2022-07-19 21:06:04 +02:00
|
|
|
* block backend via NBD in qemu.
|
2020-10-14 11:12:19 +02:00
|
|
|
*
|
|
|
|
* This function must be called while in the monitor context.
|
|
|
|
*/
|
|
|
|
int
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuBlockExportAddNBD(virDomainObj *vm,
|
|
|
|
virStorageSource *src,
|
2020-10-14 11:12:19 +02:00
|
|
|
const char *exportname,
|
|
|
|
bool writable,
|
|
|
|
const char *bitmap)
|
|
|
|
{
|
2021-03-11 08:16:13 +01:00
|
|
|
qemuDomainObjPrivate *priv = vm->privateData;
|
2021-10-26 17:19:37 +02:00
|
|
|
g_autoptr(virJSONValue) nbdprops = NULL;
|
|
|
|
const char *bitmaps[2] = { bitmap, NULL };
|
2020-10-14 11:12:19 +02:00
|
|
|
|
2021-10-26 17:19:37 +02:00
|
|
|
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCK_EXPORT_ADD))
|
2023-10-16 16:42:16 +02:00
|
|
|
return qemuMonitorNBDServerAdd(priv->mon, qemuBlockStorageSourceGetEffectiveNodename(src),
|
2021-10-26 17:19:37 +02:00
|
|
|
exportname, writable, bitmap);
|
2020-10-14 11:49:50 +02:00
|
|
|
|
2023-10-16 16:42:16 +02:00
|
|
|
if (!(nbdprops = qemuBlockExportGetNBDProps(qemuBlockStorageSourceGetEffectiveNodename(src),
|
|
|
|
exportname, writable, bitmaps)))
|
2021-10-26 17:19:37 +02:00
|
|
|
return -1;
|
2020-10-14 11:12:19 +02:00
|
|
|
|
2021-10-26 17:19:37 +02:00
|
|
|
return qemuMonitorBlockExportAdd(priv->mon, &nbdprops);
|
2020-10-14 11:12:19 +02:00
|
|
|
}
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
|
2022-12-05 13:11:19 +01:00
|
|
|
/**
|
|
|
|
* qemuBlockCommit:
|
|
|
|
* @vm: domain object
|
|
|
|
* @disk: disk object where we are about to block commit
|
|
|
|
* @baseSource: disk source within backing chain to commit data into
|
|
|
|
* @topSource: disk source within backing chain with data we will commit
|
|
|
|
* @top_parent: disk source that has @topSource as backing disk
|
2023-01-09 14:35:52 +01:00
|
|
|
* @bandwidth: bandwidth limit in bytes/s
|
2022-12-05 13:11:19 +01:00
|
|
|
* @asyncJob: qemu async job type
|
2023-01-02 14:46:51 +01:00
|
|
|
* @autofinalize: virTristateBool controlling qemu block job finalization
|
2022-12-05 13:11:19 +01:00
|
|
|
* @flags: bitwise-OR of virDomainBlockCommitFlags
|
|
|
|
*
|
|
|
|
* Starts a block commit job for @disk. If @asyncJob is different then
|
|
|
|
* VIR_ASYNC_JOB_NONE the job will be started as synchronous.
|
|
|
|
*
|
2023-01-02 14:46:51 +01:00
|
|
|
* The @autofinalize argument controls if the qemu block job will be automatically
|
|
|
|
* finalized. This is used when deleting external snapshots where we need to
|
|
|
|
* disable automatic finalization for some use-case. The default value passed
|
|
|
|
* to this argument should be VIR_TRISTATE_BOOL_YES.
|
|
|
|
*
|
2022-12-13 16:46:59 +01:00
|
|
|
* Returns qemuBlockJobData pointer on success, NULL on error. Caller is responsible
|
|
|
|
* to call virObjectUnref on the pointer.
|
2022-12-05 13:11:19 +01:00
|
|
|
*/
|
2022-12-13 16:46:59 +01:00
|
|
|
qemuBlockJobData *
|
2022-09-08 14:53:46 +02:00
|
|
|
qemuBlockCommit(virDomainObj *vm,
|
|
|
|
virDomainDiskDef *disk,
|
|
|
|
virStorageSource *baseSource,
|
|
|
|
virStorageSource *topSource,
|
|
|
|
virStorageSource *top_parent,
|
2023-01-09 14:35:52 +01:00
|
|
|
unsigned long long bandwidth,
|
2022-12-05 13:11:19 +01:00
|
|
|
virDomainAsyncJob asyncJob,
|
2023-01-02 14:46:51 +01:00
|
|
|
virTristateBool autofinalize,
|
2022-09-08 14:53:46 +02:00
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
qemuDomainObjPrivate *priv = vm->privateData;
|
|
|
|
virQEMUDriver *driver = priv->driver;
|
2022-12-13 16:46:59 +01:00
|
|
|
int rc = -1;
|
2022-09-08 14:53:46 +02:00
|
|
|
bool clean_access = false;
|
|
|
|
g_autofree char *backingPath = NULL;
|
|
|
|
qemuBlockJobData *job = NULL;
|
2022-12-13 16:46:59 +01:00
|
|
|
qemuBlockJobData *ret = NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
g_autoptr(virStorageSource) mirror = NULL;
|
|
|
|
|
|
|
|
if (virDomainObjCheckActive(vm) < 0)
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
if (!qemuDomainDiskBlockJobIsSupported(disk))
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
if (virStorageSourceIsEmpty(disk->src)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("disk %1$s has no source file to be committed"),
|
2022-09-08 14:53:46 +02:00
|
|
|
disk->dst);
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (qemuDomainDiskBlockJobIsActive(disk))
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
if (qemuDomainSupportsCheckpointsBlockjobs(vm) < 0)
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
if (topSource == disk->src) {
|
|
|
|
/* XXX Should we auto-pivot when COMMIT_ACTIVE is not specified? */
|
|
|
|
if (!(flags & VIR_DOMAIN_BLOCK_COMMIT_ACTIVE)) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("commit of '%1$s' active layer requires active flag"),
|
2022-09-08 14:53:46 +02:00
|
|
|
disk->dst);
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
}
|
|
|
|
} else if (flags & VIR_DOMAIN_BLOCK_COMMIT_ACTIVE) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("active commit requested but '%1$s' is not active"),
|
2022-09-08 14:53:46 +02:00
|
|
|
topSource->path);
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!virStorageSourceHasBacking(topSource)) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("top '%1$s' in chain for '%2$s' has no backing file"),
|
2022-09-08 14:53:46 +02:00
|
|
|
topSource->path, disk->src->path);
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((flags & VIR_DOMAIN_BLOCK_COMMIT_SHALLOW) &&
|
|
|
|
baseSource != topSource->backingStore) {
|
|
|
|
virReportError(VIR_ERR_INVALID_ARG,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("base '%1$s' is not immediately below '%2$s' in chain for '%3$s'"),
|
2022-09-08 14:53:46 +02:00
|
|
|
baseSource->path, topSource->path, disk->src->path);
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* For an active commit, clone enough of the base to act as the mirror */
|
|
|
|
if (topSource == disk->src) {
|
|
|
|
if (!(mirror = virStorageSourceCopy(baseSource, false)))
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
if (virStorageSourceInitChainElement(mirror,
|
|
|
|
disk->src,
|
|
|
|
true) < 0)
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & VIR_DOMAIN_BLOCK_COMMIT_RELATIVE &&
|
|
|
|
topSource != disk->src) {
|
|
|
|
if (top_parent &&
|
|
|
|
qemuBlockUpdateRelativeBacking(vm, top_parent, disk->src) < 0)
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
if (virStorageSourceGetRelativeBackingPath(topSource, baseSource,
|
|
|
|
&backingPath) < 0)
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
if (!backingPath) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
|
|
|
|
_("can't keep relative backing relationship"));
|
2022-12-13 16:46:59 +01:00
|
|
|
return NULL;
|
2022-09-08 14:53:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* For the commit to succeed, we must allow qemu to open both the
|
|
|
|
* 'base' image and the parent of 'top' as read/write; 'top' might
|
|
|
|
* not have a parent, or might already be read-write. XXX It
|
|
|
|
* would also be nice to revert 'base' to read-only, as well as
|
|
|
|
* revoke access to files removed from the chain, when the commit
|
|
|
|
* operation succeeds, but doing that requires tracking the
|
|
|
|
* operation in XML across libvirtd restarts. */
|
|
|
|
clean_access = true;
|
|
|
|
if (qemuDomainStorageSourceAccessAllow(driver, vm, baseSource,
|
|
|
|
false, false, false) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2024-11-26 09:12:01 +01:00
|
|
|
if (baseSource->dataFileStore) {
|
|
|
|
if (qemuDomainStorageSourceAccessAllow(driver, vm, baseSource->dataFileStore,
|
|
|
|
false, false, false) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (qemuBlockReopenReadWrite(vm, baseSource->dataFileStore, asyncJob) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2022-09-08 14:53:46 +02:00
|
|
|
if (top_parent && top_parent != disk->src) {
|
|
|
|
/* While top_parent is topmost image, we don't need to remember its
|
|
|
|
* owner as it will be overwritten upon finishing the commit. Hence,
|
|
|
|
* pass chainTop = false. */
|
|
|
|
if (qemuDomainStorageSourceAccessAllow(driver, vm, top_parent,
|
|
|
|
false, false, false) < 0)
|
|
|
|
goto cleanup;
|
2024-11-26 09:12:01 +01:00
|
|
|
|
|
|
|
if (top_parent->dataFileStore) {
|
|
|
|
if (qemuDomainStorageSourceAccessAllow(driver, vm, top_parent->dataFileStore,
|
|
|
|
false, false, false) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (qemuBlockReopenReadWrite(vm, top_parent->dataFileStore, asyncJob) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2022-09-08 14:53:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(job = qemuBlockJobDiskNewCommit(vm, disk, top_parent, topSource,
|
|
|
|
baseSource,
|
|
|
|
flags & VIR_DOMAIN_BLOCK_COMMIT_DELETE,
|
2023-02-10 17:16:43 +01:00
|
|
|
autofinalize,
|
2022-09-08 14:53:46 +02:00
|
|
|
flags)))
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
|
|
|
|
|
|
|
|
if (!backingPath && top_parent &&
|
|
|
|
!(backingPath = qemuBlockGetBackingStoreString(baseSource, false)))
|
|
|
|
goto cleanup;
|
|
|
|
|
2022-12-05 13:11:19 +01:00
|
|
|
if (asyncJob != VIR_ASYNC_JOB_NONE)
|
|
|
|
qemuBlockJobSyncBegin(job);
|
|
|
|
|
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
|
|
|
goto cleanup;
|
2022-09-08 14:53:46 +02:00
|
|
|
|
2022-12-13 16:46:59 +01:00
|
|
|
rc = qemuMonitorBlockCommit(priv->mon,
|
|
|
|
qemuDomainDiskGetTopNodename(disk),
|
|
|
|
job->name,
|
2023-10-16 16:20:27 +02:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(topSource),
|
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(baseSource),
|
2022-12-13 16:46:59 +01:00
|
|
|
backingPath, bandwidth,
|
|
|
|
autofinalize);
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
qemuDomainObjExitMonitor(vm);
|
|
|
|
|
2022-12-13 16:46:59 +01:00
|
|
|
if (rc < 0)
|
2022-09-08 14:53:46 +02:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (mirror) {
|
|
|
|
disk->mirror = g_steal_pointer(&mirror);
|
|
|
|
disk->mirrorJob = VIR_DOMAIN_BLOCK_JOB_TYPE_ACTIVE_COMMIT;
|
|
|
|
}
|
|
|
|
qemuBlockJobStarted(job, vm);
|
2022-12-13 16:46:59 +01:00
|
|
|
ret = virObjectRef(job);
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
cleanup:
|
2022-12-13 16:46:59 +01:00
|
|
|
if (rc < 0 && clean_access) {
|
2022-09-08 14:53:46 +02:00
|
|
|
virErrorPtr orig_err;
|
|
|
|
virErrorPreserveLast(&orig_err);
|
2024-11-26 09:12:01 +01:00
|
|
|
|
2022-09-08 14:53:46 +02:00
|
|
|
/* Revert access to read-only, if possible. */
|
2024-11-26 09:12:01 +01:00
|
|
|
if (baseSource->dataFileStore) {
|
|
|
|
qemuDomainStorageSourceAccessAllow(driver, vm, baseSource->dataFileStore,
|
|
|
|
true, false, false);
|
|
|
|
qemuBlockReopenReadOnly(vm, baseSource->dataFileStore, asyncJob);
|
|
|
|
}
|
2022-09-08 14:53:46 +02:00
|
|
|
qemuDomainStorageSourceAccessAllow(driver, vm, baseSource,
|
|
|
|
true, false, false);
|
2024-11-26 09:12:01 +01:00
|
|
|
if (top_parent && top_parent != disk->src) {
|
|
|
|
if (top_parent->dataFileStore) {
|
|
|
|
qemuDomainStorageSourceAccessAllow(driver, vm, top_parent->dataFileStore,
|
|
|
|
true, false, false);
|
|
|
|
|
|
|
|
qemuBlockReopenReadWrite(vm, top_parent->dataFileStore, asyncJob);
|
|
|
|
}
|
2022-09-08 14:53:46 +02:00
|
|
|
qemuDomainStorageSourceAccessAllow(driver, vm, top_parent,
|
|
|
|
true, false, false);
|
2024-11-26 09:12:01 +01:00
|
|
|
}
|
2022-09-08 14:53:46 +02:00
|
|
|
|
|
|
|
virErrorRestore(&orig_err);
|
|
|
|
}
|
|
|
|
qemuBlockJobStartupFinalize(vm, job);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2022-08-16 11:31:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* Called while holding the VM job lock, to implement a block job
|
|
|
|
* abort with pivot; this updates the VM definition as appropriate, on
|
|
|
|
* either success or failure. */
|
|
|
|
int
|
|
|
|
qemuBlockPivot(virDomainObj *vm,
|
|
|
|
qemuBlockJobData *job,
|
2022-12-05 12:53:34 +01:00
|
|
|
virDomainAsyncJob asyncJob,
|
2022-08-16 11:31:26 +02:00
|
|
|
virDomainDiskDef *disk)
|
|
|
|
{
|
|
|
|
g_autoptr(qemuBlockStorageSourceChainData) chainattachdata = NULL;
|
|
|
|
int ret = -1;
|
|
|
|
qemuDomainObjPrivate *priv = vm->privateData;
|
|
|
|
g_autoptr(virJSONValue) bitmapactions = NULL;
|
|
|
|
g_autoptr(virJSONValue) reopenactions = NULL;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if (job->state != QEMU_BLOCKJOB_STATE_READY) {
|
|
|
|
virReportError(VIR_ERR_BLOCK_COPY_ACTIVE,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("block job '%1$s' not ready for pivot yet"),
|
2022-08-16 11:31:26 +02:00
|
|
|
job->name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ((qemuBlockJobType) job->type) {
|
|
|
|
case QEMU_BLOCKJOB_TYPE_NONE:
|
|
|
|
case QEMU_BLOCKJOB_TYPE_LAST:
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("invalid job type '%1$d'"), job->type);
|
2022-08-16 11:31:26 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
case QEMU_BLOCKJOB_TYPE_PULL:
|
|
|
|
case QEMU_BLOCKJOB_TYPE_COMMIT:
|
|
|
|
case QEMU_BLOCKJOB_TYPE_BACKUP:
|
|
|
|
case QEMU_BLOCKJOB_TYPE_INTERNAL:
|
|
|
|
case QEMU_BLOCKJOB_TYPE_CREATE:
|
2024-07-17 21:21:37 +03:00
|
|
|
case QEMU_BLOCKJOB_TYPE_SNAPSHOT_SAVE:
|
|
|
|
case QEMU_BLOCKJOB_TYPE_SNAPSHOT_DELETE:
|
2024-11-07 12:57:42 +01:00
|
|
|
case QEMU_BLOCKJOB_TYPE_SNAPSHOT_LOAD:
|
2022-08-16 11:31:26 +02:00
|
|
|
case QEMU_BLOCKJOB_TYPE_BROKEN:
|
|
|
|
virReportError(VIR_ERR_OPERATION_INVALID,
|
2023-03-09 13:15:22 +01:00
|
|
|
_("job type '%1$s' does not support pivot"),
|
2022-08-16 11:31:26 +02:00
|
|
|
qemuBlockjobTypeToString(job->type));
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
case QEMU_BLOCKJOB_TYPE_COPY:
|
|
|
|
if (!job->jobflagsmissing) {
|
|
|
|
bool shallow = job->jobflags & VIR_DOMAIN_BLOCK_COPY_SHALLOW;
|
|
|
|
bool reuse = job->jobflags & VIR_DOMAIN_BLOCK_COPY_REUSE_EXT;
|
|
|
|
|
|
|
|
bitmapactions = virJSONValueNewArray();
|
|
|
|
|
|
|
|
if (qemuMonitorTransactionBitmapAdd(bitmapactions,
|
2023-10-16 16:20:27 +02:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(disk->mirror),
|
2022-08-16 11:31:26 +02:00
|
|
|
"libvirt-tmp-activewrite",
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
0) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* Open and install the backing chain of 'mirror' late if we can use
|
|
|
|
* blockdev-snapshot to do it. This is to appease oVirt that wants
|
|
|
|
* to copy data into the backing chain while the top image is being
|
|
|
|
* copied shallow */
|
|
|
|
if (reuse && shallow &&
|
|
|
|
virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV_SNAPSHOT_ALLOW_WRITE_ONLY) &&
|
|
|
|
virStorageSourceHasBacking(disk->mirror)) {
|
|
|
|
|
2023-10-26 15:44:34 +02:00
|
|
|
if (qemuProcessPrepareHostStorageSourceChain(vm, disk->mirror->backingStore) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2022-08-16 11:31:26 +02:00
|
|
|
if (!(chainattachdata = qemuBuildStorageSourceChainAttachPrepareBlockdev(disk->mirror->backingStore)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
reopenactions = virJSONValueNewArray();
|
|
|
|
|
|
|
|
if (qemuMonitorTransactionSnapshotBlockdev(reopenactions,
|
2023-10-16 16:20:27 +02:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(disk->mirror->backingStore),
|
|
|
|
qemuBlockStorageSourceGetFormatNodename(disk->mirror)))
|
2022-08-16 11:31:26 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case QEMU_BLOCKJOB_TYPE_ACTIVE_COMMIT:
|
|
|
|
bitmapactions = virJSONValueNewArray();
|
|
|
|
|
|
|
|
if (qemuMonitorTransactionBitmapAdd(bitmapactions,
|
2023-10-16 16:20:27 +02:00
|
|
|
qemuBlockStorageSourceGetEffectiveNodename(job->data.commit.base),
|
2022-08-16 11:31:26 +02:00
|
|
|
"libvirt-tmp-activewrite",
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
0) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-12-05 12:53:34 +01:00
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
|
|
|
return -1;
|
2022-08-16 11:31:26 +02:00
|
|
|
|
|
|
|
if (chainattachdata) {
|
|
|
|
if ((rc = qemuBlockStorageSourceChainAttach(priv->mon, chainattachdata)) == 0) {
|
|
|
|
/* install backing images on success, or unplug them on failure */
|
|
|
|
if ((rc = qemuMonitorTransaction(priv->mon, &reopenactions)) != 0)
|
|
|
|
qemuBlockStorageSourceChainDetach(priv->mon, chainattachdata);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bitmapactions && rc == 0)
|
|
|
|
ignore_value(qemuMonitorTransaction(priv->mon, &bitmapactions));
|
|
|
|
|
|
|
|
if (rc == 0)
|
|
|
|
ret = qemuMonitorJobComplete(priv->mon, job->name);
|
|
|
|
|
|
|
|
qemuDomainObjExitMonitor(vm);
|
|
|
|
|
|
|
|
/* The pivot failed. The block job in QEMU remains in the synchronised state */
|
|
|
|
if (ret < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (disk && disk->mirror)
|
|
|
|
disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_PIVOT;
|
|
|
|
job->state = QEMU_BLOCKJOB_STATE_PIVOTING;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2022-07-13 10:16:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuBlockFinalize:
|
|
|
|
* @vm: domain object
|
|
|
|
* @job: qemu block job data object
|
|
|
|
* @asyncJob: qemu async job type
|
|
|
|
*
|
|
|
|
* When qemu job is started with autofinalize disabled it will wait in pending
|
|
|
|
* state for block job finalize to be called manually in order to finish the
|
|
|
|
* job. This is useful when we are running jobs on multiple disks to make
|
|
|
|
* a synchronization point before we finish.
|
|
|
|
*
|
|
|
|
* Return -1 on error, 0 on success.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
qemuBlockFinalize(virDomainObj *vm,
|
|
|
|
qemuBlockJobData *job,
|
|
|
|
virDomainAsyncJob asyncJob)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
qemuDomainObjPrivate *priv = vm->privateData;
|
|
|
|
|
|
|
|
if (qemuDomainObjEnterMonitorAsync(vm, asyncJob) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
ret = qemuMonitorJobFinalize(priv->mon, job->name);
|
|
|
|
|
|
|
|
qemuDomainObjExitMonitor(vm);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|