mirror of https://gitee.com/openkylin/qemu.git
Block layer patches:
- block: AioContext management, part 1 - qmp: forbid qmp_cont in RUN_STATE_FINISH_MIGRATE - nvme: fix copy direction in DMA reads going to CMB - file-posix: Fix block status for unaligned raw images with O_DIRECT - file-posix: Fix xfs_write_zeroes() after EOF - Documentation and iotests improvements -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJc4sPmAAoJEH8JsnLIjy/WLnAQAJuLDGqcJ4bIMZN5kNcWJu+j phPpGENVEKpaTSc2EEnJnrgXZwprtYnQGhu/ezMToLlNoXs3ytBYHrFhIEATT1K9 PRN2SIn2mBk7EwLc3J0XVhHOkvksRIXVP62xm7C4M9EolPj/QJk8j2XwpEFdYYSj Khh/72cqFBdDPIoWVYGTQQGyQBv9N2iitYfMjkCaAIv9aaXNIsEGhPSaQiJNTzEG CbCVqRwlWkSPSdX9rc4iY5WUNLeTtdA6wWtaLuj7MZmQ8ynfzG1bYZX9OqDQ6bM3 hBdjLJTsyFJIuRotY8AmO+WYkUWoODuh5i9GRxARyidfMjW2nkSSaFfDMhCj/eWZ 7gVUE/+W5gUCfG8oTDPf1KKetxddMcd1V8B4YHRiQUOwHqHHzEGYMV5h+rMrsuNl MxiHE7UIqF+3TG2KTfzAsTxFxDSgsek0RzUV+hfCoygDh9vVNhk7D2oi4wxiN3iX LZPpi7nwztBzDcPncNCcisbEWVmu9GTs3YNyPh6piCab5gcCtkBcvht5OPN7hSiw VyO5AEXHtGJW2gFx4u6rWfiFlj80m97rP2dxtaJD/48WzGY7WZ056JPnY+6Lv+8t tjgDbkSp8Tu3DHmZwWB9XnRT+HRsSNx9MUXwbRpVujujxw6Obl7aMhwpG/BoS+ax EdJbW0N8k6ZigFyj+V6z =kdOA -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches: - block: AioContext management, part 1 - qmp: forbid qmp_cont in RUN_STATE_FINISH_MIGRATE - nvme: fix copy direction in DMA reads going to CMB - file-posix: Fix block status for unaligned raw images with O_DIRECT - file-posix: Fix xfs_write_zeroes() after EOF - Documentation and iotests improvements # gpg: Signature made Mon 20 May 2019 16:12:38 BST # gpg: using RSA key 7F09B272C88F2FD6 # gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" [full] # Primary key fingerprint: DC3D EB15 9A9A F95D 3D74 56FE 7F09 B272 C88F 2FD6 * remotes/kevin/tags/for-upstream: (24 commits) iotests: Make 245 faster and more reliable iotests.py: Fix VM.run_job iotests.py: Let assert_qmp() accept an array block: Improve "Block node is read-only" message qemu-img.texi: Describe human-readable info output qemu-img.texi: Be specific about JSON object types iotests: Test unaligned raw images with O_DIRECT block/file-posix: Unaligned O_DIRECT block-status test-block-iothread: Test AioContext propagation for block jobs blockjob: Remove AioContext notifiers blockjob: Propagate AioContext change to all job nodes block: Add blk_set_allow_aio_context_change() block: Implement .(can_)set_aio_ctx for BlockBackend test-block-iothread: Test AioContext propagation through the tree block: Propagate AioContext change to parents block: Move recursion to bdrv_set_aio_context() block: Make bdrv_attach/detach_aio_context() static block: Add bdrv_try_set_aio_context() nvme: fix copy direction in DMA reads going to CMB iotest: fix 169: do not run qmp_cont in RUN_STATE_FINISH_MIGRATE ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
2259637b95
174
block.c
174
block.c
|
@ -936,6 +936,20 @@ static int bdrv_child_cb_inactivate(BdrvChild *child)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool bdrv_child_cb_can_set_aio_ctx(BdrvChild *child, AioContext *ctx,
|
||||
GSList **ignore, Error **errp)
|
||||
{
|
||||
BlockDriverState *bs = child->opaque;
|
||||
return bdrv_can_set_aio_context(bs, ctx, ignore, errp);
|
||||
}
|
||||
|
||||
static void bdrv_child_cb_set_aio_ctx(BdrvChild *child, AioContext *ctx,
|
||||
GSList **ignore)
|
||||
{
|
||||
BlockDriverState *bs = child->opaque;
|
||||
return bdrv_set_aio_context_ignore(bs, ctx, ignore);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the options and flags that a temporary snapshot should get, based on
|
||||
* the originally requested flags (the originally requested image will have
|
||||
|
@ -1003,6 +1017,8 @@ const BdrvChildRole child_file = {
|
|||
.attach = bdrv_child_cb_attach,
|
||||
.detach = bdrv_child_cb_detach,
|
||||
.inactivate = bdrv_child_cb_inactivate,
|
||||
.can_set_aio_ctx = bdrv_child_cb_can_set_aio_ctx,
|
||||
.set_aio_ctx = bdrv_child_cb_set_aio_ctx,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -1029,6 +1045,8 @@ const BdrvChildRole child_format = {
|
|||
.attach = bdrv_child_cb_attach,
|
||||
.detach = bdrv_child_cb_detach,
|
||||
.inactivate = bdrv_child_cb_inactivate,
|
||||
.can_set_aio_ctx = bdrv_child_cb_can_set_aio_ctx,
|
||||
.set_aio_ctx = bdrv_child_cb_set_aio_ctx,
|
||||
};
|
||||
|
||||
static void bdrv_backing_attach(BdrvChild *c)
|
||||
|
@ -1152,6 +1170,8 @@ const BdrvChildRole child_backing = {
|
|||
.drained_end = bdrv_child_cb_drained_end,
|
||||
.inactivate = bdrv_child_cb_inactivate,
|
||||
.update_filename = bdrv_backing_update_filename,
|
||||
.can_set_aio_ctx = bdrv_child_cb_can_set_aio_ctx,
|
||||
.set_aio_ctx = bdrv_child_cb_set_aio_ctx,
|
||||
};
|
||||
|
||||
static int bdrv_open_flags(BlockDriverState *bs, int flags)
|
||||
|
@ -1689,6 +1709,8 @@ static int bdrv_child_check_perm(BdrvChild *c, BlockReopenQueue *q,
|
|||
GSList *ignore_children, Error **errp);
|
||||
static void bdrv_child_abort_perm_update(BdrvChild *c);
|
||||
static void bdrv_child_set_perm(BdrvChild *c, uint64_t perm, uint64_t shared);
|
||||
static void bdrv_get_cumulative_perm(BlockDriverState *bs, uint64_t *perm,
|
||||
uint64_t *shared_perm);
|
||||
|
||||
typedef struct BlockReopenQueueEntry {
|
||||
bool prepared;
|
||||
|
@ -1775,7 +1797,20 @@ static int bdrv_check_perm(BlockDriverState *bs, BlockReopenQueue *q,
|
|||
if ((cumulative_perms & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED)) &&
|
||||
!bdrv_is_writable_after_reopen(bs, q))
|
||||
{
|
||||
error_setg(errp, "Block node is read-only");
|
||||
if (!bdrv_is_writable_after_reopen(bs, NULL)) {
|
||||
error_setg(errp, "Block node is read-only");
|
||||
} else {
|
||||
uint64_t current_perms, current_shared;
|
||||
bdrv_get_cumulative_perm(bs, ¤t_perms, ¤t_shared);
|
||||
if (current_perms & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED)) {
|
||||
error_setg(errp, "Cannot make block node read-only, there is "
|
||||
"a writer on it");
|
||||
} else {
|
||||
error_setg(errp, "Cannot make block node read-only and create "
|
||||
"a writer on it");
|
||||
}
|
||||
}
|
||||
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
|
@ -5666,10 +5701,9 @@ static void bdrv_do_remove_aio_context_notifier(BdrvAioNotifier *ban)
|
|||
g_free(ban);
|
||||
}
|
||||
|
||||
void bdrv_detach_aio_context(BlockDriverState *bs)
|
||||
static void bdrv_detach_aio_context(BlockDriverState *bs)
|
||||
{
|
||||
BdrvAioNotifier *baf, *baf_tmp;
|
||||
BdrvChild *child;
|
||||
|
||||
assert(!bs->walking_aio_notifiers);
|
||||
bs->walking_aio_notifiers = true;
|
||||
|
@ -5688,9 +5722,6 @@ void bdrv_detach_aio_context(BlockDriverState *bs)
|
|||
if (bs->drv && bs->drv->bdrv_detach_aio_context) {
|
||||
bs->drv->bdrv_detach_aio_context(bs);
|
||||
}
|
||||
QLIST_FOREACH(child, &bs->children, next) {
|
||||
bdrv_detach_aio_context(child->bs);
|
||||
}
|
||||
|
||||
if (bs->quiesce_counter) {
|
||||
aio_enable_external(bs->aio_context);
|
||||
|
@ -5698,11 +5729,10 @@ void bdrv_detach_aio_context(BlockDriverState *bs)
|
|||
bs->aio_context = NULL;
|
||||
}
|
||||
|
||||
void bdrv_attach_aio_context(BlockDriverState *bs,
|
||||
AioContext *new_context)
|
||||
static void bdrv_attach_aio_context(BlockDriverState *bs,
|
||||
AioContext *new_context)
|
||||
{
|
||||
BdrvAioNotifier *ban, *ban_tmp;
|
||||
BdrvChild *child;
|
||||
|
||||
if (bs->quiesce_counter) {
|
||||
aio_disable_external(new_context);
|
||||
|
@ -5710,9 +5740,6 @@ void bdrv_attach_aio_context(BlockDriverState *bs,
|
|||
|
||||
bs->aio_context = new_context;
|
||||
|
||||
QLIST_FOREACH(child, &bs->children, next) {
|
||||
bdrv_attach_aio_context(child->bs, new_context);
|
||||
}
|
||||
if (bs->drv && bs->drv->bdrv_attach_aio_context) {
|
||||
bs->drv->bdrv_attach_aio_context(bs, new_context);
|
||||
}
|
||||
|
@ -5729,16 +5756,36 @@ void bdrv_attach_aio_context(BlockDriverState *bs,
|
|||
bs->walking_aio_notifiers = false;
|
||||
}
|
||||
|
||||
/* The caller must own the AioContext lock for the old AioContext of bs, but it
|
||||
* must not own the AioContext lock for new_context (unless new_context is
|
||||
* the same as the current context of bs). */
|
||||
void bdrv_set_aio_context(BlockDriverState *bs, AioContext *new_context)
|
||||
/* @ignore will accumulate all visited BdrvChild object. The caller is
|
||||
* responsible for freeing the list afterwards. */
|
||||
void bdrv_set_aio_context_ignore(BlockDriverState *bs,
|
||||
AioContext *new_context, GSList **ignore)
|
||||
{
|
||||
BdrvChild *child;
|
||||
|
||||
if (bdrv_get_aio_context(bs) == new_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
bdrv_drained_begin(bs);
|
||||
|
||||
QLIST_FOREACH(child, &bs->children, next) {
|
||||
if (g_slist_find(*ignore, child)) {
|
||||
continue;
|
||||
}
|
||||
*ignore = g_slist_prepend(*ignore, child);
|
||||
bdrv_set_aio_context_ignore(child->bs, new_context, ignore);
|
||||
}
|
||||
QLIST_FOREACH(child, &bs->parents, next_parent) {
|
||||
if (g_slist_find(*ignore, child)) {
|
||||
continue;
|
||||
}
|
||||
if (child->role->set_aio_ctx) {
|
||||
*ignore = g_slist_prepend(*ignore, child);
|
||||
child->role->set_aio_ctx(child, new_context, ignore);
|
||||
}
|
||||
}
|
||||
|
||||
bdrv_detach_aio_context(bs);
|
||||
|
||||
/* This function executes in the old AioContext so acquire the new one in
|
||||
|
@ -5750,6 +5797,101 @@ void bdrv_set_aio_context(BlockDriverState *bs, AioContext *new_context)
|
|||
aio_context_release(new_context);
|
||||
}
|
||||
|
||||
/* The caller must own the AioContext lock for the old AioContext of bs, but it
|
||||
* must not own the AioContext lock for new_context (unless new_context is
|
||||
* the same as the current context of bs). */
|
||||
void bdrv_set_aio_context(BlockDriverState *bs, AioContext *new_context)
|
||||
{
|
||||
GSList *ignore_list = NULL;
|
||||
bdrv_set_aio_context_ignore(bs, new_context, &ignore_list);
|
||||
g_slist_free(ignore_list);
|
||||
}
|
||||
|
||||
static bool bdrv_parent_can_set_aio_context(BdrvChild *c, AioContext *ctx,
|
||||
GSList **ignore, Error **errp)
|
||||
{
|
||||
if (g_slist_find(*ignore, c)) {
|
||||
return true;
|
||||
}
|
||||
*ignore = g_slist_prepend(*ignore, c);
|
||||
|
||||
/* A BdrvChildRole that doesn't handle AioContext changes cannot
|
||||
* tolerate any AioContext changes */
|
||||
if (!c->role->can_set_aio_ctx) {
|
||||
char *user = bdrv_child_user_desc(c);
|
||||
error_setg(errp, "Changing iothreads is not supported by %s", user);
|
||||
g_free(user);
|
||||
return false;
|
||||
}
|
||||
if (!c->role->can_set_aio_ctx(c, ctx, ignore, errp)) {
|
||||
assert(!errp || *errp);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bdrv_child_can_set_aio_context(BdrvChild *c, AioContext *ctx,
|
||||
GSList **ignore, Error **errp)
|
||||
{
|
||||
if (g_slist_find(*ignore, c)) {
|
||||
return true;
|
||||
}
|
||||
*ignore = g_slist_prepend(*ignore, c);
|
||||
return bdrv_can_set_aio_context(c->bs, ctx, ignore, errp);
|
||||
}
|
||||
|
||||
/* @ignore will accumulate all visited BdrvChild object. The caller is
|
||||
* responsible for freeing the list afterwards. */
|
||||
bool bdrv_can_set_aio_context(BlockDriverState *bs, AioContext *ctx,
|
||||
GSList **ignore, Error **errp)
|
||||
{
|
||||
BdrvChild *c;
|
||||
|
||||
if (bdrv_get_aio_context(bs) == ctx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QLIST_FOREACH(c, &bs->parents, next_parent) {
|
||||
if (!bdrv_parent_can_set_aio_context(c, ctx, ignore, errp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
QLIST_FOREACH(c, &bs->children, next) {
|
||||
if (!bdrv_child_can_set_aio_context(c, ctx, ignore, errp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int bdrv_child_try_set_aio_context(BlockDriverState *bs, AioContext *ctx,
|
||||
BdrvChild *ignore_child, Error **errp)
|
||||
{
|
||||
GSList *ignore;
|
||||
bool ret;
|
||||
|
||||
ignore = ignore_child ? g_slist_prepend(NULL, ignore_child) : NULL;
|
||||
ret = bdrv_can_set_aio_context(bs, ctx, &ignore, errp);
|
||||
g_slist_free(ignore);
|
||||
|
||||
if (!ret) {
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
ignore = ignore_child ? g_slist_prepend(NULL, ignore_child) : NULL;
|
||||
bdrv_set_aio_context_ignore(bs, ctx, &ignore);
|
||||
g_slist_free(ignore);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bdrv_try_set_aio_context(BlockDriverState *bs, AioContext *ctx,
|
||||
Error **errp)
|
||||
{
|
||||
return bdrv_child_try_set_aio_context(bs, ctx, NULL, errp);
|
||||
}
|
||||
|
||||
void bdrv_add_aio_context_notifier(BlockDriverState *bs,
|
||||
void (*attached_aio_context)(AioContext *new_context, void *opaque),
|
||||
void (*detach_aio_context)(void *opaque), void *opaque)
|
||||
|
|
|
@ -300,13 +300,6 @@ static void backup_clean(Job *job)
|
|||
s->target = NULL;
|
||||
}
|
||||
|
||||
static void backup_attached_aio_context(BlockJob *job, AioContext *aio_context)
|
||||
{
|
||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
||||
|
||||
blk_set_aio_context(s->target, aio_context);
|
||||
}
|
||||
|
||||
void backup_do_checkpoint(BlockJob *job, Error **errp)
|
||||
{
|
||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
||||
|
@ -558,7 +551,6 @@ static const BlockJobDriver backup_job_driver = {
|
|||
.abort = backup_abort,
|
||||
.clean = backup_clean,
|
||||
},
|
||||
.attached_aio_context = backup_attached_aio_context,
|
||||
.drain = backup_drain,
|
||||
};
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ struct BlockBackend {
|
|||
uint64_t shared_perm;
|
||||
bool disable_perm;
|
||||
|
||||
bool allow_aio_context_change;
|
||||
bool allow_write_beyond_eof;
|
||||
|
||||
NotifierList remove_bs_notifiers, insert_bs_notifiers;
|
||||
|
@ -124,6 +125,11 @@ static void blk_root_drained_end(BdrvChild *child);
|
|||
static void blk_root_change_media(BdrvChild *child, bool load);
|
||||
static void blk_root_resize(BdrvChild *child);
|
||||
|
||||
static bool blk_root_can_set_aio_ctx(BdrvChild *child, AioContext *ctx,
|
||||
GSList **ignore, Error **errp);
|
||||
static void blk_root_set_aio_ctx(BdrvChild *child, AioContext *ctx,
|
||||
GSList **ignore);
|
||||
|
||||
static char *blk_root_get_parent_desc(BdrvChild *child)
|
||||
{
|
||||
BlockBackend *blk = child->opaque;
|
||||
|
@ -300,6 +306,9 @@ static const BdrvChildRole child_root = {
|
|||
|
||||
.attach = blk_root_attach,
|
||||
.detach = blk_root_detach,
|
||||
|
||||
.can_set_aio_ctx = blk_root_can_set_aio_ctx,
|
||||
.set_aio_ctx = blk_root_set_aio_ctx,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -1084,6 +1093,11 @@ void blk_set_allow_write_beyond_eof(BlockBackend *blk, bool allow)
|
|||
blk->allow_write_beyond_eof = allow;
|
||||
}
|
||||
|
||||
void blk_set_allow_aio_context_change(BlockBackend *blk, bool allow)
|
||||
{
|
||||
blk->allow_aio_context_change = allow;
|
||||
}
|
||||
|
||||
static int blk_check_byte_request(BlockBackend *blk, int64_t offset,
|
||||
size_t size)
|
||||
{
|
||||
|
@ -1852,7 +1866,8 @@ static AioContext *blk_aiocb_get_aio_context(BlockAIOCB *acb)
|
|||
return blk_get_aio_context(blk_acb->blk);
|
||||
}
|
||||
|
||||
void blk_set_aio_context(BlockBackend *blk, AioContext *new_context)
|
||||
static void blk_do_set_aio_context(BlockBackend *blk, AioContext *new_context,
|
||||
bool update_root_node)
|
||||
{
|
||||
BlockDriverState *bs = blk_bs(blk);
|
||||
ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
|
||||
|
@ -1864,10 +1879,46 @@ void blk_set_aio_context(BlockBackend *blk, AioContext *new_context)
|
|||
throttle_group_attach_aio_context(tgm, new_context);
|
||||
bdrv_drained_end(bs);
|
||||
}
|
||||
bdrv_set_aio_context(bs, new_context);
|
||||
if (update_root_node) {
|
||||
GSList *ignore = g_slist_prepend(NULL, blk->root);
|
||||
bdrv_set_aio_context_ignore(bs, new_context, &ignore);
|
||||
g_slist_free(ignore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void blk_set_aio_context(BlockBackend *blk, AioContext *new_context)
|
||||
{
|
||||
blk_do_set_aio_context(blk, new_context, true);
|
||||
}
|
||||
|
||||
static bool blk_root_can_set_aio_ctx(BdrvChild *child, AioContext *ctx,
|
||||
GSList **ignore, Error **errp)
|
||||
{
|
||||
BlockBackend *blk = child->opaque;
|
||||
|
||||
if (blk->allow_aio_context_change) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Only manually created BlockBackends that are not attached to anything
|
||||
* can change their AioContext without updating their user. */
|
||||
if (!blk->name || blk->dev) {
|
||||
/* TODO Add BB name/QOM path */
|
||||
error_setg(errp, "Cannot change iothread of active block backend");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void blk_root_set_aio_ctx(BdrvChild *child, AioContext *ctx,
|
||||
GSList **ignore)
|
||||
{
|
||||
BlockBackend *blk = child->opaque;
|
||||
blk_do_set_aio_context(blk, ctx, false);
|
||||
}
|
||||
|
||||
void blk_add_aio_context_notifier(BlockBackend *blk,
|
||||
void (*attached_aio_context)(AioContext *new_context, void *opaque),
|
||||
void (*detach_aio_context)(void *opaque), void *opaque)
|
||||
|
|
|
@ -1444,9 +1444,22 @@ out:
|
|||
#ifdef CONFIG_XFS
|
||||
static int xfs_write_zeroes(BDRVRawState *s, int64_t offset, uint64_t bytes)
|
||||
{
|
||||
int64_t len;
|
||||
struct xfs_flock64 fl;
|
||||
int err;
|
||||
|
||||
len = lseek(s->fd, 0, SEEK_END);
|
||||
if (len < 0) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (offset + bytes > len) {
|
||||
/* XFS_IOC_ZERO_RANGE does not increase the file length */
|
||||
if (ftruncate(s->fd, offset + bytes) < 0) {
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
|
||||
memset(&fl, 0, sizeof(fl));
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = offset;
|
||||
|
@ -2475,6 +2488,8 @@ static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
|
|||
off_t data = 0, hole = 0;
|
||||
int ret;
|
||||
|
||||
assert(QEMU_IS_ALIGNED(offset | bytes, bs->bl.request_alignment));
|
||||
|
||||
ret = fd_open(bs);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
|
@ -2500,6 +2515,20 @@ static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
|
|||
/* On a data extent, compute bytes to the end of the extent,
|
||||
* possibly including a partial sector at EOF. */
|
||||
*pnum = MIN(bytes, hole - offset);
|
||||
|
||||
/*
|
||||
* We are not allowed to return partial sectors, though, so
|
||||
* round up if necessary.
|
||||
*/
|
||||
if (!QEMU_IS_ALIGNED(*pnum, bs->bl.request_alignment)) {
|
||||
int64_t file_length = raw_getlength(bs);
|
||||
if (file_length > 0) {
|
||||
/* Ignore errors, this is just a safeguard */
|
||||
assert(hole == file_length);
|
||||
}
|
||||
*pnum = ROUND_UP(*pnum, bs->bl.request_alignment);
|
||||
}
|
||||
|
||||
ret = BDRV_BLOCK_DATA;
|
||||
} else {
|
||||
/* On a hole, compute bytes to the beginning of the next extent. */
|
||||
|
|
|
@ -769,7 +769,7 @@ static bool coroutine_fn wait_serialising_requests(BdrvTrackedRequest *self)
|
|||
static int bdrv_check_byte_request(BlockDriverState *bs, int64_t offset,
|
||||
size_t size)
|
||||
{
|
||||
if (size > BDRV_REQUEST_MAX_SECTORS << BDRV_SECTOR_BITS) {
|
||||
if (size > BDRV_REQUEST_MAX_BYTES) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
@ -1017,7 +1017,7 @@ static int coroutine_fn bdrv_driver_preadv(BlockDriverState *bs,
|
|||
|
||||
assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||
assert((bytes >> BDRV_SECTOR_BITS) <= BDRV_REQUEST_MAX_SECTORS);
|
||||
assert(bytes <= BDRV_REQUEST_MAX_BYTES);
|
||||
assert(drv->bdrv_co_readv);
|
||||
|
||||
return drv->bdrv_co_readv(bs, sector_num, nb_sectors, qiov);
|
||||
|
@ -1070,7 +1070,7 @@ static int coroutine_fn bdrv_driver_pwritev(BlockDriverState *bs,
|
|||
|
||||
assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||
assert((bytes >> BDRV_SECTOR_BITS) <= BDRV_REQUEST_MAX_SECTORS);
|
||||
assert(bytes <= BDRV_REQUEST_MAX_BYTES);
|
||||
|
||||
assert(drv->bdrv_co_writev);
|
||||
ret = drv->bdrv_co_writev(bs, sector_num, nb_sectors, qiov,
|
||||
|
|
|
@ -1142,13 +1142,6 @@ static bool mirror_drained_poll(BlockJob *job)
|
|||
return !!s->in_flight;
|
||||
}
|
||||
|
||||
static void mirror_attached_aio_context(BlockJob *job, AioContext *new_context)
|
||||
{
|
||||
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
||||
|
||||
blk_set_aio_context(s->target, new_context);
|
||||
}
|
||||
|
||||
static void mirror_drain(BlockJob *job)
|
||||
{
|
||||
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
|
||||
|
@ -1178,7 +1171,6 @@ static const BlockJobDriver mirror_job_driver = {
|
|||
.complete = mirror_complete,
|
||||
},
|
||||
.drained_poll = mirror_drained_poll,
|
||||
.attached_aio_context = mirror_attached_aio_context,
|
||||
.drain = mirror_drain,
|
||||
};
|
||||
|
||||
|
@ -1196,7 +1188,6 @@ static const BlockJobDriver commit_active_job_driver = {
|
|||
.complete = mirror_complete,
|
||||
},
|
||||
.drained_poll = mirror_drained_poll,
|
||||
.attached_aio_context = mirror_attached_aio_context,
|
||||
.drain = mirror_drain,
|
||||
};
|
||||
|
||||
|
@ -1612,6 +1603,7 @@ static void mirror_start_job(const char *job_id, BlockDriverState *bs,
|
|||
* ensure that. */
|
||||
blk_set_force_allow_inactivate(s->target);
|
||||
}
|
||||
blk_set_allow_aio_context_change(s->target, true);
|
||||
|
||||
s->replaces = g_strdup(replaces);
|
||||
s->on_source_error = on_source_error;
|
||||
|
|
|
@ -796,8 +796,9 @@ int qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs,
|
|||
return cluster_offset;
|
||||
}
|
||||
|
||||
nb_csectors = ((cluster_offset + compressed_size - 1) >> 9) -
|
||||
(cluster_offset >> 9);
|
||||
nb_csectors =
|
||||
(cluster_offset + compressed_size - 1) / QCOW2_COMPRESSED_SECTOR_SIZE -
|
||||
(cluster_offset / QCOW2_COMPRESSED_SECTOR_SIZE);
|
||||
|
||||
cluster_offset |= QCOW_OFLAG_COMPRESSED |
|
||||
((uint64_t)nb_csectors << s->csize_shift);
|
||||
|
|
|
@ -1172,12 +1172,11 @@ void qcow2_free_any_clusters(BlockDriverState *bs, uint64_t l2_entry,
|
|||
switch (ctype) {
|
||||
case QCOW2_CLUSTER_COMPRESSED:
|
||||
{
|
||||
int nb_csectors;
|
||||
nb_csectors = ((l2_entry >> s->csize_shift) &
|
||||
s->csize_mask) + 1;
|
||||
qcow2_free_clusters(bs,
|
||||
(l2_entry & s->cluster_offset_mask) & ~511,
|
||||
nb_csectors * 512, type);
|
||||
int64_t offset = (l2_entry & s->cluster_offset_mask)
|
||||
& QCOW2_COMPRESSED_SECTOR_MASK;
|
||||
int size = QCOW2_COMPRESSED_SECTOR_SIZE *
|
||||
(((l2_entry >> s->csize_shift) & s->csize_mask) + 1);
|
||||
qcow2_free_clusters(bs, offset, size, type);
|
||||
}
|
||||
break;
|
||||
case QCOW2_CLUSTER_NORMAL:
|
||||
|
@ -1317,9 +1316,12 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs,
|
|||
nb_csectors = ((entry >> s->csize_shift) &
|
||||
s->csize_mask) + 1;
|
||||
if (addend != 0) {
|
||||
uint64_t coffset = (entry & s->cluster_offset_mask)
|
||||
& QCOW2_COMPRESSED_SECTOR_MASK;
|
||||
ret = update_refcount(
|
||||
bs, (entry & s->cluster_offset_mask) & ~511,
|
||||
nb_csectors * 512, abs(addend), addend < 0,
|
||||
bs, coffset,
|
||||
nb_csectors * QCOW2_COMPRESSED_SECTOR_SIZE,
|
||||
abs(addend), addend < 0,
|
||||
QCOW2_DISCARD_SNAPSHOT);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
|
@ -1635,9 +1637,10 @@ static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res,
|
|||
nb_csectors = ((l2_entry >> s->csize_shift) &
|
||||
s->csize_mask) + 1;
|
||||
l2_entry &= s->cluster_offset_mask;
|
||||
ret = qcow2_inc_refcounts_imrt(bs, res,
|
||||
refcount_table, refcount_table_size,
|
||||
l2_entry & ~511, nb_csectors * 512);
|
||||
ret = qcow2_inc_refcounts_imrt(
|
||||
bs, res, refcount_table, refcount_table_size,
|
||||
l2_entry & QCOW2_COMPRESSED_SECTOR_MASK,
|
||||
nb_csectors * QCOW2_COMPRESSED_SECTOR_SIZE);
|
||||
if (ret < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
@ -4187,7 +4187,8 @@ qcow2_co_preadv_compressed(BlockDriverState *bs,
|
|||
|
||||
coffset = file_cluster_offset & s->cluster_offset_mask;
|
||||
nb_csectors = ((file_cluster_offset >> s->csize_shift) & s->csize_mask) + 1;
|
||||
csize = nb_csectors * 512 - (coffset & 511);
|
||||
csize = nb_csectors * QCOW2_COMPRESSED_SECTOR_SIZE -
|
||||
(coffset & ~QCOW2_COMPRESSED_SECTOR_MASK);
|
||||
|
||||
buf = g_try_malloc(csize);
|
||||
if (!buf) {
|
||||
|
|
|
@ -74,6 +74,10 @@
|
|||
#define MIN_CLUSTER_BITS 9
|
||||
#define MAX_CLUSTER_BITS 21
|
||||
|
||||
/* Defined in the qcow2 spec (compressed cluster descriptor) */
|
||||
#define QCOW2_COMPRESSED_SECTOR_SIZE 512U
|
||||
#define QCOW2_COMPRESSED_SECTOR_MASK (~(QCOW2_COMPRESSED_SECTOR_SIZE - 1))
|
||||
|
||||
/* Must be at least 2 to cover COW */
|
||||
#define MIN_L2_CACHE_SIZE 2 /* cache entries */
|
||||
|
||||
|
|
77
blockjob.c
77
blockjob.c
|
@ -81,10 +81,6 @@ BlockJob *block_job_get(const char *id)
|
|||
}
|
||||
}
|
||||
|
||||
static void block_job_attached_aio_context(AioContext *new_context,
|
||||
void *opaque);
|
||||
static void block_job_detach_aio_context(void *opaque);
|
||||
|
||||
void block_job_free(Job *job)
|
||||
{
|
||||
BlockJob *bjob = container_of(job, BlockJob, job);
|
||||
|
@ -92,28 +88,10 @@ void block_job_free(Job *job)
|
|||
|
||||
bs->job = NULL;
|
||||
block_job_remove_all_bdrv(bjob);
|
||||
blk_remove_aio_context_notifier(bjob->blk,
|
||||
block_job_attached_aio_context,
|
||||
block_job_detach_aio_context, bjob);
|
||||
blk_unref(bjob->blk);
|
||||
error_free(bjob->blocker);
|
||||
}
|
||||
|
||||
static void block_job_attached_aio_context(AioContext *new_context,
|
||||
void *opaque)
|
||||
{
|
||||
BlockJob *job = opaque;
|
||||
const JobDriver *drv = job->job.driver;
|
||||
BlockJobDriver *bjdrv = container_of(drv, BlockJobDriver, job_driver);
|
||||
|
||||
job->job.aio_context = new_context;
|
||||
if (bjdrv->attached_aio_context) {
|
||||
bjdrv->attached_aio_context(job, new_context);
|
||||
}
|
||||
|
||||
job_resume(&job->job);
|
||||
}
|
||||
|
||||
void block_job_drain(Job *job)
|
||||
{
|
||||
BlockJob *bjob = container_of(job, BlockJob, job);
|
||||
|
@ -126,23 +104,6 @@ void block_job_drain(Job *job)
|
|||
}
|
||||
}
|
||||
|
||||
static void block_job_detach_aio_context(void *opaque)
|
||||
{
|
||||
BlockJob *job = opaque;
|
||||
|
||||
/* In case the job terminates during aio_poll()... */
|
||||
job_ref(&job->job);
|
||||
|
||||
job_pause(&job->job);
|
||||
|
||||
while (!job->job.paused && !job_is_completed(&job->job)) {
|
||||
job_drain(&job->job);
|
||||
}
|
||||
|
||||
job->job.aio_context = NULL;
|
||||
job_unref(&job->job);
|
||||
}
|
||||
|
||||
static char *child_job_get_parent_desc(BdrvChild *c)
|
||||
{
|
||||
BlockJob *job = c->opaque;
|
||||
|
@ -183,11 +144,46 @@ static void child_job_drained_end(BdrvChild *c)
|
|||
job_resume(&job->job);
|
||||
}
|
||||
|
||||
static bool child_job_can_set_aio_ctx(BdrvChild *c, AioContext *ctx,
|
||||
GSList **ignore, Error **errp)
|
||||
{
|
||||
BlockJob *job = c->opaque;
|
||||
GSList *l;
|
||||
|
||||
for (l = job->nodes; l; l = l->next) {
|
||||
BdrvChild *sibling = l->data;
|
||||
if (!bdrv_child_can_set_aio_context(sibling, ctx, ignore, errp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void child_job_set_aio_ctx(BdrvChild *c, AioContext *ctx,
|
||||
GSList **ignore)
|
||||
{
|
||||
BlockJob *job = c->opaque;
|
||||
GSList *l;
|
||||
|
||||
for (l = job->nodes; l; l = l->next) {
|
||||
BdrvChild *sibling = l->data;
|
||||
if (g_slist_find(*ignore, sibling)) {
|
||||
continue;
|
||||
}
|
||||
*ignore = g_slist_prepend(*ignore, sibling);
|
||||
bdrv_set_aio_context_ignore(sibling->bs, ctx, ignore);
|
||||
}
|
||||
|
||||
job->job.aio_context = ctx;
|
||||
}
|
||||
|
||||
static const BdrvChildRole child_job = {
|
||||
.get_parent_desc = child_job_get_parent_desc,
|
||||
.drained_begin = child_job_drained_begin,
|
||||
.drained_poll = child_job_drained_poll,
|
||||
.drained_end = child_job_drained_end,
|
||||
.can_set_aio_ctx = child_job_can_set_aio_ctx,
|
||||
.set_aio_ctx = child_job_set_aio_ctx,
|
||||
.stay_at_node = true,
|
||||
};
|
||||
|
||||
|
@ -438,8 +434,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
|
|||
|
||||
bdrv_op_unblock(bs, BLOCK_OP_TYPE_DATAPLANE, job->blocker);
|
||||
|
||||
blk_add_aio_context_notifier(blk, block_job_attached_aio_context,
|
||||
block_job_detach_aio_context, job);
|
||||
blk_set_allow_aio_context_change(blk, true);
|
||||
|
||||
/* Only set speed when necessary to avoid NotSupported error */
|
||||
if (speed != 0) {
|
||||
|
|
|
@ -238,7 +238,7 @@ static uint16_t nvme_dma_read_prp(NvmeCtrl *n, uint8_t *ptr, uint32_t len,
|
|||
}
|
||||
qemu_sglist_destroy(&qsg);
|
||||
} else {
|
||||
if (unlikely(qemu_iovec_to_buf(&iov, 0, ptr, len) != len)) {
|
||||
if (unlikely(qemu_iovec_from_buf(&iov, 0, ptr, len) != len)) {
|
||||
trace_nvme_err_invalid_dma();
|
||||
status = NVME_INVALID_FIELD | NVME_DNR;
|
||||
}
|
||||
|
|
|
@ -586,6 +586,16 @@ void bdrv_coroutine_enter(BlockDriverState *bs, Coroutine *co);
|
|||
* This function must be called with iothread lock held.
|
||||
*/
|
||||
void bdrv_set_aio_context(BlockDriverState *bs, AioContext *new_context);
|
||||
void bdrv_set_aio_context_ignore(BlockDriverState *bs,
|
||||
AioContext *new_context, GSList **ignore);
|
||||
int bdrv_try_set_aio_context(BlockDriverState *bs, AioContext *ctx,
|
||||
Error **errp);
|
||||
int bdrv_child_try_set_aio_context(BlockDriverState *bs, AioContext *ctx,
|
||||
BdrvChild *ignore_child, Error **errp);
|
||||
bool bdrv_child_can_set_aio_context(BdrvChild *c, AioContext *ctx,
|
||||
GSList **ignore, Error **errp);
|
||||
bool bdrv_can_set_aio_context(BlockDriverState *bs, AioContext *ctx,
|
||||
GSList **ignore, Error **errp);
|
||||
int bdrv_probe_blocksizes(BlockDriverState *bs, BlockSizes *bsz);
|
||||
int bdrv_probe_geometry(BlockDriverState *bs, HDGeometry *geo);
|
||||
|
||||
|
|
|
@ -691,6 +691,10 @@ struct BdrvChildRole {
|
|||
* can update its reference. */
|
||||
int (*update_filename)(BdrvChild *child, BlockDriverState *new_base,
|
||||
const char *filename, Error **errp);
|
||||
|
||||
bool (*can_set_aio_ctx)(BdrvChild *child, AioContext *ctx,
|
||||
GSList **ignore, Error **errp);
|
||||
void (*set_aio_ctx)(BdrvChild *child, AioContext *ctx, GSList **ignore);
|
||||
};
|
||||
|
||||
extern const BdrvChildRole child_file;
|
||||
|
@ -962,27 +966,6 @@ void bdrv_parse_filename_strip_prefix(const char *filename, const char *prefix,
|
|||
void bdrv_add_before_write_notifier(BlockDriverState *bs,
|
||||
NotifierWithReturn *notifier);
|
||||
|
||||
/**
|
||||
* bdrv_detach_aio_context:
|
||||
*
|
||||
* May be called from .bdrv_detach_aio_context() to detach children from the
|
||||
* current #AioContext. This is only needed by block drivers that manage their
|
||||
* own children. Both ->file and ->backing are automatically handled and
|
||||
* block drivers should not call this function on them explicitly.
|
||||
*/
|
||||
void bdrv_detach_aio_context(BlockDriverState *bs);
|
||||
|
||||
/**
|
||||
* bdrv_attach_aio_context:
|
||||
*
|
||||
* May be called from .bdrv_attach_aio_context() to attach children to the new
|
||||
* #AioContext. This is only needed by block drivers that manage their own
|
||||
* children. Both ->file and ->backing are automatically handled and block
|
||||
* drivers should not call this function on them explicitly.
|
||||
*/
|
||||
void bdrv_attach_aio_context(BlockDriverState *bs,
|
||||
AioContext *new_context);
|
||||
|
||||
/**
|
||||
* bdrv_add_aio_context_notifier:
|
||||
*
|
||||
|
|
|
@ -103,6 +103,7 @@ int blk_set_perm(BlockBackend *blk, uint64_t perm, uint64_t shared_perm,
|
|||
void blk_get_perm(BlockBackend *blk, uint64_t *perm, uint64_t *shared_perm);
|
||||
|
||||
void blk_set_allow_write_beyond_eof(BlockBackend *blk, bool allow);
|
||||
void blk_set_allow_aio_context_change(BlockBackend *blk, bool allow);
|
||||
void blk_iostatus_enable(BlockBackend *blk);
|
||||
bool blk_iostatus_is_enabled(const BlockBackend *blk);
|
||||
BlockDeviceIoStatus blk_iostatus(const BlockBackend *blk);
|
||||
|
|
|
@ -230,6 +230,7 @@ overridden with a pattern byte specified by @var{pattern}.
|
|||
|
||||
Perform a consistency check on the disk image @var{filename}. The command can
|
||||
output in the format @var{ofmt} which is either @code{human} or @code{json}.
|
||||
The JSON output is an object of QAPI type @code{ImageCheck}.
|
||||
|
||||
If @code{-r} is specified, qemu-img tries to repair any inconsistencies found
|
||||
during the check. @code{-r leaks} repairs only cluster leaks, whereas
|
||||
|
@ -406,8 +407,7 @@ The size syntax is similar to dd(1)'s size syntax.
|
|||
Give information about the disk image @var{filename}. Use it in
|
||||
particular to know the size reserved on disk which can be different
|
||||
from the displayed size. If VM snapshots are stored in the disk image,
|
||||
they are displayed too. The command can output in the format @var{ofmt}
|
||||
which is either @code{human} or @code{json}.
|
||||
they are displayed too.
|
||||
|
||||
If a disk image has a backing file chain, information about each disk image in
|
||||
the chain can be recursively enumerated by using the option @code{--backing-chain}.
|
||||
|
@ -424,6 +424,51 @@ To enumerate information about each disk image in the above chain, starting from
|
|||
qemu-img info --backing-chain snap2.qcow2
|
||||
@end example
|
||||
|
||||
The command can output in the format @var{ofmt} which is either @code{human} or
|
||||
@code{json}. The JSON output is an object of QAPI type @code{ImageInfo}; with
|
||||
@code{--backing-chain}, it is an array of @code{ImageInfo} objects.
|
||||
|
||||
@code{--output=human} reports the following information (for every image in the
|
||||
chain):
|
||||
@table @var
|
||||
@item image
|
||||
The image file name
|
||||
|
||||
@item file format
|
||||
The image format
|
||||
|
||||
@item virtual size
|
||||
The size of the guest disk
|
||||
|
||||
@item disk size
|
||||
How much space the image file occupies on the host file system (may be shown as
|
||||
0 if this information is unavailable, e.g. because there is no file system)
|
||||
|
||||
@item cluster_size
|
||||
Cluster size of the image format, if applicable
|
||||
|
||||
@item encrypted
|
||||
Whether the image is encrypted (only present if so)
|
||||
|
||||
@item cleanly shut down
|
||||
This is shown as @code{no} if the image is dirty and will have to be
|
||||
auto-repaired the next time it is opened in qemu.
|
||||
|
||||
@item backing file
|
||||
The backing file name, if present
|
||||
|
||||
@item backing file format
|
||||
The format of the backing file, if the image enforces it
|
||||
|
||||
@item Snapshot list
|
||||
A list of all internal snapshots
|
||||
|
||||
@item Format specific information
|
||||
Further information whose structure depends on the image format. This section
|
||||
is a textual representation of the respective @code{ImageInfoSpecific*} QAPI
|
||||
object (e.g. @code{ImageInfoSpecificQCow2} for qcow2 images).
|
||||
@end table
|
||||
|
||||
@item map [--object @var{objectdef}] [--image-opts] [-f @var{fmt}] [--output=@var{ofmt}] [-U] @var{filename}
|
||||
|
||||
Dump the metadata of image @var{filename} and its backing file chain.
|
||||
|
@ -485,7 +530,8 @@ Calculate the file size required for a new image. This information can be used
|
|||
to size logical volumes or SAN LUNs appropriately for the image that will be
|
||||
placed in them. The values reported are guaranteed to be large enough to fit
|
||||
the image. The command can output in the format @var{ofmt} which is either
|
||||
@code{human} or @code{json}.
|
||||
@code{human} or @code{json}. The JSON output is an object of QAPI type
|
||||
@code{BlockMeasureInfo}.
|
||||
|
||||
If the size @var{N} is given then act as if creating a new empty image file
|
||||
using @command{qemu-img create}. If @var{filename} is given then act as if
|
||||
|
|
|
@ -538,7 +538,7 @@ static int do_write_compressed(BlockBackend *blk, char *buf, int64_t offset,
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (bytes >> 9 > BDRV_REQUEST_MAX_SECTORS) {
|
||||
if (bytes > BDRV_REQUEST_MAX_BYTES) {
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
|
@ -1781,10 +1781,9 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
|
|||
if (bytes < 0) {
|
||||
print_cvtnum_err(bytes, argv[optind]);
|
||||
return bytes;
|
||||
} else if (bytes >> BDRV_SECTOR_BITS > BDRV_REQUEST_MAX_SECTORS) {
|
||||
} else if (bytes > BDRV_REQUEST_MAX_BYTES) {
|
||||
printf("length cannot exceed %"PRIu64", given %s\n",
|
||||
(uint64_t)BDRV_REQUEST_MAX_SECTORS << BDRV_SECTOR_BITS,
|
||||
argv[optind]);
|
||||
(uint64_t)BDRV_REQUEST_MAX_BYTES, argv[optind]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
3
qmp.c
3
qmp.c
|
@ -156,6 +156,9 @@ void qmp_cont(Error **errp)
|
|||
return;
|
||||
} else if (runstate_check(RUN_STATE_SUSPENDED)) {
|
||||
return;
|
||||
} else if (runstate_check(RUN_STATE_FINISH_MIGRATE)) {
|
||||
error_setg(errp, "Migration is not finalized yet");
|
||||
return;
|
||||
}
|
||||
|
||||
for (blk = blk_next(NULL); blk; blk = blk_next(blk)) {
|
||||
|
|
|
@ -102,12 +102,17 @@ class TestDirtyBitmapMigration(iotests.QMPTestCase):
|
|||
event = self.vm_a.event_wait('MIGRATION')
|
||||
if event['data']['status'] == 'completed':
|
||||
break
|
||||
while True:
|
||||
result = self.vm_a.qmp('query-status')
|
||||
if (result['return']['status'] == 'postmigrate'):
|
||||
break
|
||||
|
||||
# test that bitmap is still here
|
||||
removed = (not migrate_bitmaps) and persistent
|
||||
self.check_bitmap(self.vm_a, False if removed else sha256)
|
||||
|
||||
self.vm_a.qmp('cont')
|
||||
result = self.vm_a.qmp('cont')
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# test that bitmap is still here after invalidation
|
||||
self.check_bitmap(self.vm_a, sha256)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Test qemu-img vs. unaligned images
|
||||
# (See also 253, which is the O_DIRECT version)
|
||||
#
|
||||
# Copyright (C) 2018-2019 Red Hat, Inc.
|
||||
#
|
||||
|
@ -37,6 +38,9 @@ _supported_fmt raw
|
|||
_supported_proto file
|
||||
_supported_os Linux
|
||||
|
||||
_default_cache_mode writeback
|
||||
_supported_cache_modes writeback writethrough unsafe
|
||||
|
||||
echo
|
||||
echo "=== Check mapping of unaligned raw image ==="
|
||||
echo
|
||||
|
|
|
@ -862,7 +862,8 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||
|
||||
# hd2 <- hd0
|
||||
result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0',
|
||||
device = 'hd0', base_node = 'hd2', speed = 512 * 1024)
|
||||
device = 'hd0', base_node = 'hd2',
|
||||
auto_finalize = False)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# We can't remove hd2 while the stream job is ongoing
|
||||
|
@ -873,7 +874,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||
opts['backing'] = None
|
||||
self.reopen(opts, {}, "Cannot change 'backing' link from 'hd0' to 'hd1'")
|
||||
|
||||
self.wait_until_completed(drive = 'stream0')
|
||||
self.vm.run_job('stream0', auto_finalize = False, auto_dismiss = True)
|
||||
|
||||
# Reopen the chain during a block-stream job (from hd2 to hd1)
|
||||
def test_block_stream_4(self):
|
||||
|
@ -886,12 +887,16 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||
|
||||
# hd1 <- hd0
|
||||
result = self.vm.qmp('block-stream', conv_keys = True, job_id = 'stream0',
|
||||
device = 'hd1', speed = 512 * 1024)
|
||||
device = 'hd1', auto_finalize = False)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# We can't reopen with the original options because that would
|
||||
# make hd1 read-only and block-stream requires it to be read-write
|
||||
self.reopen(opts, {}, "Can't set node 'hd1' to r/o with copy-on-read enabled")
|
||||
# (Which error message appears depends on whether the stream job is
|
||||
# already done with copying at this point.)
|
||||
self.reopen(opts, {},
|
||||
["Can't set node 'hd1' to r/o with copy-on-read enabled",
|
||||
"Cannot make block node read-only, there is a writer on it"])
|
||||
|
||||
# We can't remove hd2 while the stream job is ongoing
|
||||
opts['backing']['backing'] = None
|
||||
|
@ -901,7 +906,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||
opts['backing'] = None
|
||||
self.reopen(opts)
|
||||
|
||||
self.wait_until_completed(drive = 'stream0')
|
||||
self.vm.run_job('stream0', auto_finalize = False, auto_dismiss = True)
|
||||
|
||||
# Reopen the chain during a block-commit job (from hd0 to hd2)
|
||||
def test_block_commit_1(self):
|
||||
|
@ -913,7 +918,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
result = self.vm.qmp('block-commit', conv_keys = True, job_id = 'commit0',
|
||||
device = 'hd0', speed = 1024 * 1024)
|
||||
device = 'hd0')
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# We can't remove hd2 while the commit job is ongoing
|
||||
|
@ -944,7 +949,8 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
result = self.vm.qmp('block-commit', conv_keys = True, job_id = 'commit0',
|
||||
device = 'hd0', top_node = 'hd1', speed = 1024 * 1024)
|
||||
device = 'hd0', top_node = 'hd1',
|
||||
auto_finalize = False)
|
||||
self.assert_qmp(result, 'return', {})
|
||||
|
||||
# We can't remove hd2 while the commit job is ongoing
|
||||
|
@ -956,7 +962,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||
self.reopen(opts, {}, "Cannot change backing link if 'hd0' has an implicit backing file")
|
||||
|
||||
# hd2 <- hd0
|
||||
self.wait_until_completed(drive = 'commit0')
|
||||
self.vm.run_job('commit0', auto_finalize = False, auto_dismiss = True)
|
||||
|
||||
self.assert_qmp(self.get_node('hd0'), 'ro', False)
|
||||
self.assertEqual(self.get_node('hd1'), None)
|
||||
|
|
|
@ -3,3 +3,15 @@
|
|||
Ran 18 tests
|
||||
|
||||
OK
|
||||
{"execute": "job-finalize", "arguments": {"id": "commit0"}}
|
||||
{"return": {}}
|
||||
{"data": {"id": "commit0", "type": "commit"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
|
||||
{"data": {"device": "commit0", "len": 3145728, "offset": 3145728, "speed": 0, "type": "commit"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
|
||||
{"execute": "job-finalize", "arguments": {"id": "stream0"}}
|
||||
{"return": {}}
|
||||
{"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"}}
|
||||
{"execute": "job-finalize", "arguments": {"id": "stream0"}}
|
||||
{"return": {}}
|
||||
{"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"}}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Test qemu-img vs. unaligned images; O_DIRECT version
|
||||
# (Originates from 221)
|
||||
#
|
||||
# Copyright (C) 2019 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
seq="$(basename $0)"
|
||||
echo "QA output created by $seq"
|
||||
|
||||
status=1 # failure is the default!
|
||||
|
||||
_cleanup()
|
||||
{
|
||||
_cleanup_test_img
|
||||
}
|
||||
trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||
|
||||
# get standard environment, filters and checks
|
||||
. ./common.rc
|
||||
. ./common.filter
|
||||
|
||||
_supported_fmt raw
|
||||
_supported_proto file
|
||||
_supported_os Linux
|
||||
|
||||
_default_cache_mode none
|
||||
_supported_cache_modes none directsync
|
||||
|
||||
echo
|
||||
echo "=== Check mapping of unaligned raw image ==="
|
||||
echo
|
||||
|
||||
# We do not know how large a physical sector is, but it is certainly
|
||||
# going to be a factor of 1 MB
|
||||
size=$((1 * 1024 * 1024 - 1))
|
||||
|
||||
# qemu-img create rounds size up to BDRV_SECTOR_SIZE
|
||||
_make_test_img $size
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"driver=$IMGFMT,file.driver=file,file.filename=$TEST_IMG,cache.direct=on" \
|
||||
| _filter_qemu_img_map
|
||||
|
||||
# so we resize it and check again
|
||||
truncate --size=$size "$TEST_IMG"
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"driver=$IMGFMT,file.driver=file,file.filename=$TEST_IMG,cache.direct=on" \
|
||||
| _filter_qemu_img_map
|
||||
|
||||
# qemu-io with O_DIRECT always writes whole physical sectors. Again,
|
||||
# we do not know how large a physical sector is, so we just start
|
||||
# writing from a 64 kB boundary, which should always be aligned.
|
||||
offset=$((1 * 1024 * 1024 - 64 * 1024))
|
||||
$QEMU_IO -c "w $offset $((size - offset))" "$TEST_IMG" | _filter_qemu_io
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"driver=$IMGFMT,file.driver=file,file.filename=$TEST_IMG,cache.direct=on" \
|
||||
| _filter_qemu_img_map
|
||||
|
||||
# Resize it and check again -- contrary to 221, we may not get partial
|
||||
# sectors here, so there should be only two areas (one zero, one
|
||||
# data).
|
||||
truncate --size=$size "$TEST_IMG"
|
||||
$QEMU_IMG map --output=json --image-opts \
|
||||
"driver=$IMGFMT,file.driver=file,file.filename=$TEST_IMG,cache.direct=on" \
|
||||
| _filter_qemu_img_map
|
||||
|
||||
# success, all done
|
||||
echo '*** done'
|
||||
rm -f $seq.full
|
||||
status=0
|
|
@ -0,0 +1,14 @@
|
|||
QA output created by 253
|
||||
|
||||
=== Check mapping of unaligned raw image ===
|
||||
|
||||
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048575
|
||||
[{ "start": 0, "length": 1048576, "depth": 0, "zero": true, "data": false, "offset": OFFSET}]
|
||||
[{ "start": 0, "length": 1048576, "depth": 0, "zero": true, "data": false, "offset": OFFSET}]
|
||||
wrote 65535/65535 bytes at offset 983040
|
||||
63.999 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
||||
[{ "start": 0, "length": 983040, "depth": 0, "zero": true, "data": false, "offset": OFFSET},
|
||||
{ "start": 983040, "length": 65536, "depth": 0, "zero": false, "data": true, "offset": OFFSET}]
|
||||
[{ "start": 0, "length": 983040, "depth": 0, "zero": true, "data": false, "offset": OFFSET},
|
||||
{ "start": 983040, "length": 65536, "depth": 0, "zero": false, "data": true, "offset": OFFSET}]
|
||||
*** done
|
|
@ -250,3 +250,4 @@
|
|||
248 rw auto quick
|
||||
249 rw auto quick
|
||||
252 rw auto backing quick
|
||||
253 rw auto quick
|
||||
|
|
|
@ -552,7 +552,7 @@ def run_job(self, job, auto_finalize=True, auto_dismiss=False):
|
|||
elif status == 'null':
|
||||
return error
|
||||
else:
|
||||
iotests.log(ev)
|
||||
log(ev)
|
||||
|
||||
def node_info(self, node_name):
|
||||
nodes = self.qmp('query-named-block-nodes')
|
||||
|
@ -596,9 +596,23 @@ def assert_qmp_absent(self, d, path):
|
|||
self.fail('path "%s" has value "%s"' % (path, str(result)))
|
||||
|
||||
def assert_qmp(self, d, path, value):
|
||||
'''Assert that the value for a specific path in a QMP dict matches'''
|
||||
'''Assert that the value for a specific path in a QMP dict
|
||||
matches. When given a list of values, assert that any of
|
||||
them matches.'''
|
||||
|
||||
result = self.dictpath(d, path)
|
||||
self.assertEqual(result, value, 'values not equal "%s" and "%s"' % (str(result), str(value)))
|
||||
|
||||
# [] makes no sense as a list of valid values, so treat it as
|
||||
# an actual single value.
|
||||
if isinstance(value, list) and value != []:
|
||||
for v in value:
|
||||
if result == v:
|
||||
return
|
||||
self.fail('no match for "%s" in %s' % (str(result), str(value)))
|
||||
else:
|
||||
self.assertEqual(result, value,
|
||||
'values not equal "%s" and "%s"'
|
||||
% (str(result), str(value)))
|
||||
|
||||
def assert_no_active_block_jobs(self):
|
||||
result = self.vm.qmp('query-block-jobs')
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "block/blockjob_int.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "iothread.h"
|
||||
|
||||
static int coroutine_fn bdrv_test_co_prwv(BlockDriverState *bs,
|
||||
|
@ -459,6 +460,204 @@ static void test_attach_blockjob(void)
|
|||
blk_unref(blk);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that changing the AioContext for one node in a tree (here through blk)
|
||||
* changes all other nodes as well:
|
||||
*
|
||||
* blk
|
||||
* |
|
||||
* | bs_verify [blkverify]
|
||||
* | / \
|
||||
* | / \
|
||||
* bs_a [bdrv_test] bs_b [bdrv_test]
|
||||
*
|
||||
*/
|
||||
static void test_propagate_basic(void)
|
||||
{
|
||||
IOThread *iothread = iothread_new();
|
||||
AioContext *ctx = iothread_get_aio_context(iothread);
|
||||
BlockBackend *blk;
|
||||
BlockDriverState *bs_a, *bs_b, *bs_verify;
|
||||
QDict *options;
|
||||
|
||||
/* Create bs_a and its BlockBackend */
|
||||
blk = blk_new(BLK_PERM_ALL, BLK_PERM_ALL);
|
||||
bs_a = bdrv_new_open_driver(&bdrv_test, "bs_a", BDRV_O_RDWR, &error_abort);
|
||||
blk_insert_bs(blk, bs_a, &error_abort);
|
||||
|
||||
/* Create bs_b */
|
||||
bs_b = bdrv_new_open_driver(&bdrv_test, "bs_b", BDRV_O_RDWR, &error_abort);
|
||||
|
||||
/* Create blkverify filter that references both bs_a and bs_b */
|
||||
options = qdict_new();
|
||||
qdict_put_str(options, "driver", "blkverify");
|
||||
qdict_put_str(options, "test", "bs_a");
|
||||
qdict_put_str(options, "raw", "bs_b");
|
||||
|
||||
bs_verify = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
||||
|
||||
/* Switch the AioContext */
|
||||
blk_set_aio_context(blk, ctx);
|
||||
g_assert(blk_get_aio_context(blk) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_a) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_verify) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_b) == ctx);
|
||||
|
||||
/* Switch the AioContext back */
|
||||
ctx = qemu_get_aio_context();
|
||||
blk_set_aio_context(blk, ctx);
|
||||
g_assert(blk_get_aio_context(blk) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_a) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_verify) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_b) == ctx);
|
||||
|
||||
bdrv_unref(bs_verify);
|
||||
bdrv_unref(bs_b);
|
||||
bdrv_unref(bs_a);
|
||||
blk_unref(blk);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that diamonds in the graph don't lead to endless recursion:
|
||||
*
|
||||
* blk
|
||||
* |
|
||||
* bs_verify [blkverify]
|
||||
* / \
|
||||
* / \
|
||||
* bs_b [raw] bs_c[raw]
|
||||
* \ /
|
||||
* \ /
|
||||
* bs_a [bdrv_test]
|
||||
*/
|
||||
static void test_propagate_diamond(void)
|
||||
{
|
||||
IOThread *iothread = iothread_new();
|
||||
AioContext *ctx = iothread_get_aio_context(iothread);
|
||||
BlockBackend *blk;
|
||||
BlockDriverState *bs_a, *bs_b, *bs_c, *bs_verify;
|
||||
QDict *options;
|
||||
|
||||
/* Create bs_a */
|
||||
bs_a = bdrv_new_open_driver(&bdrv_test, "bs_a", BDRV_O_RDWR, &error_abort);
|
||||
|
||||
/* Create bs_b and bc_c */
|
||||
options = qdict_new();
|
||||
qdict_put_str(options, "driver", "raw");
|
||||
qdict_put_str(options, "file", "bs_a");
|
||||
qdict_put_str(options, "node-name", "bs_b");
|
||||
bs_b = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
||||
|
||||
options = qdict_new();
|
||||
qdict_put_str(options, "driver", "raw");
|
||||
qdict_put_str(options, "file", "bs_a");
|
||||
qdict_put_str(options, "node-name", "bs_c");
|
||||
bs_c = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
||||
|
||||
/* Create blkverify filter that references both bs_b and bs_c */
|
||||
options = qdict_new();
|
||||
qdict_put_str(options, "driver", "blkverify");
|
||||
qdict_put_str(options, "test", "bs_b");
|
||||
qdict_put_str(options, "raw", "bs_c");
|
||||
|
||||
bs_verify = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
||||
blk = blk_new(BLK_PERM_ALL, BLK_PERM_ALL);
|
||||
blk_insert_bs(blk, bs_verify, &error_abort);
|
||||
|
||||
/* Switch the AioContext */
|
||||
blk_set_aio_context(blk, ctx);
|
||||
g_assert(blk_get_aio_context(blk) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_verify) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_a) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_b) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_c) == ctx);
|
||||
|
||||
/* Switch the AioContext back */
|
||||
ctx = qemu_get_aio_context();
|
||||
blk_set_aio_context(blk, ctx);
|
||||
g_assert(blk_get_aio_context(blk) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_verify) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_a) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_b) == ctx);
|
||||
g_assert(bdrv_get_aio_context(bs_c) == ctx);
|
||||
|
||||
blk_unref(blk);
|
||||
bdrv_unref(bs_verify);
|
||||
bdrv_unref(bs_c);
|
||||
bdrv_unref(bs_b);
|
||||
bdrv_unref(bs_a);
|
||||
}
|
||||
|
||||
static void test_propagate_mirror(void)
|
||||
{
|
||||
IOThread *iothread = iothread_new();
|
||||
AioContext *ctx = iothread_get_aio_context(iothread);
|
||||
AioContext *main_ctx = qemu_get_aio_context();
|
||||
BlockDriverState *src, *target;
|
||||
BlockBackend *blk;
|
||||
Job *job;
|
||||
Error *local_err = NULL;
|
||||
|
||||
/* Create src and target*/
|
||||
src = bdrv_new_open_driver(&bdrv_test, "src", BDRV_O_RDWR, &error_abort);
|
||||
target = bdrv_new_open_driver(&bdrv_test, "target", BDRV_O_RDWR,
|
||||
&error_abort);
|
||||
|
||||
/* Start a mirror job */
|
||||
mirror_start("job0", src, target, NULL, JOB_DEFAULT, 0, 0, 0,
|
||||
MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN,
|
||||
BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
|
||||
false, "filter_node", MIRROR_COPY_MODE_BACKGROUND,
|
||||
&error_abort);
|
||||
job = job_get("job0");
|
||||
|
||||
/* Change the AioContext of src */
|
||||
bdrv_try_set_aio_context(src, ctx, &error_abort);
|
||||
g_assert(bdrv_get_aio_context(src) == ctx);
|
||||
g_assert(bdrv_get_aio_context(target) == ctx);
|
||||
g_assert(job->aio_context == ctx);
|
||||
|
||||
/* Change the AioContext of target */
|
||||
aio_context_acquire(ctx);
|
||||
bdrv_try_set_aio_context(target, main_ctx, &error_abort);
|
||||
aio_context_release(ctx);
|
||||
g_assert(bdrv_get_aio_context(src) == main_ctx);
|
||||
g_assert(bdrv_get_aio_context(target) == main_ctx);
|
||||
|
||||
/* With a BlockBackend on src, changing target must fail */
|
||||
blk = blk_new(0, BLK_PERM_ALL);
|
||||
blk_insert_bs(blk, src, &error_abort);
|
||||
|
||||
bdrv_try_set_aio_context(target, ctx, &local_err);
|
||||
g_assert(local_err);
|
||||
error_free(local_err);
|
||||
|
||||
g_assert(blk_get_aio_context(blk) == main_ctx);
|
||||
g_assert(bdrv_get_aio_context(src) == main_ctx);
|
||||
g_assert(bdrv_get_aio_context(target) == main_ctx);
|
||||
|
||||
/* ...unless we explicitly allow it */
|
||||
aio_context_acquire(ctx);
|
||||
blk_set_allow_aio_context_change(blk, true);
|
||||
bdrv_try_set_aio_context(target, ctx, &error_abort);
|
||||
aio_context_release(ctx);
|
||||
|
||||
g_assert(blk_get_aio_context(blk) == ctx);
|
||||
g_assert(bdrv_get_aio_context(src) == ctx);
|
||||
g_assert(bdrv_get_aio_context(target) == ctx);
|
||||
|
||||
job_cancel_sync_all();
|
||||
|
||||
aio_context_acquire(ctx);
|
||||
blk_set_aio_context(blk, main_ctx);
|
||||
bdrv_try_set_aio_context(target, main_ctx, &error_abort);
|
||||
aio_context_release(ctx);
|
||||
|
||||
blk_unref(blk);
|
||||
bdrv_unref(src);
|
||||
bdrv_unref(target);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
|
@ -474,6 +673,9 @@ int main(int argc, char **argv)
|
|||
}
|
||||
|
||||
g_test_add_func("/attach/blockjob", test_attach_blockjob);
|
||||
g_test_add_func("/propagate/basic", test_propagate_basic);
|
||||
g_test_add_func("/propagate/diamond", test_propagate_diamond);
|
||||
g_test_add_func("/propagate/mirror", test_propagate_mirror);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue