mirror of
https://github.com/xemu-project/xemu.git
synced 2025-02-14 17:07:24 +00:00
nbd patches for 2020-10-27
- Tweak the new block-export-add QMP command - Allow multiple -B options for qemu-nbd - Add qemu:allocation-depth metadata context as qemu-nbd -A - Improve iotest use of NBD -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAl+cdhIACgkQp6FrSiUn Q2o1uwf+Irf0Gzk3D0UpOuHiq+UDUa8S8TbhL7j0HkArCqIAWlXZOrS1L1yfAXgb QMTEkdsalHJebx5BJPzT2NO64gg1tOo3qdlMr/lULoZtSqMuOMkE5LfTPZeEVddf vUyqabBl75KIt24Aznw6KsHTkV9UH2FgdY5f840dYgiGvFM8Uzmc+sXlAdiCbFY/ 3BnenL0bux6I8C6t6RM0Yp2gEBOKsf1PvovvrN/GM/Tl6YtEj6YM5yfgy2onGKHU j6CDltaOCRCF48z6qf+z7+3lB/yGHx9xSZ8l9f9cDcf3oU7QqG08GdDYznGgIXlK gIEu9p3gLcKgintz8+lt9OYOkaAcRA== =qhU9 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2020-10-27-v2' into staging nbd patches for 2020-10-27 - Tweak the new block-export-add QMP command - Allow multiple -B options for qemu-nbd - Add qemu:allocation-depth metadata context as qemu-nbd -A - Improve iotest use of NBD # gpg: Signature made Fri 30 Oct 2020 20:22:42 GMT # gpg: using RSA key 71C2CC22B1C4602927D2F3AAA7A16B4A2527436A # gpg: Good signature from "Eric Blake <eblake@redhat.com>" [full] # gpg: aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" [full] # gpg: aka "[jpeg image of size 6874]" [full] # Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2 F3AA A7A1 6B4A 2527 436A * remotes/ericb/tags/pull-nbd-2020-10-27-v2: nbd: Add 'qemu-nbd -A' to expose allocation depth nbd: Add new qemu:allocation-depth metadata context block: Return depth level during bdrv_is_allocated_above nbd: Allow export of multiple bitmaps for one device nbd: Refactor counting of metadata contexts nbd: Simplify qemu bitmap context name nbd: Update qapi to support exporting multiple bitmaps nbd: Utilize QAPI_CLONE for type conversion qapi: Add QAPI_LIST_PREPEND() macro block: Simplify QAPI_LIST_ADD iotests/291: Stop NBD server iotests/291: Filter irrelevant parts of img-info Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
6f2ef80b0c
22
block.c
22
block.c
@ -5220,7 +5220,7 @@ BlockDriverState *bdrv_find_node(const char *node_name)
|
||||
BlockDeviceInfoList *bdrv_named_nodes_list(bool flat,
|
||||
Error **errp)
|
||||
{
|
||||
BlockDeviceInfoList *list, *entry;
|
||||
BlockDeviceInfoList *list;
|
||||
BlockDriverState *bs;
|
||||
|
||||
list = NULL;
|
||||
@ -5230,22 +5230,12 @@ BlockDeviceInfoList *bdrv_named_nodes_list(bool flat,
|
||||
qapi_free_BlockDeviceInfoList(list);
|
||||
return NULL;
|
||||
}
|
||||
entry = g_malloc0(sizeof(*entry));
|
||||
entry->value = info;
|
||||
entry->next = list;
|
||||
list = entry;
|
||||
QAPI_LIST_PREPEND(list, info);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
#define QAPI_LIST_ADD(list, element) do { \
|
||||
typeof(list) _tmp = g_new(typeof(*(list)), 1); \
|
||||
_tmp->value = (element); \
|
||||
_tmp->next = (list); \
|
||||
(list) = _tmp; \
|
||||
} while (0)
|
||||
|
||||
typedef struct XDbgBlockGraphConstructor {
|
||||
XDbgBlockGraph *graph;
|
||||
GHashTable *graph_nodes;
|
||||
@ -5300,7 +5290,7 @@ static void xdbg_graph_add_node(XDbgBlockGraphConstructor *gr, void *node,
|
||||
n->type = type;
|
||||
n->name = g_strdup(name);
|
||||
|
||||
QAPI_LIST_ADD(gr->graph->nodes, n);
|
||||
QAPI_LIST_PREPEND(gr->graph->nodes, n);
|
||||
}
|
||||
|
||||
static void xdbg_graph_add_edge(XDbgBlockGraphConstructor *gr, void *parent,
|
||||
@ -5319,14 +5309,14 @@ static void xdbg_graph_add_edge(XDbgBlockGraphConstructor *gr, void *parent,
|
||||
uint64_t flag = bdrv_qapi_perm_to_blk_perm(qapi_perm);
|
||||
|
||||
if (flag & child->perm) {
|
||||
QAPI_LIST_ADD(edge->perm, qapi_perm);
|
||||
QAPI_LIST_PREPEND(edge->perm, qapi_perm);
|
||||
}
|
||||
if (flag & child->shared_perm) {
|
||||
QAPI_LIST_ADD(edge->shared_perm, qapi_perm);
|
||||
QAPI_LIST_PREPEND(edge->shared_perm, qapi_perm);
|
||||
}
|
||||
}
|
||||
|
||||
QAPI_LIST_ADD(gr->graph->edges, edge);
|
||||
QAPI_LIST_PREPEND(gr->graph->edges, edge);
|
||||
}
|
||||
|
||||
|
||||
|
@ -156,7 +156,7 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
|
||||
/* Copy if allocated above the base */
|
||||
ret = bdrv_is_allocated_above(blk_bs(s->top), s->base_overlay, true,
|
||||
offset, COMMIT_BUFFER_SIZE, &n);
|
||||
copy = (ret == 1);
|
||||
copy = (ret > 0);
|
||||
trace_commit_one_iteration(s, offset, n, ret);
|
||||
if (copy) {
|
||||
assert(n < SIZE_MAX);
|
||||
|
@ -47,7 +47,8 @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
|
||||
int64_t bytes,
|
||||
int64_t *pnum,
|
||||
int64_t *map,
|
||||
BlockDriverState **file);
|
||||
BlockDriverState **file,
|
||||
int *depth);
|
||||
int generated_co_wrapper
|
||||
bdrv_common_block_status_above(BlockDriverState *bs,
|
||||
BlockDriverState *base,
|
||||
@ -57,7 +58,8 @@ bdrv_common_block_status_above(BlockDriverState *bs,
|
||||
int64_t bytes,
|
||||
int64_t *pnum,
|
||||
int64_t *map,
|
||||
BlockDriverState **file);
|
||||
BlockDriverState **file,
|
||||
int *depth);
|
||||
|
||||
int coroutine_fn bdrv_co_readv_vmstate(BlockDriverState *bs,
|
||||
QEMUIOVector *qiov, int64_t pos);
|
||||
|
31
block/io.c
31
block/io.c
@ -2362,20 +2362,28 @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
|
||||
int64_t bytes,
|
||||
int64_t *pnum,
|
||||
int64_t *map,
|
||||
BlockDriverState **file)
|
||||
BlockDriverState **file,
|
||||
int *depth)
|
||||
{
|
||||
int ret;
|
||||
BlockDriverState *p;
|
||||
int64_t eof = 0;
|
||||
int dummy;
|
||||
|
||||
assert(!include_base || base); /* Can't include NULL base */
|
||||
|
||||
if (!depth) {
|
||||
depth = &dummy;
|
||||
}
|
||||
*depth = 0;
|
||||
|
||||
if (!include_base && bs == base) {
|
||||
*pnum = bytes;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = bdrv_co_block_status(bs, want_zero, offset, bytes, pnum, map, file);
|
||||
++*depth;
|
||||
if (ret < 0 || *pnum == 0 || ret & BDRV_BLOCK_ALLOCATED || bs == base) {
|
||||
return ret;
|
||||
}
|
||||
@ -2392,6 +2400,7 @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
|
||||
{
|
||||
ret = bdrv_co_block_status(p, want_zero, offset, bytes, pnum, map,
|
||||
file);
|
||||
++*depth;
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
@ -2450,7 +2459,7 @@ int bdrv_block_status_above(BlockDriverState *bs, BlockDriverState *base,
|
||||
int64_t *map, BlockDriverState **file)
|
||||
{
|
||||
return bdrv_common_block_status_above(bs, base, false, true, offset, bytes,
|
||||
pnum, map, file);
|
||||
pnum, map, file, NULL);
|
||||
}
|
||||
|
||||
int bdrv_block_status(BlockDriverState *bs, int64_t offset, int64_t bytes,
|
||||
@ -2478,7 +2487,7 @@ int coroutine_fn bdrv_co_is_zero_fast(BlockDriverState *bs, int64_t offset,
|
||||
}
|
||||
|
||||
ret = bdrv_common_block_status_above(bs, NULL, false, false, offset,
|
||||
bytes, &pnum, NULL, NULL);
|
||||
bytes, &pnum, NULL, NULL, NULL);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
@ -2495,7 +2504,7 @@ int coroutine_fn bdrv_is_allocated(BlockDriverState *bs, int64_t offset,
|
||||
|
||||
ret = bdrv_common_block_status_above(bs, bs, true, false, offset,
|
||||
bytes, pnum ? pnum : &dummy, NULL,
|
||||
NULL);
|
||||
NULL, NULL);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
@ -2505,8 +2514,9 @@ int coroutine_fn bdrv_is_allocated(BlockDriverState *bs, int64_t offset,
|
||||
/*
|
||||
* Given an image chain: ... -> [BASE] -> [INTER1] -> [INTER2] -> [TOP]
|
||||
*
|
||||
* Return 1 if (a prefix of) the given range is allocated in any image
|
||||
* between BASE and TOP (BASE is only included if include_base is set).
|
||||
* Return a positive depth if (a prefix of) the given range is allocated
|
||||
* in any image between BASE and TOP (BASE is only included if include_base
|
||||
* is set). Depth 1 is TOP, 2 is the first backing layer, and so forth.
|
||||
* BASE can be NULL to check if the given offset is allocated in any
|
||||
* image of the chain. Return 0 otherwise, or negative errno on
|
||||
* failure.
|
||||
@ -2523,13 +2533,18 @@ int bdrv_is_allocated_above(BlockDriverState *top,
|
||||
bool include_base, int64_t offset,
|
||||
int64_t bytes, int64_t *pnum)
|
||||
{
|
||||
int depth;
|
||||
int ret = bdrv_common_block_status_above(top, base, include_base, false,
|
||||
offset, bytes, pnum, NULL, NULL);
|
||||
offset, bytes, pnum, NULL, NULL,
|
||||
&depth);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return !!(ret & BDRV_BLOCK_ALLOCATED);
|
||||
if (ret & BDRV_BLOCK_ALLOCATED) {
|
||||
return depth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int coroutine_fn
|
||||
|
@ -846,7 +846,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s)
|
||||
}
|
||||
|
||||
assert(count);
|
||||
if (ret == 1) {
|
||||
if (ret > 0) {
|
||||
bdrv_set_dirty_bitmap(s->dirty_bitmap, offset, count);
|
||||
}
|
||||
offset += count;
|
||||
|
26
block/nbd.c
26
block/nbd.c
@ -135,6 +135,7 @@ typedef struct BDRVNBDState {
|
||||
QCryptoTLSCreds *tlscreds;
|
||||
const char *hostname;
|
||||
char *x_dirty_bitmap;
|
||||
bool alloc_depth;
|
||||
|
||||
bool wait_connect;
|
||||
NBDConnectThread *connect_thread;
|
||||
@ -961,6 +962,16 @@ static int nbd_parse_blockstatus_payload(BDRVNBDState *s,
|
||||
trace_nbd_parse_blockstatus_compliance("extent length too large");
|
||||
}
|
||||
|
||||
/*
|
||||
* HACK: if we are using x-dirty-bitmaps to access
|
||||
* qemu:allocation-depth, treat all depths > 2 the same as 2,
|
||||
* since nbd_client_co_block_status is only expecting the low two
|
||||
* bits to be set.
|
||||
*/
|
||||
if (s->alloc_depth && extent->flags > 2) {
|
||||
extent->flags = 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1795,11 +1806,16 @@ static int nbd_client_handshake(BlockDriverState *bs, QIOChannelSocket *sioc,
|
||||
s->sioc = NULL;
|
||||
return ret;
|
||||
}
|
||||
if (s->x_dirty_bitmap && !s->info.base_allocation) {
|
||||
error_setg(errp, "requested x-dirty-bitmap %s not found",
|
||||
s->x_dirty_bitmap);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
if (s->x_dirty_bitmap) {
|
||||
if (!s->info.base_allocation) {
|
||||
error_setg(errp, "requested x-dirty-bitmap %s not found",
|
||||
s->x_dirty_bitmap);
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
if (strcmp(s->x_dirty_bitmap, "qemu:allocation-depth") == 0) {
|
||||
s->alloc_depth = true;
|
||||
}
|
||||
}
|
||||
if (s->info.flags & NBD_FLAG_READ_ONLY) {
|
||||
ret = bdrv_apply_auto_read_only(bs, "NBD export is read-only", errp);
|
||||
|
@ -167,7 +167,7 @@ static int coroutine_fn stream_run(Job *job, Error **errp)
|
||||
n = len - offset;
|
||||
}
|
||||
|
||||
copy = (ret == 1);
|
||||
copy = (ret > 0);
|
||||
}
|
||||
trace_stream_one_iteration(s, offset, n, ret);
|
||||
if (copy) {
|
||||
|
@ -14,6 +14,8 @@
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "hw/block/block.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/clone-visitor.h"
|
||||
#include "qapi/qapi-visit-block-export.h"
|
||||
#include "qapi/qapi-commands-block-export.h"
|
||||
#include "block/nbd.h"
|
||||
#include "io/channel-socket.h"
|
||||
@ -195,7 +197,8 @@ void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
|
||||
* the device name as a default here for compatibility.
|
||||
*/
|
||||
if (!arg->has_name) {
|
||||
arg->name = arg->device;
|
||||
arg->has_name = true;
|
||||
arg->name = g_strdup(arg->device);
|
||||
}
|
||||
|
||||
export_opts = g_new(BlockExportOptions, 1);
|
||||
@ -205,15 +208,13 @@ void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
|
||||
.node_name = g_strdup(bdrv_get_node_name(bs)),
|
||||
.has_writable = arg->has_writable,
|
||||
.writable = arg->writable,
|
||||
.u.nbd = {
|
||||
.has_name = true,
|
||||
.name = g_strdup(arg->name),
|
||||
.has_description = arg->has_description,
|
||||
.description = g_strdup(arg->description),
|
||||
.has_bitmap = arg->has_bitmap,
|
||||
.bitmap = g_strdup(arg->bitmap),
|
||||
},
|
||||
};
|
||||
QAPI_CLONE_MEMBERS(BlockExportOptionsNbdBase, &export_opts->u.nbd,
|
||||
qapi_NbdServerAddOptions_base(arg));
|
||||
if (arg->has_bitmap) {
|
||||
export_opts->u.nbd.has_bitmaps = true;
|
||||
QAPI_LIST_PREPEND(export_opts->u.nbd.bitmaps, g_strdup(arg->bitmap));
|
||||
}
|
||||
|
||||
/*
|
||||
* nbd-server-add doesn't complain when a read-only device should be
|
||||
|
@ -17,19 +17,31 @@ namespace "qemu".
|
||||
|
||||
== "qemu" namespace ==
|
||||
|
||||
The "qemu" namespace currently contains only one type of context,
|
||||
related to exposing the contents of a dirty bitmap alongside the
|
||||
associated disk contents. That context has the following form:
|
||||
The "qemu" namespace currently contains two available metadata context
|
||||
types. The first is related to exposing the contents of a dirty
|
||||
bitmap alongside the associated disk contents. That metadata context
|
||||
is named with the following form:
|
||||
|
||||
qemu:dirty-bitmap:<dirty-bitmap-export-name>
|
||||
|
||||
Each dirty-bitmap metadata context defines only one flag for extents
|
||||
in reply for NBD_CMD_BLOCK_STATUS:
|
||||
|
||||
bit 0: NBD_STATE_DIRTY, means that the extent is "dirty"
|
||||
bit 0: NBD_STATE_DIRTY, set when the extent is "dirty"
|
||||
|
||||
The second is related to exposing the source of various extents within
|
||||
the image, with a single metadata context named:
|
||||
|
||||
qemu:allocation-depth
|
||||
|
||||
In the allocation depth context, the entire 32-bit value represents a
|
||||
depth of which layer in a thin-provisioned backing chain provided the
|
||||
data (0 for unallocated, 1 for the active layer, 2 for the first
|
||||
backing layer, and so forth).
|
||||
|
||||
For NBD_OPT_LIST_META_CONTEXT the following queries are supported
|
||||
in addition to "qemu:dirty-bitmap:<dirty-bitmap-export-name>":
|
||||
in addition to the specific "qemu:allocation-depth" and
|
||||
"qemu:dirty-bitmap:<dirty-bitmap-export-name>":
|
||||
|
||||
* "qemu:" - returns list of all available metadata contexts in the
|
||||
namespace.
|
||||
@ -55,3 +67,4 @@ the operation of that feature.
|
||||
NBD_CMD_BLOCK_STATUS for "qemu:dirty-bitmap:", NBD_CMD_CACHE
|
||||
* 4.2: NBD_FLAG_CAN_MULTI_CONN for shareable read-only exports,
|
||||
NBD_CMD_FLAG_FAST_ZERO
|
||||
* 5.2: NBD_CMD_BLOCK_STATUS for "qemu:allocation-depth"
|
||||
|
@ -257,7 +257,8 @@ the 'wait' field, which is only applicable to sockets in server mode
|
||||
''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Use the more generic commands ``block-export-add`` and ``block-export-del``
|
||||
instead.
|
||||
instead. As part of this deprecation, where ``nbd-server-add`` used a
|
||||
single ``bitmap``, the new ``block-export-add`` uses a list of ``bitmaps``.
|
||||
|
||||
Human Monitor Protocol (HMP) commands
|
||||
-------------------------------------
|
||||
|
@ -72,10 +72,16 @@ driver options if ``--image-opts`` is specified.
|
||||
|
||||
Export the disk as read-only.
|
||||
|
||||
.. option:: -A, --allocation-depth
|
||||
|
||||
Expose allocation depth information via the
|
||||
``qemu:allocation-depth`` metadata context accessible through
|
||||
NBD_OPT_SET_META_CONTEXT.
|
||||
|
||||
.. option:: -B, --bitmap=NAME
|
||||
|
||||
If *filename* has a qcow2 persistent bitmap *NAME*, expose
|
||||
that bitmap via the ``qemu:dirty-bitmap:NAME`` context
|
||||
that bitmap via the ``qemu:dirty-bitmap:NAME`` metadata context
|
||||
accessible through NBD_OPT_SET_META_CONTEXT.
|
||||
|
||||
.. option:: -s, --snapshot
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2019 Red Hat, Inc.
|
||||
* Copyright (C) 2016-2020 Red Hat, Inc.
|
||||
* Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws>
|
||||
*
|
||||
* Network Block Device
|
||||
@ -47,7 +47,7 @@ typedef struct NBDOptionReply NBDOptionReply;
|
||||
typedef struct NBDOptionReplyMetaContext {
|
||||
NBDOptionReply h; /* h.type = NBD_REP_META_CONTEXT, h.length > 4 */
|
||||
uint32_t context_id;
|
||||
/* meta context name follows */
|
||||
/* metadata context name follows */
|
||||
} QEMU_PACKED NBDOptionReplyMetaContext;
|
||||
|
||||
/* Transmission phase structs
|
||||
@ -229,7 +229,7 @@ enum {
|
||||
#define NBD_MAX_BUFFER_SIZE (32 * 1024 * 1024)
|
||||
|
||||
/*
|
||||
* Maximum size of a protocol string (export name, meta context name,
|
||||
* Maximum size of a protocol string (export name, metadata context name,
|
||||
* etc.). Use malloc rather than stack allocation for storage of a
|
||||
* string.
|
||||
*/
|
||||
@ -259,6 +259,8 @@ enum {
|
||||
/* Extent flags for qemu:dirty-bitmap in NBD_REPLY_TYPE_BLOCK_STATUS */
|
||||
#define NBD_STATE_DIRTY (1 << 0)
|
||||
|
||||
/* No flags needed for qemu:allocation-depth in NBD_REPLY_TYPE_BLOCK_STATUS */
|
||||
|
||||
static inline bool nbd_reply_type_is_error(int type)
|
||||
{
|
||||
return type & (1 << 15);
|
||||
|
@ -22,4 +22,17 @@ int qapi_enum_parse(const QEnumLookup *lookup, const char *buf,
|
||||
|
||||
int parse_qapi_name(const char *name, bool complete);
|
||||
|
||||
/*
|
||||
* For any GenericList @list, insert @element at the front.
|
||||
*
|
||||
* Note that this macro evaluates @element exactly once, so it is safe
|
||||
* to have side-effects with that argument.
|
||||
*/
|
||||
#define QAPI_LIST_PREPEND(list, element) do { \
|
||||
typeof(list) _tmp = g_malloc(sizeof(*(list))); \
|
||||
_tmp->value = (element); \
|
||||
_tmp->next = (list); \
|
||||
(list) = _tmp; \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
222
nbd/server.c
222
nbd/server.c
@ -27,7 +27,9 @@
|
||||
#include "qemu/units.h"
|
||||
|
||||
#define NBD_META_ID_BASE_ALLOCATION 0
|
||||
#define NBD_META_ID_DIRTY_BITMAP 1
|
||||
#define NBD_META_ID_ALLOCATION_DEPTH 1
|
||||
/* Dirty bitmaps use 'NBD_META_ID_DIRTY_BITMAP + i', so keep this id last. */
|
||||
#define NBD_META_ID_DIRTY_BITMAP 2
|
||||
|
||||
/*
|
||||
* NBD_MAX_BLOCK_STATUS_EXTENTS: 1 MiB of extents data. An empirical
|
||||
@ -94,8 +96,9 @@ struct NBDExport {
|
||||
BlockBackend *eject_notifier_blk;
|
||||
Notifier eject_notifier;
|
||||
|
||||
BdrvDirtyBitmap *export_bitmap;
|
||||
char *export_bitmap_context;
|
||||
bool allocation_depth;
|
||||
BdrvDirtyBitmap **export_bitmaps;
|
||||
size_t nr_export_bitmaps;
|
||||
};
|
||||
|
||||
static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports);
|
||||
@ -105,10 +108,13 @@ static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports);
|
||||
* NBD_OPT_LIST_META_CONTEXT. */
|
||||
typedef struct NBDExportMetaContexts {
|
||||
NBDExport *exp;
|
||||
bool valid; /* means that negotiation of the option finished without
|
||||
errors */
|
||||
size_t count; /* number of negotiated contexts */
|
||||
bool base_allocation; /* export base:allocation context (block status) */
|
||||
bool bitmap; /* export qemu:dirty-bitmap:<export bitmap name> */
|
||||
bool allocation_depth; /* export qemu:allocation-depth */
|
||||
bool *bitmaps; /*
|
||||
* export qemu:dirty-bitmap:<export bitmap name>,
|
||||
* sized by exp->nr_export_bitmaps
|
||||
*/
|
||||
} NBDExportMetaContexts;
|
||||
|
||||
struct NBDClient {
|
||||
@ -446,7 +452,9 @@ static int nbd_negotiate_handle_list(NBDClient *client, Error **errp)
|
||||
|
||||
static void nbd_check_meta_export(NBDClient *client)
|
||||
{
|
||||
client->export_meta.valid &= client->exp == client->export_meta.exp;
|
||||
if (client->exp != client->export_meta.exp) {
|
||||
client->export_meta.count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send a reply to NBD_OPT_EXPORT_NAME.
|
||||
@ -852,11 +860,14 @@ static bool nbd_meta_base_query(NBDClient *client, NBDExportMetaContexts *meta,
|
||||
/* nbd_meta_qemu_query
|
||||
*
|
||||
* Handle queries to 'qemu' namespace. For now, only the qemu:dirty-bitmap:
|
||||
* context is available. Return true if @query has been handled.
|
||||
* and qemu:allocation-depth contexts are available. Return true if @query
|
||||
* has been handled.
|
||||
*/
|
||||
static bool nbd_meta_qemu_query(NBDClient *client, NBDExportMetaContexts *meta,
|
||||
const char *query)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!nbd_strshift(&query, "qemu:")) {
|
||||
return false;
|
||||
}
|
||||
@ -864,27 +875,44 @@ static bool nbd_meta_qemu_query(NBDClient *client, NBDExportMetaContexts *meta,
|
||||
|
||||
if (!*query) {
|
||||
if (client->opt == NBD_OPT_LIST_META_CONTEXT) {
|
||||
meta->bitmap = !!meta->exp->export_bitmap;
|
||||
meta->allocation_depth = meta->exp->allocation_depth;
|
||||
memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps);
|
||||
}
|
||||
trace_nbd_negotiate_meta_query_parse("empty");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nbd_strshift(&query, "dirty-bitmap:")) {
|
||||
trace_nbd_negotiate_meta_query_parse("dirty-bitmap:");
|
||||
if (!meta->exp->export_bitmap) {
|
||||
trace_nbd_negotiate_meta_query_skip("no dirty-bitmap exported");
|
||||
return true;
|
||||
}
|
||||
if (nbd_meta_empty_or_pattern(client,
|
||||
meta->exp->export_bitmap_context +
|
||||
strlen("qemu:dirty-bitmap:"), query)) {
|
||||
meta->bitmap = true;
|
||||
}
|
||||
if (strcmp(query, "allocation-depth") == 0) {
|
||||
trace_nbd_negotiate_meta_query_parse("allocation-depth");
|
||||
meta->allocation_depth = meta->exp->allocation_depth;
|
||||
return true;
|
||||
}
|
||||
|
||||
trace_nbd_negotiate_meta_query_skip("not dirty-bitmap");
|
||||
if (nbd_strshift(&query, "dirty-bitmap:")) {
|
||||
trace_nbd_negotiate_meta_query_parse("dirty-bitmap:");
|
||||
if (!*query) {
|
||||
if (client->opt == NBD_OPT_LIST_META_CONTEXT) {
|
||||
memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps);
|
||||
}
|
||||
trace_nbd_negotiate_meta_query_parse("empty");
|
||||
return true;
|
||||
}
|
||||
|
||||
for (i = 0; i < meta->exp->nr_export_bitmaps; i++) {
|
||||
const char *bm_name;
|
||||
|
||||
bm_name = bdrv_dirty_bitmap_name(meta->exp->export_bitmaps[i]);
|
||||
if (strcmp(bm_name, query) == 0) {
|
||||
meta->bitmaps[i] = true;
|
||||
trace_nbd_negotiate_meta_query_parse(query);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
trace_nbd_negotiate_meta_query_skip("no dirty-bitmap match");
|
||||
return true;
|
||||
}
|
||||
|
||||
trace_nbd_negotiate_meta_query_skip("unknown qemu context");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -942,9 +970,11 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
|
||||
{
|
||||
int ret;
|
||||
g_autofree char *export_name = NULL;
|
||||
NBDExportMetaContexts local_meta;
|
||||
g_autofree bool *bitmaps = NULL;
|
||||
NBDExportMetaContexts local_meta = {0};
|
||||
uint32_t nb_queries;
|
||||
int i;
|
||||
size_t i;
|
||||
size_t count = 0;
|
||||
|
||||
if (!client->structured_reply) {
|
||||
return nbd_opt_invalid(client, errp,
|
||||
@ -958,6 +988,7 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
|
||||
meta = &local_meta;
|
||||
}
|
||||
|
||||
g_free(meta->bitmaps);
|
||||
memset(meta, 0, sizeof(*meta));
|
||||
|
||||
ret = nbd_opt_read_name(client, &export_name, NULL, errp);
|
||||
@ -972,6 +1003,10 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
|
||||
return nbd_opt_drop(client, NBD_REP_ERR_UNKNOWN, errp,
|
||||
"export '%s' not present", sane_name);
|
||||
}
|
||||
meta->bitmaps = g_new0(bool, meta->exp->nr_export_bitmaps);
|
||||
if (client->opt == NBD_OPT_LIST_META_CONTEXT) {
|
||||
bitmaps = meta->bitmaps;
|
||||
}
|
||||
|
||||
ret = nbd_opt_read(client, &nb_queries, sizeof(nb_queries), false, errp);
|
||||
if (ret <= 0) {
|
||||
@ -984,7 +1019,8 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
|
||||
if (client->opt == NBD_OPT_LIST_META_CONTEXT && !nb_queries) {
|
||||
/* enable all known contexts */
|
||||
meta->base_allocation = true;
|
||||
meta->bitmap = !!meta->exp->export_bitmap;
|
||||
meta->allocation_depth = meta->exp->allocation_depth;
|
||||
memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps);
|
||||
} else {
|
||||
for (i = 0; i < nb_queries; ++i) {
|
||||
ret = nbd_negotiate_meta_query(client, meta, errp);
|
||||
@ -1001,21 +1037,42 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
if (meta->bitmap) {
|
||||
ret = nbd_negotiate_send_meta_context(client,
|
||||
meta->exp->export_bitmap_context,
|
||||
NBD_META_ID_DIRTY_BITMAP,
|
||||
if (meta->allocation_depth) {
|
||||
ret = nbd_negotiate_send_meta_context(client, "qemu:allocation-depth",
|
||||
NBD_META_ID_ALLOCATION_DEPTH,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
for (i = 0; i < meta->exp->nr_export_bitmaps; i++) {
|
||||
const char *bm_name;
|
||||
g_autofree char *context = NULL;
|
||||
|
||||
if (!meta->bitmaps[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bm_name = bdrv_dirty_bitmap_name(meta->exp->export_bitmaps[i]);
|
||||
context = g_strdup_printf("qemu:dirty-bitmap:%s", bm_name);
|
||||
|
||||
ret = nbd_negotiate_send_meta_context(client, context,
|
||||
NBD_META_ID_DIRTY_BITMAP + i,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
ret = nbd_negotiate_send_rep(client, NBD_REP_ACK, errp);
|
||||
if (ret == 0) {
|
||||
meta->valid = true;
|
||||
meta->count = count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -1359,6 +1416,7 @@ void nbd_client_put(NBDClient *client)
|
||||
QTAILQ_REMOVE(&client->exp->clients, client, next);
|
||||
blk_exp_unref(&client->exp->common);
|
||||
}
|
||||
g_free(client->export_meta.bitmaps);
|
||||
g_free(client);
|
||||
}
|
||||
}
|
||||
@ -1474,6 +1532,8 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
|
||||
uint64_t perm, shared_perm;
|
||||
bool readonly = !exp_args->writable;
|
||||
bool shared = !exp_args->writable;
|
||||
strList *bitmaps;
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
assert(exp_args->type == BLOCK_EXPORT_TYPE_NBD);
|
||||
@ -1533,12 +1593,18 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
|
||||
}
|
||||
exp->size = QEMU_ALIGN_DOWN(size, BDRV_SECTOR_SIZE);
|
||||
|
||||
if (arg->bitmap) {
|
||||
for (bitmaps = arg->bitmaps; bitmaps; bitmaps = bitmaps->next) {
|
||||
exp->nr_export_bitmaps++;
|
||||
}
|
||||
exp->export_bitmaps = g_new0(BdrvDirtyBitmap *, exp->nr_export_bitmaps);
|
||||
for (i = 0, bitmaps = arg->bitmaps; bitmaps;
|
||||
i++, bitmaps = bitmaps->next) {
|
||||
const char *bitmap = bitmaps->value;
|
||||
BlockDriverState *bs = blk_bs(blk);
|
||||
BdrvDirtyBitmap *bm = NULL;
|
||||
|
||||
while (bs) {
|
||||
bm = bdrv_find_dirty_bitmap(bs, arg->bitmap);
|
||||
bm = bdrv_find_dirty_bitmap(bs, bitmap);
|
||||
if (bm != NULL) {
|
||||
break;
|
||||
}
|
||||
@ -1548,7 +1614,7 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
|
||||
|
||||
if (bm == NULL) {
|
||||
ret = -ENOENT;
|
||||
error_setg(errp, "Bitmap '%s' is not found", arg->bitmap);
|
||||
error_setg(errp, "Bitmap '%s' is not found", bitmap);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@ -1562,18 +1628,21 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
|
||||
ret = -EINVAL;
|
||||
error_setg(errp,
|
||||
"Enabled bitmap '%s' incompatible with readonly export",
|
||||
arg->bitmap);
|
||||
bitmap);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bdrv_dirty_bitmap_set_busy(bm, true);
|
||||
exp->export_bitmap = bm;
|
||||
assert(strlen(arg->bitmap) <= BDRV_BITMAP_MAX_NAME_SIZE);
|
||||
exp->export_bitmap_context = g_strdup_printf("qemu:dirty-bitmap:%s",
|
||||
arg->bitmap);
|
||||
assert(strlen(exp->export_bitmap_context) < NBD_MAX_STRING_SIZE);
|
||||
exp->export_bitmaps[i] = bm;
|
||||
assert(strlen(bitmap) <= BDRV_BITMAP_MAX_NAME_SIZE);
|
||||
}
|
||||
|
||||
/* Mark bitmaps busy in a separate loop, to simplify roll-back concerns. */
|
||||
for (i = 0; i < exp->nr_export_bitmaps; i++) {
|
||||
bdrv_dirty_bitmap_set_busy(exp->export_bitmaps[i], true);
|
||||
}
|
||||
|
||||
exp->allocation_depth = arg->allocation_depth;
|
||||
|
||||
blk_add_aio_context_notifier(blk, blk_aio_attached, blk_aio_detach, exp);
|
||||
|
||||
QTAILQ_INSERT_TAIL(&exports, exp, next);
|
||||
@ -1581,6 +1650,7 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
g_free(exp->export_bitmaps);
|
||||
g_free(exp->name);
|
||||
g_free(exp->description);
|
||||
return ret;
|
||||
@ -1630,6 +1700,7 @@ static void nbd_export_request_shutdown(BlockExport *blk_exp)
|
||||
|
||||
static void nbd_export_delete(BlockExport *blk_exp)
|
||||
{
|
||||
size_t i;
|
||||
NBDExport *exp = container_of(blk_exp, NBDExport, common);
|
||||
|
||||
assert(exp->name == NULL);
|
||||
@ -1647,9 +1718,8 @@ static void nbd_export_delete(BlockExport *blk_exp)
|
||||
blk_aio_detach, exp);
|
||||
}
|
||||
|
||||
if (exp->export_bitmap) {
|
||||
bdrv_dirty_bitmap_set_busy(exp->export_bitmap, false);
|
||||
g_free(exp->export_bitmap_context);
|
||||
for (i = 0; i < exp->nr_export_bitmaps; i++) {
|
||||
bdrv_dirty_bitmap_set_busy(exp->export_bitmaps[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1959,6 +2029,29 @@ static int blockstatus_to_extents(BlockDriverState *bs, uint64_t offset,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int blockalloc_to_extents(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, NBDExtentArray *ea)
|
||||
{
|
||||
while (bytes) {
|
||||
int64_t num;
|
||||
int ret = bdrv_is_allocated_above(bs, NULL, false, offset, bytes,
|
||||
&num);
|
||||
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (nbd_extent_array_add(ea, num, ret) < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
offset += num;
|
||||
bytes -= num;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* nbd_co_send_extents
|
||||
*
|
||||
@ -1998,7 +2091,11 @@ static int nbd_co_send_block_status(NBDClient *client, uint64_t handle,
|
||||
unsigned int nb_extents = dont_fragment ? 1 : NBD_MAX_BLOCK_STATUS_EXTENTS;
|
||||
g_autoptr(NBDExtentArray) ea = nbd_extent_array_new(nb_extents);
|
||||
|
||||
ret = blockstatus_to_extents(bs, offset, length, ea);
|
||||
if (context_id == NBD_META_ID_BASE_ALLOCATION) {
|
||||
ret = blockstatus_to_extents(bs, offset, length, ea);
|
||||
} else {
|
||||
ret = blockalloc_to_extents(bs, offset, length, ea);
|
||||
}
|
||||
if (ret < 0) {
|
||||
return nbd_co_send_structured_error(
|
||||
client, handle, -ret, "can't get block status", errp);
|
||||
@ -2258,6 +2355,7 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
|
||||
int flags;
|
||||
NBDExport *exp = client->exp;
|
||||
char *msg;
|
||||
size_t i;
|
||||
|
||||
switch (request->type) {
|
||||
case NBD_CMD_CACHE:
|
||||
@ -2331,18 +2429,16 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
|
||||
return nbd_send_generic_reply(client, request->handle, -EINVAL,
|
||||
"need non-zero length", errp);
|
||||
}
|
||||
if (client->export_meta.valid &&
|
||||
(client->export_meta.base_allocation ||
|
||||
client->export_meta.bitmap))
|
||||
{
|
||||
if (client->export_meta.count) {
|
||||
bool dont_fragment = request->flags & NBD_CMD_FLAG_REQ_ONE;
|
||||
int contexts_remaining = client->export_meta.count;
|
||||
|
||||
if (client->export_meta.base_allocation) {
|
||||
ret = nbd_co_send_block_status(client, request->handle,
|
||||
blk_bs(exp->common.blk),
|
||||
request->from,
|
||||
request->len, dont_fragment,
|
||||
!client->export_meta.bitmap,
|
||||
!--contexts_remaining,
|
||||
NBD_META_ID_BASE_ALLOCATION,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
@ -2350,17 +2446,35 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
|
||||
}
|
||||
}
|
||||
|
||||
if (client->export_meta.bitmap) {
|
||||
ret = nbd_co_send_bitmap(client, request->handle,
|
||||
client->exp->export_bitmap,
|
||||
request->from, request->len,
|
||||
dont_fragment,
|
||||
true, NBD_META_ID_DIRTY_BITMAP, errp);
|
||||
if (client->export_meta.allocation_depth) {
|
||||
ret = nbd_co_send_block_status(client, request->handle,
|
||||
blk_bs(exp->common.blk),
|
||||
request->from, request->len,
|
||||
dont_fragment,
|
||||
!--contexts_remaining,
|
||||
NBD_META_ID_ALLOCATION_DEPTH,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < client->exp->nr_export_bitmaps; i++) {
|
||||
if (!client->export_meta.bitmaps[i]) {
|
||||
continue;
|
||||
}
|
||||
ret = nbd_co_send_bitmap(client, request->handle,
|
||||
client->exp->export_bitmaps[i],
|
||||
request->from, request->len,
|
||||
dont_fragment, !--contexts_remaining,
|
||||
NBD_META_ID_DIRTY_BITMAP + i, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
assert(!contexts_remaining);
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
return nbd_send_generic_reply(client, request->handle, -EINVAL,
|
||||
|
@ -3905,9 +3905,12 @@
|
||||
#
|
||||
# @tls-creds: TLS credentials ID
|
||||
#
|
||||
# @x-dirty-bitmap: A "qemu:dirty-bitmap:NAME" string to query in place of
|
||||
# @x-dirty-bitmap: A metadata context name such as "qemu:dirty-bitmap:NAME"
|
||||
# or "qemu:allocation-depth" to query in place of the
|
||||
# traditional "base:allocation" block status (see
|
||||
# NBD_OPT_LIST_META_CONTEXT in the NBD protocol) (since 3.0)
|
||||
# NBD_OPT_LIST_META_CONTEXT in the NBD protocol; and
|
||||
# yes, naming this option x-context would have made
|
||||
# more sense) (since 3.0)
|
||||
#
|
||||
# @reconnect-delay: On an unexpected disconnect, the nbd client tries to
|
||||
# connect again until succeeding or encountering a serious
|
||||
|
@ -63,10 +63,10 @@
|
||||
'*max-connections': 'uint32' } }
|
||||
|
||||
##
|
||||
# @BlockExportOptionsNbd:
|
||||
# @BlockExportOptionsNbdBase:
|
||||
#
|
||||
# An NBD block export (options shared between nbd-server-add and the NBD branch
|
||||
# of block-export-add).
|
||||
# An NBD block export (common options shared between nbd-server-add and
|
||||
# the NBD branch of block-export-add).
|
||||
#
|
||||
# @name: Export name. If unspecified, the @device parameter is used as the
|
||||
# export name. (Since 2.12)
|
||||
@ -74,15 +74,32 @@
|
||||
# @description: Free-form description of the export, up to 4096 bytes.
|
||||
# (Since 5.0)
|
||||
#
|
||||
# @bitmap: Also export the dirty bitmap reachable from @device, so the
|
||||
# NBD client can use NBD_OPT_SET_META_CONTEXT with
|
||||
# "qemu:dirty-bitmap:NAME" to inspect the bitmap. (since 4.0)
|
||||
#
|
||||
# Since: 5.0
|
||||
##
|
||||
{ 'struct': 'BlockExportOptionsNbdBase',
|
||||
'data': { '*name': 'str', '*description': 'str' } }
|
||||
|
||||
##
|
||||
# @BlockExportOptionsNbd:
|
||||
#
|
||||
# An NBD block export (distinct options used in the NBD branch of
|
||||
# block-export-add).
|
||||
#
|
||||
# @bitmaps: Also export each of the named dirty bitmaps reachable from
|
||||
# @device, so the NBD client can use NBD_OPT_SET_META_CONTEXT with
|
||||
# the metadata context name "qemu:dirty-bitmap:BITMAP" to inspect
|
||||
# each bitmap.
|
||||
#
|
||||
# @allocation-depth: Also export the allocation depth map for @device, so
|
||||
# the NBD client can use NBD_OPT_SET_META_CONTEXT with
|
||||
# the metadata context name "qemu:allocation-depth" to
|
||||
# inspect allocation details. (since 5.2)
|
||||
#
|
||||
# Since: 5.2
|
||||
##
|
||||
{ 'struct': 'BlockExportOptionsNbd',
|
||||
'data': { '*name': 'str', '*description': 'str',
|
||||
'*bitmap': 'str' } }
|
||||
'base': 'BlockExportOptionsNbdBase',
|
||||
'data': { '*bitmaps': ['str'], '*allocation-depth': 'bool' } }
|
||||
|
||||
##
|
||||
# @BlockExportOptionsVhostUserBlk:
|
||||
@ -106,19 +123,24 @@
|
||||
##
|
||||
# @NbdServerAddOptions:
|
||||
#
|
||||
# An NBD block export.
|
||||
# An NBD block export, per legacy nbd-server-add command.
|
||||
#
|
||||
# @device: The device name or node name of the node to be exported
|
||||
#
|
||||
# @writable: Whether clients should be able to write to the device via the
|
||||
# NBD connection (default false).
|
||||
#
|
||||
# @bitmap: Also export a single dirty bitmap reachable from @device, so the
|
||||
# NBD client can use NBD_OPT_SET_META_CONTEXT with the metadata
|
||||
# context name "qemu:dirty-bitmap:BITMAP" to inspect the bitmap
|
||||
# (since 4.0).
|
||||
#
|
||||
# Since: 5.0
|
||||
##
|
||||
{ 'struct': 'NbdServerAddOptions',
|
||||
'base': 'BlockExportOptionsNbd',
|
||||
'base': 'BlockExportOptionsNbdBase',
|
||||
'data': { 'device': 'str',
|
||||
'*writable': 'bool' } }
|
||||
'*writable': 'bool', '*bitmap': 'str' } }
|
||||
|
||||
##
|
||||
# @nbd-server-add:
|
||||
|
30
qemu-nbd.c
30
qemu-nbd.c
@ -100,6 +100,7 @@ static void usage(const char *name)
|
||||
"\n"
|
||||
"Exposing part of the image:\n"
|
||||
" -o, --offset=OFFSET offset into the image\n"
|
||||
" -A, --allocation-depth expose the allocation depth\n"
|
||||
" -B, --bitmap=NAME expose a persistent dirty bitmap\n"
|
||||
"\n"
|
||||
"General purpose options:\n"
|
||||
@ -524,7 +525,7 @@ int main(int argc, char **argv)
|
||||
char *device = NULL;
|
||||
QemuOpts *sn_opts = NULL;
|
||||
const char *sn_id_or_name = NULL;
|
||||
const char *sopt = "hVb:o:p:rsnc:dvk:e:f:tl:x:T:D:B:L";
|
||||
const char *sopt = "hVb:o:p:rsnc:dvk:e:f:tl:x:T:D:AB:L";
|
||||
struct option lopt[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
@ -533,6 +534,7 @@ int main(int argc, char **argv)
|
||||
{ "socket", required_argument, NULL, 'k' },
|
||||
{ "offset", required_argument, NULL, 'o' },
|
||||
{ "read-only", no_argument, NULL, 'r' },
|
||||
{ "allocation-depth", no_argument, NULL, 'A' },
|
||||
{ "bitmap", required_argument, NULL, 'B' },
|
||||
{ "connect", required_argument, NULL, 'c' },
|
||||
{ "disconnect", no_argument, NULL, 'd' },
|
||||
@ -574,7 +576,8 @@ int main(int argc, char **argv)
|
||||
QDict *options = NULL;
|
||||
const char *export_name = NULL; /* defaults to "" later for server mode */
|
||||
const char *export_description = NULL;
|
||||
const char *bitmap = NULL;
|
||||
strList *bitmaps = NULL;
|
||||
bool alloc_depth = false;
|
||||
const char *tlscredsid = NULL;
|
||||
bool imageOpts = false;
|
||||
bool writethrough = true;
|
||||
@ -689,8 +692,11 @@ int main(int argc, char **argv)
|
||||
readonly = true;
|
||||
flags &= ~BDRV_O_RDWR;
|
||||
break;
|
||||
case 'A':
|
||||
alloc_depth = true;
|
||||
break;
|
||||
case 'B':
|
||||
bitmap = optarg;
|
||||
QAPI_LIST_PREPEND(bitmaps, g_strdup(optarg));
|
||||
break;
|
||||
case 'k':
|
||||
sockpath = optarg;
|
||||
@ -786,8 +792,8 @@ int main(int argc, char **argv)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (export_name || export_description || dev_offset ||
|
||||
device || disconnect || fmt || sn_id_or_name || bitmap ||
|
||||
seen_aio || seen_discard || seen_cache) {
|
||||
device || disconnect || fmt || sn_id_or_name || bitmaps ||
|
||||
alloc_depth || seen_aio || seen_discard || seen_cache) {
|
||||
error_report("List mode is incompatible with per-device settings");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -1067,12 +1073,14 @@ int main(int argc, char **argv)
|
||||
.has_writable = true,
|
||||
.writable = !readonly,
|
||||
.u.nbd = {
|
||||
.has_name = true,
|
||||
.name = g_strdup(export_name),
|
||||
.has_description = !!export_description,
|
||||
.description = g_strdup(export_description),
|
||||
.has_bitmap = !!bitmap,
|
||||
.bitmap = g_strdup(bitmap),
|
||||
.has_name = true,
|
||||
.name = g_strdup(export_name),
|
||||
.has_description = !!export_description,
|
||||
.description = g_strdup(export_description),
|
||||
.has_bitmaps = !!bitmaps,
|
||||
.bitmaps = bitmaps,
|
||||
.has_allocation_depth = alloc_depth,
|
||||
.allocation_depth = alloc_depth,
|
||||
},
|
||||
};
|
||||
blk_exp_add(export_opts, &error_fatal);
|
||||
|
@ -42,6 +42,14 @@ _require_command QEMU_NBD
|
||||
# compat=0.10 does not support bitmaps
|
||||
_unsupported_imgopts 'compat=0.10'
|
||||
|
||||
# Filter irrelevant format-specific information from the qemu-img info
|
||||
# output (we only want the bitmaps, basically)
|
||||
_filter_irrelevant_img_info()
|
||||
{
|
||||
grep -v -e 'compat' -e 'compression type' -e 'data file' -e 'extended l2' \
|
||||
-e 'lazy refcounts' -e 'refcount bits'
|
||||
}
|
||||
|
||||
echo
|
||||
echo "=== Initial image setup ==="
|
||||
echo
|
||||
@ -79,7 +87,7 @@ echo
|
||||
|
||||
# Only bitmaps from the active layer are copied
|
||||
$QEMU_IMG convert --bitmaps -O qcow2 "$TEST_IMG.orig" "$TEST_IMG"
|
||||
_img_info --format-specific
|
||||
_img_info --format-specific | _filter_irrelevant_img_info
|
||||
# But we can also merge in bitmaps from other layers. This test is a bit
|
||||
# contrived to cover more code paths, in reality, you could merge directly
|
||||
# into b0 without going through tmp
|
||||
@ -89,7 +97,7 @@ $QEMU_IMG bitmap --add --merge b0 -b "$TEST_IMG.base" -F $IMGFMT \
|
||||
$QEMU_IMG bitmap --merge tmp -f $IMGFMT "$TEST_IMG" b0
|
||||
$QEMU_IMG bitmap --remove --image-opts \
|
||||
driver=$IMGFMT,file.driver=file,file.filename="$TEST_IMG" tmp
|
||||
_img_info --format-specific
|
||||
_img_info --format-specific | _filter_irrelevant_img_info
|
||||
|
||||
echo
|
||||
echo "=== Merge from top layer into backing image ==="
|
||||
@ -98,7 +106,7 @@ echo
|
||||
$QEMU_IMG rebase -u -F qcow2 -b "$TEST_IMG.base" "$TEST_IMG"
|
||||
$QEMU_IMG bitmap --add --merge b2 -b "$TEST_IMG" -F $IMGFMT \
|
||||
-f $IMGFMT "$TEST_IMG.base" b3
|
||||
_img_info --format-specific --backing-chain
|
||||
_img_info --format-specific --backing-chain | _filter_irrelevant_img_info
|
||||
|
||||
echo
|
||||
echo "=== Check bitmap contents ==="
|
||||
@ -107,19 +115,19 @@ echo
|
||||
# x-dirty-bitmap is a hack for reading bitmaps; it abuses block status to
|
||||
# report "data":false for portions of the bitmap which are set
|
||||
IMG="driver=nbd,server.type=unix,server.path=$nbd_unix_socket"
|
||||
nbd_server_start_unix_socket -r -f qcow2 -B b0 "$TEST_IMG"
|
||||
nbd_server_start_unix_socket -r -f qcow2 \
|
||||
-B b0 -B b1 -B b2 -B b3 "$TEST_IMG"
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b0" | _filter_qemu_img_map
|
||||
nbd_server_start_unix_socket -r -f qcow2 -B b1 "$TEST_IMG"
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b1" | _filter_qemu_img_map
|
||||
nbd_server_start_unix_socket -r -f qcow2 -B b2 "$TEST_IMG"
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map
|
||||
nbd_server_start_unix_socket -r -f qcow2 -B b3 "$TEST_IMG"
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b3" | _filter_qemu_img_map
|
||||
|
||||
nbd_server_stop
|
||||
|
||||
# success, all done
|
||||
echo '*** done'
|
||||
rm -f $seq.full
|
||||
|
@ -26,9 +26,6 @@ file format: IMGFMT
|
||||
virtual size: 10 MiB (10485760 bytes)
|
||||
cluster_size: 65536
|
||||
Format specific information:
|
||||
compat: 1.1
|
||||
compression type: zlib
|
||||
lazy refcounts: false
|
||||
bitmaps:
|
||||
[0]:
|
||||
flags:
|
||||
@ -39,17 +36,12 @@ Format specific information:
|
||||
[0]: auto
|
||||
name: b2
|
||||
granularity: 65536
|
||||
refcount bits: 16
|
||||
corrupt: false
|
||||
extended l2: false
|
||||
image: TEST_DIR/t.IMGFMT
|
||||
file format: IMGFMT
|
||||
virtual size: 10 MiB (10485760 bytes)
|
||||
cluster_size: 65536
|
||||
Format specific information:
|
||||
compat: 1.1
|
||||
compression type: zlib
|
||||
lazy refcounts: false
|
||||
bitmaps:
|
||||
[0]:
|
||||
flags:
|
||||
@ -64,9 +56,7 @@ Format specific information:
|
||||
flags:
|
||||
name: b0
|
||||
granularity: 65536
|
||||
refcount bits: 16
|
||||
corrupt: false
|
||||
extended l2: false
|
||||
|
||||
=== Merge from top layer into backing image ===
|
||||
|
||||
@ -77,9 +67,6 @@ cluster_size: 65536
|
||||
backing file: TEST_DIR/t.IMGFMT.base
|
||||
backing file format: IMGFMT
|
||||
Format specific information:
|
||||
compat: 1.1
|
||||
compression type: zlib
|
||||
lazy refcounts: false
|
||||
bitmaps:
|
||||
[0]:
|
||||
flags:
|
||||
@ -94,18 +81,13 @@ Format specific information:
|
||||
flags:
|
||||
name: b0
|
||||
granularity: 65536
|
||||
refcount bits: 16
|
||||
corrupt: false
|
||||
extended l2: false
|
||||
|
||||
image: TEST_DIR/t.IMGFMT.base
|
||||
file format: IMGFMT
|
||||
virtual size: 10 MiB (10485760 bytes)
|
||||
cluster_size: 65536
|
||||
Format specific information:
|
||||
compat: 1.1
|
||||
compression type: zlib
|
||||
lazy refcounts: false
|
||||
bitmaps:
|
||||
[0]:
|
||||
flags:
|
||||
@ -117,9 +99,7 @@ Format specific information:
|
||||
[0]: auto
|
||||
name: b3
|
||||
granularity: 65536
|
||||
refcount bits: 16
|
||||
corrupt: false
|
||||
extended l2: false
|
||||
|
||||
=== Check bitmap contents ===
|
||||
|
||||
|
77
tests/qemu-iotests/309
Executable file
77
tests/qemu-iotests/309
Executable file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Test qemu-nbd -A
|
||||
#
|
||||
# Copyright (C) 2018-2020 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
seq="$(basename $0)"
|
||||
echo "QA output created by $seq"
|
||||
|
||||
status=1 # failure is the default!
|
||||
|
||||
_cleanup()
|
||||
{
|
||||
_cleanup_test_img
|
||||
nbd_server_stop
|
||||
}
|
||||
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||
|
||||
# get standard environment, filters and checks
|
||||
. ./common.rc
|
||||
. ./common.filter
|
||||
. ./common.nbd
|
||||
|
||||
_supported_fmt qcow2
|
||||
_supported_proto file
|
||||
_supported_os Linux
|
||||
_require_command QEMU_NBD
|
||||
|
||||
echo
|
||||
echo "=== Initial image setup ==="
|
||||
echo
|
||||
|
||||
TEST_IMG="$TEST_IMG.base" _make_test_img 4M
|
||||
$QEMU_IO -c 'w 0 2M' -f $IMGFMT "$TEST_IMG.base" | _filter_qemu_io
|
||||
_make_test_img -b "$TEST_IMG.base" -F $IMGFMT 4M
|
||||
$QEMU_IO -c 'w 1M 2M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io
|
||||
|
||||
echo
|
||||
echo "=== Check allocation over NBD ==="
|
||||
echo
|
||||
|
||||
$QEMU_IMG map --output=json -f qcow2 "$TEST_IMG"
|
||||
IMG="driver=nbd,server.type=unix,server.path=$nbd_unix_socket"
|
||||
nbd_server_start_unix_socket -r -f qcow2 -A "$TEST_IMG"
|
||||
# Normal -f raw NBD block status loses access to allocation information
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"$IMG" | _filter_qemu_img_map
|
||||
# But when we use -A, coupled with x-dirty-bitmap in the client for feeding
|
||||
# 2-bit block status from an alternative NBD metadata context (note that
|
||||
# the client code for x-dirty-bitmap intentionally collapses all depths
|
||||
# beyond 2 into a single value), we can determine:
|
||||
# unallocated (depth 0) => "zero":false, "data":true
|
||||
# local (depth 1) => "zero":false, "data":false
|
||||
# backing (depth 2+) => "zero":true, "data":true
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"$IMG,x-dirty-bitmap=qemu:allocation-depth" | _filter_qemu_img_map
|
||||
# More accurate results can be obtained by other NBD clients such as
|
||||
# libnbd, but this test works without such external dependencies.
|
||||
|
||||
# success, all done
|
||||
echo '*** done'
|
||||
rm -f $seq.full
|
||||
status=0
|
22
tests/qemu-iotests/309.out
Normal file
22
tests/qemu-iotests/309.out
Normal file
@ -0,0 +1,22 @@
|
||||
QA output created by 309
|
||||
|
||||
=== Initial image setup ===
|
||||
|
||||
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=4194304
|
||||
wrote 2097152/2097152 bytes at offset 0
|
||||
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
|
||||
wrote 2097152/2097152 bytes at offset 1048576
|
||||
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
|
||||
=== Check allocation over NBD ===
|
||||
|
||||
[{ "start": 0, "length": 1048576, "depth": 1, "zero": false, "data": true, "offset": 327680},
|
||||
{ "start": 1048576, "length": 2097152, "depth": 0, "zero": false, "data": true, "offset": 327680},
|
||||
{ "start": 3145728, "length": 1048576, "depth": 1, "zero": true, "data": false}]
|
||||
[{ "start": 0, "length": 3145728, "depth": 0, "zero": false, "data": true, "offset": OFFSET},
|
||||
{ "start": 3145728, "length": 1048576, "depth": 0, "zero": true, "data": false, "offset": OFFSET}]
|
||||
[{ "start": 0, "length": 1048576, "depth": 0, "zero": true, "data": true, "offset": OFFSET},
|
||||
{ "start": 1048576, "length": 2097152, "depth": 0, "zero": false, "data": false},
|
||||
{ "start": 3145728, "length": 1048576, "depth": 0, "zero": false, "data": true, "offset": OFFSET}]
|
||||
*** done
|
@ -315,3 +315,4 @@
|
||||
304 rw quick
|
||||
305 rw quick
|
||||
307 rw quick export
|
||||
309 rw auto quick
|
||||
|
Loading…
x
Reference in New Issue
Block a user