Block layer patches

- qcow2 spec: Rename "zlib" compression to "deflate"
 - Honour graph read lock even in the main thread + prerequisite fixes
 - aio-posix: do not nest poll handlers (fixes infinite recursion)
 - Refactor QMP blockdev transactions
 - graph-lock: Disable locking for now
 - iotests/245: Check if 'compress' driver is available
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAmRnrxURHGt3b2xmQHJl
 ZGhhdC5jb20ACgkQfwmycsiPL9aHyw/9H0xpceVb0kcC5CStOWCcq4PJHzkl/8/m
 c6ABFe0fgEuN2FCiKiCKOt6+V7qaIAw0+YLgPr/LGIsbIBzdxF3Xgd2UyIH6o4dK
 bSaIAaes6ZLTcYGIYEVJtHuwNgvzhjyBlW5qqwTpN0YArKS411eHyQ3wlUkCEVwK
 ZNmDY/MC8jq8r1xfwpPi7CaH6k1I6HhDmyl1PdURW9hmoAKZQZMhEdA5reJrUwZ9
 EhfgbLIaK0kkLLsufJ9YIkd+b/P3mUbH30kekNMOiA0XlnhWm1Djol5pxlnNiflg
 CGh6CAyhJKdXzwV567cSF11NYCsFmiY+c/l0xRIGscujwvO4iD7wFT5xk2geUAKV
 yaox8JA7Le36g7lO2CRadlS24/Ekqnle6q09g2i8s2tZwB4fS286vaZz6QDPmf7W
 VSQp9vuDj6ZcVjMsuo2+LzF3yA2Vqvgd9s032iBAjRDSGLAoOdQZjBJrreypJ0Oi
 pVFwgK+9QNCZBsqVhwVOgElSoK/3Vbl1kqpi30Ikgc0epAn0suM1g2QQPJ2Zt/MJ
 xqMlTv+48OW3vq3ebr8GXqkhvG/u0ku6I1G6ZyCrjOce89osK8QUaovERyi1eOmo
 ouoZ8UJJa6VfEkkmdhq2vF6u/MP4PeZ8MW3pYQy6qEnSOPDKpLnR30Z/s/HZCZcm
 H4QIbfQnzic=
 =edNP
 -----END PGP SIGNATURE-----

Merge tag 'for-upstream' of https://repo.or.cz/qemu/kevin into staging

Block layer patches

- qcow2 spec: Rename "zlib" compression to "deflate"
- Honour graph read lock even in the main thread + prerequisite fixes
- aio-posix: do not nest poll handlers (fixes infinite recursion)
- Refactor QMP blockdev transactions
- graph-lock: Disable locking for now
- iotests/245: Check if 'compress' driver is available

# -----BEGIN PGP SIGNATURE-----
#
# iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAmRnrxURHGt3b2xmQHJl
# ZGhhdC5jb20ACgkQfwmycsiPL9aHyw/9H0xpceVb0kcC5CStOWCcq4PJHzkl/8/m
# c6ABFe0fgEuN2FCiKiCKOt6+V7qaIAw0+YLgPr/LGIsbIBzdxF3Xgd2UyIH6o4dK
# bSaIAaes6ZLTcYGIYEVJtHuwNgvzhjyBlW5qqwTpN0YArKS411eHyQ3wlUkCEVwK
# ZNmDY/MC8jq8r1xfwpPi7CaH6k1I6HhDmyl1PdURW9hmoAKZQZMhEdA5reJrUwZ9
# EhfgbLIaK0kkLLsufJ9YIkd+b/P3mUbH30kekNMOiA0XlnhWm1Djol5pxlnNiflg
# CGh6CAyhJKdXzwV567cSF11NYCsFmiY+c/l0xRIGscujwvO4iD7wFT5xk2geUAKV
# yaox8JA7Le36g7lO2CRadlS24/Ekqnle6q09g2i8s2tZwB4fS286vaZz6QDPmf7W
# VSQp9vuDj6ZcVjMsuo2+LzF3yA2Vqvgd9s032iBAjRDSGLAoOdQZjBJrreypJ0Oi
# pVFwgK+9QNCZBsqVhwVOgElSoK/3Vbl1kqpi30Ikgc0epAn0suM1g2QQPJ2Zt/MJ
# xqMlTv+48OW3vq3ebr8GXqkhvG/u0ku6I1G6ZyCrjOce89osK8QUaovERyi1eOmo
# ouoZ8UJJa6VfEkkmdhq2vF6u/MP4PeZ8MW3pYQy6qEnSOPDKpLnR30Z/s/HZCZcm
# H4QIbfQnzic=
# =edNP
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 19 May 2023 10:17:09 AM PDT
# gpg:                using RSA key DC3DEB159A9AF95D3D7456FE7F09B272C88F2FD6
# gpg:                issuer "kwolf@redhat.com"
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" [full]

* tag 'for-upstream' of https://repo.or.cz/qemu/kevin: (21 commits)
  iotests: Test commit with iothreads and ongoing I/O
  nbd/server: Fix drained_poll to wake coroutine in right AioContext
  graph-lock: Disable locking for now
  tested: add test for nested aio_poll() in poll handlers
  aio-posix: do not nest poll handlers
  iotests/245: Check if 'compress' driver is available
  graph-lock: Honour read locks even in the main thread
  blockjob: Adhere to rate limit even when reentered early
  test-bdrv-drain: Call bdrv_co_unref() in coroutine context
  test-bdrv-drain: Take graph lock more selectively
  qemu-img: Take graph lock more selectively
  qcow2: Unlock the graph in qcow2_do_open() where necessary
  block/export: Fix null pointer dereference in error path
  block: Call .bdrv_co_create(_opts) unlocked
  docs/interop/qcow2.txt: fix description about "zlib" clusters
  blockdev: qmp_transaction: drop extra generic layer
  blockdev: use state.bitmap in block-dirty-bitmap-add action
  blockdev: transaction: refactor handling transaction properties
  blockdev: qmp_transaction: refactor loop to classic for
  blockdev: transactions: rename some things
  ...

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2023-05-22 07:04:50 -07:00
commit ad3387396a
36 changed files with 664 additions and 502 deletions

View file

