mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Merge remote-tracking branch 'remotes/hreitz/tags/pull-block-2021-09-01' into staging
Block patches: - Make the backup-top filter driver available for user-created block nodes (i.e. via blockdev-add) - Allow running iotests with gdb or valgrind being attached to qemu instances - Fix the raw format driver's permissions: There is no metadata, so we only need WRITE or RESIZE when the parent needs it - Basic reopen implementation for win32 files (file-win32.c) so that qemu-img commit can work - uclibc/musl build fix for the FUSE export code - Some iotests delinting - block-hmp-cmds.c refactoring # gpg: Signature made Wed 01 Sep 2021 16:01:54 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-01: (56 commits) block/file-win32: add reopen handlers block/export/fuse.c: fix fuse-lseek on uclibc or musl block/block-copy: block_copy_state_new(): drop extra arguments iotests/image-fleecing: add test-case for copy-before-write filter iotests/image-fleecing: prepare for adding new test-case iotests/image-fleecing: rename tgt_node iotests/image-fleecing: proper source device iotests.py: hmp_qemu_io: support qdev iotests: move 222 to tests/image-fleecing iotests/222: constantly use single quotes for strings iotests/222: fix pylint and mypy complains python:QEMUMachine: template typing for self returning methods python/qemu/machine: QEMUMachine: improve qmp() method python/qemu/machine.py: refactor _qemu_args() qapi: publish copy-before-write filter block/copy-before-write: make public block driver block/block-copy: make setting progress optional block/copy-before-write: initialize block-copy bitmap block/copy-before-write: cbw_init(): use options block/copy-before-write: bdrv_cbw_append(): drop unused compress arg ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
@@ -305,11 +305,11 @@ build-tcg-disabled:
|
||||
- cd tests/qemu-iotests/
|
||||
- ./check -raw 001 002 003 004 005 008 009 010 011 012 021 025 032 033 048
|
||||
052 063 077 086 101 104 106 113 148 150 151 152 157 159 160 163
|
||||
170 171 183 184 192 194 208 221 222 226 227 236 253 277
|
||||
170 171 183 184 192 194 208 221 226 227 236 253 277 image-fleecing
|
||||
- ./check -qcow2 028 051 056 057 058 065 068 082 085 091 095 096 102 122
|
||||
124 132 139 142 144 145 151 152 155 157 165 194 196 200 202
|
||||
208 209 216 218 222 227 234 246 247 248 250 254 255 257 258
|
||||
260 261 262 263 264 270 272 273 277 279
|
||||
208 209 216 218 227 234 246 247 248 250 254 255 257 258
|
||||
260 261 262 263 264 270 272 273 277 279 image-fleecing
|
||||
|
||||
build-user:
|
||||
extends: .native_build_job_template
|
||||
|
||||
+2
-2
@@ -2391,8 +2391,8 @@ F: block/mirror.c
|
||||
F: qapi/job.json
|
||||
F: block/block-copy.c
|
||||
F: include/block/block-copy.c
|
||||
F: block/backup-top.h
|
||||
F: block/backup-top.c
|
||||
F: block/copy-before-write.h
|
||||
F: block/copy-before-write.c
|
||||
F: include/block/aio_task.h
|
||||
F: block/aio_task.c
|
||||
F: util/qemu-co-shared-resource.c
|
||||
|
||||
@@ -5048,6 +5048,37 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Not for empty child */
|
||||
int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs,
|
||||
Error **errp)
|
||||
{
|
||||
int ret;
|
||||
Transaction *tran = tran_new();
|
||||
g_autoptr(GHashTable) found = NULL;
|
||||
g_autoptr(GSList) refresh_list = NULL;
|
||||
BlockDriverState *old_bs = child->bs;
|
||||
|
||||
bdrv_ref(old_bs);
|
||||
bdrv_drained_begin(old_bs);
|
||||
bdrv_drained_begin(new_bs);
|
||||
|
||||
bdrv_replace_child_tran(child, new_bs, tran);
|
||||
|
||||
found = g_hash_table_new(NULL, NULL);
|
||||
refresh_list = bdrv_topological_dfs(refresh_list, found, old_bs);
|
||||
refresh_list = bdrv_topological_dfs(refresh_list, found, new_bs);
|
||||
|
||||
ret = bdrv_list_refresh_perms(refresh_list, NULL, tran, errp);
|
||||
|
||||
tran_finalize(tran, ret);
|
||||
|
||||
bdrv_drained_end(old_bs);
|
||||
bdrv_drained_end(new_bs);
|
||||
bdrv_unref(old_bs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bdrv_delete(BlockDriverState *bs)
|
||||
{
|
||||
assert(bdrv_op_blocker_is_empty(bs));
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
/*
|
||||
* backup-top filter driver
|
||||
*
|
||||
* The driver performs Copy-Before-Write (CBW) operation: it is injected above
|
||||
* some node, and before each write it copies _old_ data to the target node.
|
||||
*
|
||||
* Copyright (c) 2018-2019 Virtuozzo International GmbH.
|
||||
*
|
||||
* Author:
|
||||
* Sementsov-Ogievskiy Vladimir <vsementsov@virtuozzo.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qapi/error.h"
|
||||
#include "block/block_int.h"
|
||||
#include "block/qdict.h"
|
||||
#include "block/block-copy.h"
|
||||
|
||||
#include "block/backup-top.h"
|
||||
|
||||
typedef struct BDRVBackupTopState {
|
||||
BlockCopyState *bcs;
|
||||
BdrvChild *target;
|
||||
int64_t cluster_size;
|
||||
} BDRVBackupTopState;
|
||||
|
||||
static coroutine_fn int backup_top_co_preadv(
|
||||
BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
||||
QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
return bdrv_co_preadv(bs->backing, offset, bytes, qiov, flags);
|
||||
}
|
||||
|
||||
static coroutine_fn int backup_top_cbw(BlockDriverState *bs, uint64_t offset,
|
||||
uint64_t bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
BDRVBackupTopState *s = bs->opaque;
|
||||
uint64_t off, end;
|
||||
|
||||
if (flags & BDRV_REQ_WRITE_UNCHANGED) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
off = QEMU_ALIGN_DOWN(offset, s->cluster_size);
|
||||
end = QEMU_ALIGN_UP(offset + bytes, s->cluster_size);
|
||||
|
||||
return block_copy(s->bcs, off, end - off, true);
|
||||
}
|
||||
|
||||
static int coroutine_fn backup_top_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int bytes)
|
||||
{
|
||||
int ret = backup_top_cbw(bs, offset, bytes, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_co_pdiscard(bs->backing, offset, bytes);
|
||||
}
|
||||
|
||||
static int coroutine_fn backup_top_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
int ret = backup_top_cbw(bs, offset, bytes, flags);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_co_pwrite_zeroes(bs->backing, offset, bytes, flags);
|
||||
}
|
||||
|
||||
static coroutine_fn int backup_top_co_pwritev(BlockDriverState *bs,
|
||||
uint64_t offset,
|
||||
uint64_t bytes,
|
||||
QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
int ret = backup_top_cbw(bs, offset, bytes, flags);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_co_pwritev(bs->backing, offset, bytes, qiov, flags);
|
||||
}
|
||||
|
||||
static int coroutine_fn backup_top_co_flush(BlockDriverState *bs)
|
||||
{
|
||||
if (!bs->backing) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bdrv_co_flush(bs->backing->bs);
|
||||
}
|
||||
|
||||
static void backup_top_refresh_filename(BlockDriverState *bs)
|
||||
{
|
||||
if (bs->backing == NULL) {
|
||||
/*
|
||||
* we can be here after failed bdrv_attach_child in
|
||||
* bdrv_set_backing_hd
|
||||
*/
|
||||
return;
|
||||
}
|
||||
pstrcpy(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
bs->backing->bs->filename);
|
||||
}
|
||||
|
||||
static void backup_top_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||
BdrvChildRole role,
|
||||
BlockReopenQueue *reopen_queue,
|
||||
uint64_t perm, uint64_t shared,
|
||||
uint64_t *nperm, uint64_t *nshared)
|
||||
{
|
||||
if (!(role & BDRV_CHILD_FILTERED)) {
|
||||
/*
|
||||
* Target child
|
||||
*
|
||||
* Share write to target (child_file), to not interfere
|
||||
* with guest writes to its disk which may be in target backing chain.
|
||||
* Can't resize during a backup block job because we check the size
|
||||
* only upfront.
|
||||
*/
|
||||
*nshared = BLK_PERM_ALL & ~BLK_PERM_RESIZE;
|
||||
*nperm = BLK_PERM_WRITE;
|
||||
} else {
|
||||
/* Source child */
|
||||
bdrv_default_perms(bs, c, role, reopen_queue,
|
||||
perm, shared, nperm, nshared);
|
||||
|
||||
if (perm & BLK_PERM_WRITE) {
|
||||
*nperm = *nperm | BLK_PERM_CONSISTENT_READ;
|
||||
}
|
||||
*nshared &= ~(BLK_PERM_WRITE | BLK_PERM_RESIZE);
|
||||
}
|
||||
}
|
||||
|
||||
BlockDriver bdrv_backup_top_filter = {
|
||||
.format_name = "backup-top",
|
||||
.instance_size = sizeof(BDRVBackupTopState),
|
||||
|
||||
.bdrv_co_preadv = backup_top_co_preadv,
|
||||
.bdrv_co_pwritev = backup_top_co_pwritev,
|
||||
.bdrv_co_pwrite_zeroes = backup_top_co_pwrite_zeroes,
|
||||
.bdrv_co_pdiscard = backup_top_co_pdiscard,
|
||||
.bdrv_co_flush = backup_top_co_flush,
|
||||
|
||||
.bdrv_refresh_filename = backup_top_refresh_filename,
|
||||
|
||||
.bdrv_child_perm = backup_top_child_perm,
|
||||
|
||||
.is_filter = true,
|
||||
};
|
||||
|
||||
BlockDriverState *bdrv_backup_top_append(BlockDriverState *source,
|
||||
BlockDriverState *target,
|
||||
const char *filter_node_name,
|
||||
uint64_t cluster_size,
|
||||
BackupPerf *perf,
|
||||
BdrvRequestFlags write_flags,
|
||||
BlockCopyState **bcs,
|
||||
Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
int ret;
|
||||
BDRVBackupTopState *state;
|
||||
BlockDriverState *top;
|
||||
bool appended = false;
|
||||
|
||||
assert(source->total_sectors == target->total_sectors);
|
||||
|
||||
top = bdrv_new_open_driver(&bdrv_backup_top_filter, filter_node_name,
|
||||
BDRV_O_RDWR, errp);
|
||||
if (!top) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state = top->opaque;
|
||||
top->total_sectors = source->total_sectors;
|
||||
top->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||
(BDRV_REQ_FUA & source->supported_write_flags);
|
||||
top->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||
((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK) &
|
||||
source->supported_zero_flags);
|
||||
|
||||
bdrv_ref(target);
|
||||
state->target = bdrv_attach_child(top, target, "target", &child_of_bds,
|
||||
BDRV_CHILD_DATA, errp);
|
||||
if (!state->target) {
|
||||
bdrv_unref(target);
|
||||
bdrv_unref(top);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bdrv_drained_begin(source);
|
||||
|
||||
ret = bdrv_append(top, source, errp);
|
||||
if (ret < 0) {
|
||||
error_prepend(errp, "Cannot append backup-top filter: ");
|
||||
goto fail;
|
||||
}
|
||||
appended = true;
|
||||
|
||||
state->cluster_size = cluster_size;
|
||||
state->bcs = block_copy_state_new(top->backing, state->target,
|
||||
cluster_size, perf->use_copy_range,
|
||||
write_flags, errp);
|
||||
if (!state->bcs) {
|
||||
error_prepend(errp, "Cannot create block-copy-state: ");
|
||||
goto fail;
|
||||
}
|
||||
*bcs = state->bcs;
|
||||
|
||||
bdrv_drained_end(source);
|
||||
|
||||
return top;
|
||||
|
||||
fail:
|
||||
if (appended) {
|
||||
bdrv_backup_top_drop(top);
|
||||
} else {
|
||||
bdrv_unref(top);
|
||||
}
|
||||
|
||||
bdrv_drained_end(source);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void bdrv_backup_top_drop(BlockDriverState *bs)
|
||||
{
|
||||
BDRVBackupTopState *s = bs->opaque;
|
||||
|
||||
bdrv_drop_filter(bs, &error_abort);
|
||||
|
||||
block_copy_state_free(s->bcs);
|
||||
|
||||
bdrv_unref(bs);
|
||||
}
|
||||
+26
-90
@@ -27,13 +27,11 @@
|
||||
#include "qemu/bitmap.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
#include "block/backup-top.h"
|
||||
|
||||
#define BACKUP_CLUSTER_SIZE_DEFAULT (1 << 16)
|
||||
#include "block/copy-before-write.h"
|
||||
|
||||
typedef struct BackupBlockJob {
|
||||
BlockJob common;
|
||||
BlockDriverState *backup_top;
|
||||
BlockDriverState *cbw;
|
||||
BlockDriverState *source_bs;
|
||||
BlockDriverState *target_bs;
|
||||
|
||||
@@ -104,7 +102,7 @@ static void backup_clean(Job *job)
|
||||
{
|
||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
|
||||
block_job_remove_all_bdrv(&s->common);
|
||||
bdrv_backup_top_drop(s->backup_top);
|
||||
bdrv_cbw_drop(s->cbw);
|
||||
}
|
||||
|
||||
void backup_do_checkpoint(BlockJob *job, Error **errp)
|
||||
@@ -235,18 +233,16 @@ static void backup_init_bcs_bitmap(BackupBlockJob *job)
|
||||
BdrvDirtyBitmap *bcs_bitmap = block_copy_dirty_bitmap(job->bcs);
|
||||
|
||||
if (job->sync_mode == MIRROR_SYNC_MODE_BITMAP) {
|
||||
bdrv_clear_dirty_bitmap(bcs_bitmap, NULL);
|
||||
ret = bdrv_dirty_bitmap_merge_internal(bcs_bitmap, job->sync_bitmap,
|
||||
NULL, true);
|
||||
assert(ret);
|
||||
} else {
|
||||
if (job->sync_mode == MIRROR_SYNC_MODE_TOP) {
|
||||
/*
|
||||
* We can't hog the coroutine to initialize this thoroughly.
|
||||
* Set a flag and resume work when we are able to yield safely.
|
||||
*/
|
||||
block_copy_set_skip_unallocated(job->bcs, true);
|
||||
}
|
||||
bdrv_set_dirty_bitmap(bcs_bitmap, 0, job->len);
|
||||
} else if (job->sync_mode == MIRROR_SYNC_MODE_TOP) {
|
||||
/*
|
||||
* We can't hog the coroutine to initialize this thoroughly.
|
||||
* Set a flag and resume work when we are able to yield safely.
|
||||
*/
|
||||
block_copy_set_skip_unallocated(job->bcs, true);
|
||||
}
|
||||
|
||||
estimate = bdrv_get_dirty_count(bcs_bitmap);
|
||||
@@ -354,43 +350,6 @@ static const BlockJobDriver backup_job_driver = {
|
||||
.set_speed = backup_set_speed,
|
||||
};
|
||||
|
||||
static int64_t backup_calculate_cluster_size(BlockDriverState *target,
|
||||
Error **errp)
|
||||
{
|
||||
int ret;
|
||||
BlockDriverInfo bdi;
|
||||
bool target_does_cow = bdrv_backing_chain_next(target);
|
||||
|
||||
/*
|
||||
* If there is no backing file on the target, we cannot rely on COW if our
|
||||
* backup cluster size is smaller than the target cluster size. Even for
|
||||
* targets with a backing file, try to avoid COW if possible.
|
||||
*/
|
||||
ret = bdrv_get_info(target, &bdi);
|
||||
if (ret == -ENOTSUP && !target_does_cow) {
|
||||
/* Cluster size is not defined */
|
||||
warn_report("The target block device doesn't provide "
|
||||
"information about the block size and it doesn't have a "
|
||||
"backing file. The default block size of %u bytes is "
|
||||
"used. If the actual block size of the target exceeds "
|
||||
"this default, the backup may be unusable",
|
||||
BACKUP_CLUSTER_SIZE_DEFAULT);
|
||||
return BACKUP_CLUSTER_SIZE_DEFAULT;
|
||||
} else if (ret < 0 && !target_does_cow) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"Couldn't determine the cluster size of the target image, "
|
||||
"which has no backing file");
|
||||
error_append_hint(errp,
|
||||
"Aborting, since this may create an unusable destination image\n");
|
||||
return ret;
|
||||
} else if (ret < 0 && target_does_cow) {
|
||||
/* Not fatal; just trudge on ahead. */
|
||||
return BACKUP_CLUSTER_SIZE_DEFAULT;
|
||||
}
|
||||
|
||||
return MAX(BACKUP_CLUSTER_SIZE_DEFAULT, bdi.cluster_size);
|
||||
}
|
||||
|
||||
BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
BlockDriverState *target, int64_t speed,
|
||||
MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap,
|
||||
@@ -407,8 +366,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
int64_t len, target_len;
|
||||
BackupBlockJob *job = NULL;
|
||||
int64_t cluster_size;
|
||||
BdrvRequestFlags write_flags;
|
||||
BlockDriverState *backup_top = NULL;
|
||||
BlockDriverState *cbw = NULL;
|
||||
BlockCopyState *bcs = NULL;
|
||||
|
||||
assert(bs);
|
||||
@@ -449,11 +407,6 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cluster_size = backup_calculate_cluster_size(target, errp);
|
||||
if (cluster_size < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (perf->max_workers < 1) {
|
||||
error_setg(errp, "max-workers must be greater than zero");
|
||||
return NULL;
|
||||
@@ -465,13 +418,6 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (perf->max_chunk && perf->max_chunk < cluster_size) {
|
||||
error_setg(errp, "Required max-chunk (%" PRIi64 ") is less than backup "
|
||||
"cluster size (%" PRIi64 ")", perf->max_chunk, cluster_size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
if (sync_bitmap) {
|
||||
/* If we need to write to this bitmap, check that we can: */
|
||||
if (bitmap_mode != BITMAP_SYNC_MODE_NEVER &&
|
||||
@@ -504,39 +450,28 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* If source is in backing chain of target assume that target is going to be
|
||||
* used for "image fleecing", i.e. it should represent a kind of snapshot of
|
||||
* source at backup-start point in time. And target is going to be read by
|
||||
* somebody (for example, used as NBD export) during backup job.
|
||||
*
|
||||
* In this case, we need to add BDRV_REQ_SERIALISING write flag to avoid
|
||||
* intersection of backup writes and third party reads from target,
|
||||
* otherwise reading from target we may occasionally read already updated by
|
||||
* guest data.
|
||||
*
|
||||
* For more information see commit f8d59dfb40bb and test
|
||||
* tests/qemu-iotests/222
|
||||
*/
|
||||
write_flags = (bdrv_chain_contains(target, bs) ? BDRV_REQ_SERIALISING : 0) |
|
||||
(compress ? BDRV_REQ_WRITE_COMPRESSED : 0),
|
||||
cbw = bdrv_cbw_append(bs, target, filter_node_name, &bcs, errp);
|
||||
if (!cbw) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
backup_top = bdrv_backup_top_append(bs, target, filter_node_name,
|
||||
cluster_size, perf,
|
||||
write_flags, &bcs, errp);
|
||||
if (!backup_top) {
|
||||
cluster_size = block_copy_cluster_size(bcs);
|
||||
|
||||
if (perf->max_chunk && perf->max_chunk < cluster_size) {
|
||||
error_setg(errp, "Required max-chunk (%" PRIi64 ") is less than backup "
|
||||
"cluster size (%" PRIi64 ")", perf->max_chunk, cluster_size);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* job->len is fixed, so we can't allow resize */
|
||||
job = block_job_create(job_id, &backup_job_driver, txn, backup_top,
|
||||
job = block_job_create(job_id, &backup_job_driver, txn, cbw,
|
||||
0, BLK_PERM_ALL,
|
||||
speed, creation_flags, cb, opaque, errp);
|
||||
if (!job) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
job->backup_top = backup_top;
|
||||
job->cbw = cbw;
|
||||
job->source_bs = bs;
|
||||
job->target_bs = target;
|
||||
job->on_source_error = on_source_error;
|
||||
@@ -549,10 +484,11 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
job->len = len;
|
||||
job->perf = *perf;
|
||||
|
||||
block_copy_set_copy_opts(bcs, perf->use_copy_range, compress);
|
||||
block_copy_set_progress_meter(bcs, &job->common.job.progress);
|
||||
block_copy_set_speed(bcs, speed);
|
||||
|
||||
/* Required permissions are already taken by backup-top target */
|
||||
/* Required permissions are taken by copy-before-write filter target */
|
||||
block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
|
||||
&error_abort);
|
||||
|
||||
@@ -562,8 +498,8 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
||||
if (sync_bitmap) {
|
||||
bdrv_reclaim_dirty_bitmap(sync_bitmap, NULL);
|
||||
}
|
||||
if (backup_top) {
|
||||
bdrv_backup_top_drop(backup_top);
|
||||
if (cbw) {
|
||||
bdrv_cbw_drop(cbw);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
@@ -869,6 +869,14 @@ int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Change BlockDriverState associated with @blk.
|
||||
*/
|
||||
int blk_replace_bs(BlockBackend *blk, BlockDriverState *new_bs, Error **errp)
|
||||
{
|
||||
return bdrv_replace_child_bs(blk->root, new_bs, errp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the permission bitmasks that the user of the BlockBackend needs.
|
||||
*/
|
||||
|
||||
+115
-35
@@ -21,12 +21,14 @@
|
||||
#include "qemu/units.h"
|
||||
#include "qemu/coroutine.h"
|
||||
#include "block/aio_task.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
#define BLOCK_COPY_MAX_COPY_RANGE (16 * MiB)
|
||||
#define BLOCK_COPY_MAX_BUFFER (1 * MiB)
|
||||
#define BLOCK_COPY_MAX_MEM (128 * MiB)
|
||||
#define BLOCK_COPY_MAX_WORKERS 64
|
||||
#define BLOCK_COPY_SLICE_TIME 100000000ULL /* ns */
|
||||
#define BLOCK_COPY_CLUSTER_SIZE_DEFAULT (1 << 16)
|
||||
|
||||
typedef enum {
|
||||
COPY_READ_WRITE_CLUSTER,
|
||||
@@ -290,9 +292,11 @@ static void coroutine_fn block_copy_task_end(BlockCopyTask *task, int ret)
|
||||
bdrv_set_dirty_bitmap(task->s->copy_bitmap, task->offset, task->bytes);
|
||||
}
|
||||
QLIST_REMOVE(task, list);
|
||||
progress_set_remaining(task->s->progress,
|
||||
bdrv_get_dirty_count(task->s->copy_bitmap) +
|
||||
task->s->in_flight_bytes);
|
||||
if (task->s->progress) {
|
||||
progress_set_remaining(task->s->progress,
|
||||
bdrv_get_dirty_count(task->s->copy_bitmap) +
|
||||
task->s->in_flight_bytes);
|
||||
}
|
||||
qemu_co_queue_restart_all(&task->wait_queue);
|
||||
}
|
||||
|
||||
@@ -315,35 +319,14 @@ static uint32_t block_copy_max_transfer(BdrvChild *source, BdrvChild *target)
|
||||
target->bs->bl.max_transfer));
|
||||
}
|
||||
|
||||
BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
int64_t cluster_size, bool use_copy_range,
|
||||
BdrvRequestFlags write_flags, Error **errp)
|
||||
void block_copy_set_copy_opts(BlockCopyState *s, bool use_copy_range,
|
||||
bool compress)
|
||||
{
|
||||
BlockCopyState *s;
|
||||
BdrvDirtyBitmap *copy_bitmap;
|
||||
/* Keep BDRV_REQ_SERIALISING set (or not set) in block_copy_state_new() */
|
||||
s->write_flags = (s->write_flags & BDRV_REQ_SERIALISING) |
|
||||
(compress ? BDRV_REQ_WRITE_COMPRESSED : 0);
|
||||
|
||||
copy_bitmap = bdrv_create_dirty_bitmap(source->bs, cluster_size, NULL,
|
||||
errp);
|
||||
if (!copy_bitmap) {
|
||||
return NULL;
|
||||
}
|
||||
bdrv_disable_dirty_bitmap(copy_bitmap);
|
||||
|
||||
s = g_new(BlockCopyState, 1);
|
||||
*s = (BlockCopyState) {
|
||||
.source = source,
|
||||
.target = target,
|
||||
.copy_bitmap = copy_bitmap,
|
||||
.cluster_size = cluster_size,
|
||||
.len = bdrv_dirty_bitmap_size(copy_bitmap),
|
||||
.write_flags = write_flags,
|
||||
.mem = shres_create(BLOCK_COPY_MAX_MEM),
|
||||
.max_transfer = QEMU_ALIGN_DOWN(
|
||||
block_copy_max_transfer(source, target),
|
||||
cluster_size),
|
||||
};
|
||||
|
||||
if (s->max_transfer < cluster_size) {
|
||||
if (s->max_transfer < s->cluster_size) {
|
||||
/*
|
||||
* copy_range does not respect max_transfer. We don't want to bother
|
||||
* with requests smaller than block-copy cluster size, so fallback to
|
||||
@@ -351,7 +334,7 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
* behalf).
|
||||
*/
|
||||
s->method = COPY_READ_WRITE_CLUSTER;
|
||||
} else if (write_flags & BDRV_REQ_WRITE_COMPRESSED) {
|
||||
} else if (compress) {
|
||||
/* Compression supports only cluster-size writes and no copy-range. */
|
||||
s->method = COPY_READ_WRITE_CLUSTER;
|
||||
} else {
|
||||
@@ -361,6 +344,96 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
*/
|
||||
s->method = use_copy_range ? COPY_RANGE_SMALL : COPY_READ_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t block_copy_calculate_cluster_size(BlockDriverState *target,
|
||||
Error **errp)
|
||||
{
|
||||
int ret;
|
||||
BlockDriverInfo bdi;
|
||||
bool target_does_cow = bdrv_backing_chain_next(target);
|
||||
|
||||
/*
|
||||
* If there is no backing file on the target, we cannot rely on COW if our
|
||||
* backup cluster size is smaller than the target cluster size. Even for
|
||||
* targets with a backing file, try to avoid COW if possible.
|
||||
*/
|
||||
ret = bdrv_get_info(target, &bdi);
|
||||
if (ret == -ENOTSUP && !target_does_cow) {
|
||||
/* Cluster size is not defined */
|
||||
warn_report("The target block device doesn't provide "
|
||||
"information about the block size and it doesn't have a "
|
||||
"backing file. The default block size of %u bytes is "
|
||||
"used. If the actual block size of the target exceeds "
|
||||
"this default, the backup may be unusable",
|
||||
BLOCK_COPY_CLUSTER_SIZE_DEFAULT);
|
||||
return BLOCK_COPY_CLUSTER_SIZE_DEFAULT;
|
||||
} else if (ret < 0 && !target_does_cow) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"Couldn't determine the cluster size of the target image, "
|
||||
"which has no backing file");
|
||||
error_append_hint(errp,
|
||||
"Aborting, since this may create an unusable destination image\n");
|
||||
return ret;
|
||||
} else if (ret < 0 && target_does_cow) {
|
||||
/* Not fatal; just trudge on ahead. */
|
||||
return BLOCK_COPY_CLUSTER_SIZE_DEFAULT;
|
||||
}
|
||||
|
||||
return MAX(BLOCK_COPY_CLUSTER_SIZE_DEFAULT, bdi.cluster_size);
|
||||
}
|
||||
|
||||
BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
Error **errp)
|
||||
{
|
||||
BlockCopyState *s;
|
||||
int64_t cluster_size;
|
||||
BdrvDirtyBitmap *copy_bitmap;
|
||||
bool is_fleecing;
|
||||
|
||||
cluster_size = block_copy_calculate_cluster_size(target->bs, errp);
|
||||
if (cluster_size < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
copy_bitmap = bdrv_create_dirty_bitmap(source->bs, cluster_size, NULL,
|
||||
errp);
|
||||
if (!copy_bitmap) {
|
||||
return NULL;
|
||||
}
|
||||
bdrv_disable_dirty_bitmap(copy_bitmap);
|
||||
|
||||
/*
|
||||
* If source is in backing chain of target assume that target is going to be
|
||||
* used for "image fleecing", i.e. it should represent a kind of snapshot of
|
||||
* source at backup-start point in time. And target is going to be read by
|
||||
* somebody (for example, used as NBD export) during backup job.
|
||||
*
|
||||
* In this case, we need to add BDRV_REQ_SERIALISING write flag to avoid
|
||||
* intersection of backup writes and third party reads from target,
|
||||
* otherwise reading from target we may occasionally read already updated by
|
||||
* guest data.
|
||||
*
|
||||
* For more information see commit f8d59dfb40bb and test
|
||||
* tests/qemu-iotests/222
|
||||
*/
|
||||
is_fleecing = bdrv_chain_contains(target->bs, source->bs);
|
||||
|
||||
s = g_new(BlockCopyState, 1);
|
||||
*s = (BlockCopyState) {
|
||||
.source = source,
|
||||
.target = target,
|
||||
.copy_bitmap = copy_bitmap,
|
||||
.cluster_size = cluster_size,
|
||||
.len = bdrv_dirty_bitmap_size(copy_bitmap),
|
||||
.write_flags = (is_fleecing ? BDRV_REQ_SERIALISING : 0),
|
||||
.mem = shres_create(BLOCK_COPY_MAX_MEM),
|
||||
.max_transfer = QEMU_ALIGN_DOWN(
|
||||
block_copy_max_transfer(source, target),
|
||||
cluster_size),
|
||||
};
|
||||
|
||||
block_copy_set_copy_opts(s, false, false);
|
||||
|
||||
ratelimit_init(&s->rate_limit);
|
||||
qemu_co_mutex_init(&s->lock);
|
||||
@@ -522,7 +595,7 @@ static coroutine_fn int block_copy_task_entry(AioTask *task)
|
||||
t->call_state->ret = ret;
|
||||
t->call_state->error_is_read = error_is_read;
|
||||
}
|
||||
} else {
|
||||
} else if (s->progress) {
|
||||
progress_work_done(s->progress, t->bytes);
|
||||
}
|
||||
}
|
||||
@@ -628,9 +701,11 @@ int64_t block_copy_reset_unallocated(BlockCopyState *s,
|
||||
if (!ret) {
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
bdrv_reset_dirty_bitmap(s->copy_bitmap, offset, bytes);
|
||||
progress_set_remaining(s->progress,
|
||||
bdrv_get_dirty_count(s->copy_bitmap) +
|
||||
s->in_flight_bytes);
|
||||
if (s->progress) {
|
||||
progress_set_remaining(s->progress,
|
||||
bdrv_get_dirty_count(s->copy_bitmap) +
|
||||
s->in_flight_bytes);
|
||||
}
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
}
|
||||
|
||||
@@ -933,6 +1008,11 @@ BdrvDirtyBitmap *block_copy_dirty_bitmap(BlockCopyState *s)
|
||||
return s->copy_bitmap;
|
||||
}
|
||||
|
||||
int64_t block_copy_cluster_size(BlockCopyState *s)
|
||||
{
|
||||
return s->cluster_size;
|
||||
}
|
||||
|
||||
void block_copy_set_skip_unallocated(BlockCopyState *s, bool skip)
|
||||
{
|
||||
qatomic_set(&s->skip_unallocated, skip);
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* copy-before-write filter driver
|
||||
*
|
||||
* The driver performs Copy-Before-Write (CBW) operation: it is injected above
|
||||
* some node, and before each write it copies _old_ data to the target node.
|
||||
*
|
||||
* Copyright (c) 2018-2021 Virtuozzo International GmbH.
|
||||
*
|
||||
* Author:
|
||||
* Sementsov-Ogievskiy Vladimir <vsementsov@virtuozzo.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qapi/error.h"
|
||||
#include "block/block_int.h"
|
||||
#include "block/qdict.h"
|
||||
#include "block/block-copy.h"
|
||||
|
||||
#include "block/copy-before-write.h"
|
||||
|
||||
typedef struct BDRVCopyBeforeWriteState {
|
||||
BlockCopyState *bcs;
|
||||
BdrvChild *target;
|
||||
} BDRVCopyBeforeWriteState;
|
||||
|
||||
static coroutine_fn int cbw_co_preadv(
|
||||
BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
||||
QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
|
||||
}
|
||||
|
||||
static coroutine_fn int cbw_do_copy_before_write(BlockDriverState *bs,
|
||||
uint64_t offset, uint64_t bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
uint64_t off, end;
|
||||
int64_t cluster_size = block_copy_cluster_size(s->bcs);
|
||||
|
||||
if (flags & BDRV_REQ_WRITE_UNCHANGED) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
off = QEMU_ALIGN_DOWN(offset, cluster_size);
|
||||
end = QEMU_ALIGN_UP(offset + bytes, cluster_size);
|
||||
|
||||
return block_copy(s->bcs, off, end - off, true);
|
||||
}
|
||||
|
||||
static int coroutine_fn cbw_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int bytes)
|
||||
{
|
||||
int ret = cbw_do_copy_before_write(bs, offset, bytes, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_co_pdiscard(bs->file, offset, bytes);
|
||||
}
|
||||
|
||||
static int coroutine_fn cbw_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
int ret = cbw_do_copy_before_write(bs, offset, bytes, flags);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_co_pwrite_zeroes(bs->file, offset, bytes, flags);
|
||||
}
|
||||
|
||||
static coroutine_fn int cbw_co_pwritev(BlockDriverState *bs,
|
||||
uint64_t offset,
|
||||
uint64_t bytes,
|
||||
QEMUIOVector *qiov, int flags)
|
||||
{
|
||||
int ret = cbw_do_copy_before_write(bs, offset, bytes, flags);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
|
||||
}
|
||||
|
||||
static int coroutine_fn cbw_co_flush(BlockDriverState *bs)
|
||||
{
|
||||
if (!bs->file) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bdrv_co_flush(bs->file->bs);
|
||||
}
|
||||
|
||||
static void cbw_refresh_filename(BlockDriverState *bs)
|
||||
{
|
||||
pstrcpy(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
bs->file->bs->filename);
|
||||
}
|
||||
|
||||
static void cbw_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||
BdrvChildRole role,
|
||||
BlockReopenQueue *reopen_queue,
|
||||
uint64_t perm, uint64_t shared,
|
||||
uint64_t *nperm, uint64_t *nshared)
|
||||
{
|
||||
if (!(role & BDRV_CHILD_FILTERED)) {
|
||||
/*
|
||||
* Target child
|
||||
*
|
||||
* Share write to target (child_file), to not interfere
|
||||
* with guest writes to its disk which may be in target backing chain.
|
||||
* Can't resize during a backup block job because we check the size
|
||||
* only upfront.
|
||||
*/
|
||||
*nshared = BLK_PERM_ALL & ~BLK_PERM_RESIZE;
|
||||
*nperm = BLK_PERM_WRITE;
|
||||
} else {
|
||||
/* Source child */
|
||||
bdrv_default_perms(bs, c, role, reopen_queue,
|
||||
perm, shared, nperm, nshared);
|
||||
|
||||
if (!QLIST_EMPTY(&bs->parents)) {
|
||||
if (perm & BLK_PERM_WRITE) {
|
||||
*nperm = *nperm | BLK_PERM_CONSISTENT_READ;
|
||||
}
|
||||
*nshared &= ~(BLK_PERM_WRITE | BLK_PERM_RESIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int cbw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
BdrvDirtyBitmap *copy_bitmap;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
|
||||
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
s->target = bdrv_open_child(NULL, options, "target", bs, &child_of_bds,
|
||||
BDRV_CHILD_DATA, false, errp);
|
||||
if (!s->target) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bs->total_sectors = bs->file->bs->total_sectors;
|
||||
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||
(BDRV_REQ_FUA & bs->file->bs->supported_write_flags);
|
||||
bs->supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||
((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK) &
|
||||
bs->file->bs->supported_zero_flags);
|
||||
|
||||
s->bcs = block_copy_state_new(bs->file, s->target, errp);
|
||||
if (!s->bcs) {
|
||||
error_prepend(errp, "Cannot create block-copy-state: ");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
copy_bitmap = block_copy_dirty_bitmap(s->bcs);
|
||||
bdrv_set_dirty_bitmap(copy_bitmap, 0, bdrv_dirty_bitmap_size(copy_bitmap));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cbw_close(BlockDriverState *bs)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
|
||||
block_copy_state_free(s->bcs);
|
||||
s->bcs = NULL;
|
||||
}
|
||||
|
||||
BlockDriver bdrv_cbw_filter = {
|
||||
.format_name = "copy-before-write",
|
||||
.instance_size = sizeof(BDRVCopyBeforeWriteState),
|
||||
|
||||
.bdrv_open = cbw_open,
|
||||
.bdrv_close = cbw_close,
|
||||
|
||||
.bdrv_co_preadv = cbw_co_preadv,
|
||||
.bdrv_co_pwritev = cbw_co_pwritev,
|
||||
.bdrv_co_pwrite_zeroes = cbw_co_pwrite_zeroes,
|
||||
.bdrv_co_pdiscard = cbw_co_pdiscard,
|
||||
.bdrv_co_flush = cbw_co_flush,
|
||||
|
||||
.bdrv_refresh_filename = cbw_refresh_filename,
|
||||
|
||||
.bdrv_child_perm = cbw_child_perm,
|
||||
|
||||
.is_filter = true,
|
||||
};
|
||||
|
||||
BlockDriverState *bdrv_cbw_append(BlockDriverState *source,
|
||||
BlockDriverState *target,
|
||||
const char *filter_node_name,
|
||||
BlockCopyState **bcs,
|
||||
Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
BDRVCopyBeforeWriteState *state;
|
||||
BlockDriverState *top;
|
||||
QDict *opts;
|
||||
|
||||
assert(source->total_sectors == target->total_sectors);
|
||||
|
||||
opts = qdict_new();
|
||||
qdict_put_str(opts, "driver", "copy-before-write");
|
||||
if (filter_node_name) {
|
||||
qdict_put_str(opts, "node-name", filter_node_name);
|
||||
}
|
||||
qdict_put_str(opts, "file", bdrv_get_node_name(source));
|
||||
qdict_put_str(opts, "target", bdrv_get_node_name(target));
|
||||
|
||||
top = bdrv_insert_node(source, opts, BDRV_O_RDWR, errp);
|
||||
if (!top) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state = top->opaque;
|
||||
*bcs = state->bcs;
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
void bdrv_cbw_drop(BlockDriverState *bs)
|
||||
{
|
||||
bdrv_drop_filter(bs, &error_abort);
|
||||
bdrv_unref(bs);
|
||||
}
|
||||
|
||||
static void cbw_init(void)
|
||||
{
|
||||
bdrv_register(&bdrv_cbw_filter);
|
||||
}
|
||||
|
||||
block_init(cbw_init);
|
||||
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
* backup-top filter driver
|
||||
* copy-before-write filter driver
|
||||
*
|
||||
* The driver performs Copy-Before-Write (CBW) operation: it is injected above
|
||||
* some node, and before each write it copies _old_ data to the target node.
|
||||
*
|
||||
* Copyright (c) 2018-2019 Virtuozzo International GmbH.
|
||||
* Copyright (c) 2018-2021 Virtuozzo International GmbH.
|
||||
*
|
||||
* Author:
|
||||
* Sementsov-Ogievskiy Vladimir <vsementsov@virtuozzo.com>
|
||||
@@ -23,20 +23,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BACKUP_TOP_H
|
||||
#define BACKUP_TOP_H
|
||||
#ifndef COPY_BEFORE_WRITE_H
|
||||
#define COPY_BEFORE_WRITE_H
|
||||
|
||||
#include "block/block_int.h"
|
||||
#include "block/block-copy.h"
|
||||
|
||||
BlockDriverState *bdrv_backup_top_append(BlockDriverState *source,
|
||||
BlockDriverState *target,
|
||||
const char *filter_node_name,
|
||||
uint64_t cluster_size,
|
||||
BackupPerf *perf,
|
||||
BdrvRequestFlags write_flags,
|
||||
BlockCopyState **bcs,
|
||||
Error **errp);
|
||||
void bdrv_backup_top_drop(BlockDriverState *bs);
|
||||
BlockDriverState *bdrv_cbw_append(BlockDriverState *source,
|
||||
BlockDriverState *target,
|
||||
const char *filter_node_name,
|
||||
BlockCopyState **bcs,
|
||||
Error **errp);
|
||||
void bdrv_cbw_drop(BlockDriverState *bs);
|
||||
|
||||
#endif /* BACKUP_TOP_H */
|
||||
#endif /* COPY_BEFORE_WRITE_H */
|
||||
@@ -31,6 +31,9 @@
|
||||
#include <fuse.h>
|
||||
#include <fuse_lowlevel.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/fs.h>
|
||||
#endif
|
||||
|
||||
/* Prevent overly long bounce buffer allocations */
|
||||
#define FUSE_MAX_BOUNCE_BYTES (MIN(BDRV_REQUEST_MAX_BYTES, 64 * 1024 * 1024))
|
||||
|
||||
+100
-1
@@ -58,6 +58,10 @@ typedef struct BDRVRawState {
|
||||
QEMUWin32AIOState *aio;
|
||||
} BDRVRawState;
|
||||
|
||||
typedef struct BDRVRawReopenState {
|
||||
HANDLE hfile;
|
||||
} BDRVRawReopenState;
|
||||
|
||||
/*
|
||||
* Read/writes the data to/from a given linear buffer.
|
||||
*
|
||||
@@ -392,7 +396,7 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
}
|
||||
|
||||
s->hfile = CreateFile(filename, access_flags,
|
||||
FILE_SHARE_READ, NULL,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
|
||||
OPEN_EXISTING, overlapped, NULL);
|
||||
if (s->hfile == INVALID_HANDLE_VALUE) {
|
||||
int err = GetLastError();
|
||||
@@ -634,6 +638,97 @@ static int coroutine_fn raw_co_create_opts(BlockDriver *drv,
|
||||
return raw_co_create(&options, errp);
|
||||
}
|
||||
|
||||
static int raw_reopen_prepare(BDRVReopenState *state,
|
||||
BlockReopenQueue *queue, Error **errp)
|
||||
{
|
||||
BDRVRawState *s = state->bs->opaque;
|
||||
BDRVRawReopenState *rs;
|
||||
int access_flags;
|
||||
DWORD overlapped;
|
||||
int ret = 0;
|
||||
|
||||
if (s->type != FTYPE_FILE) {
|
||||
error_setg(errp, "Can only reopen files");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rs = g_new0(BDRVRawReopenState, 1);
|
||||
|
||||
/*
|
||||
* We do not support changing any options (only flags). By leaving
|
||||
* all options in state->options, we tell the generic reopen code
|
||||
* that we do not support changing any of them, so it will verify
|
||||
* that their values did not change.
|
||||
*/
|
||||
|
||||
raw_parse_flags(state->flags, s->aio != NULL, &access_flags, &overlapped);
|
||||
rs->hfile = CreateFile(state->bs->filename, access_flags,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
|
||||
OPEN_EXISTING, overlapped, NULL);
|
||||
|
||||
if (rs->hfile == INVALID_HANDLE_VALUE) {
|
||||
int err = GetLastError();
|
||||
|
||||
error_setg_win32(errp, err, "Could not reopen '%s'",
|
||||
state->bs->filename);
|
||||
if (err == ERROR_ACCESS_DENIED) {
|
||||
ret = -EACCES;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (s->aio) {
|
||||
ret = win32_aio_attach(s->aio, rs->hfile);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "Could not enable AIO");
|
||||
CloseHandle(rs->hfile);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
state->opaque = rs;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
g_free(rs);
|
||||
state->opaque = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void raw_reopen_commit(BDRVReopenState *state)
|
||||
{
|
||||
BDRVRawState *s = state->bs->opaque;
|
||||
BDRVRawReopenState *rs = state->opaque;
|
||||
|
||||
assert(rs != NULL);
|
||||
|
||||
CloseHandle(s->hfile);
|
||||
s->hfile = rs->hfile;
|
||||
|
||||
g_free(rs);
|
||||
state->opaque = NULL;
|
||||
}
|
||||
|
||||
static void raw_reopen_abort(BDRVReopenState *state)
|
||||
{
|
||||
BDRVRawReopenState *rs = state->opaque;
|
||||
|
||||
if (!rs) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rs->hfile != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(rs->hfile);
|
||||
}
|
||||
|
||||
g_free(rs);
|
||||
state->opaque = NULL;
|
||||
}
|
||||
|
||||
static QemuOptsList raw_create_opts = {
|
||||
.name = "raw-create-opts",
|
||||
.head = QTAILQ_HEAD_INITIALIZER(raw_create_opts.head),
|
||||
@@ -659,6 +754,10 @@ BlockDriver bdrv_file = {
|
||||
.bdrv_co_create_opts = raw_co_create_opts,
|
||||
.bdrv_has_zero_init = bdrv_has_zero_init_1,
|
||||
|
||||
.bdrv_reopen_prepare = raw_reopen_prepare,
|
||||
.bdrv_reopen_commit = raw_reopen_commit,
|
||||
.bdrv_reopen_abort = raw_reopen_abort,
|
||||
|
||||
.bdrv_aio_preadv = raw_aio_preadv,
|
||||
.bdrv_aio_pwritev = raw_aio_pwritev,
|
||||
.bdrv_aio_flush = raw_aio_flush,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ block_ss.add(files(
|
||||
'aio_task.c',
|
||||
'amend.c',
|
||||
'backup.c',
|
||||
'backup-top.c',
|
||||
'copy-before-write.c',
|
||||
'blkdebug.c',
|
||||
'blklogwrites.c',
|
||||
'blkverify.c',
|
||||
|
||||
@@ -251,10 +251,10 @@ void hmp_drive_mirror(Monitor *mon, const QDict *qdict)
|
||||
|
||||
if (!filename) {
|
||||
error_setg(&err, QERR_MISSING_PARAMETER, "target");
|
||||
hmp_handle_error(mon, err);
|
||||
return;
|
||||
goto end;
|
||||
}
|
||||
qmp_drive_mirror(&mirror, &err);
|
||||
end:
|
||||
hmp_handle_error(mon, err);
|
||||
}
|
||||
|
||||
@@ -281,11 +281,11 @@ void hmp_drive_backup(Monitor *mon, const QDict *qdict)
|
||||
|
||||
if (!filename) {
|
||||
error_setg(&err, QERR_MISSING_PARAMETER, "target");
|
||||
hmp_handle_error(mon, err);
|
||||
return;
|
||||
goto end;
|
||||
}
|
||||
|
||||
qmp_drive_backup(&backup, &err);
|
||||
end:
|
||||
hmp_handle_error(mon, err);
|
||||
}
|
||||
|
||||
@@ -356,8 +356,7 @@ void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict)
|
||||
* will be taken internally. Today it's actually required.
|
||||
*/
|
||||
error_setg(&err, QERR_MISSING_PARAMETER, "snapshot-file");
|
||||
hmp_handle_error(mon, err);
|
||||
return;
|
||||
goto end;
|
||||
}
|
||||
|
||||
mode = reuse ? NEW_IMAGE_MODE_EXISTING : NEW_IMAGE_MODE_ABSOLUTE_PATHS;
|
||||
@@ -365,6 +364,7 @@ void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict)
|
||||
filename, false, NULL,
|
||||
!!format, format,
|
||||
true, mode, &err);
|
||||
end:
|
||||
hmp_handle_error(mon, err);
|
||||
}
|
||||
|
||||
|
||||
+20
-1
@@ -580,6 +580,25 @@ static void raw_cancel_in_flight(BlockDriverState *bs)
|
||||
bdrv_cancel_in_flight(bs->file->bs);
|
||||
}
|
||||
|
||||
static void raw_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||
BdrvChildRole role,
|
||||
BlockReopenQueue *reopen_queue,
|
||||
uint64_t parent_perm, uint64_t parent_shared,
|
||||
uint64_t *nperm, uint64_t *nshared)
|
||||
{
|
||||
bdrv_default_perms(bs, c, role, reopen_queue, parent_perm,
|
||||
parent_shared, nperm, nshared);
|
||||
|
||||
/*
|
||||
* bdrv_default_perms() may add WRITE and/or RESIZE (see comment in
|
||||
* bdrv_default_perms_for_storage() for an explanation) but we only need
|
||||
* them if they are in parent_perm. Drop WRITE and RESIZE whenever possible
|
||||
* to avoid permission conflicts.
|
||||
*/
|
||||
*nperm &= ~(BLK_PERM_WRITE | BLK_PERM_RESIZE);
|
||||
*nperm |= parent_perm & (BLK_PERM_WRITE | BLK_PERM_RESIZE);
|
||||
}
|
||||
|
||||
BlockDriver bdrv_raw = {
|
||||
.format_name = "raw",
|
||||
.instance_size = sizeof(BDRVRawState),
|
||||
@@ -588,7 +607,7 @@ BlockDriver bdrv_raw = {
|
||||
.bdrv_reopen_commit = &raw_reopen_commit,
|
||||
.bdrv_reopen_abort = &raw_reopen_abort,
|
||||
.bdrv_open = &raw_open,
|
||||
.bdrv_child_perm = bdrv_default_perms,
|
||||
.bdrv_child_perm = raw_child_perm,
|
||||
.bdrv_co_create_opts = &raw_co_create_opts,
|
||||
.bdrv_co_preadv = &raw_co_preadv,
|
||||
.bdrv_co_pwritev = &raw_co_pwritev,
|
||||
|
||||
@@ -224,6 +224,35 @@ another application on the host may have locked the file, possibly leading to a
|
||||
test failure. If using such devices are explicitly desired, consider adding
|
||||
``locking=off`` option to disable image locking.
|
||||
|
||||
Debugging a test case
|
||||
-----------------------
|
||||
The following options to the ``check`` script can be useful when debugging
|
||||
a failing test:
|
||||
|
||||
* ``-gdb`` wraps every QEMU invocation in a ``gdbserver``, which waits for a
|
||||
connection from a gdb client. The options given to ``gdbserver`` (e.g. the
|
||||
address on which to listen for connections) are taken from the ``$GDB_OPTIONS``
|
||||
environment variable. By default (if ``$GDB_OPTIONS`` is empty), it listens on
|
||||
``localhost:12345``.
|
||||
It is possible to connect to it for example with
|
||||
``gdb -iex "target remote $addr"``, where ``$addr`` is the address
|
||||
``gdbserver`` listens on.
|
||||
If the ``-gdb`` option is not used, ``$GDB_OPTIONS`` is ignored,
|
||||
regardless of whether it is set or not.
|
||||
|
||||
* ``-valgrind`` attaches a valgrind instance to QEMU. If it detects
|
||||
warnings, it will print and save the log in
|
||||
``$TEST_DIR/<valgrind_pid>.valgrind``.
|
||||
The final command line will be ``valgrind --log-file=$TEST_DIR/
|
||||
<valgrind_pid>.valgrind --error-exitcode=99 $QEMU ...``
|
||||
|
||||
* ``-d`` (debug) just increases the logging verbosity, showing
|
||||
for example the QMP commands and answers.
|
||||
|
||||
* ``-p`` (print) redirects QEMU’s stdout and stderr to the test output,
|
||||
instead of saving it into a log file in
|
||||
``$TEST_DIR/qemu-machine-<random_string>``.
|
||||
|
||||
Test case groups
|
||||
----------------
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
|
||||
static bool check_prop_still_unset(Object *obj, const char *name,
|
||||
const void *old_val, const char *new_val,
|
||||
Error **errp)
|
||||
bool allow_override, Error **errp)
|
||||
{
|
||||
const GlobalProperty *prop = qdev_find_global_prop(obj, name);
|
||||
|
||||
if (!old_val) {
|
||||
if (!old_val || (!prop && allow_override)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -93,16 +93,34 @@ static void set_drive_helper(Object *obj, Visitor *v, const char *name,
|
||||
BlockBackend *blk;
|
||||
bool blk_created = false;
|
||||
int ret;
|
||||
BlockDriverState *bs;
|
||||
AioContext *ctx;
|
||||
|
||||
if (!visit_type_str(v, name, &str, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Should this really be an error? If no, the old value
|
||||
* needs to be released before we store the new one.
|
||||
*/
|
||||
if (!check_prop_still_unset(obj, name, *ptr, str, errp)) {
|
||||
if (!check_prop_still_unset(obj, name, *ptr, str, true, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (*ptr) {
|
||||
/* BlockBackend alread exists. So, we want to change attached node */
|
||||
blk = *ptr;
|
||||
ctx = blk_get_aio_context(blk);
|
||||
bs = bdrv_lookup_bs(NULL, str, errp);
|
||||
if (!bs) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx != bdrv_get_aio_context(bs)) {
|
||||
error_setg(errp, "Different aio context is not supported for new "
|
||||
"node");
|
||||
}
|
||||
|
||||
aio_context_acquire(ctx);
|
||||
blk_replace_bs(blk, bs, errp);
|
||||
aio_context_release(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,7 +132,7 @@ static void set_drive_helper(Object *obj, Visitor *v, const char *name,
|
||||
|
||||
blk = blk_by_name(str);
|
||||
if (!blk) {
|
||||
BlockDriverState *bs = bdrv_lookup_bs(NULL, str, NULL);
|
||||
bs = bdrv_lookup_bs(NULL, str, NULL);
|
||||
if (bs) {
|
||||
/*
|
||||
* If the device supports iothreads, it will make sure to move the
|
||||
@@ -123,8 +141,7 @@ static void set_drive_helper(Object *obj, Visitor *v, const char *name,
|
||||
* aware of iothreads require their BlockBackends to be in the main
|
||||
* AioContext.
|
||||
*/
|
||||
AioContext *ctx = iothread ? bdrv_get_aio_context(bs) :
|
||||
qemu_get_aio_context();
|
||||
ctx = iothread ? bdrv_get_aio_context(bs) : qemu_get_aio_context();
|
||||
blk = blk_new(ctx, 0, BLK_PERM_ALL);
|
||||
blk_created = true;
|
||||
|
||||
@@ -196,6 +213,7 @@ static void release_drive(Object *obj, const char *name, void *opaque)
|
||||
const PropertyInfo qdev_prop_drive = {
|
||||
.name = "str",
|
||||
.description = "Node name or ID of a block device to use as a backend",
|
||||
.realized_set_allowed = true,
|
||||
.get = get_drive,
|
||||
.set = set_drive,
|
||||
.release = release_drive,
|
||||
@@ -204,6 +222,7 @@ const PropertyInfo qdev_prop_drive = {
|
||||
const PropertyInfo qdev_prop_drive_iothread = {
|
||||
.name = "str",
|
||||
.description = "Node name or ID of a block device to use as a backend",
|
||||
.realized_set_allowed = true,
|
||||
.get = get_drive,
|
||||
.set = set_drive_iothread,
|
||||
.release = release_drive,
|
||||
@@ -238,7 +257,7 @@ static void set_chr(Object *obj, Visitor *v, const char *name, void *opaque,
|
||||
* TODO Should this really be an error? If no, the old value
|
||||
* needs to be released before we store the new one.
|
||||
*/
|
||||
if (!check_prop_still_unset(obj, name, be->chr, str, errp)) {
|
||||
if (!check_prop_still_unset(obj, name, be->chr, str, false, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -408,7 +427,7 @@ static void set_netdev(Object *obj, Visitor *v, const char *name,
|
||||
* TODO Should this really be an error? If no, the old value
|
||||
* needs to be released before we store the new one.
|
||||
*/
|
||||
if (!check_prop_still_unset(obj, name, ncs[i], str, errp)) {
|
||||
if (!check_prop_still_unset(obj, name, ncs[i], str, false, errp)) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ void qdev_prop_set_after_realize(DeviceState *dev, const char *name,
|
||||
|
||||
/* returns: true if property is allowed to be set, false otherwise */
|
||||
static bool qdev_prop_allow_set(Object *obj, const char *name,
|
||||
Error **errp)
|
||||
const PropertyInfo *info, Error **errp)
|
||||
{
|
||||
DeviceState *dev = DEVICE(obj);
|
||||
|
||||
if (dev->realized) {
|
||||
if (dev->realized && !info->realized_set_allowed) {
|
||||
qdev_prop_set_after_realize(dev, name, errp);
|
||||
return false;
|
||||
}
|
||||
@@ -79,7 +79,7 @@ static void field_prop_set(Object *obj, Visitor *v, const char *name,
|
||||
{
|
||||
Property *prop = opaque;
|
||||
|
||||
if (!qdev_prop_allow_set(obj, name, errp)) {
|
||||
if (!qdev_prop_allow_set(obj, name, prop->info, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,11 @@ typedef struct BlockCopyState BlockCopyState;
|
||||
typedef struct BlockCopyCallState BlockCopyCallState;
|
||||
|
||||
BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
int64_t cluster_size, bool use_copy_range,
|
||||
BdrvRequestFlags write_flags,
|
||||
Error **errp);
|
||||
|
||||
/* Function should be called prior any actual copy request */
|
||||
void block_copy_set_copy_opts(BlockCopyState *s, bool use_copy_range,
|
||||
bool compress);
|
||||
void block_copy_set_progress_meter(BlockCopyState *s, ProgressMeter *pm);
|
||||
|
||||
void block_copy_state_free(BlockCopyState *s);
|
||||
@@ -89,6 +90,7 @@ void block_copy_kick(BlockCopyCallState *call_state);
|
||||
void block_copy_call_cancel(BlockCopyCallState *call_state);
|
||||
|
||||
BdrvDirtyBitmap *block_copy_dirty_bitmap(BlockCopyState *s);
|
||||
int64_t block_copy_cluster_size(BlockCopyState *s);
|
||||
void block_copy_set_skip_unallocated(BlockCopyState *s, bool skip);
|
||||
|
||||
#endif /* BLOCK_COPY_H */
|
||||
|
||||
@@ -361,6 +361,8 @@ int bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top,
|
||||
Error **errp);
|
||||
int bdrv_replace_node(BlockDriverState *from, BlockDriverState *to,
|
||||
Error **errp);
|
||||
int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs,
|
||||
Error **errp);
|
||||
BlockDriverState *bdrv_insert_node(BlockDriverState *bs, QDict *node_options,
|
||||
int flags, Error **errp);
|
||||
int bdrv_drop_filter(BlockDriverState *bs, Error **errp);
|
||||
|
||||
@@ -32,6 +32,7 @@ struct PropertyInfo {
|
||||
const char *name;
|
||||
const char *description;
|
||||
const QEnumLookup *enum_table;
|
||||
bool realized_set_allowed; /* allow setting property on realized device */
|
||||
int (*print)(Object *obj, Property *prop, char *dest, size_t len);
|
||||
void (*set_default_value)(ObjectProperty *op, const Property *prop);
|
||||
ObjectProperty *(*create)(ObjectClass *oc, const char *name,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user