Block patches:
- Block-status cache for data regions - qcow2 optimization (when using subclusters) - iotests delinting, and let 297 (lint checker) cover named iotests - qcow2 check improvements - Added -F (target backing file format) option to qemu-img convert - Mirror job fix - Fix for when a migration is initiated while a backup job runs - Fix for uncached qemu-img convert to a volume with 4k sectors (for an unaligned image) - Minor gluster driver fix -----BEGIN PGP SIGNATURE----- iQJGBAABCAAwFiEEy2LXoO44KeRfAE00ofpA0JgBnN8FAmFCL78SHGhyZWl0ekBy ZWRoYXQuY29tAAoJEKH6QNCYAZzfrBgQALQ1eY3tScoNgutgVJs+oX9OvuziJGuB oX6SpvPfFZZyvpzykYtdBFmWW9f2GyAHftEMJgJ1qA+MvpYnBLMdsM/PwRUGNsd4 Y6GrYrQLSTQPHsYvMgYpqUBIe4Sk0ECyXrtTfcx81XFKov93vLDQGIAH37T85sdQ QxoF1klslOT+RfrMB0lzmOvRY/QD6I2jIOrPUynWinyg16Bl12xCsht0iWTlPdHs ph2WhdUknIUUOM+cvFvTTNVqfybXJBqFk/vD/mNcr3PUVG/RhDqY6SAH2jh2Pcy4 IZ1xt7c8O6Co4wHiB5gM+YixxjhKA50bk8c/+VksVN5KuQ/5C3tFHC8t9LWjxIKj DxbrNSUqoj+X0kiWY6MzvwPOL4FxdNpl94NKneZg0PU2nbGkHncEsJGUlsyBugoz iWbAWb7FeHUSOXRrjLSgX/+tFIZWhDYYol5ac4o7ONWf7Vr9/5D50I72D292P6lo d04B/RhoFxkL34h+ECpz00FLB86McMB2zECG8u/8KnQpZSqKOywUxw8gmc9uJYv2 fC/zwzBktIDq8XCZUZDrU4NdS6FjiVtqglKN5yVPbNXSLOVMqbaLjUkAx/zklNma HzyzJPbEcVY3CvR7jic1yxHXntkUD3GNcD4hW7iqHGNPkLTBFQjctGpldmquYBlh jQypaaKUgRNa =/jVK -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/hreitz/tags/pull-block-2021-09-15' into staging Block patches: - Block-status cache for data regions - qcow2 optimization (when using subclusters) - iotests delinting, and let 297 (lint checker) cover named iotests - qcow2 check improvements - Added -F (target backing file format) option to qemu-img convert - Mirror job fix - Fix for when a migration is initiated while a backup job runs - Fix for uncached qemu-img convert to a volume with 4k sectors (for an unaligned image) - Minor gluster driver fix # gpg: Signature made Wed 15 Sep 2021 18:39:11 BST # gpg: using RSA key CB62D7A0EE3829E45F004D34A1FA40D098019CDF # gpg: issuer "hreitz@redhat.com" # gpg: Good signature from "Hanna Reitz <hreitz@redhat.com>" [marginal] # gpg: WARNING: This key is not certified with sufficiently trusted signatures! # gpg: It is not certain that the signature belongs to the owner. # Primary key fingerprint: CB62 D7A0 EE38 29E4 5F00 4D34 A1FA 40D0 9801 9CDF * remotes/hreitz/tags/pull-block-2021-09-15: (32 commits) qemu-img: Add -F shorthand to convert qcow2-refcount: check_refblocks(): add separate message for reserved qcow2-refcount: check_refcounts_l1(): check reserved bits qcow2-refcount: improve style of check_refcounts_l1() qcow2-refcount: check_refcounts_l2(): check reserved bits qcow2-refcount: check_refcounts_l2(): check l2_bitmap qcow2-refcount: fix_l2_entry_by_zero(): also zero L2 entry bitmap qcow2-refcount: introduce fix_l2_entry_by_zero() qcow2: introduce qcow2_parse_compressed_l2_entry() helper qcow2: compressed read: simplify cluster descriptor passing qcow2-refcount: improve style of check_refcounts_l2() qemu-img: Allow target be aligned to sector size qcow2: handle_dependencies(): relax conflict detection qcow2: refactor handle_dependencies() loop body simplebench: add img_bench_templater.py block: bdrv_inactivate_recurse(): check for permissions and fix crash tests: add migrate-during-backup block/mirror: fix NULL pointer dereference in mirror_wait_on_conflicts() iotests/297: Cover tests/ mirror-top-perms: Fix AbnormalShutdown path ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
57b6f58c1d
88
block.c
88
block.c
@ -49,6 +49,8 @@
|
|||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "qemu/cutils.h"
|
#include "qemu/cutils.h"
|
||||||
#include "qemu/id.h"
|
#include "qemu/id.h"
|
||||||
|
#include "qemu/range.h"
|
||||||
|
#include "qemu/rcu.h"
|
||||||
#include "block/coroutines.h"
|
#include "block/coroutines.h"
|
||||||
|
|
||||||
#ifdef CONFIG_BSD
|
#ifdef CONFIG_BSD
|
||||||
@ -401,6 +403,9 @@ BlockDriverState *bdrv_new(void)
|
|||||||
|
|
||||||
qemu_co_queue_init(&bs->flush_queue);
|
qemu_co_queue_init(&bs->flush_queue);
|
||||||
|
|
||||||
|
qemu_co_mutex_init(&bs->bsc_modify_lock);
|
||||||
|
bs->block_status_cache = g_new0(BdrvBlockStatusCache, 1);
|
||||||
|
|
||||||
for (i = 0; i < bdrv_drain_all_count; i++) {
|
for (i = 0; i < bdrv_drain_all_count; i++) {
|
||||||
bdrv_drained_begin(bs);
|
bdrv_drained_begin(bs);
|
||||||
}
|
}
|
||||||
@ -4694,6 +4699,8 @@ static void bdrv_close(BlockDriverState *bs)
|
|||||||
bs->explicit_options = NULL;
|
bs->explicit_options = NULL;
|
||||||
qobject_unref(bs->full_open_options);
|
qobject_unref(bs->full_open_options);
|
||||||
bs->full_open_options = NULL;
|
bs->full_open_options = NULL;
|
||||||
|
g_free(bs->block_status_cache);
|
||||||
|
bs->block_status_cache = NULL;
|
||||||
|
|
||||||
bdrv_release_named_dirty_bitmaps(bs);
|
bdrv_release_named_dirty_bitmaps(bs);
|
||||||
assert(QLIST_EMPTY(&bs->dirty_bitmaps));
|
assert(QLIST_EMPTY(&bs->dirty_bitmaps));
|
||||||
@ -6319,6 +6326,7 @@ static int bdrv_inactivate_recurse(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
BdrvChild *child, *parent;
|
BdrvChild *child, *parent;
|
||||||
int ret;
|
int ret;
|
||||||
|
uint64_t cumulative_perms, cumulative_shared_perms;
|
||||||
|
|
||||||
if (!bs->drv) {
|
if (!bs->drv) {
|
||||||
return -ENOMEDIUM;
|
return -ENOMEDIUM;
|
||||||
@ -6349,6 +6357,13 @@ static int bdrv_inactivate_recurse(BlockDriverState *bs)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bdrv_get_cumulative_perm(bs, &cumulative_perms,
|
||||||
|
&cumulative_shared_perms);
|
||||||
|
if (cumulative_perms & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED)) {
|
||||||
|
/* Our inactive parents still need write access. Inactivation failed. */
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
|
||||||
bs->open_flags |= BDRV_O_INACTIVE;
|
bs->open_flags |= BDRV_O_INACTIVE;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -7684,3 +7699,76 @@ BlockDriverState *bdrv_backing_chain_next(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
return bdrv_skip_filters(bdrv_cow_bs(bdrv_skip_filters(bs)));
|
return bdrv_skip_filters(bdrv_cow_bs(bdrv_skip_filters(bs)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether [offset, offset + bytes) overlaps with the cached
|
||||||
|
* block-status data region.
|
||||||
|
*
|
||||||
|
* If so, and @pnum is not NULL, set *pnum to `bsc.data_end - offset`,
|
||||||
|
* which is what bdrv_bsc_is_data()'s interface needs.
|
||||||
|
* Otherwise, *pnum is not touched.
|
||||||
|
*/
|
||||||
|
static bool bdrv_bsc_range_overlaps_locked(BlockDriverState *bs,
|
||||||
|
int64_t offset, int64_t bytes,
|
||||||
|
int64_t *pnum)
|
||||||
|
{
|
||||||
|
BdrvBlockStatusCache *bsc = qatomic_rcu_read(&bs->block_status_cache);
|
||||||
|
bool overlaps;
|
||||||
|
|
||||||
|
overlaps =
|
||||||
|
qatomic_read(&bsc->valid) &&
|
||||||
|
ranges_overlap(offset, bytes, bsc->data_start,
|
||||||
|
bsc->data_end - bsc->data_start);
|
||||||
|
|
||||||
|
if (overlaps && pnum) {
|
||||||
|
*pnum = bsc->data_end - offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See block_int.h for this function's documentation.
|
||||||
|
*/
|
||||||
|
bool bdrv_bsc_is_data(BlockDriverState *bs, int64_t offset, int64_t *pnum)
|
||||||
|
{
|
||||||
|
RCU_READ_LOCK_GUARD();
|
||||||
|
|
||||||
|
return bdrv_bsc_range_overlaps_locked(bs, offset, 1, pnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See block_int.h for this function's documentation.
|
||||||
|
*/
|
||||||
|
void bdrv_bsc_invalidate_range(BlockDriverState *bs,
|
||||||
|
int64_t offset, int64_t bytes)
|
||||||
|
{
|
||||||
|
RCU_READ_LOCK_GUARD();
|
||||||
|
|
||||||
|
if (bdrv_bsc_range_overlaps_locked(bs, offset, bytes, NULL)) {
|
||||||
|
qatomic_set(&bs->block_status_cache->valid, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See block_int.h for this function's documentation.
|
||||||
|
*/
|
||||||
|
void bdrv_bsc_fill(BlockDriverState *bs, int64_t offset, int64_t bytes)
|
||||||
|
{
|
||||||
|
BdrvBlockStatusCache *new_bsc = g_new(BdrvBlockStatusCache, 1);
|
||||||
|
BdrvBlockStatusCache *old_bsc;
|
||||||
|
|
||||||
|
*new_bsc = (BdrvBlockStatusCache) {
|
||||||
|
.valid = true,
|
||||||
|
.data_start = offset,
|
||||||
|
.data_end = offset + bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
QEMU_LOCK_GUARD(&bs->bsc_modify_lock);
|
||||||
|
|
||||||
|
old_bsc = qatomic_rcu_read(&bs->block_status_cache);
|
||||||
|
qatomic_rcu_set(&bs->block_status_cache, new_bsc);
|
||||||
|
if (old_bsc) {
|
||||||
|
g_free_rcu(old_bsc, rcu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2744,7 +2744,8 @@ static int find_allocation(BlockDriverState *bs, off_t start,
|
|||||||
* the specified offset) that are known to be in the same
|
* the specified offset) that are known to be in the same
|
||||||
* allocated/unallocated state.
|
* allocated/unallocated state.
|
||||||
*
|
*
|
||||||
* 'bytes' is the max value 'pnum' should be set to.
|
* 'bytes' is a soft cap for 'pnum'. If the information is free, 'pnum' may
|
||||||
|
* well exceed it.
|
||||||
*/
|
*/
|
||||||
static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
|
static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
|
||||||
bool want_zero,
|
bool want_zero,
|
||||||
@ -2782,7 +2783,7 @@ static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
|
|||||||
} else if (data == offset) {
|
} else if (data == offset) {
|
||||||
/* On a data extent, compute bytes to the end of the extent,
|
/* On a data extent, compute bytes to the end of the extent,
|
||||||
* possibly including a partial sector at EOF. */
|
* possibly including a partial sector at EOF. */
|
||||||
*pnum = MIN(bytes, hole - offset);
|
*pnum = hole - offset;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We are not allowed to return partial sectors, though, so
|
* We are not allowed to return partial sectors, though, so
|
||||||
@ -2801,7 +2802,7 @@ static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
|
|||||||
} else {
|
} else {
|
||||||
/* On a hole, compute bytes to the beginning of the next extent. */
|
/* On a hole, compute bytes to the beginning of the next extent. */
|
||||||
assert(hole == offset);
|
assert(hole == offset);
|
||||||
*pnum = MIN(bytes, data - offset);
|
*pnum = data - offset;
|
||||||
ret = BDRV_BLOCK_ZERO;
|
ret = BDRV_BLOCK_ZERO;
|
||||||
}
|
}
|
||||||
*map = offset;
|
*map = offset;
|
||||||
|
@ -1461,7 +1461,8 @@ exit:
|
|||||||
* the specified offset) that are known to be in the same
|
* the specified offset) that are known to be in the same
|
||||||
* allocated/unallocated state.
|
* allocated/unallocated state.
|
||||||
*
|
*
|
||||||
* 'bytes' is the max value 'pnum' should be set to.
|
* 'bytes' is a soft cap for 'pnum'. If the information is free, 'pnum' may
|
||||||
|
* well exceed it.
|
||||||
*
|
*
|
||||||
* (Based on raw_co_block_status() from file-posix.c.)
|
* (Based on raw_co_block_status() from file-posix.c.)
|
||||||
*/
|
*/
|
||||||
@ -1477,6 +1478,8 @@ static int coroutine_fn qemu_gluster_co_block_status(BlockDriverState *bs,
|
|||||||
off_t data = 0, hole = 0;
|
off_t data = 0, hole = 0;
|
||||||
int ret = -EINVAL;
|
int ret = -EINVAL;
|
||||||
|
|
||||||
|
assert(QEMU_IS_ALIGNED(offset | bytes, bs->bl.request_alignment));
|
||||||
|
|
||||||
if (!s->fd) {
|
if (!s->fd) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1500,12 +1503,26 @@ static int coroutine_fn qemu_gluster_co_block_status(BlockDriverState *bs,
|
|||||||
} else if (data == offset) {
|
} else if (data == offset) {
|
||||||
/* On a data extent, compute bytes to the end of the extent,
|
/* On a data extent, compute bytes to the end of the extent,
|
||||||
* possibly including a partial sector at EOF. */
|
* possibly including a partial sector at EOF. */
|
||||||
*pnum = MIN(bytes, hole - offset);
|
*pnum = hole - offset;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We are not allowed to return partial sectors, though, so
|
||||||
|
* round up if necessary.
|
||||||
|
*/
|
||||||
|
if (!QEMU_IS_ALIGNED(*pnum, bs->bl.request_alignment)) {
|
||||||
|
int64_t file_length = qemu_gluster_getlength(bs);
|
||||||
|
if (file_length > 0) {
|
||||||
|
/* Ignore errors, this is just a safeguard */
|
||||||
|
assert(hole == file_length);
|
||||||
|
}
|
||||||
|
*pnum = ROUND_UP(*pnum, bs->bl.request_alignment);
|
||||||
|
}
|
||||||
|
|
||||||
ret = BDRV_BLOCK_DATA;
|
ret = BDRV_BLOCK_DATA;
|
||||||
} else {
|
} else {
|
||||||
/* On a hole, compute bytes to the beginning of the next extent. */
|
/* On a hole, compute bytes to the beginning of the next extent. */
|
||||||
assert(hole == offset);
|
assert(hole == offset);
|
||||||
*pnum = MIN(bytes, data - offset);
|
*pnum = data - offset;
|
||||||
ret = BDRV_BLOCK_ZERO;
|
ret = BDRV_BLOCK_ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
62
block/io.c
62
block/io.c
@ -1883,6 +1883,9 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,
|
|||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Invalidate the cached block-status data range if this write overlaps */
|
||||||
|
bdrv_bsc_invalidate_range(bs, offset, bytes);
|
||||||
|
|
||||||
assert(alignment % bs->bl.request_alignment == 0);
|
assert(alignment % bs->bl.request_alignment == 0);
|
||||||
head = offset % alignment;
|
head = offset % alignment;
|
||||||
tail = (offset + bytes) % alignment;
|
tail = (offset + bytes) % alignment;
|
||||||
@ -2447,9 +2450,65 @@ static int coroutine_fn bdrv_co_block_status(BlockDriverState *bs,
|
|||||||
aligned_bytes = ROUND_UP(offset + bytes, align) - aligned_offset;
|
aligned_bytes = ROUND_UP(offset + bytes, align) - aligned_offset;
|
||||||
|
|
||||||
if (bs->drv->bdrv_co_block_status) {
|
if (bs->drv->bdrv_co_block_status) {
|
||||||
|
/*
|
||||||
|
* Use the block-status cache only for protocol nodes: Format
|
||||||
|
* drivers are generally quick to inquire the status, but protocol
|
||||||
|
* drivers often need to get information from outside of qemu, so
|
||||||
|
* we do not have control over the actual implementation. There
|
||||||
|
* have been cases where inquiring the status took an unreasonably
|
||||||
|
* long time, and we can do nothing in qemu to fix it.
|
||||||
|
* This is especially problematic for images with large data areas,
|
||||||
|
* because finding the few holes in them and giving them special
|
||||||
|
* treatment does not gain much performance. Therefore, we try to
|
||||||
|
* cache the last-identified data region.
|
||||||
|
*
|
||||||
|
* Second, limiting ourselves to protocol nodes allows us to assume
|
||||||
|
* the block status for data regions to be DATA | OFFSET_VALID, and
|
||||||
|
* that the host offset is the same as the guest offset.
|
||||||
|
*
|
||||||
|
* Note that it is possible that external writers zero parts of
|
||||||
|
* the cached regions without the cache being invalidated, and so
|
||||||
|
* we may report zeroes as data. This is not catastrophic,
|
||||||
|
* however, because reporting zeroes as data is fine.
|
||||||
|
*/
|
||||||
|
if (QLIST_EMPTY(&bs->children) &&
|
||||||
|
bdrv_bsc_is_data(bs, aligned_offset, pnum))
|
||||||
|
{
|
||||||
|
ret = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID;
|
||||||
|
local_file = bs;
|
||||||
|
local_map = aligned_offset;
|
||||||
|
} else {
|
||||||
ret = bs->drv->bdrv_co_block_status(bs, want_zero, aligned_offset,
|
ret = bs->drv->bdrv_co_block_status(bs, want_zero, aligned_offset,
|
||||||
aligned_bytes, pnum, &local_map,
|
aligned_bytes, pnum, &local_map,
|
||||||
&local_file);
|
&local_file);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that checking QLIST_EMPTY(&bs->children) is also done when
|
||||||
|
* the cache is queried above. Technically, we do not need to check
|
||||||
|
* it here; the worst that can happen is that we fill the cache for
|
||||||
|
* non-protocol nodes, and then it is never used. However, filling
|
||||||
|
* the cache requires an RCU update, so double check here to avoid
|
||||||
|
* such an update if possible.
|
||||||
|
*/
|
||||||
|
if (ret == (BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID) &&
|
||||||
|
QLIST_EMPTY(&bs->children))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* When a protocol driver reports BLOCK_OFFSET_VALID, the
|
||||||
|
* returned local_map value must be the same as the offset we
|
||||||
|
* have passed (aligned_offset), and local_bs must be the node
|
||||||
|
* itself.
|
||||||
|
* Assert this, because we follow this rule when reading from
|
||||||
|
* the cache (see the `local_file = bs` and
|
||||||
|
* `local_map = aligned_offset` assignments above), and the
|
||||||
|
* result the cache delivers must be the same as the driver
|
||||||
|
* would deliver.
|
||||||
|
*/
|
||||||
|
assert(local_file == bs);
|
||||||
|
assert(local_map == aligned_offset);
|
||||||
|
bdrv_bsc_fill(bs, aligned_offset, *pnum);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Default code for filters */
|
/* Default code for filters */
|
||||||
|
|
||||||
@ -3002,6 +3061,9 @@ int coroutine_fn bdrv_co_pdiscard(BdrvChild *child, int64_t offset,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Invalidate the cached block-status data range if this discard overlaps */
|
||||||
|
bdrv_bsc_invalidate_range(bs, offset, bytes);
|
||||||
|
|
||||||
/* Discard is advisory, but some devices track and coalesce
|
/* Discard is advisory, but some devices track and coalesce
|
||||||
* unaligned requests, so we must pass everything down rather than
|
* unaligned requests, so we must pass everything down rather than
|
||||||
* round here. Still, most devices will just silently ignore
|
* round here. Still, most devices will just silently ignore
|
||||||
|
@ -781,9 +781,6 @@ retry:
|
|||||||
iscsi_allocmap_set_allocated(iscsilun, offset, *pnum);
|
iscsi_allocmap_set_allocated(iscsilun, offset, *pnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*pnum > bytes) {
|
|
||||||
*pnum = bytes;
|
|
||||||
}
|
|
||||||
out_unlock:
|
out_unlock:
|
||||||
qemu_mutex_unlock(&iscsilun->mutex);
|
qemu_mutex_unlock(&iscsilun->mutex);
|
||||||
g_free(iTask.err_str);
|
g_free(iTask.err_str);
|
||||||
|
@ -160,18 +160,25 @@ static void coroutine_fn mirror_wait_on_conflicts(MirrorOp *self,
|
|||||||
if (ranges_overlap(self_start_chunk, self_nb_chunks,
|
if (ranges_overlap(self_start_chunk, self_nb_chunks,
|
||||||
op_start_chunk, op_nb_chunks))
|
op_start_chunk, op_nb_chunks))
|
||||||
{
|
{
|
||||||
|
if (self) {
|
||||||
/*
|
/*
|
||||||
* If the operation is already (indirectly) waiting for us, or
|
* If the operation is already (indirectly) waiting for us,
|
||||||
* will wait for us as soon as it wakes up, then just go on
|
* or will wait for us as soon as it wakes up, then just go
|
||||||
* (instead of producing a deadlock in the former case).
|
* on (instead of producing a deadlock in the former case).
|
||||||
*/
|
*/
|
||||||
if (op->waiting_for_op) {
|
if (op->waiting_for_op) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->waiting_for_op = op;
|
self->waiting_for_op = op;
|
||||||
|
}
|
||||||
|
|
||||||
qemu_co_queue_wait(&op->waiting_requests, NULL);
|
qemu_co_queue_wait(&op->waiting_requests, NULL);
|
||||||
|
|
||||||
|
if (self) {
|
||||||
self->waiting_for_op = NULL;
|
self->waiting_for_op = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -556,8 +556,7 @@ static int coroutine_fn do_perform_cow_write(BlockDriverState *bs,
|
|||||||
* offset needs to be aligned to a cluster boundary.
|
* offset needs to be aligned to a cluster boundary.
|
||||||
*
|
*
|
||||||
* If the cluster is unallocated then *host_offset will be 0.
|
* If the cluster is unallocated then *host_offset will be 0.
|
||||||
* If the cluster is compressed then *host_offset will contain the
|
* If the cluster is compressed then *host_offset will contain the l2 entry.
|
||||||
* complete compressed cluster descriptor.
|
|
||||||
*
|
*
|
||||||
* On entry, *bytes is the maximum number of contiguous bytes starting at
|
* On entry, *bytes is the maximum number of contiguous bytes starting at
|
||||||
* offset that we are interested in.
|
* offset that we are interested in.
|
||||||
@ -660,7 +659,7 @@ int qcow2_get_host_offset(BlockDriverState *bs, uint64_t offset,
|
|||||||
ret = -EIO;
|
ret = -EIO;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
*host_offset = l2_entry & L2E_COMPRESSED_OFFSET_SIZE_MASK;
|
*host_offset = l2_entry;
|
||||||
break;
|
break;
|
||||||
case QCOW2_SUBCLUSTER_ZERO_PLAIN:
|
case QCOW2_SUBCLUSTER_ZERO_PLAIN:
|
||||||
case QCOW2_SUBCLUSTER_UNALLOCATED_PLAIN:
|
case QCOW2_SUBCLUSTER_UNALLOCATED_PLAIN:
|
||||||
@ -1400,7 +1399,22 @@ static int handle_dependencies(BlockDriverState *bs, uint64_t guest_offset,
|
|||||||
|
|
||||||
if (end <= old_start || start >= old_end) {
|
if (end <= old_start || start >= old_end) {
|
||||||
/* No intersection */
|
/* No intersection */
|
||||||
} else {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_alloc->keep_old_clusters &&
|
||||||
|
(end <= l2meta_cow_start(old_alloc) ||
|
||||||
|
start >= l2meta_cow_end(old_alloc)))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Clusters intersect but COW areas don't. And cluster itself is
|
||||||
|
* already allocated. So, there is no actual conflict.
|
||||||
|
*/
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Conflict */
|
||||||
|
|
||||||
if (start < old_start) {
|
if (start < old_start) {
|
||||||
/* Stop at the start of a running allocation */
|
/* Stop at the start of a running allocation */
|
||||||
bytes = old_start - start;
|
bytes = old_start - start;
|
||||||
@ -1408,23 +1422,26 @@ static int handle_dependencies(BlockDriverState *bs, uint64_t guest_offset,
|
|||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stop if already an l2meta exists. After yielding, it wouldn't
|
/*
|
||||||
|
* Stop if an l2meta already exists. After yielding, it wouldn't
|
||||||
* be valid any more, so we'd have to clean up the old L2Metas
|
* be valid any more, so we'd have to clean up the old L2Metas
|
||||||
* and deal with requests depending on them before starting to
|
* and deal with requests depending on them before starting to
|
||||||
* gather new ones. Not worth the trouble. */
|
* gather new ones. Not worth the trouble.
|
||||||
|
*/
|
||||||
if (bytes == 0 && *m) {
|
if (bytes == 0 && *m) {
|
||||||
*cur_bytes = 0;
|
*cur_bytes = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes == 0) {
|
if (bytes == 0) {
|
||||||
/* Wait for the dependency to complete. We need to recheck
|
/*
|
||||||
* the free/allocated clusters when we continue. */
|
* Wait for the dependency to complete. We need to recheck
|
||||||
|
* the free/allocated clusters when we continue.
|
||||||
|
*/
|
||||||
qemu_co_queue_wait(&old_alloc->dependent_requests, &s->lock);
|
qemu_co_queue_wait(&old_alloc->dependent_requests, &s->lock);
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Make sure that existing clusters and new allocations are only used up to
|
/* Make sure that existing clusters and new allocations are only used up to
|
||||||
* the next dependency if we shortened the request above */
|
* the next dependency if we shortened the request above */
|
||||||
@ -2463,3 +2480,18 @@ fail:
|
|||||||
g_free(l1_table);
|
g_free(l1_table);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void qcow2_parse_compressed_l2_entry(BlockDriverState *bs, uint64_t l2_entry,
|
||||||
|
uint64_t *coffset, int *csize)
|
||||||
|
{
|
||||||
|
BDRVQcow2State *s = bs->opaque;
|
||||||
|
int nb_csectors;
|
||||||
|
|
||||||
|
assert(qcow2_get_cluster_type(bs, l2_entry) == QCOW2_CLUSTER_COMPRESSED);
|
||||||
|
|
||||||
|
*coffset = l2_entry & s->cluster_offset_mask;
|
||||||
|
|
||||||
|
nb_csectors = ((l2_entry >> s->csize_shift) & s->csize_mask) + 1;
|
||||||
|
*csize = nb_csectors * QCOW2_COMPRESSED_SECTOR_SIZE -
|
||||||
|
(*coffset & (QCOW2_COMPRESSED_SECTOR_SIZE - 1));
|
||||||
|
}
|
||||||
|
@ -1177,11 +1177,11 @@ void qcow2_free_any_cluster(BlockDriverState *bs, uint64_t l2_entry,
|
|||||||
switch (ctype) {
|
switch (ctype) {
|
||||||
case QCOW2_CLUSTER_COMPRESSED:
|
case QCOW2_CLUSTER_COMPRESSED:
|
||||||
{
|
{
|
||||||
int64_t offset = (l2_entry & s->cluster_offset_mask)
|
uint64_t coffset;
|
||||||
& QCOW2_COMPRESSED_SECTOR_MASK;
|
int csize;
|
||||||
int size = QCOW2_COMPRESSED_SECTOR_SIZE *
|
|
||||||
(((l2_entry >> s->csize_shift) & s->csize_mask) + 1);
|
qcow2_parse_compressed_l2_entry(bs, l2_entry, &coffset, &csize);
|
||||||
qcow2_free_clusters(bs, offset, size, type);
|
qcow2_free_clusters(bs, coffset, csize, type);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QCOW2_CLUSTER_NORMAL:
|
case QCOW2_CLUSTER_NORMAL:
|
||||||
@ -1247,7 +1247,7 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
|||||||
bool l1_allocated = false;
|
bool l1_allocated = false;
|
||||||
int64_t old_entry, old_l2_offset;
|
int64_t old_entry, old_l2_offset;
|
||||||
unsigned slice, slice_size2, n_slices;
|
unsigned slice, slice_size2, n_slices;
|
||||||
int i, j, l1_modified = 0, nb_csectors;
|
int i, j, l1_modified = 0;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
assert(addend >= -1 && addend <= 1);
|
assert(addend >= -1 && addend <= 1);
|
||||||
@ -1318,14 +1318,14 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
|||||||
|
|
||||||
switch (qcow2_get_cluster_type(bs, entry)) {
|
switch (qcow2_get_cluster_type(bs, entry)) {
|
||||||
case QCOW2_CLUSTER_COMPRESSED:
|
case QCOW2_CLUSTER_COMPRESSED:
|
||||||
nb_csectors = ((entry >> s->csize_shift) &
|
|
||||||
s->csize_mask) + 1;
|
|
||||||
if (addend != 0) {
|
if (addend != 0) {
|
||||||
uint64_t coffset = (entry & s->cluster_offset_mask)
|
uint64_t coffset;
|
||||||
& QCOW2_COMPRESSED_SECTOR_MASK;
|
int csize;
|
||||||
|
|
||||||
|
qcow2_parse_compressed_l2_entry(bs, entry,
|
||||||
|
&coffset, &csize);
|
||||||
ret = update_refcount(
|
ret = update_refcount(
|
||||||
bs, coffset,
|
bs, coffset, csize,
|
||||||
nb_csectors * QCOW2_COMPRESSED_SECTOR_SIZE,
|
|
||||||
abs(addend), addend < 0,
|
abs(addend), addend < 0,
|
||||||
QCOW2_DISCARD_SNAPSHOT);
|
QCOW2_DISCARD_SNAPSHOT);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@ -1587,6 +1587,66 @@ enum {
|
|||||||
CHECK_FRAG_INFO = 0x2, /* update BlockFragInfo counters */
|
CHECK_FRAG_INFO = 0x2, /* update BlockFragInfo counters */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fix L2 entry by making it QCOW2_CLUSTER_ZERO_PLAIN (or making all its present
|
||||||
|
* subclusters QCOW2_SUBCLUSTER_ZERO_PLAIN).
|
||||||
|
*
|
||||||
|
* This function decrements res->corruptions on success, so the caller is
|
||||||
|
* responsible to increment res->corruptions prior to the call.
|
||||||
|
*
|
||||||
|
* On failure in-memory @l2_table may be modified.
|
||||||
|
*/
|
||||||
|
static int fix_l2_entry_by_zero(BlockDriverState *bs, BdrvCheckResult *res,
|
||||||
|
uint64_t l2_offset,
|
||||||
|
uint64_t *l2_table, int l2_index, bool active,
|
||||||
|
bool *metadata_overlap)
|
||||||
|
{
|
||||||
|
BDRVQcow2State *s = bs->opaque;
|
||||||
|
int ret;
|
||||||
|
int idx = l2_index * (l2_entry_size(s) / sizeof(uint64_t));
|
||||||
|
uint64_t l2e_offset = l2_offset + (uint64_t)l2_index * l2_entry_size(s);
|
||||||
|
int ign = active ? QCOW2_OL_ACTIVE_L2 : QCOW2_OL_INACTIVE_L2;
|
||||||
|
|
||||||
|
if (has_subclusters(s)) {
|
||||||
|
uint64_t l2_bitmap = get_l2_bitmap(s, l2_table, l2_index);
|
||||||
|
|
||||||
|
/* Allocated subclusters become zero */
|
||||||
|
l2_bitmap |= l2_bitmap << 32;
|
||||||
|
l2_bitmap &= QCOW_L2_BITMAP_ALL_ZEROES;
|
||||||
|
|
||||||
|
set_l2_bitmap(s, l2_table, l2_index, l2_bitmap);
|
||||||
|
set_l2_entry(s, l2_table, l2_index, 0);
|
||||||
|
} else {
|
||||||
|
set_l2_entry(s, l2_table, l2_index, QCOW_OFLAG_ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = qcow2_pre_write_overlap_check(bs, ign, l2e_offset, l2_entry_size(s),
|
||||||
|
false);
|
||||||
|
if (metadata_overlap) {
|
||||||
|
*metadata_overlap = ret < 0;
|
||||||
|
}
|
||||||
|
if (ret < 0) {
|
||||||
|
fprintf(stderr, "ERROR: Overlap check failed\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = bdrv_pwrite_sync(bs->file, l2e_offset, &l2_table[idx],
|
||||||
|
l2_entry_size(s));
|
||||||
|
if (ret < 0) {
|
||||||
|
fprintf(stderr, "ERROR: Failed to overwrite L2 "
|
||||||
|
"table entry: %s\n", strerror(-ret));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
res->corruptions--;
|
||||||
|
res->corruptions_fixed++;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
res->check_errors++;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Increases the refcount in the given refcount table for the all clusters
|
* Increases the refcount in the given refcount table for the all clusters
|
||||||
* referenced in the L2 table. While doing so, performs some checks on L2
|
* referenced in the L2 table. While doing so, performs some checks on L2
|
||||||
@ -1601,26 +1661,41 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
|||||||
int flags, BdrvCheckMode fix, bool active)
|
int flags, BdrvCheckMode fix, bool active)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
uint64_t *l2_table, l2_entry;
|
uint64_t l2_entry, l2_bitmap;
|
||||||
uint64_t next_contiguous_offset = 0;
|
uint64_t next_contiguous_offset = 0;
|
||||||
int i, l2_size, nb_csectors, ret;
|
int i, ret;
|
||||||
|
size_t l2_size_bytes = s->l2_size * l2_entry_size(s);
|
||||||
|
g_autofree uint64_t *l2_table = g_malloc(l2_size_bytes);
|
||||||
|
bool metadata_overlap;
|
||||||
|
|
||||||
/* Read L2 table from disk */
|
/* Read L2 table from disk */
|
||||||
l2_size = s->l2_size * l2_entry_size(s);
|
ret = bdrv_pread(bs->file, l2_offset, l2_table, l2_size_bytes);
|
||||||
l2_table = g_malloc(l2_size);
|
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, l2_offset, l2_table, l2_size);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
fprintf(stderr, "ERROR: I/O error in check_refcounts_l2\n");
|
fprintf(stderr, "ERROR: I/O error in check_refcounts_l2\n");
|
||||||
res->check_errors++;
|
res->check_errors++;
|
||||||
goto fail;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do the actual checks */
|
/* Do the actual checks */
|
||||||
for(i = 0; i < s->l2_size; i++) {
|
for (i = 0; i < s->l2_size; i++) {
|
||||||
l2_entry = get_l2_entry(s, l2_table, i);
|
uint64_t coffset;
|
||||||
|
int csize;
|
||||||
|
QCow2ClusterType type;
|
||||||
|
|
||||||
switch (qcow2_get_cluster_type(bs, l2_entry)) {
|
l2_entry = get_l2_entry(s, l2_table, i);
|
||||||
|
l2_bitmap = get_l2_bitmap(s, l2_table, i);
|
||||||
|
type = qcow2_get_cluster_type(bs, l2_entry);
|
||||||
|
|
||||||
|
if (type != QCOW2_CLUSTER_COMPRESSED) {
|
||||||
|
/* Check reserved bits of Standard Cluster Descriptor */
|
||||||
|
if (l2_entry & L2E_STD_RESERVED_MASK) {
|
||||||
|
fprintf(stderr, "ERROR found l2 entry with reserved bits set: "
|
||||||
|
"%" PRIx64 "\n", l2_entry);
|
||||||
|
res->corruptions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
case QCOW2_CLUSTER_COMPRESSED:
|
case QCOW2_CLUSTER_COMPRESSED:
|
||||||
/* Compressed clusters don't have QCOW_OFLAG_COPIED */
|
/* Compressed clusters don't have QCOW_OFLAG_COPIED */
|
||||||
if (l2_entry & QCOW_OFLAG_COPIED) {
|
if (l2_entry & QCOW_OFLAG_COPIED) {
|
||||||
@ -1638,23 +1713,28 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (l2_bitmap) {
|
||||||
|
fprintf(stderr, "ERROR compressed cluster %d with non-zero "
|
||||||
|
"subcluster allocation bitmap, entry=0x%" PRIx64 "\n",
|
||||||
|
i, l2_entry);
|
||||||
|
res->corruptions++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark cluster as used */
|
/* Mark cluster as used */
|
||||||
nb_csectors = ((l2_entry >> s->csize_shift) &
|
qcow2_parse_compressed_l2_entry(bs, l2_entry, &coffset, &csize);
|
||||||
s->csize_mask) + 1;
|
|
||||||
l2_entry &= s->cluster_offset_mask;
|
|
||||||
ret = qcow2_inc_refcounts_imrt(
|
ret = qcow2_inc_refcounts_imrt(
|
||||||
bs, res, refcount_table, refcount_table_size,
|
bs, res, refcount_table, refcount_table_size, coffset, csize);
|
||||||
l2_entry & QCOW2_COMPRESSED_SECTOR_MASK,
|
|
||||||
nb_csectors * QCOW2_COMPRESSED_SECTOR_SIZE);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & CHECK_FRAG_INFO) {
|
if (flags & CHECK_FRAG_INFO) {
|
||||||
res->bfi.allocated_clusters++;
|
res->bfi.allocated_clusters++;
|
||||||
res->bfi.compressed_clusters++;
|
res->bfi.compressed_clusters++;
|
||||||
|
|
||||||
/* Compressed clusters are fragmented by nature. Since they
|
/*
|
||||||
|
* Compressed clusters are fragmented by nature. Since they
|
||||||
* take up sub-sector space but we only have sector granularity
|
* take up sub-sector space but we only have sector granularity
|
||||||
* I/O we need to re-read the same sectors even for adjacent
|
* I/O we need to re-read the same sectors even for adjacent
|
||||||
* compressed clusters.
|
* compressed clusters.
|
||||||
@ -1668,13 +1748,19 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
|||||||
{
|
{
|
||||||
uint64_t offset = l2_entry & L2E_OFFSET_MASK;
|
uint64_t offset = l2_entry & L2E_OFFSET_MASK;
|
||||||
|
|
||||||
|
if ((l2_bitmap >> 32) & l2_bitmap) {
|
||||||
|
res->corruptions++;
|
||||||
|
fprintf(stderr, "ERROR offset=%" PRIx64 ": Allocated "
|
||||||
|
"cluster has corrupted subcluster allocation bitmap\n",
|
||||||
|
offset);
|
||||||
|
}
|
||||||
|
|
||||||
/* Correct offsets are cluster aligned */
|
/* Correct offsets are cluster aligned */
|
||||||
if (offset_into_cluster(s, offset)) {
|
if (offset_into_cluster(s, offset)) {
|
||||||
bool contains_data;
|
bool contains_data;
|
||||||
res->corruptions++;
|
res->corruptions++;
|
||||||
|
|
||||||
if (has_subclusters(s)) {
|
if (has_subclusters(s)) {
|
||||||
uint64_t l2_bitmap = get_l2_bitmap(s, l2_table, i);
|
|
||||||
contains_data = (l2_bitmap & QCOW_L2_BITMAP_ALL_ALLOC);
|
contains_data = (l2_bitmap & QCOW_L2_BITMAP_ALL_ALLOC);
|
||||||
} else {
|
} else {
|
||||||
contains_data = !(l2_entry & QCOW_OFLAG_ZERO);
|
contains_data = !(l2_entry & QCOW_OFLAG_ZERO);
|
||||||
@ -1687,40 +1773,30 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
|||||||
fix & BDRV_FIX_ERRORS ? "Repairing" : "ERROR",
|
fix & BDRV_FIX_ERRORS ? "Repairing" : "ERROR",
|
||||||
offset);
|
offset);
|
||||||
if (fix & BDRV_FIX_ERRORS) {
|
if (fix & BDRV_FIX_ERRORS) {
|
||||||
int idx = i * (l2_entry_size(s) / sizeof(uint64_t));
|
ret = fix_l2_entry_by_zero(bs, res, l2_offset,
|
||||||
uint64_t l2e_offset =
|
l2_table, i, active,
|
||||||
l2_offset + (uint64_t)i * l2_entry_size(s);
|
&metadata_overlap);
|
||||||
int ign = active ? QCOW2_OL_ACTIVE_L2 :
|
if (metadata_overlap) {
|
||||||
QCOW2_OL_INACTIVE_L2;
|
/*
|
||||||
|
* Something is seriously wrong, so abort checking
|
||||||
l2_entry = has_subclusters(s) ? 0 : QCOW_OFLAG_ZERO;
|
* this L2 table.
|
||||||
set_l2_entry(s, l2_table, i, l2_entry);
|
*/
|
||||||
ret = qcow2_pre_write_overlap_check(bs, ign,
|
return ret;
|
||||||
l2e_offset, l2_entry_size(s), false);
|
|
||||||
if (ret < 0) {
|
|
||||||
fprintf(stderr, "ERROR: Overlap check failed\n");
|
|
||||||
res->check_errors++;
|
|
||||||
/* Something is seriously wrong, so abort checking
|
|
||||||
* this L2 table */
|
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = bdrv_pwrite_sync(bs->file, l2e_offset,
|
if (ret == 0) {
|
||||||
&l2_table[idx],
|
/*
|
||||||
l2_entry_size(s));
|
* Skip marking the cluster as used
|
||||||
if (ret < 0) {
|
* (it is unused now).
|
||||||
fprintf(stderr, "ERROR: Failed to overwrite L2 "
|
*/
|
||||||
"table entry: %s\n", strerror(-ret));
|
|
||||||
res->check_errors++;
|
|
||||||
/* Do not abort, continue checking the rest of this
|
|
||||||
* L2 table's entries */
|
|
||||||
} else {
|
|
||||||
res->corruptions--;
|
|
||||||
res->corruptions_fixed++;
|
|
||||||
/* Skip marking the cluster as used
|
|
||||||
* (it is unused now) */
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Failed to fix.
|
||||||
|
* Do not abort, continue checking the rest of this
|
||||||
|
* L2 table's entries.
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "ERROR offset=%" PRIx64 ": Data cluster is "
|
fprintf(stderr, "ERROR offset=%" PRIx64 ": Data cluster is "
|
||||||
@ -1743,14 +1819,23 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
|||||||
refcount_table_size,
|
refcount_table_size,
|
||||||
offset, s->cluster_size);
|
offset, s->cluster_size);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case QCOW2_CLUSTER_ZERO_PLAIN:
|
case QCOW2_CLUSTER_ZERO_PLAIN:
|
||||||
|
/* Impossible when image has subclusters */
|
||||||
|
assert(!l2_bitmap);
|
||||||
|
break;
|
||||||
|
|
||||||
case QCOW2_CLUSTER_UNALLOCATED:
|
case QCOW2_CLUSTER_UNALLOCATED:
|
||||||
|
if (l2_bitmap & QCOW_L2_BITMAP_ALL_ALLOC) {
|
||||||
|
res->corruptions++;
|
||||||
|
fprintf(stderr, "ERROR: Unallocated "
|
||||||
|
"cluster has non-zero subcluster allocation map\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -1758,12 +1843,7 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(l2_table);
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fail:
|
|
||||||
g_free(l2_table);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1782,47 +1862,60 @@ static int check_refcounts_l1(BlockDriverState *bs,
|
|||||||
int flags, BdrvCheckMode fix, bool active)
|
int flags, BdrvCheckMode fix, bool active)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
uint64_t *l1_table = NULL, l2_offset, l1_size2;
|
size_t l1_size_bytes = l1_size * L1E_SIZE;
|
||||||
|
g_autofree uint64_t *l1_table = NULL;
|
||||||
|
uint64_t l2_offset;
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
|
||||||
l1_size2 = l1_size * L1E_SIZE;
|
if (!l1_size) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark L1 table as used */
|
/* Mark L1 table as used */
|
||||||
ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, refcount_table_size,
|
ret = qcow2_inc_refcounts_imrt(bs, res, refcount_table, refcount_table_size,
|
||||||
l1_table_offset, l1_size2);
|
l1_table_offset, l1_size_bytes);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
l1_table = g_try_malloc(l1_size_bytes);
|
||||||
|
if (l1_table == NULL) {
|
||||||
|
res->check_errors++;
|
||||||
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read L1 table entries from disk */
|
/* Read L1 table entries from disk */
|
||||||
if (l1_size2 > 0) {
|
ret = bdrv_pread(bs->file, l1_table_offset, l1_table, l1_size_bytes);
|
||||||
l1_table = g_try_malloc(l1_size2);
|
|
||||||
if (l1_table == NULL) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
res->check_errors++;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
ret = bdrv_pread(bs->file, l1_table_offset, l1_table, l1_size2);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
fprintf(stderr, "ERROR: I/O error in check_refcounts_l1\n");
|
fprintf(stderr, "ERROR: I/O error in check_refcounts_l1\n");
|
||||||
res->check_errors++;
|
res->check_errors++;
|
||||||
goto fail;
|
return ret;
|
||||||
}
|
}
|
||||||
for(i = 0;i < l1_size; i++)
|
|
||||||
|
for (i = 0; i < l1_size; i++) {
|
||||||
be64_to_cpus(&l1_table[i]);
|
be64_to_cpus(&l1_table[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do the actual checks */
|
/* Do the actual checks */
|
||||||
for(i = 0; i < l1_size; i++) {
|
for (i = 0; i < l1_size; i++) {
|
||||||
l2_offset = l1_table[i];
|
if (!l1_table[i]) {
|
||||||
if (l2_offset) {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l1_table[i] & L1E_RESERVED_MASK) {
|
||||||
|
fprintf(stderr, "ERROR found L1 entry with reserved bits set: "
|
||||||
|
"%" PRIx64 "\n", l1_table[i]);
|
||||||
|
res->corruptions++;
|
||||||
|
}
|
||||||
|
|
||||||
|
l2_offset = l1_table[i] & L1E_OFFSET_MASK;
|
||||||
|
|
||||||
/* Mark L2 table as used */
|
/* Mark L2 table as used */
|
||||||
l2_offset &= L1E_OFFSET_MASK;
|
|
||||||
ret = qcow2_inc_refcounts_imrt(bs, res,
|
ret = qcow2_inc_refcounts_imrt(bs, res,
|
||||||
refcount_table, refcount_table_size,
|
refcount_table, refcount_table_size,
|
||||||
l2_offset, s->cluster_size);
|
l2_offset, s->cluster_size);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* L2 tables are cluster aligned */
|
/* L2 tables are cluster aligned */
|
||||||
@ -1837,16 +1930,11 @@ static int check_refcounts_l1(BlockDriverState *bs,
|
|||||||
refcount_table_size, l2_offset, flags,
|
refcount_table_size, l2_offset, flags,
|
||||||
fix, active);
|
fix, active);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_free(l1_table);
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
g_free(l1_table);
|
|
||||||
return ret;
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2001,9 +2089,17 @@ static int check_refblocks(BlockDriverState *bs, BdrvCheckResult *res,
|
|||||||
|
|
||||||
for(i = 0; i < s->refcount_table_size; i++) {
|
for(i = 0; i < s->refcount_table_size; i++) {
|
||||||
uint64_t offset, cluster;
|
uint64_t offset, cluster;
|
||||||
offset = s->refcount_table[i];
|
offset = s->refcount_table[i] & REFT_OFFSET_MASK;
|
||||||
cluster = offset >> s->cluster_bits;
|
cluster = offset >> s->cluster_bits;
|
||||||
|
|
||||||
|
if (s->refcount_table[i] & REFT_RESERVED_MASK) {
|
||||||
|
fprintf(stderr, "ERROR refcount table entry %" PRId64 " has "
|
||||||
|
"reserved bits set\n", i);
|
||||||
|
res->corruptions++;
|
||||||
|
*rebuild = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* Refcount blocks are cluster aligned */
|
/* Refcount blocks are cluster aligned */
|
||||||
if (offset_into_cluster(s, offset)) {
|
if (offset_into_cluster(s, offset)) {
|
||||||
fprintf(stderr, "ERROR refcount block %" PRId64 " is not "
|
fprintf(stderr, "ERROR refcount block %" PRId64 " is not "
|
||||||
|
@ -74,7 +74,7 @@ typedef struct {
|
|||||||
|
|
||||||
static int coroutine_fn
|
static int coroutine_fn
|
||||||
qcow2_co_preadv_compressed(BlockDriverState *bs,
|
qcow2_co_preadv_compressed(BlockDriverState *bs,
|
||||||
uint64_t cluster_descriptor,
|
uint64_t l2_entry,
|
||||||
uint64_t offset,
|
uint64_t offset,
|
||||||
uint64_t bytes,
|
uint64_t bytes,
|
||||||
QEMUIOVector *qiov,
|
QEMUIOVector *qiov,
|
||||||
@ -2205,7 +2205,7 @@ typedef struct Qcow2AioTask {
|
|||||||
|
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
QCow2SubclusterType subcluster_type; /* only for read */
|
QCow2SubclusterType subcluster_type; /* only for read */
|
||||||
uint64_t host_offset; /* or full descriptor in compressed clusters */
|
uint64_t host_offset; /* or l2_entry for compressed read */
|
||||||
uint64_t offset;
|
uint64_t offset;
|
||||||
uint64_t bytes;
|
uint64_t bytes;
|
||||||
QEMUIOVector *qiov;
|
QEMUIOVector *qiov;
|
||||||
@ -4693,22 +4693,19 @@ qcow2_co_pwritev_compressed_part(BlockDriverState *bs,
|
|||||||
|
|
||||||
static int coroutine_fn
|
static int coroutine_fn
|
||||||
qcow2_co_preadv_compressed(BlockDriverState *bs,
|
qcow2_co_preadv_compressed(BlockDriverState *bs,
|
||||||
uint64_t cluster_descriptor,
|
uint64_t l2_entry,
|
||||||
uint64_t offset,
|
uint64_t offset,
|
||||||
uint64_t bytes,
|
uint64_t bytes,
|
||||||
QEMUIOVector *qiov,
|
QEMUIOVector *qiov,
|
||||||
size_t qiov_offset)
|
size_t qiov_offset)
|
||||||
{
|
{
|
||||||
BDRVQcow2State *s = bs->opaque;
|
BDRVQcow2State *s = bs->opaque;
|
||||||
int ret = 0, csize, nb_csectors;
|
int ret = 0, csize;
|
||||||
uint64_t coffset;
|
uint64_t coffset;
|
||||||
uint8_t *buf, *out_buf;
|
uint8_t *buf, *out_buf;
|
||||||
int offset_in_cluster = offset_into_cluster(s, offset);
|
int offset_in_cluster = offset_into_cluster(s, offset);
|
||||||
|
|
||||||
coffset = cluster_descriptor & s->cluster_offset_mask;
|
qcow2_parse_compressed_l2_entry(bs, l2_entry, &coffset, &csize);
|
||||||
nb_csectors = ((cluster_descriptor >> s->csize_shift) & s->csize_mask) + 1;
|
|
||||||
csize = nb_csectors * QCOW2_COMPRESSED_SECTOR_SIZE -
|
|
||||||
(coffset & ~QCOW2_COMPRESSED_SECTOR_MASK);
|
|
||||||
|
|
||||||
buf = g_try_malloc(csize);
|
buf = g_try_malloc(csize);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
|
@ -110,7 +110,6 @@
|
|||||||
|
|
||||||
/* Defined in the qcow2 spec (compressed cluster descriptor) */
|
/* Defined in the qcow2 spec (compressed cluster descriptor) */
|
||||||
#define QCOW2_COMPRESSED_SECTOR_SIZE 512U
|
#define QCOW2_COMPRESSED_SECTOR_SIZE 512U
|
||||||
#define QCOW2_COMPRESSED_SECTOR_MASK (~(QCOW2_COMPRESSED_SECTOR_SIZE - 1ULL))
|
|
||||||
|
|
||||||
/* Must be at least 2 to cover COW */
|
/* Must be at least 2 to cover COW */
|
||||||
#define MIN_L2_CACHE_SIZE 2 /* cache entries */
|
#define MIN_L2_CACHE_SIZE 2 /* cache entries */
|
||||||
@ -587,10 +586,12 @@ typedef enum QCow2MetadataOverlap {
|
|||||||
(QCOW2_OL_CACHED | QCOW2_OL_INACTIVE_L2)
|
(QCOW2_OL_CACHED | QCOW2_OL_INACTIVE_L2)
|
||||||
|
|
||||||
#define L1E_OFFSET_MASK 0x00fffffffffffe00ULL
|
#define L1E_OFFSET_MASK 0x00fffffffffffe00ULL
|
||||||
|
#define L1E_RESERVED_MASK 0x7f000000000001ffULL
|
||||||
#define L2E_OFFSET_MASK 0x00fffffffffffe00ULL
|
#define L2E_OFFSET_MASK 0x00fffffffffffe00ULL
|
||||||
#define L2E_COMPRESSED_OFFSET_SIZE_MASK 0x3fffffffffffffffULL
|
#define L2E_STD_RESERVED_MASK 0x3f000000000001feULL
|
||||||
|
|
||||||
#define REFT_OFFSET_MASK 0xfffffffffffffe00ULL
|
#define REFT_OFFSET_MASK 0xfffffffffffffe00ULL
|
||||||
|
#define REFT_RESERVED_MASK 0x1ffULL
|
||||||
|
|
||||||
#define INV_OFFSET (-1ULL)
|
#define INV_OFFSET (-1ULL)
|
||||||
|
|
||||||
@ -914,6 +915,8 @@ int qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
|||||||
uint64_t offset,
|
uint64_t offset,
|
||||||
int compressed_size,
|
int compressed_size,
|
||||||
uint64_t *host_offset);
|
uint64_t *host_offset);
|
||||||
|
void qcow2_parse_compressed_l2_entry(BlockDriverState *bs, uint64_t l2_entry,
|
||||||
|
uint64_t *coffset, int *csize);
|
||||||
|
|
||||||
int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m);
|
int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m);
|
||||||
void qcow2_alloc_cluster_abort(BlockDriverState *bs, QCowL2Meta *m);
|
void qcow2_alloc_cluster_abort(BlockDriverState *bs, QCowL2Meta *m);
|
||||||
|
@ -415,7 +415,7 @@ Command description:
|
|||||||
4
|
4
|
||||||
Error on reading data
|
Error on reading data
|
||||||
|
|
||||||
.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps [--skip-broken-bitmaps]] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-r RATE_LIMIT] [-m NUM_COROUTINES] [-W] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME
|
.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps [--skip-broken-bitmaps]] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE [-F backing_fmt]] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-r RATE_LIMIT] [-m NUM_COROUTINES] [-W] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME
|
||||||
|
|
||||||
Convert the disk image *FILENAME* or a snapshot *SNAPSHOT_PARAM*
|
Convert the disk image *FILENAME* or a snapshot *SNAPSHOT_PARAM*
|
||||||
to disk image *OUTPUT_FILENAME* using format *OUTPUT_FMT*. It can
|
to disk image *OUTPUT_FILENAME* using format *OUTPUT_FMT*. It can
|
||||||
@ -439,7 +439,7 @@ Command description:
|
|||||||
You can use the *BACKING_FILE* option to force the output image to be
|
You can use the *BACKING_FILE* option to force the output image to be
|
||||||
created as a copy on write image of the specified base image; the
|
created as a copy on write image of the specified base image; the
|
||||||
*BACKING_FILE* should have the same content as the input's base image,
|
*BACKING_FILE* should have the same content as the input's base image,
|
||||||
however the path, image format, etc may differ.
|
however the path, image format (as given by *BACKING_FMT*), etc may differ.
|
||||||
|
|
||||||
If a relative path name is given, the backing file is looked up relative to
|
If a relative path name is given, the backing file is looked up relative to
|
||||||
the directory containing *OUTPUT_FILENAME*.
|
the directory containing *OUTPUT_FILENAME*.
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include "qemu/hbitmap.h"
|
#include "qemu/hbitmap.h"
|
||||||
#include "block/snapshot.h"
|
#include "block/snapshot.h"
|
||||||
#include "qemu/throttle.h"
|
#include "qemu/throttle.h"
|
||||||
|
#include "qemu/rcu.h"
|
||||||
|
|
||||||
#define BLOCK_FLAG_LAZY_REFCOUNTS 8
|
#define BLOCK_FLAG_LAZY_REFCOUNTS 8
|
||||||
|
|
||||||
@ -347,6 +348,15 @@ struct BlockDriver {
|
|||||||
* clamped to bdrv_getlength() and aligned to request_alignment,
|
* clamped to bdrv_getlength() and aligned to request_alignment,
|
||||||
* as well as non-NULL pnum, map, and file; in turn, the driver
|
* as well as non-NULL pnum, map, and file; in turn, the driver
|
||||||
* must return an error or set pnum to an aligned non-zero value.
|
* must return an error or set pnum to an aligned non-zero value.
|
||||||
|
*
|
||||||
|
* Note that @bytes is just a hint on how big of a region the
|
||||||
|
* caller wants to inspect. It is not a limit on *pnum.
|
||||||
|
* Implementations are free to return larger values of *pnum if
|
||||||
|
* doing so does not incur a performance penalty.
|
||||||
|
*
|
||||||
|
* block/io.c's bdrv_co_block_status() will utilize an unclamped
|
||||||
|
* *pnum value for the block-status cache on protocol nodes, prior
|
||||||
|
* to clamping *pnum for return to its caller.
|
||||||
*/
|
*/
|
||||||
int coroutine_fn (*bdrv_co_block_status)(BlockDriverState *bs,
|
int coroutine_fn (*bdrv_co_block_status)(BlockDriverState *bs,
|
||||||
bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum,
|
bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum,
|
||||||
@ -840,11 +850,23 @@ struct BdrvChild {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note: the function bdrv_append() copies and swaps contents of
|
* Allows bdrv_co_block_status() to cache one data region for a
|
||||||
* BlockDriverStates, so if you add new fields to this struct, please
|
* protocol node.
|
||||||
* inspect bdrv_append() to determine if the new fields need to be
|
*
|
||||||
* copied as well.
|
* @valid: Whether the cache is valid (should be accessed with atomic
|
||||||
|
* functions so this can be reset by RCU readers)
|
||||||
|
* @data_start: Offset where we know (or strongly assume) is data
|
||||||
|
* @data_end: Offset where the data region ends (which is not necessarily
|
||||||
|
* the start of a zeroed region)
|
||||||
*/
|
*/
|
||||||
|
typedef struct BdrvBlockStatusCache {
|
||||||
|
struct rcu_head rcu;
|
||||||
|
|
||||||
|
bool valid;
|
||||||
|
int64_t data_start;
|
||||||
|
int64_t data_end;
|
||||||
|
} BdrvBlockStatusCache;
|
||||||
|
|
||||||
struct BlockDriverState {
|
struct BlockDriverState {
|
||||||
/* Protected by big QEMU lock or read-only after opening. No special
|
/* Protected by big QEMU lock or read-only after opening. No special
|
||||||
* locking needed during I/O...
|
* locking needed during I/O...
|
||||||
@ -1010,6 +1032,11 @@ struct BlockDriverState {
|
|||||||
|
|
||||||
/* BdrvChild links to this node may never be frozen */
|
/* BdrvChild links to this node may never be frozen */
|
||||||
bool never_freeze;
|
bool never_freeze;
|
||||||
|
|
||||||
|
/* Lock for block-status cache RCU writers */
|
||||||
|
CoMutex bsc_modify_lock;
|
||||||
|
/* Always non-NULL, but must only be dereferenced under an RCU read guard */
|
||||||
|
BdrvBlockStatusCache *block_status_cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlockBackendRootState {
|
struct BlockBackendRootState {
|
||||||
@ -1435,4 +1462,30 @@ static inline BlockDriverState *bdrv_primary_bs(BlockDriverState *bs)
|
|||||||
*/
|
*/
|
||||||
void bdrv_drain_all_end_quiesce(BlockDriverState *bs);
|
void bdrv_drain_all_end_quiesce(BlockDriverState *bs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given offset is in the cached block-status data
|
||||||
|
* region.
|
||||||
|
*
|
||||||
|
* If it is, and @pnum is not NULL, *pnum is set to
|
||||||
|
* `bsc.data_end - offset`, i.e. how many bytes, starting from
|
||||||
|
* @offset, are data (according to the cache).
|
||||||
|
* Otherwise, *pnum is not touched.
|
||||||
|
*/
|
||||||
|
bool bdrv_bsc_is_data(BlockDriverState *bs, int64_t offset, int64_t *pnum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If [offset, offset + bytes) overlaps with the currently cached
|
||||||
|
* block-status region, invalidate the cache.
|
||||||
|
*
|
||||||
|
* (To be used by I/O paths that cause data regions to be zero or
|
||||||
|
* holes.)
|
||||||
|
*/
|
||||||
|
void bdrv_bsc_invalidate_range(BlockDriverState *bs,
|
||||||
|
int64_t offset, int64_t bytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the range [offset, offset + bytes) as a data region.
|
||||||
|
*/
|
||||||
|
void bdrv_bsc_fill(BlockDriverState *bs, int64_t offset, int64_t bytes);
|
||||||
|
|
||||||
#endif /* BLOCK_INT_H */
|
#endif /* BLOCK_INT_H */
|
||||||
|
@ -46,7 +46,7 @@ SRST
|
|||||||
ERST
|
ERST
|
||||||
|
|
||||||
DEF("convert", img_convert,
|
DEF("convert", img_convert,
|
||||||
"convert [--object objectdef] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps] [-U] [-C] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-B backing_file] [-o options] [-l snapshot_param] [-S sparse_size] [-r rate_limit] [-m num_coroutines] [-W] [--salvage] filename [filename2 [...]] output_filename")
|
"convert [--object objectdef] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps] [-U] [-C] [-c] [-p] [-q] [-n] [-f fmt] [-t cache] [-T src_cache] [-O output_fmt] [-B backing_file [-F backing_fmt]] [-o options] [-l snapshot_param] [-S sparse_size] [-r rate_limit] [-m num_coroutines] [-W] [--salvage] filename [filename2 [...]] output_filename")
|
||||||
SRST
|
SRST
|
||||||
.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-r RATE_LIMIT] [-m NUM_COROUTINES] [-W] [--salvage] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME
|
.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-r RATE_LIMIT] [-m NUM_COROUTINES] [-W] [--salvage] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME
|
||||||
ERST
|
ERST
|
||||||
|
18
qemu-img.c
18
qemu-img.c
@ -2183,7 +2183,8 @@ static int img_convert(int argc, char **argv)
|
|||||||
int c, bs_i, flags, src_flags = BDRV_O_NO_SHARE;
|
int c, bs_i, flags, src_flags = BDRV_O_NO_SHARE;
|
||||||
const char *fmt = NULL, *out_fmt = NULL, *cache = "unsafe",
|
const char *fmt = NULL, *out_fmt = NULL, *cache = "unsafe",
|
||||||
*src_cache = BDRV_DEFAULT_CACHE, *out_baseimg = NULL,
|
*src_cache = BDRV_DEFAULT_CACHE, *out_baseimg = NULL,
|
||||||
*out_filename, *out_baseimg_param, *snapshot_name = NULL;
|
*out_filename, *out_baseimg_param, *snapshot_name = NULL,
|
||||||
|
*backing_fmt = NULL;
|
||||||
BlockDriver *drv = NULL, *proto_drv = NULL;
|
BlockDriver *drv = NULL, *proto_drv = NULL;
|
||||||
BlockDriverInfo bdi;
|
BlockDriverInfo bdi;
|
||||||
BlockDriverState *out_bs;
|
BlockDriverState *out_bs;
|
||||||
@ -2223,7 +2224,7 @@ static int img_convert(int argc, char **argv)
|
|||||||
{"skip-broken-bitmaps", no_argument, 0, OPTION_SKIP_BROKEN},
|
{"skip-broken-bitmaps", no_argument, 0, OPTION_SKIP_BROKEN},
|
||||||
{0, 0, 0, 0}
|
{0, 0, 0, 0}
|
||||||
};
|
};
|
||||||
c = getopt_long(argc, argv, ":hf:O:B:Cco:l:S:pt:T:qnm:WUr:",
|
c = getopt_long(argc, argv, ":hf:O:B:CcF:o:l:S:pt:T:qnm:WUr:",
|
||||||
long_options, NULL);
|
long_options, NULL);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
@ -2253,6 +2254,9 @@ static int img_convert(int argc, char **argv)
|
|||||||
case 'c':
|
case 'c':
|
||||||
s.compressed = true;
|
s.compressed = true;
|
||||||
break;
|
break;
|
||||||
|
case 'F':
|
||||||
|
backing_fmt = optarg;
|
||||||
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
if (accumulate_options(&options, optarg) < 0) {
|
if (accumulate_options(&options, optarg) < 0) {
|
||||||
goto fail_getopt;
|
goto fail_getopt;
|
||||||
@ -2521,7 +2525,7 @@ static int img_convert(int argc, char **argv)
|
|||||||
|
|
||||||
qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
|
qemu_opt_set_number(opts, BLOCK_OPT_SIZE,
|
||||||
s.total_sectors * BDRV_SECTOR_SIZE, &error_abort);
|
s.total_sectors * BDRV_SECTOR_SIZE, &error_abort);
|
||||||
ret = add_old_style_options(out_fmt, opts, out_baseimg, NULL);
|
ret = add_old_style_options(out_fmt, opts, out_baseimg, backing_fmt);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@ -2628,6 +2632,14 @@ static int img_convert(int argc, char **argv)
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flags & BDRV_O_NOCACHE) {
|
||||||
|
/*
|
||||||
|
* If we open the target with O_DIRECT, it may be necessary to
|
||||||
|
* extend its size to align to the physical sector size.
|
||||||
|
*/
|
||||||
|
flags |= BDRV_O_RESIZE;
|
||||||
|
}
|
||||||
|
|
||||||
if (skip_create) {
|
if (skip_create) {
|
||||||
s.target = img_open(tgt_image_opts, out_filename, out_fmt,
|
s.target = img_open(tgt_image_opts, out_filename, out_fmt,
|
||||||
flags, writethrough, s.quiet, false);
|
flags, writethrough, s.quiet, false);
|
||||||
|
95
scripts/simplebench/img_bench_templater.py
Executable file
95
scripts/simplebench/img_bench_templater.py
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Process img-bench test templates
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021 Virtuozzo International GmbH.
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
import simplebench
|
||||||
|
from results_to_text import results_to_text
|
||||||
|
from table_templater import Templater
|
||||||
|
|
||||||
|
|
||||||
|
def bench_func(env, case):
|
||||||
|
test = templater.gen(env['data'], case['data'])
|
||||||
|
|
||||||
|
p = subprocess.run(test, shell=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, universal_newlines=True)
|
||||||
|
|
||||||
|
if p.returncode == 0:
|
||||||
|
try:
|
||||||
|
m = re.search(r'Run completed in (\d+.\d+) seconds.', p.stdout)
|
||||||
|
return {'seconds': float(m.group(1))}
|
||||||
|
except Exception:
|
||||||
|
return {'error': f'failed to parse qemu-img output: {p.stdout}'}
|
||||||
|
else:
|
||||||
|
return {'error': f'qemu-img failed: {p.returncode}: {p.stdout}'}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
print("""
|
||||||
|
Usage: img_bench_templater.py < path/to/test-template.sh
|
||||||
|
|
||||||
|
This script generates performance tests from a test template (example below),
|
||||||
|
runs them, and displays the results in a table. The template is read from
|
||||||
|
stdin. It must be written in bash and end with a `qemu-img bench` invocation
|
||||||
|
(whose result is parsed to get the test instance’s result).
|
||||||
|
|
||||||
|
Use the following syntax in the template to create the various different test
|
||||||
|
instances:
|
||||||
|
|
||||||
|
column templating: {var1|var2|...} - test will use different values in
|
||||||
|
different columns. You may use several {} constructions in the test, in this
|
||||||
|
case product of all choice-sets will be used.
|
||||||
|
|
||||||
|
row templating: [var1|var2|...] - similar thing to define rows (test-cases)
|
||||||
|
|
||||||
|
Test template example:
|
||||||
|
|
||||||
|
Assume you want to compare two qemu-img binaries, called qemu-img-old and
|
||||||
|
qemu-img-new in your build directory in two test-cases with 4K writes and 64K
|
||||||
|
writes. The template may look like this:
|
||||||
|
|
||||||
|
qemu_img=/path/to/qemu/build/qemu-img-{old|new}
|
||||||
|
$qemu_img create -f qcow2 /ssd/x.qcow2 1G
|
||||||
|
$qemu_img bench -c 100 -d 8 [-s 4K|-s 64K] -w -t none -n /ssd/x.qcow2
|
||||||
|
|
||||||
|
When passing this to stdin of img_bench_templater.py, the resulting comparison
|
||||||
|
table will contain two columns (for two binaries) and two rows (for two
|
||||||
|
test-cases).
|
||||||
|
|
||||||
|
In addition to displaying the results, script also stores results in JSON
|
||||||
|
format into results.json file in current directory.
|
||||||
|
""")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
templater = Templater(sys.stdin.read())
|
||||||
|
|
||||||
|
envs = [{'id': ' / '.join(x), 'data': x} for x in templater.columns]
|
||||||
|
cases = [{'id': ' / '.join(x), 'data': x} for x in templater.rows]
|
||||||
|
|
||||||
|
result = simplebench.bench(bench_func, envs, cases, count=5,
|
||||||
|
initial_run=False)
|
||||||
|
print(results_to_text(result))
|
||||||
|
with open('results.json', 'w') as f:
|
||||||
|
json.dump(result, f, indent=4)
|
62
scripts/simplebench/table_templater.py
Normal file
62
scripts/simplebench/table_templater.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Parser for test templates
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021 Virtuozzo International GmbH.
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
from lark import Lark
|
||||||
|
|
||||||
|
grammar = """
|
||||||
|
start: ( text | column_switch | row_switch )+
|
||||||
|
|
||||||
|
column_switch: "{" text ["|" text]+ "}"
|
||||||
|
row_switch: "[" text ["|" text]+ "]"
|
||||||
|
text: /[^|{}\[\]]+/
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = Lark(grammar)
|
||||||
|
|
||||||
|
class Templater:
|
||||||
|
def __init__(self, template):
|
||||||
|
self.tree = parser.parse(template)
|
||||||
|
|
||||||
|
c_switches = []
|
||||||
|
r_switches = []
|
||||||
|
for x in self.tree.children:
|
||||||
|
if x.data == 'column_switch':
|
||||||
|
c_switches.append([el.children[0].value for el in x.children])
|
||||||
|
elif x.data == 'row_switch':
|
||||||
|
r_switches.append([el.children[0].value for el in x.children])
|
||||||
|
|
||||||
|
self.columns = list(itertools.product(*c_switches))
|
||||||
|
self.rows = list(itertools.product(*r_switches))
|
||||||
|
|
||||||
|
def gen(self, column, row):
|
||||||
|
i = 0
|
||||||
|
j = 0
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for x in self.tree.children:
|
||||||
|
if x.data == 'text':
|
||||||
|
result.append(x.children[0].value)
|
||||||
|
elif x.data == 'column_switch':
|
||||||
|
result.append(column[i])
|
||||||
|
i += 1
|
||||||
|
elif x.data == 'row_switch':
|
||||||
|
result.append(row[j])
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
return ''.join(result)
|
@ -67,7 +67,7 @@ echo
|
|||||||
_make_test_img -b "$TEST_IMG".base -F $IMGFMT
|
_make_test_img -b "$TEST_IMG".base -F $IMGFMT
|
||||||
|
|
||||||
$QEMU_IO -c "write -P 0 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -c "write -P 0 0 3M" "$TEST_IMG" 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base -o backing_fmt=$IMGFMT \
|
$QEMU_IMG convert -O $IMGFMT -B "$TEST_IMG".base -F $IMGFMT \
|
||||||
"$TEST_IMG" "$TEST_IMG".orig
|
"$TEST_IMG" "$TEST_IMG".orig
|
||||||
$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
$QEMU_IO -c "read -P 0 0 3M" "$TEST_IMG".orig 2>&1 | _filter_qemu_io | _filter_testdir
|
||||||
$QEMU_IMG convert -O $IMGFMT -c -B "$TEST_IMG".base -o backing_fmt=$IMGFMT \
|
$QEMU_IMG convert -O $IMGFMT -c -B "$TEST_IMG".base -o backing_fmt=$IMGFMT \
|
||||||
|
@ -893,7 +893,10 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
_make_test_img -o extended_l2=on 1M
|
_make_test_img -o extended_l2=on 1M
|
||||||
_concurrent_io | $QEMU_IO | _filter_qemu_io
|
# Second and third writes in _concurrent_io() are independent and may finish in
|
||||||
|
# different order. So, filter offset out to match both possible variants.
|
||||||
|
_concurrent_io | $QEMU_IO | _filter_qemu_io | \
|
||||||
|
$SED -e 's/\(20480\|40960\)/OFFSET/'
|
||||||
_concurrent_verify | $QEMU_IO | _filter_qemu_io
|
_concurrent_verify | $QEMU_IO | _filter_qemu_io
|
||||||
|
|
||||||
# success, all done
|
# success, all done
|
||||||
|
@ -719,8 +719,8 @@ blkdebug: Suspended request 'A'
|
|||||||
blkdebug: Resuming request 'A'
|
blkdebug: Resuming request 'A'
|
||||||
wrote 2048/2048 bytes at offset 30720
|
wrote 2048/2048 bytes at offset 30720
|
||||||
2 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
2 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
wrote 2048/2048 bytes at offset 20480
|
wrote 2048/2048 bytes at offset OFFSET
|
||||||
2 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
2 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
wrote 2048/2048 bytes at offset 40960
|
wrote 2048/2048 bytes at offset OFFSET
|
||||||
2 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
2 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||||
*** done
|
*** done
|
||||||
|
@ -29,7 +29,7 @@ import iotests
|
|||||||
SKIP_FILES = (
|
SKIP_FILES = (
|
||||||
'030', '040', '041', '044', '045', '055', '056', '057', '065', '093',
|
'030', '040', '041', '044', '045', '055', '056', '057', '065', '093',
|
||||||
'096', '118', '124', '132', '136', '139', '147', '148', '149',
|
'096', '118', '124', '132', '136', '139', '147', '148', '149',
|
||||||
'151', '152', '155', '163', '165', '169', '194', '196', '199', '202',
|
'151', '152', '155', '163', '165', '194', '196', '202',
|
||||||
'203', '205', '206', '207', '208', '210', '211', '212', '213', '216',
|
'203', '205', '206', '207', '208', '210', '211', '212', '213', '216',
|
||||||
'218', '219', '224', '228', '234', '235', '236', '237', '238',
|
'218', '219', '224', '228', '234', '235', '236', '237', '238',
|
||||||
'240', '242', '245', '246', '248', '255', '256', '257', '258', '260',
|
'240', '242', '245', '246', '248', '255', '256', '257', '258', '260',
|
||||||
@ -46,7 +46,7 @@ def is_python_file(filename):
|
|||||||
if filename.endswith('.py'):
|
if filename.endswith('.py'):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
with open(filename) as f:
|
with open(filename, encoding='utf-8') as f:
|
||||||
try:
|
try:
|
||||||
first_line = f.readline()
|
first_line = f.readline()
|
||||||
return re.match('^#!.*python', first_line) is not None
|
return re.match('^#!.*python', first_line) is not None
|
||||||
@ -55,8 +55,9 @@ def is_python_file(filename):
|
|||||||
|
|
||||||
|
|
||||||
def run_linters():
|
def run_linters():
|
||||||
files = [filename for filename in (set(os.listdir('.')) - set(SKIP_FILES))
|
named_tests = [f'tests/{entry}' for entry in os.listdir('tests')]
|
||||||
if is_python_file(filename)]
|
check_tests = set(os.listdir('.') + named_tests) - set(SKIP_FILES)
|
||||||
|
files = [filename for filename in check_tests if is_python_file(filename)]
|
||||||
|
|
||||||
iotests.logger.debug('Files to be checked:')
|
iotests.logger.debug('Files to be checked:')
|
||||||
iotests.logger.debug(', '.join(sorted(files)))
|
iotests.logger.debug(', '.join(sorted(files)))
|
||||||
|
@ -610,7 +610,7 @@ class VM(qtest.QEMUQtestMachine):
|
|||||||
return
|
return
|
||||||
valgrind_filename = f"{test_dir}/{self._popen.pid}.valgrind"
|
valgrind_filename = f"{test_dir}/{self._popen.pid}.valgrind"
|
||||||
if self.exitcode() == 99:
|
if self.exitcode() == 99:
|
||||||
with open(valgrind_filename) as f:
|
with open(valgrind_filename, encoding='utf-8') as f:
|
||||||
print(f.read())
|
print(f.read())
|
||||||
else:
|
else:
|
||||||
os.remove(valgrind_filename)
|
os.remove(valgrind_filename)
|
||||||
@ -703,7 +703,7 @@ class VM(qtest.QEMUQtestMachine):
|
|||||||
|
|
||||||
def flatten_qmp_object(self, obj, output=None, basestr=''):
|
def flatten_qmp_object(self, obj, output=None, basestr=''):
|
||||||
if output is None:
|
if output is None:
|
||||||
output = dict()
|
output = {}
|
||||||
if isinstance(obj, list):
|
if isinstance(obj, list):
|
||||||
for i, item in enumerate(obj):
|
for i, item in enumerate(obj):
|
||||||
self.flatten_qmp_object(item, output, basestr + str(i) + '.')
|
self.flatten_qmp_object(item, output, basestr + str(i) + '.')
|
||||||
@ -716,7 +716,7 @@ class VM(qtest.QEMUQtestMachine):
|
|||||||
|
|
||||||
def qmp_to_opts(self, obj):
|
def qmp_to_opts(self, obj):
|
||||||
obj = self.flatten_qmp_object(obj)
|
obj = self.flatten_qmp_object(obj)
|
||||||
output_list = list()
|
output_list = []
|
||||||
for key in obj:
|
for key in obj:
|
||||||
output_list += [key + '=' + obj[key]]
|
output_list += [key + '=' + obj[key]]
|
||||||
return ','.join(output_list)
|
return ','.join(output_list)
|
||||||
@ -1121,7 +1121,8 @@ def notrun(reason):
|
|||||||
# Each test in qemu-iotests has a number ("seq")
|
# Each test in qemu-iotests has a number ("seq")
|
||||||
seq = os.path.basename(sys.argv[0])
|
seq = os.path.basename(sys.argv[0])
|
||||||
|
|
||||||
with open('%s/%s.notrun' % (output_dir, seq), 'w') as outfile:
|
with open('%s/%s.notrun' % (output_dir, seq), 'w', encoding='utf-8') \
|
||||||
|
as outfile:
|
||||||
outfile.write(reason + '\n')
|
outfile.write(reason + '\n')
|
||||||
logger.warning("%s not run: %s", seq, reason)
|
logger.warning("%s not run: %s", seq, reason)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@ -1135,7 +1136,8 @@ def case_notrun(reason):
|
|||||||
# Each test in qemu-iotests has a number ("seq")
|
# Each test in qemu-iotests has a number ("seq")
|
||||||
seq = os.path.basename(sys.argv[0])
|
seq = os.path.basename(sys.argv[0])
|
||||||
|
|
||||||
with open('%s/%s.casenotrun' % (output_dir, seq), 'a') as outfile:
|
with open('%s/%s.casenotrun' % (output_dir, seq), 'a', encoding='utf-8') \
|
||||||
|
as outfile:
|
||||||
outfile.write(' [case not run] ' + reason + '\n')
|
outfile.write(' [case not run] ' + reason + '\n')
|
||||||
|
|
||||||
def _verify_image_format(supported_fmts: Sequence[str] = (),
|
def _verify_image_format(supported_fmts: Sequence[str] = (),
|
||||||
|
@ -132,10 +132,10 @@ class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
|||||||
|
|
||||||
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
node='drive0', name='bitmap0')
|
node='drive0', name='bitmap0')
|
||||||
self.discards1_sha256 = result['return']['sha256']
|
discards1_sha256 = result['return']['sha256']
|
||||||
|
|
||||||
# Check, that updating the bitmap by discards works
|
# Check, that updating the bitmap by discards works
|
||||||
assert self.discards1_sha256 != empty_sha256
|
assert discards1_sha256 != empty_sha256
|
||||||
|
|
||||||
# We want to calculate resulting sha256. Do it in bitmap0, so, disable
|
# We want to calculate resulting sha256. Do it in bitmap0, so, disable
|
||||||
# other bitmaps
|
# other bitmaps
|
||||||
@ -148,7 +148,7 @@ class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
|||||||
|
|
||||||
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
result = self.vm_a.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
node='drive0', name='bitmap0')
|
node='drive0', name='bitmap0')
|
||||||
self.all_discards_sha256 = result['return']['sha256']
|
all_discards_sha256 = result['return']['sha256']
|
||||||
|
|
||||||
# Now, enable some bitmaps, to be updated during migration
|
# Now, enable some bitmaps, to be updated during migration
|
||||||
for i in range(2, nb_bitmaps, 2):
|
for i in range(2, nb_bitmaps, 2):
|
||||||
@ -173,10 +173,11 @@ class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
|||||||
|
|
||||||
event_resume = self.vm_b.event_wait('RESUME')
|
event_resume = self.vm_b.event_wait('RESUME')
|
||||||
self.vm_b_events.append(event_resume)
|
self.vm_b_events.append(event_resume)
|
||||||
return event_resume
|
return (event_resume, discards1_sha256, all_discards_sha256)
|
||||||
|
|
||||||
def test_postcopy_success(self):
|
def test_postcopy_success(self):
|
||||||
event_resume = self.start_postcopy()
|
event_resume, discards1_sha256, all_discards_sha256 = \
|
||||||
|
self.start_postcopy()
|
||||||
|
|
||||||
# enabled bitmaps should be updated
|
# enabled bitmaps should be updated
|
||||||
apply_discards(self.vm_b, discards2)
|
apply_discards(self.vm_b, discards2)
|
||||||
@ -217,7 +218,7 @@ class TestDirtyBitmapPostcopyMigration(iotests.QMPTestCase):
|
|||||||
for i in range(0, nb_bitmaps, 5):
|
for i in range(0, nb_bitmaps, 5):
|
||||||
result = self.vm_b.qmp('x-debug-block-dirty-bitmap-sha256',
|
result = self.vm_b.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
node='drive0', name='bitmap{}'.format(i))
|
node='drive0', name='bitmap{}'.format(i))
|
||||||
sha = self.discards1_sha256 if i % 2 else self.all_discards_sha256
|
sha = discards1_sha256 if i % 2 else all_discards_sha256
|
||||||
self.assert_qmp(result, 'return/sha256', sha)
|
self.assert_qmp(result, 'return/sha256', sha)
|
||||||
|
|
||||||
def test_early_shutdown_destination(self):
|
def test_early_shutdown_destination(self):
|
||||||
|
@ -20,11 +20,10 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import iotests
|
|
||||||
import time
|
|
||||||
import itertools
|
import itertools
|
||||||
import operator
|
import operator
|
||||||
import re
|
import re
|
||||||
|
import iotests
|
||||||
from iotests import qemu_img, qemu_img_create, Timeout
|
from iotests import qemu_img, qemu_img_create, Timeout
|
||||||
|
|
||||||
|
|
||||||
@ -37,6 +36,12 @@ mig_cmd = 'exec: cat > ' + mig_file
|
|||||||
incoming_cmd = 'exec: cat ' + mig_file
|
incoming_cmd = 'exec: cat ' + mig_file
|
||||||
|
|
||||||
|
|
||||||
|
def get_bitmap_hash(vm):
|
||||||
|
result = vm.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
|
node='drive0', name='bitmap0')
|
||||||
|
return result['return']['sha256']
|
||||||
|
|
||||||
|
|
||||||
class TestDirtyBitmapMigration(iotests.QMPTestCase):
|
class TestDirtyBitmapMigration(iotests.QMPTestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.vm_a.shutdown()
|
self.vm_a.shutdown()
|
||||||
@ -62,21 +67,16 @@ class TestDirtyBitmapMigration(iotests.QMPTestCase):
|
|||||||
params['persistent'] = True
|
params['persistent'] = True
|
||||||
|
|
||||||
result = vm.qmp('block-dirty-bitmap-add', **params)
|
result = vm.qmp('block-dirty-bitmap-add', **params)
|
||||||
self.assert_qmp(result, 'return', {});
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
def get_bitmap_hash(self, vm):
|
|
||||||
result = vm.qmp('x-debug-block-dirty-bitmap-sha256',
|
|
||||||
node='drive0', name='bitmap0')
|
|
||||||
return result['return']['sha256']
|
|
||||||
|
|
||||||
def check_bitmap(self, vm, sha256):
|
def check_bitmap(self, vm, sha256):
|
||||||
result = vm.qmp('x-debug-block-dirty-bitmap-sha256',
|
result = vm.qmp('x-debug-block-dirty-bitmap-sha256',
|
||||||
node='drive0', name='bitmap0')
|
node='drive0', name='bitmap0')
|
||||||
if sha256:
|
if sha256:
|
||||||
self.assert_qmp(result, 'return/sha256', sha256);
|
self.assert_qmp(result, 'return/sha256', sha256)
|
||||||
else:
|
else:
|
||||||
self.assert_qmp(result, 'error/desc',
|
self.assert_qmp(result, 'error/desc',
|
||||||
"Dirty bitmap 'bitmap0' not found");
|
"Dirty bitmap 'bitmap0' not found")
|
||||||
|
|
||||||
def do_test_migration_resume_source(self, persistent, migrate_bitmaps):
|
def do_test_migration_resume_source(self, persistent, migrate_bitmaps):
|
||||||
granularity = 512
|
granularity = 512
|
||||||
@ -97,7 +97,7 @@ class TestDirtyBitmapMigration(iotests.QMPTestCase):
|
|||||||
self.add_bitmap(self.vm_a, granularity, persistent)
|
self.add_bitmap(self.vm_a, granularity, persistent)
|
||||||
for r in regions:
|
for r in regions:
|
||||||
self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r)
|
self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r)
|
||||||
sha256 = self.get_bitmap_hash(self.vm_a)
|
sha256 = get_bitmap_hash(self.vm_a)
|
||||||
|
|
||||||
result = self.vm_a.qmp('migrate', uri=mig_cmd)
|
result = self.vm_a.qmp('migrate', uri=mig_cmd)
|
||||||
while True:
|
while True:
|
||||||
@ -106,7 +106,7 @@ class TestDirtyBitmapMigration(iotests.QMPTestCase):
|
|||||||
break
|
break
|
||||||
while True:
|
while True:
|
||||||
result = self.vm_a.qmp('query-status')
|
result = self.vm_a.qmp('query-status')
|
||||||
if (result['return']['status'] == 'postmigrate'):
|
if result['return']['status'] == 'postmigrate':
|
||||||
break
|
break
|
||||||
|
|
||||||
# test that bitmap is still here
|
# test that bitmap is still here
|
||||||
@ -164,7 +164,7 @@ class TestDirtyBitmapMigration(iotests.QMPTestCase):
|
|||||||
self.add_bitmap(self.vm_a, granularity, persistent)
|
self.add_bitmap(self.vm_a, granularity, persistent)
|
||||||
for r in regions:
|
for r in regions:
|
||||||
self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r)
|
self.vm_a.hmp_qemu_io('drive0', 'write %d %d' % r)
|
||||||
sha256 = self.get_bitmap_hash(self.vm_a)
|
sha256 = get_bitmap_hash(self.vm_a)
|
||||||
|
|
||||||
if pre_shutdown:
|
if pre_shutdown:
|
||||||
self.vm_a.shutdown()
|
self.vm_a.shutdown()
|
||||||
@ -214,16 +214,22 @@ class TestDirtyBitmapMigration(iotests.QMPTestCase):
|
|||||||
self.check_bitmap(self.vm_b, sha256 if persistent else False)
|
self.check_bitmap(self.vm_b, sha256 if persistent else False)
|
||||||
|
|
||||||
|
|
||||||
def inject_test_case(klass, name, method, *args, **kwargs):
|
def inject_test_case(klass, suffix, method, *args, **kwargs):
|
||||||
mc = operator.methodcaller(method, *args, **kwargs)
|
mc = operator.methodcaller(method, *args, **kwargs)
|
||||||
setattr(klass, 'test_' + method + name, lambda self: mc(self))
|
# We want to add a function attribute to `klass`, so that it is
|
||||||
|
# correctly converted to a method on instantiation. The
|
||||||
|
# methodcaller object `mc` is a callable, not a function, so we
|
||||||
|
# need the lambda to turn it into a function.
|
||||||
|
# pylint: disable=unnecessary-lambda
|
||||||
|
setattr(klass, 'test_' + method + suffix, lambda self: mc(self))
|
||||||
|
|
||||||
|
|
||||||
for cmb in list(itertools.product((True, False), repeat=5)):
|
for cmb in list(itertools.product((True, False), repeat=5)):
|
||||||
name = ('_' if cmb[0] else '_not_') + 'persistent_'
|
name = ('_' if cmb[0] else '_not_') + 'persistent_'
|
||||||
name += ('_' if cmb[1] else '_not_') + 'migbitmap_'
|
name += ('_' if cmb[1] else '_not_') + 'migbitmap_'
|
||||||
name += '_online' if cmb[2] else '_offline'
|
name += '_online' if cmb[2] else '_offline'
|
||||||
name += '_shared' if cmb[3] else '_nonshared'
|
name += '_shared' if cmb[3] else '_nonshared'
|
||||||
if (cmb[4]):
|
if cmb[4]:
|
||||||
name += '__pre_shutdown'
|
name += '__pre_shutdown'
|
||||||
|
|
||||||
inject_test_case(TestDirtyBitmapMigration, name, 'do_test_migration',
|
inject_test_case(TestDirtyBitmapMigration, name, 'do_test_migration',
|
||||||
@ -270,7 +276,8 @@ class TestDirtyBitmapBackingMigration(iotests.QMPTestCase):
|
|||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
# Check that the bitmaps are there
|
# Check that the bitmaps are there
|
||||||
for node in self.vm.qmp('query-named-block-nodes', flat=True)['return']:
|
nodes = self.vm.qmp('query-named-block-nodes', flat=True)['return']
|
||||||
|
for node in nodes:
|
||||||
if 'node0' in node['node-name']:
|
if 'node0' in node['node-name']:
|
||||||
self.assert_qmp(node, 'dirty-bitmaps[0]/name', 'bmap0')
|
self.assert_qmp(node, 'dirty-bitmaps[0]/name', 'bmap0')
|
||||||
|
|
||||||
@ -287,7 +294,7 @@ class TestDirtyBitmapBackingMigration(iotests.QMPTestCase):
|
|||||||
"""
|
"""
|
||||||
Continue the source after migration.
|
Continue the source after migration.
|
||||||
"""
|
"""
|
||||||
result = self.vm.qmp('migrate', uri=f'exec: cat > /dev/null')
|
result = self.vm.qmp('migrate', uri='exec: cat > /dev/null')
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
with Timeout(10, 'Migration timeout'):
|
with Timeout(10, 'Migration timeout'):
|
||||||
|
97
tests/qemu-iotests/tests/migrate-during-backup
Executable file
97
tests/qemu-iotests/tests/migrate-during-backup
Executable file
@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# group: migration
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021 Virtuozzo International GmbH
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import iotests
|
||||||
|
from iotests import qemu_img_create, qemu_io
|
||||||
|
|
||||||
|
|
||||||
|
disk_a = os.path.join(iotests.test_dir, 'disk_a')
|
||||||
|
disk_b = os.path.join(iotests.test_dir, 'disk_b')
|
||||||
|
size = '1M'
|
||||||
|
mig_file = os.path.join(iotests.test_dir, 'mig_file')
|
||||||
|
mig_cmd = 'exec: cat > ' + mig_file
|
||||||
|
|
||||||
|
|
||||||
|
class TestMigrateDuringBackup(iotests.QMPTestCase):
|
||||||
|
def tearDown(self):
|
||||||
|
self.vm.shutdown()
|
||||||
|
os.remove(disk_a)
|
||||||
|
os.remove(disk_b)
|
||||||
|
os.remove(mig_file)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
qemu_img_create('-f', iotests.imgfmt, disk_a, size)
|
||||||
|
qemu_img_create('-f', iotests.imgfmt, disk_b, size)
|
||||||
|
qemu_io('-c', f'write 0 {size}', disk_a)
|
||||||
|
|
||||||
|
self.vm = iotests.VM().add_drive(disk_a)
|
||||||
|
self.vm.launch()
|
||||||
|
result = self.vm.qmp('blockdev-add', {
|
||||||
|
'node-name': 'target',
|
||||||
|
'driver': iotests.imgfmt,
|
||||||
|
'file': {
|
||||||
|
'driver': 'file',
|
||||||
|
'filename': disk_b
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
def test_migrate(self):
|
||||||
|
result = self.vm.qmp('blockdev-backup', device='drive0',
|
||||||
|
target='target', sync='full',
|
||||||
|
speed=1, x_perf={
|
||||||
|
'max-workers': 1,
|
||||||
|
'max-chunk': 64 * 1024
|
||||||
|
})
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
result = self.vm.qmp('job-pause', id='drive0')
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
result = self.vm.qmp('migrate-set-capabilities',
|
||||||
|
capabilities=[{'capability': 'events',
|
||||||
|
'state': True}])
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
result = self.vm.qmp('migrate', uri=mig_cmd)
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
e = self.vm.events_wait((('MIGRATION',
|
||||||
|
{'data': {'status': 'completed'}}),
|
||||||
|
('MIGRATION',
|
||||||
|
{'data': {'status': 'failed'}})))
|
||||||
|
|
||||||
|
# Don't assert that e is 'failed' now: this way we'll miss
|
||||||
|
# possible crash when backup continues :)
|
||||||
|
|
||||||
|
result = self.vm.qmp('block-job-set-speed', device='drive0',
|
||||||
|
speed=0)
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
result = self.vm.qmp('job-resume', id='drive0')
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
# For future: if something changes so that both migration
|
||||||
|
# and backup pass, let's not miss that moment, as it may
|
||||||
|
# be a bug as well as improvement.
|
||||||
|
self.assert_qmp(e, 'data/status', 'failed')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
iotests.main(supported_fmts=['qcow2'],
|
||||||
|
supported_protocols=['file'])
|
5
tests/qemu-iotests/tests/migrate-during-backup.out
Normal file
5
tests/qemu-iotests/tests/migrate-during-backup.out
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Ran 1 tests
|
||||||
|
|
||||||
|
OK
|
@ -47,7 +47,7 @@ class TestMirrorTopPerms(iotests.QMPTestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
try:
|
try:
|
||||||
self.vm.shutdown()
|
self.vm.shutdown()
|
||||||
except qemu.machine.AbnormalShutdown:
|
except qemu.machine.machine.AbnormalShutdown:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self.vm_b is not None:
|
if self.vm_b is not None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user