@ -533,7 +533,6 @@ int coroutine_fn bdrv_co_create(BlockDriver *drv, const char *filename,
int ret; int ret;
GLOBAL_STATE_CODE(); GLOBAL_STATE_CODE();
ERRP_GUARD(); ERRP_GUARD();
assert_bdrv_graph_readable();
if (!drv->bdrv_co_create_opts) { if (!drv->bdrv_co_create_opts) {
error_setg(errp, "Driver '%s' does not support image creation", error_setg(errp, "Driver '%s' does not support image creation",

View file

@ -116,7 +116,6 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
{ {
CommitBlockJob *s = container_of(job, CommitBlockJob, common.job); CommitBlockJob *s = container_of(job, CommitBlockJob, common.job);
int64_t offset; int64_t offset;
uint64_t delay_ns = 0;
int ret = 0; int ret = 0;
int64_t n = 0; /* bytes */ int64_t n = 0; /* bytes */
QEMU_AUTO_VFREE void *buf = NULL; QEMU_AUTO_VFREE void *buf = NULL;
@ -149,7 +148,7 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
/* Note that even when no rate limit is applied we need to yield /* Note that even when no rate limit is applied we need to yield
* with no pending I/O here so that bdrv_drain_all() returns. * with no pending I/O here so that bdrv_drain_all() returns.
*/ */
job_sleep_ns(&s->common.job, delay_ns); block_job_ratelimit_sleep(&s->common);
if (job_is_cancelled(&s->common.job)) { if (job_is_cancelled(&s->common.job)) {
break; break;
} }
@ -184,9 +183,7 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
job_progress_update(&s->common.job, n); job_progress_update(&s->common.job, n);
if (copy) { if (copy) {
delay_ns = block_job_ratelimit_get_delay(&s->common, n); block_job_ratelimit_processed_bytes(&s->common, n);
} else {
delay_ns = 0;
} }
} }

View file

@ -43,7 +43,6 @@ static int coroutine_fn blockdev_create_run(Job *job, Error **errp)
int ret; int ret;
GLOBAL_STATE_CODE(); GLOBAL_STATE_CODE();
GRAPH_RDLOCK_GUARD();
job_progress_set_remaining(&s->common, 1); job_progress_set_remaining(&s->common, 1);
ret = s->drv->bdrv_co_create(s->opts, errp); ret = s->drv->bdrv_co_create(s->opts, errp);

View file

@ -99,12 +99,10 @@ struct BlockCryptoCreateData {
}; };
static int block_crypto_create_write_func(QCryptoBlock *block, static int coroutine_fn GRAPH_UNLOCKED
size_t offset, block_crypto_create_write_func(QCryptoBlock *block, size_t offset,
const uint8_t *buf, const uint8_t *buf, size_t buflen, void *opaque,
size_t buflen, Error **errp)
void *opaque,
Error **errp)
{ {
struct BlockCryptoCreateData *data = opaque; struct BlockCryptoCreateData *data = opaque;
ssize_t ret; ssize_t ret;
@ -117,10 +115,9 @@ static int block_crypto_create_write_func(QCryptoBlock *block,
return 0; return 0;
} }
static int block_crypto_create_init_func(QCryptoBlock *block, static int coroutine_fn GRAPH_UNLOCKED
size_t headerlen, block_crypto_create_init_func(QCryptoBlock *block, size_t headerlen,
void *opaque, void *opaque, Error **errp)
Error **errp)
{ {
struct BlockCryptoCreateData *data = opaque; struct BlockCryptoCreateData *data = opaque;
Error *local_error = NULL; Error *local_error = NULL;
@ -314,7 +311,7 @@ static int block_crypto_open_generic(QCryptoBlockFormat format,
} }
static int coroutine_fn static int coroutine_fn GRAPH_UNLOCKED
block_crypto_co_create_generic(BlockDriverState *bs, int64_t size, block_crypto_co_create_generic(BlockDriverState *bs, int64_t size,
QCryptoBlockCreateOptions *opts, QCryptoBlockCreateOptions *opts,
PreallocMode prealloc, Error **errp) PreallocMode prealloc, Error **errp)
@ -627,7 +624,7 @@ static int block_crypto_open_luks(BlockDriverState *bs,
bs, options, flags, errp); bs, options, flags, errp);
} }
static int coroutine_fn static int coroutine_fn GRAPH_UNLOCKED
block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp) block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp)
{ {
BlockdevCreateOptionsLUKS *luks_opts; BlockdevCreateOptionsLUKS *luks_opts;
@ -665,7 +662,7 @@ fail:
return ret; return ret;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
block_crypto_co_create_opts_luks(BlockDriver *drv, const char *filename, block_crypto_co_create_opts_luks(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {
@ -727,7 +724,9 @@ fail:
* beforehand, it has been truncated and corrupted in the process. * beforehand, it has been truncated and corrupted in the process.
*/ */
if (ret) { if (ret) {
bdrv_graph_co_rdlock();
bdrv_co_delete_file_noerr(bs); bdrv_co_delete_file_noerr(bs);
bdrv_graph_co_rdunlock();
} }
bdrv_co_unref(bs); bdrv_co_unref(bs);

View file

@ -192,8 +192,10 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
return exp; return exp;
fail: fail:
blk_set_dev_ops(exp->blk, NULL, NULL); if (blk) {
blk_unref(blk); blk_set_dev_ops(blk, NULL, NULL);
blk_unref(blk);
}
aio_context_release(ctx); aio_context_release(ctx);
if (exp) { if (exp) {
g_free(exp->id); g_free(exp->id);

View file

@ -30,8 +30,10 @@ BdrvGraphLock graph_lock;
/* Protects the list of aiocontext and orphaned_reader_count */ /* Protects the list of aiocontext and orphaned_reader_count */
static QemuMutex aio_context_list_lock; static QemuMutex aio_context_list_lock;
#if 0
/* Written and read with atomic operations. */ /* Written and read with atomic operations. */
static int has_writer; static int has_writer;
#endif
/* /*
* A reader coroutine could move from an AioContext to another. * A reader coroutine could move from an AioContext to another.
@ -88,6 +90,7 @@ void unregister_aiocontext(AioContext *ctx)
g_free(ctx->bdrv_graph); g_free(ctx->bdrv_graph);
} }
#if 0
static uint32_t reader_count(void) static uint32_t reader_count(void)
{ {
BdrvGraphRWlock *brdv_graph; BdrvGraphRWlock *brdv_graph;
@ -105,10 +108,17 @@ static uint32_t reader_count(void)
assert((int32_t)rd >= 0); assert((int32_t)rd >= 0);
return rd; return rd;
} }
#endif
void bdrv_graph_wrlock(void) void bdrv_graph_wrlock(void)
{ {
GLOBAL_STATE_CODE(); GLOBAL_STATE_CODE();
/*
* TODO Some callers hold an AioContext lock when this is called, which
* causes deadlocks. Reenable once the AioContext locking is cleaned up (or
* AioContext locks are gone).
*/
#if 0
assert(!qatomic_read(&has_writer)); assert(!qatomic_read(&has_writer));
/* Make sure that constantly arriving new I/O doesn't cause starvation */ /* Make sure that constantly arriving new I/O doesn't cause starvation */
@ -139,11 +149,13 @@ void bdrv_graph_wrlock(void)
} while (reader_count() >= 1); } while (reader_count() >= 1);
bdrv_drain_all_end(); bdrv_drain_all_end();
#endif
} }
void bdrv_graph_wrunlock(void) void bdrv_graph_wrunlock(void)
{ {
GLOBAL_STATE_CODE(); GLOBAL_STATE_CODE();
#if 0
QEMU_LOCK_GUARD(&aio_context_list_lock); QEMU_LOCK_GUARD(&aio_context_list_lock);
assert(qatomic_read(&has_writer)); assert(qatomic_read(&has_writer));
@ -155,18 +167,16 @@ void bdrv_graph_wrunlock(void)
/* Wake up all coroutine that are waiting to read the graph */ /* Wake up all coroutine that are waiting to read the graph */
qemu_co_enter_all(&reader_queue, &aio_context_list_lock); qemu_co_enter_all(&reader_queue, &aio_context_list_lock);
#endif
} }
void coroutine_fn bdrv_graph_co_rdlock(void) void coroutine_fn bdrv_graph_co_rdlock(void)
{ {
/* TODO Reenable when wrlock is reenabled */
#if 0
BdrvGraphRWlock *bdrv_graph; BdrvGraphRWlock *bdrv_graph;
bdrv_graph = qemu_get_current_aio_context()->bdrv_graph; bdrv_graph = qemu_get_current_aio_context()->bdrv_graph;
/* Do not lock if in main thread */
if (qemu_in_main_thread()) {
return;
}
for (;;) { for (;;) {
qatomic_set(&bdrv_graph->reader_count, qatomic_set(&bdrv_graph->reader_count,
bdrv_graph->reader_count + 1); bdrv_graph->reader_count + 1);
@ -223,18 +233,15 @@ void coroutine_fn bdrv_graph_co_rdlock(void)
qemu_co_queue_wait(&reader_queue, &aio_context_list_lock); qemu_co_queue_wait(&reader_queue, &aio_context_list_lock);
} }
} }
#endif
} }
void coroutine_fn bdrv_graph_co_rdunlock(void) void coroutine_fn bdrv_graph_co_rdunlock(void)
{ {
#if 0
BdrvGraphRWlock *bdrv_graph; BdrvGraphRWlock *bdrv_graph;
bdrv_graph = qemu_get_current_aio_context()->bdrv_graph; bdrv_graph = qemu_get_current_aio_context()->bdrv_graph;
/* Do not lock if in main thread */
if (qemu_in_main_thread()) {
return;
}
qatomic_store_release(&bdrv_graph->reader_count, qatomic_store_release(&bdrv_graph->reader_count,
bdrv_graph->reader_count - 1); bdrv_graph->reader_count - 1);
/* make sure writer sees reader_count before we check has_writer */ /* make sure writer sees reader_count before we check has_writer */
@ -249,6 +256,7 @@ void coroutine_fn bdrv_graph_co_rdunlock(void)
if (qatomic_read(&has_writer)) { if (qatomic_read(&has_writer)) {
aio_wait_kick(); aio_wait_kick();
} }
#endif
} }
void bdrv_graph_rdlock_main_loop(void) void bdrv_graph_rdlock_main_loop(void)
@ -266,13 +274,19 @@ void bdrv_graph_rdunlock_main_loop(void)
void assert_bdrv_graph_readable(void) void assert_bdrv_graph_readable(void)
{ {
/* reader_count() is slow due to aio_context_list_lock lock contention */ /* reader_count() is slow due to aio_context_list_lock lock contention */
/* TODO Reenable when wrlock is reenabled */
#if 0
#ifdef CONFIG_DEBUG_GRAPH_LOCK #ifdef CONFIG_DEBUG_GRAPH_LOCK
assert(qemu_in_main_thread() || reader_count()); assert(qemu_in_main_thread() || reader_count());
#endif #endif
#endif
} }
void assert_bdrv_graph_writable(void) void assert_bdrv_graph_writable(void)
{ {
assert(qemu_in_main_thread()); assert(qemu_in_main_thread());
/* TODO Reenable when wrlock is reenabled */
#if 0
assert(qatomic_read(&has_writer)); assert(qatomic_read(&has_writer));
#endif
} }

View file

@ -471,12 +471,11 @@ static unsigned mirror_perform(MirrorBlockJob *s, int64_t offset,
return bytes_handled; return bytes_handled;
} }
static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s) static void coroutine_fn mirror_iteration(MirrorBlockJob *s)
{ {
BlockDriverState *source = s->mirror_top_bs->backing->bs; BlockDriverState *source = s->mirror_top_bs->backing->bs;
MirrorOp *pseudo_op; MirrorOp *pseudo_op;
int64_t offset; int64_t offset;
uint64_t delay_ns = 0, ret = 0;
/* At least the first dirty chunk is mirrored in one iteration. */ /* At least the first dirty chunk is mirrored in one iteration. */
int nb_chunks = 1; int nb_chunks = 1;
bool write_zeroes_ok = bdrv_can_write_zeroes_with_unmap(blk_bs(s->target)); bool write_zeroes_ok = bdrv_can_write_zeroes_with_unmap(blk_bs(s->target));
@ -608,16 +607,13 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
assert(io_bytes); assert(io_bytes);
offset += io_bytes; offset += io_bytes;
nb_chunks -= DIV_ROUND_UP(io_bytes, s->granularity); nb_chunks -= DIV_ROUND_UP(io_bytes, s->granularity);
delay_ns = block_job_ratelimit_get_delay(&s->common, io_bytes_acct); block_job_ratelimit_processed_bytes(&s->common, io_bytes_acct);
} }
ret = delay_ns;
fail: fail:
QTAILQ_REMOVE(&s->ops_in_flight, pseudo_op, next); QTAILQ_REMOVE(&s->ops_in_flight, pseudo_op, next);
qemu_co_queue_restart_all(&pseudo_op->waiting_requests); qemu_co_queue_restart_all(&pseudo_op->waiting_requests);
g_free(pseudo_op); g_free(pseudo_op);
return ret;
} }
static void mirror_free_init(MirrorBlockJob *s) static void mirror_free_init(MirrorBlockJob *s)
@ -1011,7 +1007,6 @@ static int coroutine_fn mirror_run(Job *job, Error **errp)
assert(!s->dbi); assert(!s->dbi);
s->dbi = bdrv_dirty_iter_new(s->dirty_bitmap); s->dbi = bdrv_dirty_iter_new(s->dirty_bitmap);
for (;;) { for (;;) {
uint64_t delay_ns = 0;
int64_t cnt, delta; int64_t cnt, delta;
bool should_complete; bool should_complete;
@ -1051,7 +1046,7 @@ static int coroutine_fn mirror_run(Job *job, Error **errp)
mirror_wait_for_free_in_flight_slot(s); mirror_wait_for_free_in_flight_slot(s);
continue; continue;
} else if (cnt != 0) { } else if (cnt != 0) {
delay_ns = mirror_iteration(s); mirror_iteration(s);
} }
} }
@ -1114,12 +1109,14 @@ static int coroutine_fn mirror_run(Job *job, Error **errp)
} }
if (job_is_ready(&s->common.job) && !should_complete) { if (job_is_ready(&s->common.job) && !should_complete) {
delay_ns = (s->in_flight == 0 && if (s->in_flight == 0 && cnt == 0) {
cnt == 0 ? BLOCK_JOB_SLICE_TIME : 0); trace_mirror_before_sleep(s, cnt, job_is_ready(&s->common.job),
BLOCK_JOB_SLICE_TIME);
job_sleep_ns(&s->common.job, BLOCK_JOB_SLICE_TIME);
}
} else {
block_job_ratelimit_sleep(&s->common);
} }
trace_mirror_before_sleep(s, cnt, job_is_ready(&s->common.job),
delay_ns);
job_sleep_ns(&s->common.job, delay_ns);
s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
} }

View file

