All paths that lead to bdrv_backup_top_drop(), except for the call from backup_clean(), imply that the BDS AioContext has already been acquired, so doing it there too can potentially lead to QEMU hanging on AIO_WAIT_WHILE(). An easy way to trigger this situation is by issuing a two actions transaction, with a proper and a bogus blockdev-backup, so the second one will trigger a rollback. This will trigger a hang with an stack trace like this one: #0 0x00007fb680c75016 in __GI_ppoll (fds=0x55e74580f7c0, nfds=1, timeout=<optimized out>, timeout@entry=0x0, sigmask=sigmask@entry=0x0) at ../sysdeps/unix/sysv/linux/ppoll.c:39 #1 0x000055e743386e09 in ppoll (__ss=0x0, __timeout=0x0, __nfds=<optimized out>, __fds=<optimized out>) at /usr/include/bits/poll2.h:77 #2 0x000055e743386e09 in qemu_poll_ns (fds=<optimized out>, nfds=<optimized out>, timeout=<optimized out>) at util/qemu-timer.c:336 #3 0x000055e743388dc4 in aio_poll (ctx=0x55e7458925d0, blocking=blocking@entry=true) at util/aio-posix.c:669 #4 0x000055e743305dea in bdrv_flush (bs=bs@entry=0x55e74593c0d0) at block/io.c:2878 #5 0x000055e7432be58e in bdrv_close (bs=0x55e74593c0d0) at block.c:4017 #6 0x000055e7432be58e in bdrv_delete (bs=<optimized out>) at block.c:4262 #7 0x000055e7432be58e in bdrv_unref (bs=bs@entry=0x55e74593c0d0) at block.c:5644 #8 0x000055e743316b9b in bdrv_backup_top_drop (bs=bs@entry=0x55e74593c0d0) at block/backup-top.c:273 #9 0x000055e74331461f in backup_job_create (job_id=0x0, bs=bs@entry=0x55e7458d5820, target=target@entry=0x55e74589f640, speed=0, sync_mode=MIRROR_SYNC_MODE_FULL, sync_bitmap=sync_bitmap@entry=0x0, bitmap_mode=BITMAP_SYNC_MODE_ON_SUCCESS, compress=false, filter_node_name=0x0, on_source_error=BLOCKDEV_ON_ERROR_REPORT, on_target_error=BLOCKDEV_ON_ERROR_REPORT, creation_flags=0, cb=0x0, opaque=0x0, txn=0x0, errp=0x7ffddfd1efb0) at block/backup.c:478 #10 0x000055e74315bc52 in do_backup_common (backup=backup@entry=0x55e746c066d0, bs=bs@entry=0x55e7458d5820, target_bs=target_bs@entry=0x55e74589f640, aio_context=aio_context@entry=0x55e7458a91e0, txn=txn@entry=0x0, errp=errp@entry=0x7ffddfd1efb0) at blockdev.c:3580 #11 0x000055e74315c37c in do_blockdev_backup (backup=backup@entry=0x55e746c066d0, txn=0x0, errp=errp@entry=0x7ffddfd1efb0) at /usr/src/debug/qemu-kvm-4.2.0-2.module+el8.2.0+5135+ed3b2489.x86_64/./qapi/qapi-types-block-core.h:1492 #12 0x000055e74315c449 in blockdev_backup_prepare (common=0x55e746a8de90, errp=0x7ffddfd1f018) at blockdev.c:1885 #13 0x000055e743160152 in qmp_transaction (dev_list=<optimized out>, has_props=<optimized out>, props=0x55e7467fe2c0, errp=errp@entry=0x7ffddfd1f088) at blockdev.c:2340 #14 0x000055e743287ff5 in qmp_marshal_transaction (args=<optimized out>, ret=<optimized out>, errp=0x7ffddfd1f0f8) at qapi/qapi-commands-transaction.c:44 #15 0x000055e74333de6c in do_qmp_dispatch (errp=0x7ffddfd1f0f0, allow_oob=<optimized out>, request=<optimized out>, cmds=0x55e743c28d60 <qmp_commands>) at qapi/qmp-dispatch.c:132 #16 0x000055e74333de6c in qmp_dispatch (cmds=0x55e743c28d60 <qmp_commands>, request=<optimized out>, allow_oob=<optimized out>) at qapi/qmp-dispatch.c:175 #17 0x000055e74325c061 in monitor_qmp_dispatch (mon=0x55e745908030, req=<optimized out>) at monitor/qmp.c:145 #18 0x000055e74325c6fa in monitor_qmp_bh_dispatcher (data=<optimized out>) at monitor/qmp.c:234 #19 0x000055e743385866 in aio_bh_call (bh=0x55e745807ae0) at util/async.c:117 #20 0x000055e743385866 in aio_bh_poll (ctx=ctx@entry=0x55e7458067a0) at util/async.c:117 #21 0x000055e743388c54 in aio_dispatch (ctx=0x55e7458067a0) at util/aio-posix.c:459 #22 0x000055e743385742 in aio_ctx_dispatch (source=<optimized out>, callback=<optimized out>, user_data=<optimized out>) at util/async.c:260 #23 0x00007fb68543e67d in g_main_dispatch (context=0x55e745893a40) at gmain.c:3176 #24 0x00007fb68543e67d in g_main_context_dispatch (context=context@entry=0x55e745893a40) at gmain.c:3829 #25 0x000055e743387d08 in glib_pollfds_poll () at util/main-loop.c:219 #26 0x000055e743387d08 in os_host_main_loop_wait (timeout=<optimized out>) at util/main-loop.c:242 #27 0x000055e743387d08 in main_loop_wait (nonblocking=<optimized out>) at util/main-loop.c:518 #28 0x000055e74316a3c1 in main_loop () at vl.c:1828 #29 0x000055e743016a72 in main (argc=<optimized out>, argv=<optimized out>, envp=<optimized out>) at vl.c:4504 Fix this by not acquiring the AioContext there, and ensuring all paths leading to it have it already acquired (backup_clean()). RHBZ: https://bugzilla.redhat.com/show_bug.cgi?id=1782111 Signed-off-by: Sergio Lopez <slp@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
272 lines
8.1 KiB
C
272 lines
8.1 KiB
C
/*
|
|
* 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;
|
|
bool active;
|
|
} 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)
|
|
{
|
|
BDRVBackupTopState *s = bs->opaque;
|
|
uint64_t end = QEMU_ALIGN_UP(offset + bytes, s->bcs->cluster_size);
|
|
uint64_t off = QEMU_ALIGN_DOWN(offset, s->bcs->cluster_size);
|
|
|
|
return block_copy(s->bcs, off, end - off, NULL);
|
|
}
|
|
|
|
static int coroutine_fn backup_top_co_pdiscard(BlockDriverState *bs,
|
|
int64_t offset, int bytes)
|
|
{
|
|
int ret = backup_top_cbw(bs, offset, bytes);
|
|
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);
|
|
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)
|
|
{
|
|
if (!(flags & BDRV_REQ_WRITE_UNCHANGED)) {
|
|
int ret = backup_top_cbw(bs, offset, bytes);
|
|
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,
|
|
const BdrvChildRole *role,
|
|
BlockReopenQueue *reopen_queue,
|
|
uint64_t perm, uint64_t shared,
|
|
uint64_t *nperm, uint64_t *nshared)
|
|
{
|
|
BDRVBackupTopState *s = bs->opaque;
|
|
|
|
if (!s->active) {
|
|
/*
|
|
* The filter node may be in process of bdrv_append(), which firstly do
|
|
* bdrv_set_backing_hd() and then bdrv_replace_node(). This means that
|
|
* we can't unshare BLK_PERM_WRITE during bdrv_append() operation. So,
|
|
* let's require nothing during bdrv_append() and refresh permissions
|
|
* after it (see bdrv_backup_top_append()).
|
|
*/
|
|
*nperm = 0;
|
|
*nshared = BLK_PERM_ALL;
|
|
return;
|
|
}
|
|
|
|
if (role == &child_file) {
|
|
/*
|
|
* Target child
|
|
*
|
|
* Share write to target (child_file), to not interfere
|
|
* with guest writes to its disk which may be in target backing chain.
|
|
*/
|
|
*nshared = BLK_PERM_ALL;
|
|
*nperm = BLK_PERM_WRITE;
|
|
} else {
|
|
/* Source child */
|
|
bdrv_filter_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;
|
|
}
|
|
}
|
|
|
|
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_co_block_status = bdrv_co_block_status_from_backing,
|
|
|
|
.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,
|
|
BdrvRequestFlags write_flags,
|
|
BlockCopyState **bcs,
|
|
Error **errp)
|
|
{
|
|
Error *local_err = NULL;
|
|
BDRVBackupTopState *state;
|
|
BlockDriverState *top = bdrv_new_open_driver(&bdrv_backup_top_filter,
|
|
filter_node_name,
|
|
BDRV_O_RDWR, errp);
|
|
|
|
if (!top) {
|
|
return NULL;
|
|
}
|
|
|
|
top->total_sectors = source->total_sectors;
|
|
top->opaque = state = g_new0(BDRVBackupTopState, 1);
|
|
|
|
bdrv_ref(target);
|
|
state->target = bdrv_attach_child(top, target, "target", &child_file, errp);
|
|
if (!state->target) {
|
|
bdrv_unref(target);
|
|
bdrv_unref(top);
|
|
return NULL;
|
|
}
|
|
|
|
bdrv_drained_begin(source);
|
|
|
|
bdrv_ref(top);
|
|
bdrv_append(top, source, &local_err);
|
|
if (local_err) {
|
|
error_prepend(&local_err, "Cannot append backup-top filter: ");
|
|
goto append_failed;
|
|
}
|
|
|
|
/*
|
|
* bdrv_append() finished successfully, now we can require permissions
|
|
* we want.
|
|
*/
|
|
state->active = true;
|
|
bdrv_child_refresh_perms(top, top->backing, &local_err);
|
|
if (local_err) {
|
|
error_prepend(&local_err,
|
|
"Cannot set permissions for backup-top filter: ");
|
|
goto failed_after_append;
|
|
}
|
|
|
|
state->bcs = block_copy_state_new(top->backing, state->target,
|
|
cluster_size, write_flags, &local_err);
|
|
if (local_err) {
|
|
error_prepend(&local_err, "Cannot create block-copy-state: ");
|
|
goto failed_after_append;
|
|
}
|
|
*bcs = state->bcs;
|
|
|
|
bdrv_drained_end(source);
|
|
|
|
return top;
|
|
|
|
failed_after_append:
|
|
state->active = false;
|
|
bdrv_backup_top_drop(top);
|
|
|
|
append_failed:
|
|
bdrv_drained_end(source);
|
|
bdrv_unref_child(top, state->target);
|
|
bdrv_unref(top);
|
|
error_propagate(errp, local_err);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void bdrv_backup_top_drop(BlockDriverState *bs)
|
|
{
|
|
BDRVBackupTopState *s = bs->opaque;
|
|
|
|
bdrv_drained_begin(bs);
|
|
|
|
block_copy_state_free(s->bcs);
|
|
|
|
s->active = false;
|
|
bdrv_child_refresh_perms(bs, bs->backing, &error_abort);
|
|
bdrv_replace_node(bs, backing_bs(bs), &error_abort);
|
|
bdrv_set_backing_hd(bs, NULL, &error_abort);
|
|
|
|
bdrv_drained_end(bs);
|
|
|
|
bdrv_unref(bs);
|
|
}
|