@ -522,8 +522,8 @@ out:
} }
static int coroutine_fn parallels_co_create(BlockdevCreateOptions* opts, static int coroutine_fn GRAPH_UNLOCKED
Error **errp) parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
{ {
BlockdevCreateOptionsParallels *parallels_opts; BlockdevCreateOptionsParallels *parallels_opts;
BlockDriverState *bs; BlockDriverState *bs;
@ -622,7 +622,7 @@ exit:
goto out; goto out;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
parallels_co_create_opts(BlockDriver *drv, const char *filename, parallels_co_create_opts(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {

View file

@ -800,8 +800,8 @@ static void qcow_close(BlockDriverState *bs)
error_free(s->migration_blocker); error_free(s->migration_blocker);
} }
static int coroutine_fn qcow_co_create(BlockdevCreateOptions *opts, static int coroutine_fn GRAPH_UNLOCKED
Error **errp) qcow_co_create(BlockdevCreateOptions *opts, Error **errp)
{ {
BlockdevCreateOptionsQcow *qcow_opts; BlockdevCreateOptionsQcow *qcow_opts;
int header_size, backing_filename_len, l1_size, shift, i; int header_size, backing_filename_len, l1_size, shift, i;
@ -921,7 +921,7 @@ exit:
return ret; return ret;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
qcow_co_create_opts(BlockDriver *drv, const char *filename, qcow_co_create_opts(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {

View file

@ -118,8 +118,9 @@ static int qcow2_crypto_hdr_read_func(QCryptoBlock *block, size_t offset,
} }
static int qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen, static int coroutine_fn GRAPH_RDLOCK
void *opaque, Error **errp) qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen, void *opaque,
Error **errp)
{ {
BlockDriverState *bs = opaque; BlockDriverState *bs = opaque;
BDRVQcow2State *s = bs->opaque; BDRVQcow2State *s = bs->opaque;
@ -144,9 +145,7 @@ static int qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen,
*/ */
clusterlen = size_to_clusters(s, headerlen) * s->cluster_size; clusterlen = size_to_clusters(s, headerlen) * s->cluster_size;
assert(qcow2_pre_write_overlap_check(bs, 0, ret, clusterlen, false) == 0); assert(qcow2_pre_write_overlap_check(bs, 0, ret, clusterlen, false) == 0);
ret = bdrv_pwrite_zeroes(bs->file, ret = bdrv_co_pwrite_zeroes(bs->file, ret, clusterlen, 0);
ret,
clusterlen, 0);
if (ret < 0) { if (ret < 0) {
error_setg_errno(errp, -ret, "Could not zero fill encryption header"); error_setg_errno(errp, -ret, "Could not zero fill encryption header");
return -1; return -1;
@ -156,9 +155,11 @@ static int qcow2_crypto_hdr_init_func(QCryptoBlock *block, size_t headerlen,
} }
static int qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset, /* The graph lock must be held when called in coroutine context */
const uint8_t *buf, size_t buflen, static int coroutine_mixed_fn
void *opaque, Error **errp) qcow2_crypto_hdr_write_func(QCryptoBlock *block, size_t offset,
const uint8_t *buf, size_t buflen,
void *opaque, Error **errp)
{ {
BlockDriverState *bs = opaque; BlockDriverState *bs = opaque;
BDRVQcow2State *s = bs->opaque; BDRVQcow2State *s = bs->opaque;
@ -1618,9 +1619,11 @@ qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
if (open_data_file) { if (open_data_file) {
/* Open external data file */ /* Open external data file */
bdrv_graph_co_rdunlock();
s->data_file = bdrv_co_open_child(NULL, options, "data-file", bs, s->data_file = bdrv_co_open_child(NULL, options, "data-file", bs,
&child_of_bds, BDRV_CHILD_DATA, &child_of_bds, BDRV_CHILD_DATA,
true, errp); true, errp);
bdrv_graph_co_rdlock();
if (*errp) { if (*errp) {
ret = -EINVAL; ret = -EINVAL;
goto fail; goto fail;
@ -1628,10 +1631,12 @@ qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
if (s->incompatible_features & QCOW2_INCOMPAT_DATA_FILE) { if (s->incompatible_features & QCOW2_INCOMPAT_DATA_FILE) {
if (!s->data_file && s->image_data_file) { if (!s->data_file && s->image_data_file) {
bdrv_graph_co_rdunlock();
s->data_file = bdrv_co_open_child(s->image_data_file, options, s->data_file = bdrv_co_open_child(s->image_data_file, options,
"data-file", bs, "data-file", bs,
&child_of_bds, &child_of_bds,
BDRV_CHILD_DATA, false, errp); BDRV_CHILD_DATA, false, errp);
bdrv_graph_co_rdlock();
if (!s->data_file) { if (!s->data_file) {
ret = -EINVAL; ret = -EINVAL;
goto fail; goto fail;
@ -1856,7 +1861,9 @@ qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
fail: fail:
g_free(s->image_data_file); g_free(s->image_data_file);
if (open_data_file && has_data_file(bs)) { if (open_data_file && has_data_file(bs)) {
bdrv_graph_co_rdunlock();
bdrv_unref_child(bs, s->data_file); bdrv_unref_child(bs, s->data_file);
bdrv_graph_co_rdlock();
s->data_file = NULL; s->data_file = NULL;
} }
g_free(s->unknown_header_fields); g_free(s->unknown_header_fields);
@ -3137,9 +3144,10 @@ static int qcow2_change_backing_file(BlockDriverState *bs,
return qcow2_update_header(bs); return qcow2_update_header(bs);
} }
static int qcow2_set_up_encryption(BlockDriverState *bs, static int coroutine_fn GRAPH_RDLOCK
QCryptoBlockCreateOptions *cryptoopts, qcow2_set_up_encryption(BlockDriverState *bs,
Error **errp) QCryptoBlockCreateOptions *cryptoopts,
Error **errp)
{ {
BDRVQcow2State *s = bs->opaque; BDRVQcow2State *s = bs->opaque;
QCryptoBlock *crypto = NULL; QCryptoBlock *crypto = NULL;
@ -3426,7 +3434,7 @@ static uint64_t qcow2_opt_get_refcount_bits_del(QemuOpts *opts, int version,
return refcount_bits; return refcount_bits;
} }
static int coroutine_fn static int coroutine_fn GRAPH_UNLOCKED
qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp) qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
{ {
BlockdevCreateOptionsQcow2 *qcow2_opts; BlockdevCreateOptionsQcow2 *qcow2_opts;
@ -3724,8 +3732,10 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
goto out; goto out;
} }
bdrv_graph_co_rdlock();
ret = qcow2_alloc_clusters(blk_bs(blk), 3 * cluster_size); ret = qcow2_alloc_clusters(blk_bs(blk), 3 * cluster_size);
if (ret < 0) { if (ret < 0) {
bdrv_graph_co_rdunlock();
error_setg_errno(errp, -ret, "Could not allocate clusters for qcow2 " error_setg_errno(errp, -ret, "Could not allocate clusters for qcow2 "
"header and refcount table"); "header and refcount table");
goto out; goto out;
@ -3743,6 +3753,8 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
/* Create a full header (including things like feature table) */ /* Create a full header (including things like feature table) */
ret = qcow2_update_header(blk_bs(blk)); ret = qcow2_update_header(blk_bs(blk));
bdrv_graph_co_rdunlock();
if (ret < 0) { if (ret < 0) {
error_setg_errno(errp, -ret, "Could not update qcow2 header"); error_setg_errno(errp, -ret, "Could not update qcow2 header");
goto out; goto out;
@ -3776,7 +3788,10 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
/* Want encryption? There you go. */ /* Want encryption? There you go. */
if (qcow2_opts->encrypt) { if (qcow2_opts->encrypt) {
bdrv_graph_co_rdlock();
ret = qcow2_set_up_encryption(blk_bs(blk), qcow2_opts->encrypt, errp); ret = qcow2_set_up_encryption(blk_bs(blk), qcow2_opts->encrypt, errp);
bdrv_graph_co_rdunlock();
if (ret < 0) { if (ret < 0) {
goto out; goto out;
} }
@ -3813,7 +3828,7 @@ out:
return ret; return ret;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts, qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts,
Error **errp) Error **errp)
{ {
@ -3933,8 +3948,10 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts,
ret = qcow2_co_create(create_options, errp); ret = qcow2_co_create(create_options, errp);
finish: finish:
if (ret < 0) { if (ret < 0) {
bdrv_graph_co_rdlock();
bdrv_co_delete_file_noerr(bs); bdrv_co_delete_file_noerr(bs);
bdrv_co_delete_file_noerr(data_bs); bdrv_co_delete_file_noerr(data_bs);
bdrv_graph_co_rdunlock();
} else { } else {
ret = 0; ret = 0;
} }

View file

@ -630,8 +630,8 @@ static void bdrv_qed_close(BlockDriverState *bs)
qemu_vfree(s->l1_table); qemu_vfree(s->l1_table);
} }
static int coroutine_fn bdrv_qed_co_create(BlockdevCreateOptions *opts, static int coroutine_fn GRAPH_UNLOCKED
Error **errp) bdrv_qed_co_create(BlockdevCreateOptions *opts, Error **errp)
{ {
BlockdevCreateOptionsQed *qed_opts; BlockdevCreateOptionsQed *qed_opts;
BlockBackend *blk = NULL; BlockBackend *blk = NULL;
@ -751,7 +751,7 @@ out:
return ret; return ret;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
bdrv_qed_co_create_opts(BlockDriver *drv, const char *filename, bdrv_qed_co_create_opts(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {

View file

@ -457,7 +457,7 @@ static int raw_has_zero_init(BlockDriverState *bs)
return bdrv_has_zero_init(bs->file->bs); return bdrv_has_zero_init(bs->file->bs);
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
raw_co_create_opts(BlockDriver *drv, const char *filename, raw_co_create_opts(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {

View file

@ -131,7 +131,6 @@ static int coroutine_fn stream_run(Job *job, Error **errp)
BlockDriverState *unfiltered_bs = bdrv_skip_filters(s->target_bs); BlockDriverState *unfiltered_bs = bdrv_skip_filters(s->target_bs);
int64_t len; int64_t len;
int64_t offset = 0; int64_t offset = 0;
uint64_t delay_ns = 0;
int error = 0; int error = 0;
int64_t n = 0; /* bytes */ int64_t n = 0; /* bytes */
@ -155,7 +154,7 @@ static int coroutine_fn stream_run(Job *job, Error **errp)
/* Note that even when no rate limit is applied we need to yield /* Note that even when no rate limit is applied we need to yield
* with no pending I/O here so that bdrv_drain_all() returns. * with no pending I/O here so that bdrv_drain_all() returns.
*/ */
job_sleep_ns(&s->common.job, delay_ns); block_job_ratelimit_sleep(&s->common);
if (job_is_cancelled(&s->common.job)) { if (job_is_cancelled(&s->common.job)) {
break; break;
} }
@ -204,9 +203,7 @@ static int coroutine_fn stream_run(Job *job, Error **errp)
/* Publish progress */ /* Publish progress */
job_progress_update(&s->common.job, n); job_progress_update(&s->common.job, n);
if (copy) { if (copy) {
delay_ns = block_job_ratelimit_get_delay(&s->common, n); block_job_ratelimit_processed_bytes(&s->common, n);
} else {
delay_ns = 0;
} }
} }

View file

@ -734,8 +734,9 @@ nonallocating_write:
return ret; return ret;
} }
static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options, static int coroutine_fn GRAPH_UNLOCKED
size_t block_size, Error **errp) vdi_co_do_create(BlockdevCreateOptions *create_options, size_t block_size,
Error **errp)
{ {
BlockdevCreateOptionsVdi *vdi_opts; BlockdevCreateOptionsVdi *vdi_opts;
int ret = 0; int ret = 0;
@ -892,13 +893,13 @@ exit:
return ret; return ret;
} }
static int coroutine_fn vdi_co_create(BlockdevCreateOptions *create_options, static int coroutine_fn GRAPH_UNLOCKED
Error **errp) vdi_co_create(BlockdevCreateOptions *create_options, Error **errp)
{ {
return vdi_co_do_create(create_options, DEFAULT_CLUSTER_SIZE, errp); return vdi_co_do_create(create_options, DEFAULT_CLUSTER_SIZE, errp);
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vdi_co_create_opts(BlockDriver *drv, const char *filename, vdi_co_create_opts(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {

View file

@ -1506,7 +1506,7 @@ exit:
* There are 2 headers, and the highest sequence number will represent * There are 2 headers, and the highest sequence number will represent
* the active header * the active header
*/ */
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vhdx_create_new_headers(BlockBackend *blk, uint64_t image_size, vhdx_create_new_headers(BlockBackend *blk, uint64_t image_size,
uint32_t log_size) uint32_t log_size)
{ {
@ -1515,6 +1515,8 @@ vhdx_create_new_headers(BlockBackend *blk, uint64_t image_size,
int ret = 0; int ret = 0;
VHDXHeader *hdr = NULL; VHDXHeader *hdr = NULL;
GRAPH_RDLOCK_GUARD();
hdr = g_new0(VHDXHeader, 1); hdr = g_new0(VHDXHeader, 1);
hdr->signature = VHDX_HEADER_SIGNATURE; hdr->signature = VHDX_HEADER_SIGNATURE;
@ -1898,7 +1900,7 @@ exit:
* .---- ~ ----------- ~ ------------ ~ ---------------- ~ -----------. * .---- ~ ----------- ~ ------------ ~ ---------------- ~ -----------.
* 1MB * 1MB
*/ */
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vhdx_co_create(BlockdevCreateOptions *opts, Error **errp) vhdx_co_create(BlockdevCreateOptions *opts, Error **errp)
{ {
BlockdevCreateOptionsVhdx *vhdx_opts; BlockdevCreateOptionsVhdx *vhdx_opts;
@ -2060,7 +2062,7 @@ delete_and_exit:
return ret; return ret;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vhdx_co_create_opts(BlockDriver *drv, const char *filename, vhdx_co_create_opts(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {

View file

@ -2165,10 +2165,9 @@ vmdk_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int64_t bytes,
return ret; return ret;
} }
static int vmdk_init_extent(BlockBackend *blk, static int GRAPH_UNLOCKED
int64_t filesize, bool flat, vmdk_init_extent(BlockBackend *blk, int64_t filesize, bool flat, bool compress,
bool compress, bool zeroed_grain, bool zeroed_grain, Error **errp)
Error **errp)
{ {
int ret, i; int ret, i;
VMDK4Header header; VMDK4Header header;
@ -2277,7 +2276,7 @@ exit:
return ret; return ret;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vmdk_create_extent(const char *filename, int64_t filesize, bool flat, vmdk_create_extent(const char *filename, int64_t filesize, bool flat,
bool compress, bool zeroed_grain, BlockBackend **pbb, bool compress, bool zeroed_grain, BlockBackend **pbb,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
@ -2358,7 +2357,7 @@ static int filename_decompose(const char *filename, char *path, char *prefix,
* non-split format. * non-split format.
* idx >= 1: get the n-th extent if in a split subformat * idx >= 1: get the n-th extent if in a split subformat
*/ */
typedef BlockBackend * coroutine_fn /* GRAPH_RDLOCK */ typedef BlockBackend * coroutine_fn GRAPH_UNLOCKED_PTR
(*vmdk_create_extent_fn)(int64_t size, int idx, bool flat, bool split, (*vmdk_create_extent_fn)(int64_t size, int idx, bool flat, bool split,
bool compress, bool zeroed_grain, void *opaque, bool compress, bool zeroed_grain, void *opaque,
Error **errp); Error **errp);
@ -2374,7 +2373,7 @@ static void vmdk_desc_add_extent(GString *desc,
g_free(basename); g_free(basename);
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vmdk_co_do_create(int64_t size, vmdk_co_do_create(int64_t size,
BlockdevVmdkSubformat subformat, BlockdevVmdkSubformat subformat,
BlockdevVmdkAdapterType adapter_type, BlockdevVmdkAdapterType adapter_type,
@ -2605,7 +2604,7 @@ typedef struct {
QemuOpts *opts; QemuOpts *opts;
} VMDKCreateOptsData; } VMDKCreateOptsData;
static BlockBackend * coroutine_fn GRAPH_RDLOCK static BlockBackend * coroutine_fn GRAPH_UNLOCKED
vmdk_co_create_opts_cb(int64_t size, int idx, bool flat, bool split, vmdk_co_create_opts_cb(int64_t size, int idx, bool flat, bool split,
bool compress, bool zeroed_grain, void *opaque, bool compress, bool zeroed_grain, void *opaque,
Error **errp) Error **errp)
@ -2647,7 +2646,7 @@ exit:
return blk; return blk;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vmdk_co_create_opts(BlockDriver *drv, const char *filename, vmdk_co_create_opts(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {
@ -2756,11 +2755,9 @@ exit:
return ret; return ret;
} }
static BlockBackend * coroutine_fn vmdk_co_create_cb(int64_t size, int idx, static BlockBackend * coroutine_fn GRAPH_UNLOCKED
bool flat, bool split, vmdk_co_create_cb(int64_t size, int idx, bool flat, bool split, bool compress,
bool compress, bool zeroed_grain, void *opaque, Error **errp)
bool zeroed_grain,
void *opaque, Error **errp)
{ {
int ret; int ret;
BlockDriverState *bs; BlockDriverState *bs;
@ -2809,7 +2806,7 @@ static BlockBackend * coroutine_fn vmdk_co_create_cb(int64_t size, int idx,
return blk; return blk;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vmdk_co_create(BlockdevCreateOptions *create_options, Error **errp) vmdk_co_create(BlockdevCreateOptions *create_options, Error **errp)
{ {
BlockdevCreateOptionsVmdk *opts; BlockdevCreateOptionsVmdk *opts;

View file

@ -967,8 +967,8 @@ static int calculate_rounded_image_size(BlockdevCreateOptionsVpc *vpc_opts,
return 0; return 0;
} }
static int coroutine_fn vpc_co_create(BlockdevCreateOptions *opts, static int coroutine_fn GRAPH_UNLOCKED
Error **errp) vpc_co_create(BlockdevCreateOptions *opts, Error **errp)
{ {
BlockdevCreateOptionsVpc *vpc_opts; BlockdevCreateOptionsVpc *vpc_opts;
BlockBackend *blk = NULL; BlockBackend *blk = NULL;
@ -1087,7 +1087,7 @@ out:
return ret; return ret;
} }
static int coroutine_fn GRAPH_RDLOCK static int coroutine_fn GRAPH_UNLOCKED
vpc_co_create_opts(BlockDriver *drv, const char *filename, vpc_co_create_opts(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp) QemuOpts *opts, Error **errp)
{ {

View file

@ -1188,79 +1188,22 @@ out_aio_context:
return NULL; return NULL;
} }
/* New and old BlockDriverState structs for atomic group operations */
typedef struct BlkActionState BlkActionState;
/**
* BlkActionOps:
* Table of operations that define an Action.
*
* @instance_size: Size of state struct, in bytes.
* @prepare: Prepare the work, must NOT be NULL.
* @commit: Commit the changes, can be NULL.
* @abort: Abort the changes on fail, can be NULL.
* @clean: Clean up resources after all transaction actions have called
* commit() or abort(). Can be NULL.
*
* Only prepare() may fail. In a single transaction, only one of commit() or
* abort() will be called. clean() will always be called if it is present.
*
* Always run under BQL.
*/
typedef struct BlkActionOps {
size_t instance_size;
void (*prepare)(BlkActionState *common, Error **errp);
void (*commit)(BlkActionState *common);
void (*abort)(BlkActionState *common);
void (*clean)(BlkActionState *common);
} BlkActionOps;
/**
* BlkActionState:
* Describes one Action's state within a Transaction.
*
* @action: QAPI-defined enum identifying which Action to perform.
* @ops: Table of ActionOps this Action can perform.
* @block_job_txn: Transaction which this action belongs to.
* @entry: List membership for all Actions in this Transaction.
*
* This structure must be arranged as first member in a subclassed type,
* assuming that the compiler will also arrange it to the same offsets as the
* base class.
*/
struct BlkActionState {
TransactionAction *action;
const BlkActionOps *ops;
JobTxn *block_job_txn;
TransactionProperties *txn_props;
QTAILQ_ENTRY(BlkActionState) entry;
};
/* internal snapshot private data */ /* internal snapshot private data */
typedef struct InternalSnapshotState { typedef struct InternalSnapshotState {
BlkActionState common;
BlockDriverState *bs; BlockDriverState *bs;
QEMUSnapshotInfo sn; QEMUSnapshotInfo sn;
bool created; bool created;
} InternalSnapshotState; } InternalSnapshotState;
static void internal_snapshot_abort(void *opaque);
static void internal_snapshot_clean(void *opaque);
TransactionActionDrv internal_snapshot_drv = {
.abort = internal_snapshot_abort,
.clean = internal_snapshot_clean,
};
static int action_check_completion_mode(BlkActionState *s, Error **errp) static void internal_snapshot_action(BlockdevSnapshotInternal *internal,
{ Transaction *tran, Error **errp)
if (s->txn_props->completion_mode != ACTION_COMPLETION_MODE_INDIVIDUAL) {
error_setg(errp,
"Action '%s' does not support Transaction property "
"completion-mode = %s",
TransactionActionKind_str(s->action->type),
ActionCompletionMode_str(s->txn_props->completion_mode));
return -1;
}
return 0;
}
static void internal_snapshot_prepare(BlkActionState *common,
Error **errp)
{ {
Error *local_err = NULL; Error *local_err = NULL;
const char *device; const char *device;
@ -1269,25 +1212,15 @@ static void internal_snapshot_prepare(BlkActionState *common,
QEMUSnapshotInfo old_sn, *sn; QEMUSnapshotInfo old_sn, *sn;
bool ret; bool ret;
int64_t rt; int64_t rt;
BlockdevSnapshotInternal *internal; InternalSnapshotState *state = g_new0(InternalSnapshotState, 1);
InternalSnapshotState *state;
AioContext *aio_context; AioContext *aio_context;
int ret1; int ret1;
g_assert(common->action->type == tran_add(tran, &internal_snapshot_drv, state);
TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC);
internal = common->action->u.blockdev_snapshot_internal_sync.data;
state = DO_UPCAST(InternalSnapshotState, common, common);
/* 1. parse input */
device = internal->device; device = internal->device;
name = internal->name; name = internal->name;
/* 2. check for validation */
if (action_check_completion_mode(common, errp) < 0) {
return;
}
bs = qmp_get_root_bs(device, errp); bs = qmp_get_root_bs(device, errp);
if (!bs) { if (!bs) {
return; return;
@ -1363,10 +1296,9 @@ out:
aio_context_release(aio_context); aio_context_release(aio_context);
} }
static void internal_snapshot_abort(BlkActionState *common) static void internal_snapshot_abort(void *opaque)
{ {
InternalSnapshotState *state = InternalSnapshotState *state = opaque;
DO_UPCAST(InternalSnapshotState, common, common);
BlockDriverState *bs = state->bs; BlockDriverState *bs = state->bs;
QEMUSnapshotInfo *sn = &state->sn; QEMUSnapshotInfo *sn = &state->sn;
AioContext *aio_context; AioContext *aio_context;
@ -1390,10 +1322,9 @@ static void internal_snapshot_abort(BlkActionState *common)
aio_context_release(aio_context); aio_context_release(aio_context);
} }
static void internal_snapshot_clean(BlkActionState *common) static void internal_snapshot_clean(void *opaque)
{ {
InternalSnapshotState *state = DO_UPCAST(InternalSnapshotState, g_autofree InternalSnapshotState *state = opaque;
common, common);
AioContext *aio_context; AioContext *aio_context;
if (!state->bs) { if (!state->bs) {
@ -1410,14 +1341,22 @@ static void internal_snapshot_clean(BlkActionState *common)
/* external snapshot private data */ /* external snapshot private data */
typedef struct ExternalSnapshotState { typedef struct ExternalSnapshotState {
BlkActionState common;
BlockDriverState *old_bs; BlockDriverState *old_bs;
BlockDriverState *new_bs; BlockDriverState *new_bs;
bool overlay_appended; bool overlay_appended;
} ExternalSnapshotState; } ExternalSnapshotState;
static void external_snapshot_prepare(BlkActionState *common, static void external_snapshot_commit(void *opaque);
Error **errp) static void external_snapshot_abort(void *opaque);
static void external_snapshot_clean(void *opaque);
TransactionActionDrv external_snapshot_drv = {
.commit = external_snapshot_commit,
.abort = external_snapshot_abort,
.clean = external_snapshot_clean,
};
static void external_snapshot_action(TransactionAction *action,
Transaction *tran, Error **errp)
{ {
int ret; int ret;
int flags = 0; int flags = 0;
@ -1430,12 +1369,12 @@ static void external_snapshot_prepare(BlkActionState *common,
const char *snapshot_ref; const char *snapshot_ref;
/* File name of the new image (for 'blockdev-snapshot-sync') */ /* File name of the new image (for 'blockdev-snapshot-sync') */
const char *new_image_file; const char *new_image_file;
ExternalSnapshotState *state = ExternalSnapshotState *state = g_new0(ExternalSnapshotState, 1);
DO_UPCAST(ExternalSnapshotState, common, common);
TransactionAction *action = common->action;
AioContext *aio_context; AioContext *aio_context;
uint64_t perm, shared; uint64_t perm, shared;
tran_add(tran, &external_snapshot_drv, state);
/* 'blockdev-snapshot' and 'blockdev-snapshot-sync' have similar /* 'blockdev-snapshot' and 'blockdev-snapshot-sync' have similar
* purpose but a different set of parameters */ * purpose but a different set of parameters */
switch (action->type) { switch (action->type) {
@ -1462,9 +1401,6 @@ static void external_snapshot_prepare(BlkActionState *common,
} }
/* start processing */ /* start processing */
if (action_check_completion_mode(common, errp) < 0) {
return;
}
state->old_bs = bdrv_lookup_bs(device, node_name, errp); state->old_bs = bdrv_lookup_bs(device, node_name, errp);
if (!state->old_bs) { if (!state->old_bs) {
@ -1588,10 +1524,9 @@ out:
aio_context_release(aio_context); aio_context_release(aio_context);
} }
static void external_snapshot_commit(BlkActionState *common) static void external_snapshot_commit(void *opaque)
{ {
ExternalSnapshotState *state = ExternalSnapshotState *state = opaque;
DO_UPCAST(ExternalSnapshotState, common, common);
AioContext *aio_context; AioContext *aio_context;
aio_context = bdrv_get_aio_context(state->old_bs); aio_context = bdrv_get_aio_context(state->old_bs);
@ -1607,10 +1542,9 @@ static void external_snapshot_commit(BlkActionState *common)
aio_context_release(aio_context); aio_context_release(aio_context);
} }
static void external_snapshot_abort(BlkActionState *common) static void external_snapshot_abort(void *opaque)
{ {
ExternalSnapshotState *state = ExternalSnapshotState *state = opaque;
DO_UPCAST(ExternalSnapshotState, common, common);
if (state->new_bs) { if (state->new_bs) {
if (state->overlay_appended) { if (state->overlay_appended) {
AioContext *aio_context; AioContext *aio_context;
@ -1650,10 +1584,9 @@ static void external_snapshot_abort(BlkActionState *common)
} }
} }
static void external_snapshot_clean(BlkActionState *common) static void external_snapshot_clean(void *opaque)
{ {
ExternalSnapshotState *state = g_autofree ExternalSnapshotState *state = opaque;
DO_UPCAST(ExternalSnapshotState, common, common);
AioContext *aio_context; AioContext *aio_context;
if (!state->old_bs) { if (!state->old_bs) {
@ -1670,7 +1603,6 @@ static void external_snapshot_clean(BlkActionState *common)
} }
typedef struct DriveBackupState { typedef struct DriveBackupState {
BlkActionState common;
BlockDriverState *bs; BlockDriverState *bs;
BlockJob *job; BlockJob *job;
} DriveBackupState; } DriveBackupState;
@ -1681,10 +1613,20 @@ static BlockJob *do_backup_common(BackupCommon *backup,
AioContext *aio_context, AioContext *aio_context,
JobTxn *txn, Error **errp); JobTxn *txn, Error **errp);
static void drive_backup_prepare(BlkActionState *common, Error **errp) static void drive_backup_commit(void *opaque);
static void drive_backup_abort(void *opaque);
static void drive_backup_clean(void *opaque);
TransactionActionDrv drive_backup_drv = {
.commit = drive_backup_commit,
.abort = drive_backup_abort,
.clean = drive_backup_clean,
};
static void drive_backup_action(DriveBackup *backup,
JobTxn *block_job_txn,
Transaction *tran, Error **errp)
{ {
DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common); DriveBackupState *state = g_new0(DriveBackupState, 1);
DriveBackup *backup;
BlockDriverState *bs; BlockDriverState *bs;
BlockDriverState *target_bs; BlockDriverState *target_bs;
BlockDriverState *source = NULL; BlockDriverState *source = NULL;
@ -1698,8 +1640,7 @@ static void drive_backup_prepare(BlkActionState *common, Error **errp)
bool set_backing_hd = false; bool set_backing_hd = false;
int ret; int ret;
assert(common->action->type == TRANSACTION_ACTION_KIND_DRIVE_BACKUP); tran_add(tran, &drive_backup_drv, state);
backup = common->action->u.drive_backup.data;
if (!backup->has_mode) { if (!backup->has_mode) {
backup->mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; backup->mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS;
@ -1820,7 +1761,7 @@ static void drive_backup_prepare(BlkActionState *common, Error **errp)
state->job = do_backup_common(qapi_DriveBackup_base(backup), state->job = do_backup_common(qapi_DriveBackup_base(backup),
bs, target_bs, aio_context, bs, target_bs, aio_context,
common->block_job_txn, errp); block_job_txn, errp);
unref: unref:
bdrv_unref(target_bs); bdrv_unref(target_bs);
@ -1828,9 +1769,9 @@ out:
aio_context_release(aio_context); aio_context_release(aio_context);
} }
static void drive_backup_commit(BlkActionState *common) static void drive_backup_commit(void *opaque)
{ {
DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common); DriveBackupState *state = opaque;
AioContext *aio_context; AioContext *aio_context;
aio_context = bdrv_get_aio_context(state->bs); aio_context = bdrv_get_aio_context(state->bs);
@ -1842,18 +1783,18 @@ static void drive_backup_commit(BlkActionState *common)
aio_context_release(aio_context); aio_context_release(aio_context);
} }
static void drive_backup_abort(BlkActionState *common) static void drive_backup_abort(void *opaque)
{ {
DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common); DriveBackupState *state = opaque;
if (state->job) { if (state->job) {
job_cancel_sync(&state->job->job, true); job_cancel_sync(&state->job->job, true);
} }
} }
static void drive_backup_clean(BlkActionState *common) static void drive_backup_clean(void *opaque)
{ {
DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common); g_autofree DriveBackupState *state = opaque;
AioContext *aio_context; AioContext *aio_context;
if (!state->bs) { if (!state->bs) {
@ -1869,23 +1810,31 @@ static void drive_backup_clean(BlkActionState *common)
} }
typedef struct BlockdevBackupState { typedef struct BlockdevBackupState {
BlkActionState common;
BlockDriverState *bs; BlockDriverState *bs;
BlockJob *job; BlockJob *job;
} BlockdevBackupState; } BlockdevBackupState;
static void blockdev_backup_prepare(BlkActionState *common, Error **errp) static void blockdev_backup_commit(void *opaque);
static void blockdev_backup_abort(void *opaque);
static void blockdev_backup_clean(void *opaque);
TransactionActionDrv blockdev_backup_drv = {
.commit = blockdev_backup_commit,
.abort = blockdev_backup_abort,
.clean = blockdev_backup_clean,
};
static void blockdev_backup_action(BlockdevBackup *backup,
JobTxn *block_job_txn,
Transaction *tran, Error **errp)
{ {
BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common); BlockdevBackupState *state = g_new0(BlockdevBackupState, 1);
BlockdevBackup *backup;
BlockDriverState *bs; BlockDriverState *bs;
BlockDriverState *target_bs; BlockDriverState *target_bs;
AioContext *aio_context; AioContext *aio_context;
AioContext *old_context; AioContext *old_context;
int ret; int ret;
assert(common->action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP); tran_add(tran, &blockdev_backup_drv, state);
backup = common->action->u.blockdev_backup.data;
bs = bdrv_lookup_bs(backup->device, backup->device, errp); bs = bdrv_lookup_bs(backup->device, backup->device, errp);
if (!bs) { if (!bs) {
@ -1917,14 +1866,14 @@ static void blockdev_backup_prepare(BlkActionState *common, Error **errp)
state->job = do_backup_common(qapi_BlockdevBackup_base(backup), state->job = do_backup_common(qapi_BlockdevBackup_base(backup),
bs, target_bs, aio_context, bs, target_bs, aio_context,
common->block_job_txn, errp); block_job_txn, errp);
aio_context_release(aio_context); aio_context_release(aio_context);
} }
static void blockdev_backup_commit(BlkActionState *common) static void blockdev_backup_commit(void *opaque)
{ {
BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common); BlockdevBackupState *state = opaque;
AioContext *aio_context; AioContext *aio_context;
aio_context = bdrv_get_aio_context(state->bs); aio_context = bdrv_get_aio_context(state->bs);
@ -1936,18 +1885,18 @@ static void blockdev_backup_commit(BlkActionState *common)
aio_context_release(aio_context); aio_context_release(aio_context);
} }
static void blockdev_backup_abort(BlkActionState *common) static void blockdev_backup_abort(void *opaque)
{ {
BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common); BlockdevBackupState *state = opaque;
if (state->job) { if (state->job) {
job_cancel_sync(&state->job->job, true); job_cancel_sync(&state->job->job, true);
} }
} }
static void blockdev_backup_clean(BlkActionState *common) static void blockdev_backup_clean(void *opaque)
{ {
BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common); g_autofree BlockdevBackupState *state = opaque;
AioContext *aio_context; AioContext *aio_context;
if (!state->bs) { if (!state->bs) {
@ -1963,27 +1912,26 @@ static void blockdev_backup_clean(BlkActionState *common)
} }
typedef struct BlockDirtyBitmapState { typedef struct BlockDirtyBitmapState {
BlkActionState common;
BdrvDirtyBitmap *bitmap; BdrvDirtyBitmap *bitmap;
BlockDriverState *bs; BlockDriverState *bs;
HBitmap *backup; HBitmap *backup;
bool prepared;
bool was_enabled; bool was_enabled;
} BlockDirtyBitmapState; } BlockDirtyBitmapState;
static void block_dirty_bitmap_add_prepare(BlkActionState *common, static void block_dirty_bitmap_add_abort(void *opaque);
Error **errp) TransactionActionDrv block_dirty_bitmap_add_drv = {
.abort = block_dirty_bitmap_add_abort,
.clean = g_free,
};
static void block_dirty_bitmap_add_action(BlockDirtyBitmapAdd *action,
Transaction *tran, Error **errp)
{ {
Error *local_err = NULL; Error *local_err = NULL;
BlockDirtyBitmapAdd *action; BlockDirtyBitmapState *state = g_new0(BlockDirtyBitmapState, 1);
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
common, common);
if (action_check_completion_mode(common, errp) < 0) { tran_add(tran, &block_dirty_bitmap_add_drv, state);
return;
}
action = common->action->u.block_dirty_bitmap_add.data;
/* AIO context taken and released within qmp_block_dirty_bitmap_add */ /* AIO context taken and released within qmp_block_dirty_bitmap_add */
qmp_block_dirty_bitmap_add(action->node, action->name, qmp_block_dirty_bitmap_add(action->node, action->name,
action->has_granularity, action->granularity, action->has_granularity, action->granularity,
@ -1992,39 +1940,37 @@ static void block_dirty_bitmap_add_prepare(BlkActionState *common,
&local_err); &local_err);
if (!local_err) { if (!local_err) {
state->prepared = true; state->bitmap = block_dirty_bitmap_lookup(action->node, action->name,
NULL, &error_abort);
} else { } else {
error_propagate(errp, local_err); error_propagate(errp, local_err);
} }
} }
static void block_dirty_bitmap_add_abort(BlkActionState *common) static void block_dirty_bitmap_add_abort(void *opaque)
{ {
BlockDirtyBitmapAdd *action; BlockDirtyBitmapState *state = opaque;
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
common, common);
action = common->action->u.block_dirty_bitmap_add.data; if (state->bitmap) {
/* Should not be able to fail: IF the bitmap was added via .prepare(), bdrv_release_dirty_bitmap(state->bitmap);
* then the node reference and bitmap name must have been valid.
*/
if (state->prepared) {
qmp_block_dirty_bitmap_remove(action->node, action->name, &error_abort);
} }
} }
static void block_dirty_bitmap_clear_prepare(BlkActionState *common, static void block_dirty_bitmap_restore(void *opaque);
Error **errp) static void block_dirty_bitmap_free_backup(void *opaque);
TransactionActionDrv block_dirty_bitmap_clear_drv = {
.abort = block_dirty_bitmap_restore,
.commit = block_dirty_bitmap_free_backup,
.clean = g_free,
};
static void block_dirty_bitmap_clear_action(BlockDirtyBitmap *action,
Transaction *tran, Error **errp)
{ {
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState, BlockDirtyBitmapState *state = g_new0(BlockDirtyBitmapState, 1);
common, common);
BlockDirtyBitmap *action;
if (action_check_completion_mode(common, errp) < 0) { tran_add(tran, &block_dirty_bitmap_clear_drv, state);
return;
}
action = common->action->u.block_dirty_bitmap_clear.data;
state->bitmap = block_dirty_bitmap_lookup(action->node, state->bitmap = block_dirty_bitmap_lookup(action->node,
action->name, action->name,
&state->bs, &state->bs,
@ -2040,36 +1986,35 @@ static void block_dirty_bitmap_clear_prepare(BlkActionState *common,
bdrv_clear_dirty_bitmap(state->bitmap, &state->backup); bdrv_clear_dirty_bitmap(state->bitmap, &state->backup);
} }
static void block_dirty_bitmap_restore(BlkActionState *common) static void block_dirty_bitmap_restore(void *opaque)
{ {
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState, BlockDirtyBitmapState *state = opaque;
common, common);
if (state->backup) { if (state->backup) {
bdrv_restore_dirty_bitmap(state->bitmap, state->backup); bdrv_restore_dirty_bitmap(state->bitmap, state->backup);
} }
} }
static void block_dirty_bitmap_free_backup(BlkActionState *common) static void block_dirty_bitmap_free_backup(void *opaque)
{ {
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState, BlockDirtyBitmapState *state = opaque;
common, common);
hbitmap_free(state->backup); hbitmap_free(state->backup);
} }
static void block_dirty_bitmap_enable_prepare(BlkActionState *common, static void block_dirty_bitmap_enable_abort(void *opaque);
Error **errp) TransactionActionDrv block_dirty_bitmap_enable_drv = {
.abort = block_dirty_bitmap_enable_abort,
.clean = g_free,
};
static void block_dirty_bitmap_enable_action(BlockDirtyBitmap *action,
Transaction *tran, Error **errp)
{ {
BlockDirtyBitmap *action; BlockDirtyBitmapState *state = g_new0(BlockDirtyBitmapState, 1);
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
common, common);
if (action_check_completion_mode(common, errp) < 0) { tran_add(tran, &block_dirty_bitmap_enable_drv, state);
return;
}
action = common->action->u.block_dirty_bitmap_enable.data;
state->bitmap = block_dirty_bitmap_lookup(action->node, state->bitmap = block_dirty_bitmap_lookup(action->node,
action->name, action->name,
NULL, NULL,
@ -2086,28 +2031,28 @@ static void block_dirty_bitmap_enable_prepare(BlkActionState *common,
bdrv_enable_dirty_bitmap(state->bitmap); bdrv_enable_dirty_bitmap(state->bitmap);
} }
static void block_dirty_bitmap_enable_abort(BlkActionState *common) static void block_dirty_bitmap_enable_abort(void *opaque)
{ {
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState, BlockDirtyBitmapState *state = opaque;
common, common);
if (!state->was_enabled) { if (!state->was_enabled) {
bdrv_disable_dirty_bitmap(state->bitmap); bdrv_disable_dirty_bitmap(state->bitmap);
} }
} }
static void block_dirty_bitmap_disable_prepare(BlkActionState *common, static void block_dirty_bitmap_disable_abort(void *opaque);
Error **errp) TransactionActionDrv block_dirty_bitmap_disable_drv = {
.abort = block_dirty_bitmap_disable_abort,
.clean = g_free,
};
static void block_dirty_bitmap_disable_action(BlockDirtyBitmap *action,
Transaction *tran, Error **errp)
{ {
BlockDirtyBitmap *action; BlockDirtyBitmapState *state = g_new0(BlockDirtyBitmapState, 1);
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
common, common);
if (action_check_completion_mode(common, errp) < 0) { tran_add(tran, &block_dirty_bitmap_disable_drv, state);
return;
}
action = common->action->u.block_dirty_bitmap_disable.data;
state->bitmap = block_dirty_bitmap_lookup(action->node, state->bitmap = block_dirty_bitmap_lookup(action->node,
action->name, action->name,
NULL, NULL,
@ -2124,46 +2069,48 @@ static void block_dirty_bitmap_disable_prepare(BlkActionState *common,
bdrv_disable_dirty_bitmap(state->bitmap); bdrv_disable_dirty_bitmap(state->bitmap);
} }
static void block_dirty_bitmap_disable_abort(BlkActionState *common) static void block_dirty_bitmap_disable_abort(void *opaque)
{ {
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState, BlockDirtyBitmapState *state = opaque;
common, common);
if (state->was_enabled) { if (state->was_enabled) {
bdrv_enable_dirty_bitmap(state->bitmap); bdrv_enable_dirty_bitmap(state->bitmap);
} }
} }
static void block_dirty_bitmap_merge_prepare(BlkActionState *common, TransactionActionDrv block_dirty_bitmap_merge_drv = {
Error **errp) .commit = block_dirty_bitmap_free_backup,
.abort = block_dirty_bitmap_restore,
.clean = g_free,
};
static void block_dirty_bitmap_merge_action(BlockDirtyBitmapMerge *action,
Transaction *tran, Error **errp)
{ {
BlockDirtyBitmapMerge *action; BlockDirtyBitmapState *state = g_new0(BlockDirtyBitmapState, 1);
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
common, common);
if (action_check_completion_mode(common, errp) < 0) { tran_add(tran, &block_dirty_bitmap_merge_drv, state);
return;
}
action = common->action->u.block_dirty_bitmap_merge.data;
state->bitmap = block_dirty_bitmap_merge(action->node, action->target, state->bitmap = block_dirty_bitmap_merge(action->node, action->target,
action->bitmaps, &state->backup, action->bitmaps, &state->backup,
errp); errp);
} }
static void block_dirty_bitmap_remove_prepare(BlkActionState *common, static void block_dirty_bitmap_remove_commit(void *opaque);
Error **errp) static void block_dirty_bitmap_remove_abort(void *opaque);
TransactionActionDrv block_dirty_bitmap_remove_drv = {
.commit = block_dirty_bitmap_remove_commit,
.abort = block_dirty_bitmap_remove_abort,
.clean = g_free,
};
static void block_dirty_bitmap_remove_action(BlockDirtyBitmap *action,
Transaction *tran, Error **errp)
{ {
BlockDirtyBitmap *action; BlockDirtyBitmapState *state = g_new0(BlockDirtyBitmapState, 1);
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
common, common);
if (action_check_completion_mode(common, errp) < 0) { tran_add(tran, &block_dirty_bitmap_remove_drv, state);
return;
}
action = common->action->u.block_dirty_bitmap_remove.data;
state->bitmap = block_dirty_bitmap_remove(action->node, action->name, state->bitmap = block_dirty_bitmap_remove(action->node, action->name,
false, &state->bs, errp); false, &state->bs, errp);
@ -2173,10 +2120,9 @@ static void block_dirty_bitmap_remove_prepare(BlkActionState *common,
} }
} }
static void block_dirty_bitmap_remove_abort(BlkActionState *common) static void block_dirty_bitmap_remove_abort(void *opaque)
{ {
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState, BlockDirtyBitmapState *state = opaque;
common, common);
if (state->bitmap) { if (state->bitmap) {
bdrv_dirty_bitmap_skip_store(state->bitmap, false); bdrv_dirty_bitmap_skip_store(state->bitmap, false);
@ -2184,210 +2130,156 @@ static void block_dirty_bitmap_remove_abort(BlkActionState *common)
} }
} }
static void block_dirty_bitmap_remove_commit(BlkActionState *common) static void block_dirty_bitmap_remove_commit(void *opaque)
{ {
BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState, BlockDirtyBitmapState *state = opaque;
common, common);
bdrv_dirty_bitmap_set_busy(state->bitmap, false); bdrv_dirty_bitmap_set_busy(state->bitmap, false);
bdrv_release_dirty_bitmap(state->bitmap); bdrv_release_dirty_bitmap(state->bitmap);
} }
static void abort_prepare(BlkActionState *common, Error **errp) static void abort_commit(void *opaque);
TransactionActionDrv abort_drv = {
.commit = abort_commit,
};
static void abort_action(Transaction *tran, Error **errp)
{ {
tran_add(tran, &abort_drv, NULL);
error_setg(errp, "Transaction aborted using Abort action"); error_setg(errp, "Transaction aborted using Abort action");
} }
static void abort_commit(BlkActionState *common) static void abort_commit(void *opaque)
{ {
g_assert_not_reached(); /* this action never succeeds */ g_assert_not_reached(); /* this action never succeeds */
} }
static const BlkActionOps actions[] = { static void transaction_action(TransactionAction *act, JobTxn *block_job_txn,
[TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT] = { Transaction *tran, Error **errp)
.instance_size = sizeof(ExternalSnapshotState), {
.prepare = external_snapshot_prepare, switch (act->type) {
.commit = external_snapshot_commit, case TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT:
.abort = external_snapshot_abort, case TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC:
.clean = external_snapshot_clean, external_snapshot_action(act, tran, errp);
}, return;
[TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_SYNC] = { case TRANSACTION_ACTION_KIND_DRIVE_BACKUP:
.instance_size = sizeof(ExternalSnapshotState), drive_backup_action(act->u.drive_backup.data,
.prepare = external_snapshot_prepare, block_job_txn, tran, errp);
.commit = external_snapshot_commit, return;
.abort = external_snapshot_abort, case TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP:
.clean = external_snapshot_clean, blockdev_backup_action(act->u.blockdev_backup.data,
}, block_job_txn, tran, errp);
[TRANSACTION_ACTION_KIND_DRIVE_BACKUP] = { return;
.instance_size = sizeof(DriveBackupState), case TRANSACTION_ACTION_KIND_ABORT:
.prepare = drive_backup_prepare, abort_action(tran, errp);
.commit = drive_backup_commit, return;
.abort = drive_backup_abort, case TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC:
.clean = drive_backup_clean, internal_snapshot_action(act->u.blockdev_snapshot_internal_sync.data,
}, tran, errp);
[TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { return;
.instance_size = sizeof(BlockdevBackupState), case TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ADD:
.prepare = blockdev_backup_prepare, block_dirty_bitmap_add_action(act->u.block_dirty_bitmap_add.data,
.commit = blockdev_backup_commit, tran, errp);
.abort = blockdev_backup_abort, return;
.clean = blockdev_backup_clean, case TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_CLEAR:
}, block_dirty_bitmap_clear_action(act->u.block_dirty_bitmap_clear.data,
[TRANSACTION_ACTION_KIND_ABORT] = { tran, errp);
.instance_size = sizeof(BlkActionState), return;
.prepare = abort_prepare, case TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ENABLE:
.commit = abort_commit, block_dirty_bitmap_enable_action(act->u.block_dirty_bitmap_enable.data,
}, tran, errp);
[TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT_INTERNAL_SYNC] = { return;
.instance_size = sizeof(InternalSnapshotState), case TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_DISABLE:
.prepare = internal_snapshot_prepare, block_dirty_bitmap_disable_action(
.abort = internal_snapshot_abort, act->u.block_dirty_bitmap_disable.data, tran, errp);
.clean = internal_snapshot_clean, return;
}, case TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_MERGE:
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ADD] = { block_dirty_bitmap_merge_action(act->u.block_dirty_bitmap_merge.data,
.instance_size = sizeof(BlockDirtyBitmapState), tran, errp);
.prepare = block_dirty_bitmap_add_prepare, return;
.abort = block_dirty_bitmap_add_abort, case TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_REMOVE:
}, block_dirty_bitmap_remove_action(act->u.block_dirty_bitmap_remove.data,
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_CLEAR] = { tran, errp);
.instance_size = sizeof(BlockDirtyBitmapState), return;
.prepare = block_dirty_bitmap_clear_prepare, /*
.commit = block_dirty_bitmap_free_backup, * Where are transactions for MIRROR, COMMIT and STREAM?
.abort = block_dirty_bitmap_restore,
},
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ENABLE] = {
.instance_size = sizeof(BlockDirtyBitmapState),
.prepare = block_dirty_bitmap_enable_prepare,
.abort = block_dirty_bitmap_enable_abort,
},
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_DISABLE] = {
.instance_size = sizeof(BlockDirtyBitmapState),
.prepare = block_dirty_bitmap_disable_prepare,
.abort = block_dirty_bitmap_disable_abort,
},
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_MERGE] = {
.instance_size = sizeof(BlockDirtyBitmapState),
.prepare = block_dirty_bitmap_merge_prepare,
.commit = block_dirty_bitmap_free_backup,
.abort = block_dirty_bitmap_restore,
},
[TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_REMOVE] = {
.instance_size = sizeof(BlockDirtyBitmapState),
.prepare = block_dirty_bitmap_remove_prepare,
.commit = block_dirty_bitmap_remove_commit,
.abort = block_dirty_bitmap_remove_abort,
},
/* Where are transactions for MIRROR, COMMIT and STREAM?
* Although these blockjobs use transaction callbacks like the backup job, * Although these blockjobs use transaction callbacks like the backup job,
* these jobs do not necessarily adhere to transaction semantics. * these jobs do not necessarily adhere to transaction semantics.
* These jobs may not fully undo all of their actions on abort, nor do they * These jobs may not fully undo all of their actions on abort, nor do they
* necessarily work in transactions with more than one job in them. * necessarily work in transactions with more than one job in them.
*/ */
}; case TRANSACTION_ACTION_KIND__MAX:
default:
/** g_assert_not_reached();
* Allocate a TransactionProperties structure if necessary, and fill };
* that structure with desired defaults if they are unset.
*/
static TransactionProperties *get_transaction_properties(
TransactionProperties *props)
{
if (!props) {
props = g_new0(TransactionProperties, 1);
}
if (!props->has_completion_mode) {
props->has_completion_mode = true;
props->completion_mode = ACTION_COMPLETION_MODE_INDIVIDUAL;
}
return props;
} }
/* /*
* 'Atomic' group operations. The operations are performed as a set, and if * 'Atomic' group operations. The operations are performed as a set, and if
* any fail then we roll back all operations in the group. * any fail then we roll back all operations in the group.
* *
* Always run under BQL. * Always run under BQL.
*/ */
void qmp_transaction(TransactionActionList *dev_list, void qmp_transaction(TransactionActionList *actions,
struct TransactionProperties *props, struct TransactionProperties *properties,
Error **errp) Error **errp)
{ {
TransactionActionList *dev_entry = dev_list; TransactionActionList *act;
bool has_props = !!props;
JobTxn *block_job_txn = NULL; JobTxn *block_job_txn = NULL;
BlkActionState *state, *next;
Error *local_err = NULL; Error *local_err = NULL;
Transaction *tran;
ActionCompletionMode comp_mode =
properties ? properties->completion_mode :
ACTION_COMPLETION_MODE_INDIVIDUAL;
GLOBAL_STATE_CODE(); GLOBAL_STATE_CODE();
QTAILQ_HEAD(, BlkActionState) snap_bdrv_states;
QTAILQ_INIT(&snap_bdrv_states);
/* Does this transaction get canceled as a group on failure? /* Does this transaction get canceled as a group on failure?
* If not, we don't really need to make a JobTxn. * If not, we don't really need to make a JobTxn.
*/ */
props = get_transaction_properties(props); if (comp_mode != ACTION_COMPLETION_MODE_INDIVIDUAL) {
if (props->completion_mode != ACTION_COMPLETION_MODE_INDIVIDUAL) { for (act = actions; act; act = act->next) {
TransactionActionKind type = act->value->type;
if (type != TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP &&
type != TRANSACTION_ACTION_KIND_DRIVE_BACKUP)
{
error_setg(errp,
"Action '%s' does not support transaction property "
"completion-mode = %s",
TransactionActionKind_str(type),
ActionCompletionMode_str(comp_mode));
return;
}
}
block_job_txn = job_txn_new(); block_job_txn = job_txn_new();
} }
/* drain all i/o before any operations */ /* drain all i/o before any operations */
bdrv_drain_all(); bdrv_drain_all();
tran = tran_new();
/* We don't do anything in this loop that commits us to the operations */ /* We don't do anything in this loop that commits us to the operations */
while (NULL != dev_entry) { for (act = actions; act; act = act->next) {
TransactionAction *dev_info = NULL; transaction_action(act->value, block_job_txn, tran, &local_err);
const BlkActionOps *ops;
dev_info = dev_entry->value;
dev_entry = dev_entry->next;
assert(dev_info->type < ARRAY_SIZE(actions));
ops = &actions[dev_info->type];
assert(ops->instance_size > 0);
state = g_malloc0(ops->instance_size);
state->ops = ops;
state->action = dev_info;
state->block_job_txn = block_job_txn;
state->txn_props = props;
QTAILQ_INSERT_TAIL(&snap_bdrv_states, state, entry);
state->ops->prepare(state, &local_err);
if (local_err) { if (local_err) {
error_propagate(errp, local_err); error_propagate(errp, local_err);
goto delete_and_fail; goto delete_and_fail;
} }
} }
QTAILQ_FOREACH(state, &snap_bdrv_states, entry) { tran_commit(tran);
if (state->ops->commit) {
state->ops->commit(state);
}
}
/* success */ /* success */
goto exit; goto exit;
delete_and_fail: delete_and_fail:
/* failure, and it is all-or-none; roll back all operations */ /* failure, and it is all-or-none; roll back all operations */
QTAILQ_FOREACH_REVERSE(state, &snap_bdrv_states, entry) { tran_abort(tran);
if (state->ops->abort) {
state->ops->abort(state);
}
}
exit: exit:
QTAILQ_FOREACH_SAFE(state, &snap_bdrv_states, entry, next) {
if (state->ops->clean) {
state->ops->clean(state);
}
g_free(state);
}
if (!has_props) {
qapi_free_TransactionProperties(props);
}
job_txn_unref(block_job_txn); job_txn_unref(block_job_txn);
} }

View file

@ -319,10 +319,28 @@ static bool block_job_set_speed(BlockJob *job, int64_t speed, Error **errp)
return block_job_set_speed_locked(job, speed, errp); return block_job_set_speed_locked(job, speed, errp);
} }
int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n) void block_job_ratelimit_processed_bytes(BlockJob *job, uint64_t n)
{ {
IO_CODE(); IO_CODE();
return ratelimit_calculate_delay(&job->limit, n); ratelimit_calculate_delay(&job->limit, n);
}
void block_job_ratelimit_sleep(BlockJob *job)
{
uint64_t delay_ns;
/*
* Sleep at least once. If the job is reentered early, keep waiting until
* we've waited for the full time that is necessary to keep the job at the
* right speed.
*
* Make sure to recalculate the delay after each (possibly interrupted)
* sleep because the speed can change while the job has yielded.
*/
do {
delay_ns = ratelimit_calculate_delay(&job->limit, 0);
job_sleep_ns(&job->job, delay_ns);
} while (delay_ns && !job_is_cancelled(&job->job));
} }
BlockJobInfo *block_job_query_locked(BlockJob *job, Error **errp) BlockJobInfo *block_job_query_locked(BlockJob *job, Error **errp)

View file

@ -214,14 +214,18 @@ version 2.
type. type.
If the incompatible bit "Compression type" is set: the field If the incompatible bit "Compression type" is set: the field
must be present and non-zero (which means non-zlib must be present and non-zero (which means non-deflate
compression type). Otherwise, this field must not be present compression type). Otherwise, this field must not be present
or must be zero (which means zlib). or must be zero (which means deflate).
Available compression type values: Available compression type values:
0: zlib <https://www.zlib.net/> 0: deflate <https://www.ietf.org/rfc/rfc1951.txt>
1: zstd <http://github.com/facebook/zstd> 1: zstd <http://github.com/facebook/zstd>
The deflate compression type is called "zlib"
<https://www.zlib.net/> in QEMU. However, clusters with the
deflate compression type do not have zlib headers.
=== Header padding === === Header padding ===

View file

@ -58,14 +58,14 @@ BlockDriver *bdrv_find_protocol(const char *filename,
Error **errp); Error **errp);
BlockDriver *bdrv_find_format(const char *format_name); BlockDriver *bdrv_find_format(const char *format_name);
int coroutine_fn GRAPH_RDLOCK int coroutine_fn GRAPH_UNLOCKED
bdrv_co_create(BlockDriver *drv, const char *filename, QemuOpts *opts, bdrv_co_create(BlockDriver *drv, const char *filename, QemuOpts *opts,
Error **errp); Error **errp);
int co_wrapper_bdrv_rdlock bdrv_create(BlockDriver *drv, const char *filename, int co_wrapper bdrv_create(BlockDriver *drv, const char *filename,
QemuOpts *opts, Error **errp); QemuOpts *opts, Error **errp);
int coroutine_fn GRAPH_RDLOCK int coroutine_fn GRAPH_UNLOCKED
bdrv_co_create_file(const char *filename, QemuOpts *opts, Error **errp); bdrv_co_create_file(const char *filename, QemuOpts *opts, Error **errp);
BlockDriverState *bdrv_new(void); BlockDriverState *bdrv_new(void);

View file

@ -250,10 +250,10 @@ struct BlockDriver {
BlockDriverState *bs, QDict *options, int flags, Error **errp); BlockDriverState *bs, QDict *options, int flags, Error **errp);
void (*bdrv_close)(BlockDriverState *bs); void (*bdrv_close)(BlockDriverState *bs);
int coroutine_fn GRAPH_RDLOCK_PTR (*bdrv_co_create)( int coroutine_fn GRAPH_UNLOCKED_PTR (*bdrv_co_create)(
BlockdevCreateOptions *opts, Error **errp); BlockdevCreateOptions *opts, Error **errp);
int coroutine_fn GRAPH_RDLOCK_PTR (*bdrv_co_create_opts)( int coroutine_fn GRAPH_UNLOCKED_PTR (*bdrv_co_create_opts)(
BlockDriver *drv, const char *filename, QemuOpts *opts, Error **errp); BlockDriver *drv, const char *filename, QemuOpts *opts, Error **errp);
int (*bdrv_amend_options)(BlockDriverState *bs, int (*bdrv_amend_options)(BlockDriverState *bs,

View file

@ -126,12 +126,18 @@ void block_job_user_resume(Job *job);
*/ */
/** /**
* block_job_ratelimit_get_delay: * block_job_ratelimit_processed_bytes:
* *
* Calculate and return delay for the next request in ns. See the documentation * To be called after some work has been done. Adjusts the delay for the next
* of ratelimit_calculate_delay() for details. * request. See the documentation of ratelimit_calculate_delay() for details.
*/ */
int64_t block_job_ratelimit_get_delay(BlockJob *job, uint64_t n); void block_job_ratelimit_processed_bytes(BlockJob *job, uint64_t n);
/**
* Put the job to sleep (assuming that it wasn't canceled) to throttle it to the
* right speed according to its rate limiting.
*/
void block_job_ratelimit_sleep(BlockJob *job);
/** /**
* block_job_error_action: * block_job_error_action:

View file

@ -757,6 +757,16 @@ void qio_channel_detach_aio_context(QIOChannel *ioc);
void coroutine_fn qio_channel_yield(QIOChannel *ioc, void coroutine_fn qio_channel_yield(QIOChannel *ioc,
GIOCondition condition); GIOCondition condition);
/**
* qio_channel_wake_read:
* @ioc: the channel object
*
* If qio_channel_yield() is currently waiting for the channel to become
* readable, interrupt it and reenter immediately. This function is safe to call
* from any thread.
*/
void qio_channel_wake_read(QIOChannel *ioc);
/** /**
* qio_channel_wait: * qio_channel_wait:
* @ioc: the channel object * @ioc: the channel object

View file

@ -19,6 +19,7 @@
*/ */
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "block/aio-wait.h"
#include "io/channel.h" #include "io/channel.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "qemu/main-loop.h" #include "qemu/main-loop.h"
@ -514,7 +515,11 @@ int qio_channel_flush(QIOChannel *ioc,
static void qio_channel_restart_read(void *opaque) static void qio_channel_restart_read(void *opaque)
{ {
QIOChannel *ioc = opaque; QIOChannel *ioc = opaque;
Coroutine *co = ioc->read_coroutine; Coroutine *co = qatomic_xchg(&ioc->read_coroutine, NULL);
if (!co) {
return;
}
/* Assert that aio_co_wake() reenters the coroutine directly */ /* Assert that aio_co_wake() reenters the coroutine directly */
assert(qemu_get_current_aio_context() == assert(qemu_get_current_aio_context() ==
@ -525,7 +530,11 @@ static void qio_channel_restart_read(void *opaque)
static void qio_channel_restart_write(void *opaque) static void qio_channel_restart_write(void *opaque)
{ {
QIOChannel *ioc = opaque; QIOChannel *ioc = opaque;
Coroutine *co = ioc->write_coroutine; Coroutine *co = qatomic_xchg(&ioc->write_coroutine, NULL);
if (!co) {
return;
}
/* Assert that aio_co_wake() reenters the coroutine directly */ /* Assert that aio_co_wake() reenters the coroutine directly */
assert(qemu_get_current_aio_context() == assert(qemu_get_current_aio_context() ==
@ -568,7 +577,11 @@ void qio_channel_detach_aio_context(QIOChannel *ioc)
void coroutine_fn qio_channel_yield(QIOChannel *ioc, void coroutine_fn qio_channel_yield(QIOChannel *ioc,
GIOCondition condition) GIOCondition condition)
{ {
AioContext *ioc_ctx = ioc->ctx ?: qemu_get_aio_context();
assert(qemu_in_coroutine()); assert(qemu_in_coroutine());
assert(in_aio_context_home_thread(ioc_ctx));
if (condition == G_IO_IN) { if (condition == G_IO_IN) {
assert(!ioc->read_coroutine); assert(!ioc->read_coroutine);
ioc->read_coroutine = qemu_coroutine_self(); ioc->read_coroutine = qemu_coroutine_self();
@ -580,18 +593,26 @@ void coroutine_fn qio_channel_yield(QIOChannel *ioc,
} }
qio_channel_set_aio_fd_handlers(ioc); qio_channel_set_aio_fd_handlers(ioc);
qemu_coroutine_yield(); qemu_coroutine_yield();
assert(in_aio_context_home_thread(ioc_ctx));
/* Allow interrupting the operation by reentering the coroutine other than /* Allow interrupting the operation by reentering the coroutine other than
* through the aio_fd_handlers. */ * through the aio_fd_handlers. */
if (condition == G_IO_IN && ioc->read_coroutine) { if (condition == G_IO_IN) {
ioc->read_coroutine = NULL; assert(ioc->read_coroutine == NULL);
qio_channel_set_aio_fd_handlers(ioc); qio_channel_set_aio_fd_handlers(ioc);
} else if (condition == G_IO_OUT && ioc->write_coroutine) { } else if (condition == G_IO_OUT) {
ioc->write_coroutine = NULL; assert(ioc->write_coroutine == NULL);
qio_channel_set_aio_fd_handlers(ioc); qio_channel_set_aio_fd_handlers(ioc);
} }
} }
void qio_channel_wake_read(QIOChannel *ioc)
{
Coroutine *co = qatomic_xchg(&ioc->read_coroutine, NULL);
if (co) {
aio_co_wake(co);
}
}
static gboolean qio_channel_wait_complete(QIOChannel *ioc, static gboolean qio_channel_wait_complete(QIOChannel *ioc,
GIOCondition condition, GIOCondition condition,

View file

@ -1599,8 +1599,7 @@ static bool nbd_drained_poll(void *opaque)
* enter it here so we don't depend on the client to wake it up. * enter it here so we don't depend on the client to wake it up.
*/ */
if (client->recv_coroutine != NULL && client->read_yielding) { if (client->recv_coroutine != NULL && client->read_yielding) {
qemu_aio_coroutine_enter(exp->common.ctx, qio_channel_wake_read(client->ioc);
client->recv_coroutine);
} }
return true; return true;

View file

@ -2938,8 +2938,6 @@ static BlockGraphInfoList *collect_image_info_list(bool image_opts,
} }
bs = blk_bs(blk); bs = blk_bs(blk);
GRAPH_RDLOCK_GUARD_MAINLOOP();
/* /*
* Note that the returned BlockGraphInfo object will not have * Note that the returned BlockGraphInfo object will not have
* information about this image's backing node, because we have opened * information about this image's backing node, because we have opened
@ -2947,7 +2945,10 @@ static BlockGraphInfoList *collect_image_info_list(bool image_opts,
* duplicate the backing chain information that we obtain by walking * duplicate the backing chain information that we obtain by walking
* the chain manually here. * the chain manually here.
*/ */
bdrv_graph_rdlock_main_loop();
bdrv_query_block_graph_info(bs, &info, &err); bdrv_query_block_graph_info(bs, &info, &err);
bdrv_graph_rdunlock_main_loop();
if (err) { if (err) {
error_report_err(err); error_report_err(err);
blk_unref(blk); blk_unref(blk);

View file

@ -611,6 +611,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
self.reopen(hd0_opts, {'file': 'hd0-file'}) self.reopen(hd0_opts, {'file': 'hd0-file'})
# Insert (and remove) a compress filter # Insert (and remove) a compress filter
@iotests.skip_if_unsupported(['compress'])
def test_insert_compress_filter(self): def test_insert_compress_filter(self):
# Add an image to the VM: hd (raw) -> hd0 (qcow2) -> hd0-file (file) # Add an image to the VM: hd (raw) -> hd0 (qcow2) -> hd0-file (file)
opts = {'driver': 'raw', 'node-name': 'hd', 'file': hd_opts(0)} opts = {'driver': 'raw', 'node-name': 'hd', 'file': hd_opts(0)}
@ -650,9 +651,9 @@ class TestBlockdevReopen(iotests.QMPTestCase):
# Check the first byte of the first three L2 entries and verify that # Check the first byte of the first three L2 entries and verify that
# the second one is compressed (0x40) while the others are not (0x80) # the second one is compressed (0x40) while the others are not (0x80)
iotests.qemu_io_log('-f', 'raw', '-c', 'read -P 0x80 0x40000 1', iotests.qemu_io('-f', 'raw', '-c', 'read -P 0x80 0x40000 1',
'-c', 'read -P 0x40 0x40008 1', '-c', 'read -P 0x40 0x40008 1',
'-c', 'read -P 0x80 0x40010 1', hd_path[0]) '-c', 'read -P 0x80 0x40010 1', hd_path[0])
# Swap the disk images of two active block devices # Swap the disk images of two active block devices
def test_swap_files(self): def test_swap_files(self):

View file

@ -10,14 +10,7 @@
{"return": {}} {"return": {}}
{"data": {"id": "stream0", "type": "stream"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"id": "stream0", "type": "stream"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
{"data": {"device": "stream0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "stream"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}} {"data": {"device": "stream0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "stream"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
....read 1/1 bytes at offset 262144 ....................
1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 1/1 bytes at offset 262152
1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
read 1/1 bytes at offset 262160
1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
................
---------------------------------------------------------------------- ----------------------------------------------------------------------
Ran 26 tests Ran 26 tests

View file

@ -462,6 +462,10 @@ class QemuStorageDaemon:
assert self._qmp is not None assert self._qmp is not None
return self._qmp.cmd(cmd, args) return self._qmp.cmd(cmd, args)
def get_qmp(self) -> QEMUMonitorProtocol:
assert self._qmp is not None
return self._qmp
def stop(self, kill_signal=15): def stop(self, kill_signal=15):
self._p.send_signal(kill_signal) self._p.send_signal(kill_signal)
self._p.wait() self._p.wait()

View file

@ -22,19 +22,19 @@
import os import os
from threading import Thread from threading import Thread
import iotests import iotests
from iotests import imgfmt, qemu_img, qemu_img_create, QMPTestCase, \ from iotests import imgfmt, qemu_img, qemu_img_create, qemu_io, \
QemuStorageDaemon QMPTestCase, QemuStorageDaemon
top = os.path.join(iotests.test_dir, 'top.img') top = os.path.join(iotests.test_dir, 'top.img')
nbd_sock = os.path.join(iotests.sock_dir, 'nbd.sock') nbd_sock = os.path.join(iotests.sock_dir, 'nbd.sock')
def do_qemu_img_bench() -> None: def do_qemu_img_bench(count: int = 2000000) -> None:
""" """
Do some I/O requests on `nbd_sock`. Do some I/O requests on `nbd_sock`.
""" """
qemu_img('bench', '-f', 'raw', '-c', '2000000', qemu_img('bench', '-f', 'raw', '-c', str(count),
f'nbd+unix:///node0?socket={nbd_sock}') f'nbd+unix:///node0?socket={nbd_sock}')
@ -84,6 +84,54 @@ class TestGraphChangesWhileIO(QMPTestCase):
bench_thr.join() bench_thr.join()
def test_commit_while_io(self) -> None:
# Run qemu-img bench in the background
bench_thr = Thread(target=do_qemu_img_bench, args=(200000, ))
bench_thr.start()
qemu_io('-c', 'write 0 64k', top)
qemu_io('-c', 'write 128k 64k', top)
result = self.qsd.qmp('blockdev-add', {
'driver': imgfmt,
'node-name': 'overlay',
'backing': None,
'file': {
'driver': 'file',
'filename': top
}
})
self.assert_qmp(result, 'return', {})
result = self.qsd.qmp('blockdev-snapshot', {
'node': 'node0',
'overlay': 'overlay',
})
self.assert_qmp(result, 'return', {})
# While qemu-img bench is running, repeatedly commit overlay to node0
while bench_thr.is_alive():
result = self.qsd.qmp('block-commit', {
'job-id': 'job0',
'device': 'overlay',
})
self.assert_qmp(result, 'return', {})
result = self.qsd.qmp('block-job-cancel', {
'device': 'job0',
})
self.assert_qmp(result, 'return', {})
cancelled = False
while not cancelled:
for event in self.qsd.get_qmp().get_events(wait=10.0):
if event['event'] != 'JOB_STATUS_CHANGE':
continue
if event['data']['status'] == 'null':
cancelled = True
bench_thr.join()
if __name__ == '__main__': if __name__ == '__main__':
# Format must support raw backing files # Format must support raw backing files
iotests.main(supported_fmts=['qcow', 'qcow2', 'qed'], iotests.main(supported_fmts=['qcow', 'qcow2', 'qed'],

View file

@ -1,5 +1,5 @@
. ..
---------------------------------------------------------------------- ----------------------------------------------------------------------
Ran 1 tests Ran 2 tests
OK OK

View file

@ -114,7 +114,10 @@ if have_block
tests += {'test-crypto-xts': [crypto, io]} tests += {'test-crypto-xts': [crypto, io]}
endif endif
if 'CONFIG_POSIX' in config_host if 'CONFIG_POSIX' in config_host
tests += {'test-image-locking': [testblock]} tests += {
'test-image-locking': [testblock],
'test-nested-aio-poll': [testblock],
}
endif endif
if config_host_data.get('CONFIG_REPLICATION') if config_host_data.get('CONFIG_REPLICATION')
tests += {'test-replication': [testblock]} tests += {'test-replication': [testblock]}

View file

@ -1004,8 +1004,6 @@ static void coroutine_fn test_co_delete_by_drain(void *opaque)
void *buffer = g_malloc(65536); void *buffer = g_malloc(65536);
QEMUIOVector qiov = QEMU_IOVEC_INIT_BUF(qiov, buffer, 65536); QEMUIOVector qiov = QEMU_IOVEC_INIT_BUF(qiov, buffer, 65536);
GRAPH_RDLOCK_GUARD();
/* Pretend some internal write operation from parent to child. /* Pretend some internal write operation from parent to child.
* Important: We have to read from the child, not from the parent! * Important: We have to read from the child, not from the parent!
* Draining works by first propagating it all up the tree to the * Draining works by first propagating it all up the tree to the
@ -1014,12 +1012,14 @@ static void coroutine_fn test_co_delete_by_drain(void *opaque)
* everything will be drained before we go back down the tree, but * everything will be drained before we go back down the tree, but
* we do not want that. We want to be in the middle of draining * we do not want that. We want to be in the middle of draining
* when this following requests returns. */ * when this following requests returns. */
bdrv_graph_co_rdlock();
bdrv_co_preadv(tts->wait_child, 0, 65536, &qiov, 0); bdrv_co_preadv(tts->wait_child, 0, 65536, &qiov, 0);
bdrv_graph_co_rdunlock();
g_assert_cmpint(bs->refcnt, ==, 1); g_assert_cmpint(bs->refcnt, ==, 1);
if (!dbdd->detach_instead_of_delete) { if (!dbdd->detach_instead_of_delete) {
blk_unref(blk); blk_co_unref(blk);
} else { } else {
BdrvChild *c, *next_c; BdrvChild *c, *next_c;
QLIST_FOREACH_SAFE(c, &bs->children, next, next_c) { QLIST_FOREACH_SAFE(c, &bs->children, next, next_c) {

View file

@ -0,0 +1,130 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Test that poll handlers are not re-entrant in nested aio_poll()
*
* Copyright Red Hat
*
* Poll handlers are usually level-triggered. That means they continue firing
* until the condition is reset (e.g. a virtqueue becomes empty). If a poll
* handler calls nested aio_poll() before the condition is reset, then infinite
* recursion occurs.
*
* aio_poll() is supposed to prevent this by disabling poll handlers in nested
* aio_poll() calls. This test case checks that this is indeed what happens.
*/
#include "qemu/osdep.h"
#include "block/aio.h"
#include "qapi/error.h"
typedef struct {
AioContext *ctx;
/* This is the EventNotifier that drives the test */
EventNotifier poll_notifier;
/* This EventNotifier is only used to wake aio_poll() */
EventNotifier dummy_notifier;
bool nested;
} TestData;
static void io_read(EventNotifier *notifier)
{
fprintf(stderr, "%s %p\n", __func__, notifier);
event_notifier_test_and_clear(notifier);
}
static bool io_poll_true(void *opaque)
{
fprintf(stderr, "%s %p\n", __func__, opaque);
return true;
}
static bool io_poll_false(void *opaque)
{
fprintf(stderr, "%s %p\n", __func__, opaque);
return false;
}
static void io_poll_ready(EventNotifier *notifier)
{
TestData *td = container_of(notifier, TestData, poll_notifier);
fprintf(stderr, "> %s\n", __func__);
g_assert(!td->nested);
td->nested = true;
/* Wake the following nested aio_poll() call */
event_notifier_set(&td->dummy_notifier);
/* This nested event loop must not call io_poll()/io_poll_ready() */
g_assert(aio_poll(td->ctx, true));
td->nested = false;
fprintf(stderr, "< %s\n", __func__);
}
/* dummy_notifier never triggers */
static void io_poll_never_ready(EventNotifier *notifier)
{
g_assert_not_reached();
}
static void test(void)
{
TestData td = {
.ctx = aio_context_new(&error_abort),
};
qemu_set_current_aio_context(td.ctx);
/* Enable polling */
aio_context_set_poll_params(td.ctx, 1000000, 2, 2, &error_abort);
/*
* The GSource is unused but this has the side-effect of changing the fdmon
* that AioContext uses.
*/
aio_get_g_source(td.ctx);
/* Make the event notifier active (set) right away */
event_notifier_init(&td.poll_notifier, 1);
aio_set_event_notifier(td.ctx, &td.poll_notifier, false,
io_read, io_poll_true, io_poll_ready);
/* This event notifier will be used later */
event_notifier_init(&td.dummy_notifier, 0);
aio_set_event_notifier(td.ctx, &td.dummy_notifier, false,
io_read, io_poll_false, io_poll_never_ready);
/* Consume aio_notify() */
g_assert(!aio_poll(td.ctx, false));
/*
* Run the io_read() handler. This has the side-effect of activating
* polling in future aio_poll() calls.
*/
g_assert(aio_poll(td.ctx, true));
/* The second time around the io_poll()/io_poll_ready() handler runs */
g_assert(aio_poll(td.ctx, true));
/* Run io_poll()/io_poll_ready() one more time to show it keeps working */
g_assert(aio_poll(td.ctx, true));
aio_set_event_notifier(td.ctx, &td.dummy_notifier, false,
NULL, NULL, NULL);
aio_set_event_notifier(td.ctx, &td.poll_notifier, false, NULL, NULL, NULL);
event_notifier_cleanup(&td.dummy_notifier);
event_notifier_cleanup(&td.poll_notifier);
aio_context_unref(td.ctx);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/nested-aio-poll", test);
return g_test_run();
}

View file

@ -353,8 +353,19 @@ static bool aio_dispatch_handler(AioContext *ctx, AioHandler *node)
poll_ready && revents == 0 && poll_ready && revents == 0 &&
aio_node_check(ctx, node->is_external) && aio_node_check(ctx, node->is_external) &&
node->io_poll_ready) { node->io_poll_ready) {
/*
* Remove temporarily to avoid infinite loops when ->io_poll_ready()
* calls aio_poll() before clearing the condition that made the poll
* handler become ready.
*/
QLIST_SAFE_REMOVE(node, node_poll);
node->io_poll_ready(node->opaque); node->io_poll_ready(node->opaque);
if (!QLIST_IS_INSERTED(node, node_poll)) {
QLIST_INSERT_HEAD(&ctx->poll_aio_handlers, node, node_poll);
}
/* /*
* Return early since revents was zero. aio_notify() does not count as * Return early since revents was zero. aio_notify() does not count as
* progress. * progress.