mirror of https://github.com/xemu-project/xemu.git
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1 iQEcBAABAgAGBQJUtSIWAAoJEJykq7OBq3PIEtUH/jxVqCQW4cXpnf+2MqVW9yfM nBAmjOURoVfOZ/cA9EKyuqjlZfo6uYl769TFxWxnLxwMpjj8n5tSaoJLdLMKP39/ ijuU77NZdXRZBk/1k8ySNGLuEoqFFIwSu/usCKtT/Ol2kPHNrcc8cdGLOj97kCs/ A7mXF/8VmVpuon34jyxUqCuWPMgpuUxjyyzKM8UhPHXS0T271qv1B6FxoqcMIN5g 48ftZsm4VJWyH+Ztw6VcaTKeAE5rr2iaNvDTF2YA1R5xa0FukGanjXMUOJZD9ioh v0wOZnw8dyqQRxgttGYe2Jh5N1hsZgY39yX1ACxJ3y6mNaIxCMGHqFA7ZY3McMg= =ZqBL -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/stefanha/tags/block-pull-request' into staging # gpg: Signature made Tue 13 Jan 2015 13:48:06 GMT using RSA key ID 81AB73C8 # gpg: Good signature from "Stefan Hajnoczi <stefanha@redhat.com>" # gpg: aka "Stefan Hajnoczi <stefanha@gmail.com>" * remotes/stefanha/tags/block-pull-request: (38 commits) NVMe: Set correct VS Value for 1.1 Compliant Controllers MAINTAINERS: Add migration/block* to block subsystem MAINTAINERS: Update email addresses for Chrysostomos Nanakos nvme: Fix get/set number of queues feature ide: Implement VPD response for ATAPI block: Split BLOCK_OP_TYPE_COMMIT to BLOCK_OP_TYPE_COMMIT_{SOURCE, TARGET} block: limited request size in write zeroes unsupported path coroutine: try harder not to delete coroutines coroutine: drop qemu_coroutine_adjust_pool_size coroutine: rewrite pool to avoid mutex QSLIST: add lock-free operations test-coroutine: avoid overflow on 32-bit systems qemu-thread: add per-thread atexit functions coroutine-ucontext: use __thread qemu-iotests: Add supported os parameter for python tests qemu-iotests: Add "_supported_os Linux" to 058 qemu-iotests: Replace "/bin/true" with "true" .gitignore: Ignore generated "common.env" libqos: Convert malloc-pc allocator to a generic allocator migration/block: fix pending() return value ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
a00369fc56
|
@ -109,3 +109,4 @@ cscope.*
|
||||||
tags
|
tags
|
||||||
TAGS
|
TAGS
|
||||||
*~
|
*~
|
||||||
|
/tests/qemu-iotests/common.env
|
||||||
|
|
|
@ -757,6 +757,7 @@ F: aio-*.c
|
||||||
F: block*
|
F: block*
|
||||||
F: block/
|
F: block/
|
||||||
F: hw/block/
|
F: hw/block/
|
||||||
|
F: migration/block*
|
||||||
F: qemu-img*
|
F: qemu-img*
|
||||||
F: qemu-io*
|
F: qemu-io*
|
||||||
F: tests/image-fuzzer/
|
F: tests/image-fuzzer/
|
||||||
|
@ -1105,7 +1106,6 @@ S: Supported
|
||||||
F: block/ssh.c
|
F: block/ssh.c
|
||||||
|
|
||||||
ARCHIPELAGO
|
ARCHIPELAGO
|
||||||
M: Chrysostomos Nanakos <cnanakos@grnet.gr>
|
|
||||||
M: Chrysostomos Nanakos <chris@include.gr>
|
M: Chrysostomos Nanakos <chris@include.gr>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: block/archipelago.c
|
F: block/archipelago.c
|
||||||
|
|
11
async.c
11
async.c
|
@ -44,10 +44,12 @@ struct QEMUBH {
|
||||||
QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
QEMUBH *bh;
|
QEMUBH *bh;
|
||||||
bh = g_new0(QEMUBH, 1);
|
bh = g_new(QEMUBH, 1);
|
||||||
bh->ctx = ctx;
|
*bh = (QEMUBH){
|
||||||
bh->cb = cb;
|
.ctx = ctx,
|
||||||
bh->opaque = opaque;
|
.cb = cb,
|
||||||
|
.opaque = opaque,
|
||||||
|
};
|
||||||
qemu_mutex_lock(&ctx->bh_lock);
|
qemu_mutex_lock(&ctx->bh_lock);
|
||||||
bh->next = ctx->first_bh;
|
bh->next = ctx->first_bh;
|
||||||
/* Make sure that the members are ready before putting bh into list */
|
/* Make sure that the members are ready before putting bh into list */
|
||||||
|
@ -300,6 +302,7 @@ AioContext *aio_context_new(Error **errp)
|
||||||
error_setg_errno(errp, -ret, "Failed to initialize event notifier");
|
error_setg_errno(errp, -ret, "Failed to initialize event notifier");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
g_source_set_can_recurse(&ctx->source, true);
|
||||||
aio_set_event_notifier(ctx, &ctx->notifier,
|
aio_set_event_notifier(ctx, &ctx->notifier,
|
||||||
(EventNotifierHandler *)
|
(EventNotifierHandler *)
|
||||||
event_notifier_test_and_clear);
|
event_notifier_test_and_clear);
|
||||||
|
|
92
block.c
92
block.c
|
@ -97,6 +97,10 @@ static QTAILQ_HEAD(, BlockDriverState) graph_bdrv_states =
|
||||||
static QLIST_HEAD(, BlockDriver) bdrv_drivers =
|
static QLIST_HEAD(, BlockDriver) bdrv_drivers =
|
||||||
QLIST_HEAD_INITIALIZER(bdrv_drivers);
|
QLIST_HEAD_INITIALIZER(bdrv_drivers);
|
||||||
|
|
||||||
|
static void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||||
|
int nr_sectors);
|
||||||
|
static void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||||
|
int nr_sectors);
|
||||||
/* If non-zero, use only whitelisted block drivers */
|
/* If non-zero, use only whitelisted block drivers */
|
||||||
static int use_bdrv_whitelist;
|
static int use_bdrv_whitelist;
|
||||||
|
|
||||||
|
@ -303,15 +307,32 @@ void path_combine(char *dest, int dest_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void bdrv_get_full_backing_filename(BlockDriverState *bs, char *dest, size_t sz)
|
void bdrv_get_full_backing_filename_from_filename(const char *backed,
|
||||||
|
const char *backing,
|
||||||
|
char *dest, size_t sz,
|
||||||
|
Error **errp)
|
||||||
{
|
{
|
||||||
if (bs->backing_file[0] == '\0' || path_has_protocol(bs->backing_file)) {
|
if (backing[0] == '\0' || path_has_protocol(backing) ||
|
||||||
pstrcpy(dest, sz, bs->backing_file);
|
path_is_absolute(backing))
|
||||||
|
{
|
||||||
|
pstrcpy(dest, sz, backing);
|
||||||
|
} else if (backed[0] == '\0' || strstart(backed, "json:", NULL)) {
|
||||||
|
error_setg(errp, "Cannot use relative backing file names for '%s'",
|
||||||
|
backed);
|
||||||
} else {
|
} else {
|
||||||
path_combine(dest, sz, bs->filename, bs->backing_file);
|
path_combine(dest, sz, backed, backing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bdrv_get_full_backing_filename(BlockDriverState *bs, char *dest, size_t sz,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
char *backed = bs->exact_filename[0] ? bs->exact_filename : bs->filename;
|
||||||
|
|
||||||
|
bdrv_get_full_backing_filename_from_filename(backed, bs->backing_file,
|
||||||
|
dest, sz, errp);
|
||||||
|
}
|
||||||
|
|
||||||
void bdrv_register(BlockDriver *bdrv)
|
void bdrv_register(BlockDriver *bdrv)
|
||||||
{
|
{
|
||||||
/* Block drivers without coroutine functions need emulation */
|
/* Block drivers without coroutine functions need emulation */
|
||||||
|
@ -1179,7 +1200,7 @@ void bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd)
|
||||||
|
|
||||||
bdrv_op_block_all(bs->backing_hd, bs->backing_blocker);
|
bdrv_op_block_all(bs->backing_hd, bs->backing_blocker);
|
||||||
/* Otherwise we won't be able to commit due to check in bdrv_commit */
|
/* Otherwise we won't be able to commit due to check in bdrv_commit */
|
||||||
bdrv_op_unblock(bs->backing_hd, BLOCK_OP_TYPE_COMMIT,
|
bdrv_op_unblock(bs->backing_hd, BLOCK_OP_TYPE_COMMIT_TARGET,
|
||||||
bs->backing_blocker);
|
bs->backing_blocker);
|
||||||
out:
|
out:
|
||||||
bdrv_refresh_limits(bs, NULL);
|
bdrv_refresh_limits(bs, NULL);
|
||||||
|
@ -1217,7 +1238,14 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *options, Error **errp)
|
||||||
QDECREF(options);
|
QDECREF(options);
|
||||||
goto free_exit;
|
goto free_exit;
|
||||||
} else {
|
} else {
|
||||||
bdrv_get_full_backing_filename(bs, backing_filename, PATH_MAX);
|
bdrv_get_full_backing_filename(bs, backing_filename, PATH_MAX,
|
||||||
|
&local_err);
|
||||||
|
if (local_err) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
QDECREF(options);
|
||||||
|
goto free_exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bs->drv || !bs->drv->supports_backing) {
|
if (!bs->drv || !bs->drv->supports_backing) {
|
||||||
|
@ -2188,8 +2216,8 @@ int bdrv_commit(BlockDriverState *bs)
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT, NULL) ||
|
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT_SOURCE, NULL) ||
|
||||||
bdrv_op_is_blocked(bs->backing_hd, BLOCK_OP_TYPE_COMMIT, NULL)) {
|
bdrv_op_is_blocked(bs->backing_hd, BLOCK_OP_TYPE_COMMIT_TARGET, NULL)) {
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3034,18 +3062,16 @@ static int coroutine_fn bdrv_aligned_preadv(BlockDriverState *bs,
|
||||||
|
|
||||||
max_nb_sectors = ROUND_UP(MAX(0, total_sectors - sector_num),
|
max_nb_sectors = ROUND_UP(MAX(0, total_sectors - sector_num),
|
||||||
align >> BDRV_SECTOR_BITS);
|
align >> BDRV_SECTOR_BITS);
|
||||||
if (max_nb_sectors > 0) {
|
if (nb_sectors < max_nb_sectors) {
|
||||||
|
ret = drv->bdrv_co_readv(bs, sector_num, nb_sectors, qiov);
|
||||||
|
} else if (max_nb_sectors > 0) {
|
||||||
QEMUIOVector local_qiov;
|
QEMUIOVector local_qiov;
|
||||||
size_t local_sectors;
|
|
||||||
|
|
||||||
max_nb_sectors = MIN(max_nb_sectors, SIZE_MAX / BDRV_SECTOR_BITS);
|
|
||||||
local_sectors = MIN(max_nb_sectors, nb_sectors);
|
|
||||||
|
|
||||||
qemu_iovec_init(&local_qiov, qiov->niov);
|
qemu_iovec_init(&local_qiov, qiov->niov);
|
||||||
qemu_iovec_concat(&local_qiov, qiov, 0,
|
qemu_iovec_concat(&local_qiov, qiov, 0,
|
||||||
local_sectors * BDRV_SECTOR_SIZE);
|
max_nb_sectors * BDRV_SECTOR_SIZE);
|
||||||
|
|
||||||
ret = drv->bdrv_co_readv(bs, sector_num, local_sectors,
|
ret = drv->bdrv_co_readv(bs, sector_num, max_nb_sectors,
|
||||||
&local_qiov);
|
&local_qiov);
|
||||||
|
|
||||||
qemu_iovec_destroy(&local_qiov);
|
qemu_iovec_destroy(&local_qiov);
|
||||||
|
@ -3218,6 +3244,9 @@ static int coroutine_fn bdrv_co_do_write_zeroes(BlockDriverState *bs,
|
||||||
|
|
||||||
if (ret == -ENOTSUP) {
|
if (ret == -ENOTSUP) {
|
||||||
/* Fall back to bounce buffer if write zeroes is unsupported */
|
/* Fall back to bounce buffer if write zeroes is unsupported */
|
||||||
|
int max_xfer_len = MIN_NON_ZERO(bs->bl.max_transfer_length,
|
||||||
|
MAX_WRITE_ZEROES_DEFAULT);
|
||||||
|
num = MIN(num, max_xfer_len);
|
||||||
iov.iov_len = num * BDRV_SECTOR_SIZE;
|
iov.iov_len = num * BDRV_SECTOR_SIZE;
|
||||||
if (iov.iov_base == NULL) {
|
if (iov.iov_base == NULL) {
|
||||||
iov.iov_base = qemu_try_blockalign(bs, num * BDRV_SECTOR_SIZE);
|
iov.iov_base = qemu_try_blockalign(bs, num * BDRV_SECTOR_SIZE);
|
||||||
|
@ -3234,7 +3263,7 @@ static int coroutine_fn bdrv_co_do_write_zeroes(BlockDriverState *bs,
|
||||||
/* Keep bounce buffer around if it is big enough for all
|
/* Keep bounce buffer around if it is big enough for all
|
||||||
* all future requests.
|
* all future requests.
|
||||||
*/
|
*/
|
||||||
if (num < max_write_zeroes) {
|
if (num < max_xfer_len) {
|
||||||
qemu_vfree(iov.iov_base);
|
qemu_vfree(iov.iov_base);
|
||||||
iov.iov_base = NULL;
|
iov.iov_base = NULL;
|
||||||
}
|
}
|
||||||
|
@ -5389,8 +5418,20 @@ void bdrv_dirty_iter_init(BlockDriverState *bs,
|
||||||
hbitmap_iter_init(hbi, bitmap->bitmap, 0);
|
hbitmap_iter_init(hbi, bitmap->bitmap, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
|
void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
||||||
int nr_sectors)
|
int64_t cur_sector, int nr_sectors)
|
||||||
|
{
|
||||||
|
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
||||||
|
int64_t cur_sector, int nr_sectors)
|
||||||
|
{
|
||||||
|
hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||||
|
int nr_sectors)
|
||||||
{
|
{
|
||||||
BdrvDirtyBitmap *bitmap;
|
BdrvDirtyBitmap *bitmap;
|
||||||
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
|
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
|
||||||
|
@ -5398,7 +5439,8 @@ void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors)
|
static void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
|
||||||
|
int nr_sectors)
|
||||||
{
|
{
|
||||||
BdrvDirtyBitmap *bitmap;
|
BdrvDirtyBitmap *bitmap;
|
||||||
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
|
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
|
||||||
|
@ -5637,16 +5679,26 @@ void bdrv_img_create(const char *filename, const char *fmt,
|
||||||
if (size == -1) {
|
if (size == -1) {
|
||||||
if (backing_file) {
|
if (backing_file) {
|
||||||
BlockDriverState *bs;
|
BlockDriverState *bs;
|
||||||
|
char *full_backing = g_new0(char, PATH_MAX);
|
||||||
int64_t size;
|
int64_t size;
|
||||||
int back_flags;
|
int back_flags;
|
||||||
|
|
||||||
|
bdrv_get_full_backing_filename_from_filename(filename, backing_file,
|
||||||
|
full_backing, PATH_MAX,
|
||||||
|
&local_err);
|
||||||
|
if (local_err) {
|
||||||
|
g_free(full_backing);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
/* backing files always opened read-only */
|
/* backing files always opened read-only */
|
||||||
back_flags =
|
back_flags =
|
||||||
flags & ~(BDRV_O_RDWR | BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING);
|
flags & ~(BDRV_O_RDWR | BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING);
|
||||||
|
|
||||||
bs = NULL;
|
bs = NULL;
|
||||||
ret = bdrv_open(&bs, backing_file, NULL, NULL, back_flags,
|
ret = bdrv_open(&bs, full_backing, NULL, NULL, back_flags,
|
||||||
backing_drv, &local_err);
|
backing_drv, &local_err);
|
||||||
|
g_free(full_backing);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,6 +360,7 @@ static void coroutine_fn backup_run(void *opaque)
|
||||||
hbitmap_free(job->bitmap);
|
hbitmap_free(job->bitmap);
|
||||||
|
|
||||||
bdrv_iostatus_disable(target);
|
bdrv_iostatus_disable(target);
|
||||||
|
bdrv_op_unblock_all(target, job->common.blocker);
|
||||||
|
|
||||||
data = g_malloc(sizeof(*data));
|
data = g_malloc(sizeof(*data));
|
||||||
data->ret = ret;
|
data->ret = ret;
|
||||||
|
@ -379,6 +380,11 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
assert(target);
|
assert(target);
|
||||||
assert(cb);
|
assert(cb);
|
||||||
|
|
||||||
|
if (bs == target) {
|
||||||
|
error_setg(errp, "Source and target cannot be the same");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((on_source_error == BLOCKDEV_ON_ERROR_STOP ||
|
if ((on_source_error == BLOCKDEV_ON_ERROR_STOP ||
|
||||||
on_source_error == BLOCKDEV_ON_ERROR_ENOSPC) &&
|
on_source_error == BLOCKDEV_ON_ERROR_ENOSPC) &&
|
||||||
!bdrv_iostatus_is_enabled(bs)) {
|
!bdrv_iostatus_is_enabled(bs)) {
|
||||||
|
@ -386,6 +392,26 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!bdrv_is_inserted(bs)) {
|
||||||
|
error_setg(errp, "Device is not inserted: %s",
|
||||||
|
bdrv_get_device_name(bs));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bdrv_is_inserted(target)) {
|
||||||
|
error_setg(errp, "Device is not inserted: %s",
|
||||||
|
bdrv_get_device_name(target));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
len = bdrv_getlength(bs);
|
len = bdrv_getlength(bs);
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
error_setg_errno(errp, -len, "unable to get length for '%s'",
|
error_setg_errno(errp, -len, "unable to get length for '%s'",
|
||||||
|
@ -399,6 +425,8 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bdrv_op_block_all(target, job->common.blocker);
|
||||||
|
|
||||||
job->on_source_error = on_source_error;
|
job->on_source_error = on_source_error;
|
||||||
job->on_target_error = on_target_error;
|
job->on_target_error = on_target_error;
|
||||||
job->target = target;
|
job->target = target;
|
||||||
|
|
|
@ -260,9 +260,6 @@ int blk_attach_dev(BlockBackend *blk, void *dev)
|
||||||
blk_ref(blk);
|
blk_ref(blk);
|
||||||
blk->dev = dev;
|
blk->dev = dev;
|
||||||
bdrv_iostatus_reset(blk->bs);
|
bdrv_iostatus_reset(blk->bs);
|
||||||
|
|
||||||
/* We're expecting I/O from the device so bump up coroutine pool size */
|
|
||||||
qemu_coroutine_adjust_pool_size(COROUTINE_POOL_RESERVATION);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +287,6 @@ void blk_detach_dev(BlockBackend *blk, void *dev)
|
||||||
blk->dev_ops = NULL;
|
blk->dev_ops = NULL;
|
||||||
blk->dev_opaque = NULL;
|
blk->dev_opaque = NULL;
|
||||||
bdrv_set_guest_block_size(blk->bs, 512);
|
bdrv_set_guest_block_size(blk->bs, 512);
|
||||||
qemu_coroutine_adjust_pool_size(-COROUTINE_POOL_RESERVATION);
|
|
||||||
blk_unref(blk);
|
blk_unref(blk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,8 @@ static void mirror_write_complete(void *opaque, int ret)
|
||||||
BlockDriverState *source = s->common.bs;
|
BlockDriverState *source = s->common.bs;
|
||||||
BlockErrorAction action;
|
BlockErrorAction action;
|
||||||
|
|
||||||
bdrv_set_dirty(source, op->sector_num, op->nb_sectors);
|
bdrv_set_dirty_bitmap(source, s->dirty_bitmap, op->sector_num,
|
||||||
|
op->nb_sectors);
|
||||||
action = mirror_error_action(s, false, -ret);
|
action = mirror_error_action(s, false, -ret);
|
||||||
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
|
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
|
||||||
s->ret = ret;
|
s->ret = ret;
|
||||||
|
@ -145,7 +146,8 @@ static void mirror_read_complete(void *opaque, int ret)
|
||||||
BlockDriverState *source = s->common.bs;
|
BlockDriverState *source = s->common.bs;
|
||||||
BlockErrorAction action;
|
BlockErrorAction action;
|
||||||
|
|
||||||
bdrv_set_dirty(source, op->sector_num, op->nb_sectors);
|
bdrv_set_dirty_bitmap(source, s->dirty_bitmap, op->sector_num,
|
||||||
|
op->nb_sectors);
|
||||||
action = mirror_error_action(s, true, -ret);
|
action = mirror_error_action(s, true, -ret);
|
||||||
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
|
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
|
||||||
s->ret = ret;
|
s->ret = ret;
|
||||||
|
@ -286,7 +288,8 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
|
||||||
next_sector += sectors_per_chunk;
|
next_sector += sectors_per_chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_reset_dirty(source, sector_num, nb_sectors);
|
bdrv_reset_dirty_bitmap(source, s->dirty_bitmap, sector_num,
|
||||||
|
nb_sectors);
|
||||||
|
|
||||||
/* Copy the dirty cluster. */
|
/* Copy the dirty cluster. */
|
||||||
s->in_flight++;
|
s->in_flight++;
|
||||||
|
@ -442,7 +445,7 @@ static void coroutine_fn mirror_run(void *opaque)
|
||||||
|
|
||||||
assert(n > 0);
|
assert(n > 0);
|
||||||
if (ret == 1) {
|
if (ret == 1) {
|
||||||
bdrv_set_dirty(bs, sector_num, n);
|
bdrv_set_dirty_bitmap(bs, s->dirty_bitmap, sector_num, n);
|
||||||
sector_num = next;
|
sector_num = next;
|
||||||
} else {
|
} else {
|
||||||
sector_num += n;
|
sector_num += n;
|
||||||
|
|
|
@ -214,7 +214,12 @@ void bdrv_query_image_info(BlockDriverState *bs,
|
||||||
info->backing_filename = g_strdup(backing_filename);
|
info->backing_filename = g_strdup(backing_filename);
|
||||||
info->has_backing_filename = true;
|
info->has_backing_filename = true;
|
||||||
bdrv_get_full_backing_filename(bs, backing_filename2,
|
bdrv_get_full_backing_filename(bs, backing_filename2,
|
||||||
sizeof(backing_filename2));
|
sizeof(backing_filename2), &err);
|
||||||
|
if (err) {
|
||||||
|
error_propagate(errp, err);
|
||||||
|
qapi_free_ImageInfo(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(backing_filename, backing_filename2) != 0) {
|
if (strcmp(backing_filename, backing_filename2) != 0) {
|
||||||
info->full_backing_filename =
|
info->full_backing_filename =
|
||||||
|
|
13
block/vmdk.c
13
block/vmdk.c
|
@ -1891,8 +1891,19 @@ static int vmdk_create(const char *filename, QemuOpts *opts, Error **errp)
|
||||||
}
|
}
|
||||||
if (backing_file) {
|
if (backing_file) {
|
||||||
BlockDriverState *bs = NULL;
|
BlockDriverState *bs = NULL;
|
||||||
ret = bdrv_open(&bs, backing_file, NULL, NULL, BDRV_O_NO_BACKING, NULL,
|
char *full_backing = g_new0(char, PATH_MAX);
|
||||||
|
bdrv_get_full_backing_filename_from_filename(filename, backing_file,
|
||||||
|
full_backing, PATH_MAX,
|
||||||
|
&local_err);
|
||||||
|
if (local_err) {
|
||||||
|
g_free(full_backing);
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
ret = -ENOENT;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
ret = bdrv_open(&bs, full_backing, NULL, NULL, BDRV_O_NO_BACKING, NULL,
|
||||||
errp);
|
errp);
|
||||||
|
g_free(full_backing);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
139
blockdev.c
139
blockdev.c
|
@ -1559,6 +1559,79 @@ static void drive_backup_clean(BlkTransactionState *common)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct BlockdevBackupState {
|
||||||
|
BlkTransactionState common;
|
||||||
|
BlockDriverState *bs;
|
||||||
|
BlockJob *job;
|
||||||
|
AioContext *aio_context;
|
||||||
|
} BlockdevBackupState;
|
||||||
|
|
||||||
|
static void blockdev_backup_prepare(BlkTransactionState *common, Error **errp)
|
||||||
|
{
|
||||||
|
BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common);
|
||||||
|
BlockdevBackup *backup;
|
||||||
|
BlockDriverState *bs, *target;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
|
||||||
|
assert(common->action->kind == TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP);
|
||||||
|
backup = common->action->blockdev_backup;
|
||||||
|
|
||||||
|
bs = bdrv_find(backup->device);
|
||||||
|
if (!bs) {
|
||||||
|
error_set(errp, QERR_DEVICE_NOT_FOUND, backup->device);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target = bdrv_find(backup->target);
|
||||||
|
if (!target) {
|
||||||
|
error_set(errp, QERR_DEVICE_NOT_FOUND, backup->target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AioContext is released in .clean() */
|
||||||
|
state->aio_context = bdrv_get_aio_context(bs);
|
||||||
|
if (state->aio_context != bdrv_get_aio_context(target)) {
|
||||||
|
state->aio_context = NULL;
|
||||||
|
error_setg(errp, "Backup between two IO threads is not implemented");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
aio_context_acquire(state->aio_context);
|
||||||
|
|
||||||
|
qmp_blockdev_backup(backup->device, backup->target,
|
||||||
|
backup->sync,
|
||||||
|
backup->has_speed, backup->speed,
|
||||||
|
backup->has_on_source_error, backup->on_source_error,
|
||||||
|
backup->has_on_target_error, backup->on_target_error,
|
||||||
|
&local_err);
|
||||||
|
if (local_err) {
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->bs = bs;
|
||||||
|
state->job = state->bs->job;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void blockdev_backup_abort(BlkTransactionState *common)
|
||||||
|
{
|
||||||
|
BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common);
|
||||||
|
BlockDriverState *bs = state->bs;
|
||||||
|
|
||||||
|
/* Only cancel if it's the job we started */
|
||||||
|
if (bs && bs->job && bs->job == state->job) {
|
||||||
|
block_job_cancel_sync(bs->job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void blockdev_backup_clean(BlkTransactionState *common)
|
||||||
|
{
|
||||||
|
BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common);
|
||||||
|
|
||||||
|
if (state->aio_context) {
|
||||||
|
aio_context_release(state->aio_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void abort_prepare(BlkTransactionState *common, Error **errp)
|
static void abort_prepare(BlkTransactionState *common, Error **errp)
|
||||||
{
|
{
|
||||||
error_setg(errp, "Transaction aborted using Abort action");
|
error_setg(errp, "Transaction aborted using Abort action");
|
||||||
|
@ -1582,6 +1655,12 @@ static const BdrvActionOps actions[] = {
|
||||||
.abort = drive_backup_abort,
|
.abort = drive_backup_abort,
|
||||||
.clean = drive_backup_clean,
|
.clean = drive_backup_clean,
|
||||||
},
|
},
|
||||||
|
[TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = {
|
||||||
|
.instance_size = sizeof(BlockdevBackupState),
|
||||||
|
.prepare = blockdev_backup_prepare,
|
||||||
|
.abort = blockdev_backup_abort,
|
||||||
|
.clean = blockdev_backup_clean,
|
||||||
|
},
|
||||||
[TRANSACTION_ACTION_KIND_ABORT] = {
|
[TRANSACTION_ACTION_KIND_ABORT] = {
|
||||||
.instance_size = sizeof(BlkTransactionState),
|
.instance_size = sizeof(BlkTransactionState),
|
||||||
.prepare = abort_prepare,
|
.prepare = abort_prepare,
|
||||||
|
@ -2139,7 +2218,7 @@ void qmp_block_commit(const char *device,
|
||||||
/* drain all i/o before commits */
|
/* drain all i/o before commits */
|
||||||
bdrv_drain_all();
|
bdrv_drain_all();
|
||||||
|
|
||||||
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT, errp)) {
|
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT_SOURCE, errp)) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2172,6 +2251,10 @@ void qmp_block_commit(const char *device,
|
||||||
|
|
||||||
assert(bdrv_get_aio_context(base_bs) == aio_context);
|
assert(bdrv_get_aio_context(base_bs) == aio_context);
|
||||||
|
|
||||||
|
if (bdrv_op_is_blocked(base_bs, BLOCK_OP_TYPE_COMMIT_TARGET, errp)) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
/* Do not allow attempts to commit an image into itself */
|
/* Do not allow attempts to commit an image into itself */
|
||||||
if (top_bs == base_bs) {
|
if (top_bs == base_bs) {
|
||||||
error_setg(errp, "cannot commit an image into itself");
|
error_setg(errp, "cannot commit an image into itself");
|
||||||
|
@ -2240,6 +2323,8 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||||
aio_context = bdrv_get_aio_context(bs);
|
aio_context = bdrv_get_aio_context(bs);
|
||||||
aio_context_acquire(aio_context);
|
aio_context_acquire(aio_context);
|
||||||
|
|
||||||
|
/* Although backup_run has this check too, we need to use bs->drv below, so
|
||||||
|
* do an early check redundantly. */
|
||||||
if (!bdrv_is_inserted(bs)) {
|
if (!bdrv_is_inserted(bs)) {
|
||||||
error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
|
error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, device);
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -2256,6 +2341,7 @@ void qmp_drive_backup(const char *device, const char *target,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Early check to avoid creating target */
|
||||||
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
|
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -2323,6 +2409,57 @@ BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp)
|
||||||
return bdrv_named_nodes_list();
|
return bdrv_named_nodes_list();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void qmp_blockdev_backup(const char *device, const char *target,
|
||||||
|
enum MirrorSyncMode sync,
|
||||||
|
bool has_speed, int64_t speed,
|
||||||
|
bool has_on_source_error,
|
||||||
|
BlockdevOnError on_source_error,
|
||||||
|
bool has_on_target_error,
|
||||||
|
BlockdevOnError on_target_error,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
BlockDriverState *bs;
|
||||||
|
BlockDriverState *target_bs;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
AioContext *aio_context;
|
||||||
|
|
||||||
|
if (!has_speed) {
|
||||||
|
speed = 0;
|
||||||
|
}
|
||||||
|
if (!has_on_source_error) {
|
||||||
|
on_source_error = BLOCKDEV_ON_ERROR_REPORT;
|
||||||
|
}
|
||||||
|
if (!has_on_target_error) {
|
||||||
|
on_target_error = BLOCKDEV_ON_ERROR_REPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bs = bdrv_find(device);
|
||||||
|
if (!bs) {
|
||||||
|
error_set(errp, QERR_DEVICE_NOT_FOUND, device);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
aio_context = bdrv_get_aio_context(bs);
|
||||||
|
aio_context_acquire(aio_context);
|
||||||
|
|
||||||
|
target_bs = bdrv_find(target);
|
||||||
|
if (!target_bs) {
|
||||||
|
error_set(errp, QERR_DEVICE_NOT_FOUND, target);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bdrv_ref(target_bs);
|
||||||
|
bdrv_set_aio_context(target_bs, aio_context);
|
||||||
|
backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
|
||||||
|
block_job_cb, bs, &local_err);
|
||||||
|
if (local_err != NULL) {
|
||||||
|
bdrv_unref(target_bs);
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
aio_context_release(aio_context);
|
||||||
|
}
|
||||||
|
|
||||||
#define DEFAULT_MIRROR_BUF_SIZE (10 << 20)
|
#define DEFAULT_MIRROR_BUF_SIZE (10 << 20)
|
||||||
|
|
||||||
void qmp_drive_mirror(const char *device, const char *target,
|
void qmp_drive_mirror(const char *device, const char *target,
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <pthread.h>
|
|
||||||
#include <ucontext.h>
|
#include <ucontext.h>
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/coroutine_int.h"
|
#include "block/coroutine_int.h"
|
||||||
|
@ -48,15 +47,8 @@ typedef struct {
|
||||||
/**
|
/**
|
||||||
* Per-thread coroutine bookkeeping
|
* Per-thread coroutine bookkeeping
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
static __thread CoroutineUContext leader;
|
||||||
/** Currently executing coroutine */
|
static __thread Coroutine *current;
|
||||||
Coroutine *current;
|
|
||||||
|
|
||||||
/** The default coroutine */
|
|
||||||
CoroutineUContext leader;
|
|
||||||
} CoroutineThreadState;
|
|
||||||
|
|
||||||
static pthread_key_t thread_state_key;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* va_args to makecontext() must be type 'int', so passing
|
* va_args to makecontext() must be type 'int', so passing
|
||||||
|
@ -68,36 +60,6 @@ union cc_arg {
|
||||||
int i[2];
|
int i[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
static CoroutineThreadState *coroutine_get_thread_state(void)
|
|
||||||
{
|
|
||||||
CoroutineThreadState *s = pthread_getspecific(thread_state_key);
|
|
||||||
|
|
||||||
if (!s) {
|
|
||||||
s = g_malloc0(sizeof(*s));
|
|
||||||
s->current = &s->leader.base;
|
|
||||||
pthread_setspecific(thread_state_key, s);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void qemu_coroutine_thread_cleanup(void *opaque)
|
|
||||||
{
|
|
||||||
CoroutineThreadState *s = opaque;
|
|
||||||
|
|
||||||
g_free(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __attribute__((constructor)) coroutine_init(void)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = pthread_key_create(&thread_state_key, qemu_coroutine_thread_cleanup);
|
|
||||||
if (ret != 0) {
|
|
||||||
fprintf(stderr, "unable to create leader key: %s\n", strerror(errno));
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void coroutine_trampoline(int i0, int i1)
|
static void coroutine_trampoline(int i0, int i1)
|
||||||
{
|
{
|
||||||
union cc_arg arg;
|
union cc_arg arg;
|
||||||
|
@ -193,15 +155,23 @@ void qemu_coroutine_delete(Coroutine *co_)
|
||||||
g_free(co);
|
g_free(co);
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineAction qemu_coroutine_switch(Coroutine *from_, Coroutine *to_,
|
/* This function is marked noinline to prevent GCC from inlining it
|
||||||
CoroutineAction action)
|
* into coroutine_trampoline(). If we allow it to do that then it
|
||||||
|
* hoists the code to get the address of the TLS variable "current"
|
||||||
|
* out of the while() loop. This is an invalid transformation because
|
||||||
|
* the sigsetjmp() call may be called when running thread A but
|
||||||
|
* return in thread B, and so we might be in a different thread
|
||||||
|
* context each time round the loop.
|
||||||
|
*/
|
||||||
|
CoroutineAction __attribute__((noinline))
|
||||||
|
qemu_coroutine_switch(Coroutine *from_, Coroutine *to_,
|
||||||
|
CoroutineAction action)
|
||||||
{
|
{
|
||||||
CoroutineUContext *from = DO_UPCAST(CoroutineUContext, base, from_);
|
CoroutineUContext *from = DO_UPCAST(CoroutineUContext, base, from_);
|
||||||
CoroutineUContext *to = DO_UPCAST(CoroutineUContext, base, to_);
|
CoroutineUContext *to = DO_UPCAST(CoroutineUContext, base, to_);
|
||||||
CoroutineThreadState *s = coroutine_get_thread_state();
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
s->current = to_;
|
current = to_;
|
||||||
|
|
||||||
ret = sigsetjmp(from->env, 0);
|
ret = sigsetjmp(from->env, 0);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
|
@ -212,14 +182,13 @@ CoroutineAction qemu_coroutine_switch(Coroutine *from_, Coroutine *to_,
|
||||||
|
|
||||||
Coroutine *qemu_coroutine_self(void)
|
Coroutine *qemu_coroutine_self(void)
|
||||||
{
|
{
|
||||||
CoroutineThreadState *s = coroutine_get_thread_state();
|
if (!current) {
|
||||||
|
current = &leader.base;
|
||||||
return s->current;
|
}
|
||||||
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool qemu_in_coroutine(void)
|
bool qemu_in_coroutine(void)
|
||||||
{
|
{
|
||||||
CoroutineThreadState *s = pthread_getspecific(thread_state_key);
|
return current && current->caller;
|
||||||
|
|
||||||
return s && s->current->caller;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,7 +198,8 @@ void virtio_blk_data_plane_create(VirtIODevice *vdev, VirtIOBlkConf *conf,
|
||||||
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_DRIVE_DEL, s->blocker);
|
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_DRIVE_DEL, s->blocker);
|
||||||
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, s->blocker);
|
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_BACKUP_SOURCE, s->blocker);
|
||||||
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_CHANGE, s->blocker);
|
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_CHANGE, s->blocker);
|
||||||
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_COMMIT, s->blocker);
|
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_COMMIT_SOURCE, s->blocker);
|
||||||
|
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_COMMIT_TARGET, s->blocker);
|
||||||
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_EJECT, s->blocker);
|
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_EJECT, s->blocker);
|
||||||
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, s->blocker);
|
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, s->blocker);
|
||||||
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT, s->blocker);
|
blk_op_unblock(conf->conf.blk, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT, s->blocker);
|
||||||
|
|
|
@ -476,7 +476,8 @@ static uint16_t nvme_get_feature(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req)
|
||||||
|
|
||||||
switch (dw10) {
|
switch (dw10) {
|
||||||
case NVME_NUMBER_OF_QUEUES:
|
case NVME_NUMBER_OF_QUEUES:
|
||||||
req->cqe.result = cpu_to_le32(n->num_queues);
|
req->cqe.result =
|
||||||
|
cpu_to_le32((n->num_queues - 1) | ((n->num_queues - 1) << 16));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return NVME_INVALID_FIELD | NVME_DNR;
|
return NVME_INVALID_FIELD | NVME_DNR;
|
||||||
|
@ -490,7 +491,8 @@ static uint16_t nvme_set_feature(NvmeCtrl *n, NvmeCmd *cmd, NvmeRequest *req)
|
||||||
|
|
||||||
switch (dw10) {
|
switch (dw10) {
|
||||||
case NVME_NUMBER_OF_QUEUES:
|
case NVME_NUMBER_OF_QUEUES:
|
||||||
req->cqe.result = cpu_to_le32(n->num_queues);
|
req->cqe.result =
|
||||||
|
cpu_to_le32((n->num_queues - 1) | ((n->num_queues - 1) << 16));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return NVME_INVALID_FIELD | NVME_DNR;
|
return NVME_INVALID_FIELD | NVME_DNR;
|
||||||
|
@ -813,7 +815,7 @@ static int nvme_init(PCIDevice *pci_dev)
|
||||||
NVME_CAP_SET_CSS(n->bar.cap, 1);
|
NVME_CAP_SET_CSS(n->bar.cap, 1);
|
||||||
NVME_CAP_SET_MPSMAX(n->bar.cap, 4);
|
NVME_CAP_SET_MPSMAX(n->bar.cap, 4);
|
||||||
|
|
||||||
n->bar.vs = 0x00010001;
|
n->bar.vs = 0x00010100;
|
||||||
n->bar.intmc = n->bar.intms = 0;
|
n->bar.intmc = n->bar.intms = 0;
|
||||||
|
|
||||||
for (i = 0; i < n->num_namespaces; i++) {
|
for (i = 0; i < n->num_namespaces; i++) {
|
||||||
|
|
111
hw/ide/atapi.c
111
hw/ide/atapi.c
|
@ -621,20 +621,107 @@ static void cmd_request_sense(IDEState *s, uint8_t *buf)
|
||||||
|
|
||||||
static void cmd_inquiry(IDEState *s, uint8_t *buf)
|
static void cmd_inquiry(IDEState *s, uint8_t *buf)
|
||||||
{
|
{
|
||||||
|
uint8_t page_code = buf[2];
|
||||||
int max_len = buf[4];
|
int max_len = buf[4];
|
||||||
|
|
||||||
buf[0] = 0x05; /* CD-ROM */
|
unsigned idx = 0;
|
||||||
buf[1] = 0x80; /* removable */
|
unsigned size_idx;
|
||||||
buf[2] = 0x00; /* ISO */
|
unsigned preamble_len;
|
||||||
buf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
|
|
||||||
buf[4] = 31; /* additional length */
|
/* If the EVPD (Enable Vital Product Data) bit is set in byte 1,
|
||||||
buf[5] = 0; /* reserved */
|
* we are being asked for a specific page of info indicated by byte 2. */
|
||||||
buf[6] = 0; /* reserved */
|
if (buf[1] & 0x01) {
|
||||||
buf[7] = 0; /* reserved */
|
preamble_len = 4;
|
||||||
padstr8(buf + 8, 8, "QEMU");
|
size_idx = 3;
|
||||||
padstr8(buf + 16, 16, "QEMU DVD-ROM");
|
|
||||||
padstr8(buf + 32, 4, s->version);
|
buf[idx++] = 0x05; /* CD-ROM */
|
||||||
ide_atapi_cmd_reply(s, 36, max_len);
|
buf[idx++] = page_code; /* Page Code */
|
||||||
|
buf[idx++] = 0x00; /* reserved */
|
||||||
|
idx++; /* length (set later) */
|
||||||
|
|
||||||
|
switch (page_code) {
|
||||||
|
case 0x00:
|
||||||
|
/* Supported Pages: List of supported VPD responses. */
|
||||||
|
buf[idx++] = 0x00; /* 0x00: Supported Pages, and: */
|
||||||
|
buf[idx++] = 0x83; /* 0x83: Device Identification. */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x83:
|
||||||
|
/* Device Identification. Each entry is optional, but the entries
|
||||||
|
* included here are modeled after libata's VPD responses.
|
||||||
|
* If the response is given, at least one entry must be present. */
|
||||||
|
|
||||||
|
/* Entry 1: Serial */
|
||||||
|
if (idx + 24 > max_len) {
|
||||||
|
/* Not enough room for even the first entry: */
|
||||||
|
/* 4 byte header + 20 byte string */
|
||||||
|
ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
|
||||||
|
ASC_DATA_PHASE_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buf[idx++] = 0x02; /* Ascii */
|
||||||
|
buf[idx++] = 0x00; /* Vendor Specific */
|
||||||
|
buf[idx++] = 0x00;
|
||||||
|
buf[idx++] = 20; /* Remaining length */
|
||||||
|
padstr8(buf + idx, 20, s->drive_serial_str);
|
||||||
|
idx += 20;
|
||||||
|
|
||||||
|
/* Entry 2: Drive Model and Serial */
|
||||||
|
if (idx + 72 > max_len) {
|
||||||
|
/* 4 (header) + 8 (vendor) + 60 (model & serial) */
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
buf[idx++] = 0x02; /* Ascii */
|
||||||
|
buf[idx++] = 0x01; /* T10 Vendor */
|
||||||
|
buf[idx++] = 0x00;
|
||||||
|
buf[idx++] = 68;
|
||||||
|
padstr8(buf + idx, 8, "ATA"); /* Generic T10 vendor */
|
||||||
|
idx += 8;
|
||||||
|
padstr8(buf + idx, 40, s->drive_model_str);
|
||||||
|
idx += 40;
|
||||||
|
padstr8(buf + idx, 20, s->drive_serial_str);
|
||||||
|
idx += 20;
|
||||||
|
|
||||||
|
/* Entry 3: WWN */
|
||||||
|
if (s->wwn && (idx + 12 <= max_len)) {
|
||||||
|
/* 4 byte header + 8 byte wwn */
|
||||||
|
buf[idx++] = 0x01; /* Binary */
|
||||||
|
buf[idx++] = 0x03; /* NAA */
|
||||||
|
buf[idx++] = 0x00;
|
||||||
|
buf[idx++] = 0x08;
|
||||||
|
stq_be_p(&buf[idx], s->wwn);
|
||||||
|
idx += 8;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
/* SPC-3, revision 23 sec. 6.4 */
|
||||||
|
ide_atapi_cmd_error(s, ILLEGAL_REQUEST,
|
||||||
|
ASC_INV_FIELD_IN_CMD_PACKET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
preamble_len = 5;
|
||||||
|
size_idx = 4;
|
||||||
|
|
||||||
|
buf[0] = 0x05; /* CD-ROM */
|
||||||
|
buf[1] = 0x80; /* removable */
|
||||||
|
buf[2] = 0x00; /* ISO */
|
||||||
|
buf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
|
||||||
|
/* buf[size_idx] set below. */
|
||||||
|
buf[5] = 0; /* reserved */
|
||||||
|
buf[6] = 0; /* reserved */
|
||||||
|
buf[7] = 0; /* reserved */
|
||||||
|
padstr8(buf + 8, 8, "QEMU");
|
||||||
|
padstr8(buf + 16, 16, "QEMU DVD-ROM");
|
||||||
|
padstr8(buf + 32, 4, s->version);
|
||||||
|
idx = 36;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
buf[size_idx] = idx - preamble_len;
|
||||||
|
ide_atapi_cmd_reply(s, idx, max_len);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cmd_get_configuration(IDEState *s, uint8_t *buf)
|
static void cmd_get_configuration(IDEState *s, uint8_t *buf)
|
||||||
|
|
|
@ -296,6 +296,7 @@ typedef struct IDEDMAOps IDEDMAOps;
|
||||||
#define ASC_INCOMPATIBLE_FORMAT 0x30
|
#define ASC_INCOMPATIBLE_FORMAT 0x30
|
||||||
#define ASC_MEDIUM_NOT_PRESENT 0x3a
|
#define ASC_MEDIUM_NOT_PRESENT 0x3a
|
||||||
#define ASC_SAVING_PARAMETERS_NOT_SUPPORTED 0x39
|
#define ASC_SAVING_PARAMETERS_NOT_SUPPORTED 0x39
|
||||||
|
#define ASC_DATA_PHASE_ERROR 0x4b
|
||||||
#define ASC_MEDIA_REMOVAL_PREVENTED 0x53
|
#define ASC_MEDIA_REMOVAL_PREVENTED 0x53
|
||||||
|
|
||||||
#define CFA_NO_ERROR 0x00
|
#define CFA_NO_ERROR 0x00
|
||||||
|
|
|
@ -133,7 +133,8 @@ typedef enum BlockOpType {
|
||||||
BLOCK_OP_TYPE_BACKUP_SOURCE,
|
BLOCK_OP_TYPE_BACKUP_SOURCE,
|
||||||
BLOCK_OP_TYPE_BACKUP_TARGET,
|
BLOCK_OP_TYPE_BACKUP_TARGET,
|
||||||
BLOCK_OP_TYPE_CHANGE,
|
BLOCK_OP_TYPE_CHANGE,
|
||||||
BLOCK_OP_TYPE_COMMIT,
|
BLOCK_OP_TYPE_COMMIT_SOURCE,
|
||||||
|
BLOCK_OP_TYPE_COMMIT_TARGET,
|
||||||
BLOCK_OP_TYPE_DATAPLANE,
|
BLOCK_OP_TYPE_DATAPLANE,
|
||||||
BLOCK_OP_TYPE_DRIVE_DEL,
|
BLOCK_OP_TYPE_DRIVE_DEL,
|
||||||
BLOCK_OP_TYPE_EJECT,
|
BLOCK_OP_TYPE_EJECT,
|
||||||
|
@ -396,7 +397,11 @@ const char *bdrv_get_encrypted_filename(BlockDriverState *bs);
|
||||||
void bdrv_get_backing_filename(BlockDriverState *bs,
|
void bdrv_get_backing_filename(BlockDriverState *bs,
|
||||||
char *filename, int filename_size);
|
char *filename, int filename_size);
|
||||||
void bdrv_get_full_backing_filename(BlockDriverState *bs,
|
void bdrv_get_full_backing_filename(BlockDriverState *bs,
|
||||||
char *dest, size_t sz);
|
char *dest, size_t sz, Error **errp);
|
||||||
|
void bdrv_get_full_backing_filename_from_filename(const char *backed,
|
||||||
|
const char *backing,
|
||||||
|
char *dest, size_t sz,
|
||||||
|
Error **errp);
|
||||||
int bdrv_is_snapshot(BlockDriverState *bs);
|
int bdrv_is_snapshot(BlockDriverState *bs);
|
||||||
|
|
||||||
int path_has_protocol(const char *path);
|
int path_has_protocol(const char *path);
|
||||||
|
@ -434,8 +439,10 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs, int granularity,
|
||||||
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
|
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
|
||||||
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
|
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
|
||||||
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
|
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
|
||||||
void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
|
void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
||||||
void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector, int nr_sectors);
|
int64_t cur_sector, int nr_sectors);
|
||||||
|
void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
||||||
|
int64_t cur_sector, int nr_sectors);
|
||||||
void bdrv_dirty_iter_init(BlockDriverState *bs,
|
void bdrv_dirty_iter_init(BlockDriverState *bs,
|
||||||
BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
|
BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
|
||||||
int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
|
int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
|
||||||
|
|
|
@ -216,14 +216,4 @@ void coroutine_fn co_aio_sleep_ns(AioContext *ctx, QEMUClockType type,
|
||||||
*/
|
*/
|
||||||
void coroutine_fn yield_until_fd_readable(int fd);
|
void coroutine_fn yield_until_fd_readable(int fd);
|
||||||
|
|
||||||
/**
|
|
||||||
* Add or subtract from the coroutine pool size
|
|
||||||
*
|
|
||||||
* The coroutine implementation keeps a pool of coroutines to be reused by
|
|
||||||
* qemu_coroutine_create(). This makes coroutine creation cheap. Heavy
|
|
||||||
* coroutine users should call this to reserve pool space. Call it again with
|
|
||||||
* a negative number to release pool space.
|
|
||||||
*/
|
|
||||||
void qemu_coroutine_adjust_pool_size(int n);
|
|
||||||
|
|
||||||
#endif /* QEMU_COROUTINE_H */
|
#endif /* QEMU_COROUTINE_H */
|
||||||
|
|
|
@ -191,8 +191,19 @@ struct { \
|
||||||
} while (/*CONSTCOND*/0)
|
} while (/*CONSTCOND*/0)
|
||||||
|
|
||||||
#define QSLIST_INSERT_HEAD(head, elm, field) do { \
|
#define QSLIST_INSERT_HEAD(head, elm, field) do { \
|
||||||
(elm)->field.sle_next = (head)->slh_first; \
|
(elm)->field.sle_next = (head)->slh_first; \
|
||||||
(head)->slh_first = (elm); \
|
(head)->slh_first = (elm); \
|
||||||
|
} while (/*CONSTCOND*/0)
|
||||||
|
|
||||||
|
#define QSLIST_INSERT_HEAD_ATOMIC(head, elm, field) do { \
|
||||||
|
do { \
|
||||||
|
(elm)->field.sle_next = (head)->slh_first; \
|
||||||
|
} while (atomic_cmpxchg(&(head)->slh_first, (elm)->field.sle_next, \
|
||||||
|
(elm)) != (elm)->field.sle_next); \
|
||||||
|
} while (/*CONSTCOND*/0)
|
||||||
|
|
||||||
|
#define QSLIST_MOVE_ATOMIC(dest, src) do { \
|
||||||
|
(dest)->slh_first = atomic_xchg(&(src)->slh_first, NULL); \
|
||||||
} while (/*CONSTCOND*/0)
|
} while (/*CONSTCOND*/0)
|
||||||
|
|
||||||
#define QSLIST_REMOVE_HEAD(head, field) do { \
|
#define QSLIST_REMOVE_HEAD(head, field) do { \
|
||||||
|
|
|
@ -61,4 +61,8 @@ bool qemu_thread_is_self(QemuThread *thread);
|
||||||
void qemu_thread_exit(void *retval);
|
void qemu_thread_exit(void *retval);
|
||||||
void qemu_thread_naming(bool enable);
|
void qemu_thread_naming(bool enable);
|
||||||
|
|
||||||
|
struct Notifier;
|
||||||
|
void qemu_thread_atexit_add(struct Notifier *notifier);
|
||||||
|
void qemu_thread_atexit_remove(struct Notifier *notifier);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -303,7 +303,7 @@ static int mig_save_device_bulk(QEMUFile *f, BlkMigDevState *bmds)
|
||||||
blk->aiocb = bdrv_aio_readv(bs, cur_sector, &blk->qiov,
|
blk->aiocb = bdrv_aio_readv(bs, cur_sector, &blk->qiov,
|
||||||
nr_sectors, blk_mig_read_cb, blk);
|
nr_sectors, blk_mig_read_cb, blk);
|
||||||
|
|
||||||
bdrv_reset_dirty(bs, cur_sector, nr_sectors);
|
bdrv_reset_dirty_bitmap(bs, bmds->dirty_bitmap, cur_sector, nr_sectors);
|
||||||
qemu_mutex_unlock_iothread();
|
qemu_mutex_unlock_iothread();
|
||||||
|
|
||||||
bmds->cur_sector = cur_sector + nr_sectors;
|
bmds->cur_sector = cur_sector + nr_sectors;
|
||||||
|
@ -496,7 +496,8 @@ static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds,
|
||||||
g_free(blk);
|
g_free(blk);
|
||||||
}
|
}
|
||||||
|
|
||||||
bdrv_reset_dirty(bmds->bs, sector, nr_sectors);
|
bdrv_reset_dirty_bitmap(bmds->bs, bmds->dirty_bitmap, sector,
|
||||||
|
nr_sectors);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sector += BDRV_SECTORS_PER_DIRTY_CHUNK;
|
sector += BDRV_SECTORS_PER_DIRTY_CHUNK;
|
||||||
|
@ -765,8 +766,8 @@ static uint64_t block_save_pending(QEMUFile *f, void *opaque, uint64_t max_size)
|
||||||
block_mig_state.read_done * BLOCK_SIZE;
|
block_mig_state.read_done * BLOCK_SIZE;
|
||||||
|
|
||||||
/* Report at least one block pending during bulk phase */
|
/* Report at least one block pending during bulk phase */
|
||||||
if (pending == 0 && !block_mig_state.bulk_completed) {
|
if (pending <= max_size && !block_mig_state.bulk_completed) {
|
||||||
pending = BLOCK_SIZE;
|
pending = max_size + BLOCK_SIZE;
|
||||||
}
|
}
|
||||||
blk_mig_unlock();
|
blk_mig_unlock();
|
||||||
qemu_mutex_unlock_iothread();
|
qemu_mutex_unlock_iothread();
|
||||||
|
|
|
@ -1254,11 +1254,19 @@
|
||||||
#
|
#
|
||||||
# A discriminated record of operations that can be performed with
|
# A discriminated record of operations that can be performed with
|
||||||
# @transaction.
|
# @transaction.
|
||||||
|
#
|
||||||
|
# Since 1.1
|
||||||
|
#
|
||||||
|
# drive-backup since 1.6
|
||||||
|
# abort since 1.6
|
||||||
|
# blockdev-snapshot-internal-sync since 1.7
|
||||||
|
# blockdev-backup since 2.3
|
||||||
##
|
##
|
||||||
{ 'union': 'TransactionAction',
|
{ 'union': 'TransactionAction',
|
||||||
'data': {
|
'data': {
|
||||||
'blockdev-snapshot-sync': 'BlockdevSnapshot',
|
'blockdev-snapshot-sync': 'BlockdevSnapshot',
|
||||||
'drive-backup': 'DriveBackup',
|
'drive-backup': 'DriveBackup',
|
||||||
|
'blockdev-backup': 'BlockdevBackup',
|
||||||
'abort': 'Abort',
|
'abort': 'Abort',
|
||||||
'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal'
|
'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal'
|
||||||
} }
|
} }
|
||||||
|
|
|
@ -423,7 +423,7 @@
|
||||||
# @device: #optional If the stats are for a virtual block device, the name
|
# @device: #optional If the stats are for a virtual block device, the name
|
||||||
# corresponding to the virtual block device.
|
# corresponding to the virtual block device.
|
||||||
#
|
#
|
||||||
# @device: #optional The node name of the device. (Since 2.3)
|
# @node-name: #optional The node name of the device. (Since 2.3)
|
||||||
#
|
#
|
||||||
# @stats: A @BlockDeviceStats for the device.
|
# @stats: A @BlockDeviceStats for the device.
|
||||||
#
|
#
|
||||||
|
@ -702,6 +702,41 @@
|
||||||
'*on-source-error': 'BlockdevOnError',
|
'*on-source-error': 'BlockdevOnError',
|
||||||
'*on-target-error': 'BlockdevOnError' } }
|
'*on-target-error': 'BlockdevOnError' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @BlockdevBackup
|
||||||
|
#
|
||||||
|
# @device: the name of the device which should be copied.
|
||||||
|
#
|
||||||
|
# @target: the name of the backup target device.
|
||||||
|
#
|
||||||
|
# @sync: what parts of the disk image should be copied to the destination
|
||||||
|
# (all the disk, only the sectors allocated in the topmost image, or
|
||||||
|
# only new I/O).
|
||||||
|
#
|
||||||
|
# @speed: #optional the maximum speed, in bytes per second. The default is 0,
|
||||||
|
# for unlimited.
|
||||||
|
#
|
||||||
|
# @on-source-error: #optional the action to take on an error on the source,
|
||||||
|
# default 'report'. 'stop' and 'enospc' can only be used
|
||||||
|
# if the block device supports io-status (see BlockInfo).
|
||||||
|
#
|
||||||
|
# @on-target-error: #optional the action to take on an error on the target,
|
||||||
|
# default 'report' (no limitations, since this applies to
|
||||||
|
# a different block device than @device).
|
||||||
|
#
|
||||||
|
# Note that @on-source-error and @on-target-error only affect background I/O.
|
||||||
|
# If an error occurs during a guest write request, the device's rerror/werror
|
||||||
|
# actions will be used.
|
||||||
|
#
|
||||||
|
# Since: 2.3
|
||||||
|
##
|
||||||
|
{ 'type': 'BlockdevBackup',
|
||||||
|
'data': { 'device': 'str', 'target': 'str',
|
||||||
|
'sync': 'MirrorSyncMode',
|
||||||
|
'*speed': 'int',
|
||||||
|
'*on-source-error': 'BlockdevOnError',
|
||||||
|
'*on-target-error': 'BlockdevOnError' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @blockdev-snapshot-sync
|
# @blockdev-snapshot-sync
|
||||||
#
|
#
|
||||||
|
@ -821,6 +856,25 @@
|
||||||
##
|
##
|
||||||
{ 'command': 'drive-backup', 'data': 'DriveBackup' }
|
{ 'command': 'drive-backup', 'data': 'DriveBackup' }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @blockdev-backup
|
||||||
|
#
|
||||||
|
# Start a point-in-time copy of a block device to a new destination. The
|
||||||
|
# status of ongoing blockdev-backup operations can be checked with
|
||||||
|
# query-block-jobs where the BlockJobInfo.type field has the value 'backup'.
|
||||||
|
# The operation can be stopped before it has completed using the
|
||||||
|
# block-job-cancel command.
|
||||||
|
#
|
||||||
|
# For the arguments, see the documentation of BlockdevBackup.
|
||||||
|
#
|
||||||
|
# Returns: Nothing on success.
|
||||||
|
# If @device or @target is not a valid block device, DeviceNotFound.
|
||||||
|
#
|
||||||
|
# Since 2.3
|
||||||
|
##
|
||||||
|
{ 'command': 'blockdev-backup', 'data': 'BlockdevBackup' }
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# @query-named-block-nodes
|
# @query-named-block-nodes
|
||||||
#
|
#
|
||||||
|
|
105
qemu-coroutine.c
105
qemu-coroutine.c
|
@ -15,31 +15,59 @@
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/thread.h"
|
#include "qemu/thread.h"
|
||||||
|
#include "qemu/atomic.h"
|
||||||
#include "block/coroutine.h"
|
#include "block/coroutine.h"
|
||||||
#include "block/coroutine_int.h"
|
#include "block/coroutine_int.h"
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
POOL_DEFAULT_SIZE = 64,
|
POOL_BATCH_SIZE = 64,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Free list to speed up creation */
|
/** Free list to speed up creation */
|
||||||
static QemuMutex pool_lock;
|
static QSLIST_HEAD(, Coroutine) release_pool = QSLIST_HEAD_INITIALIZER(pool);
|
||||||
static QSLIST_HEAD(, Coroutine) pool = QSLIST_HEAD_INITIALIZER(pool);
|
static unsigned int release_pool_size;
|
||||||
static unsigned int pool_size;
|
static __thread QSLIST_HEAD(, Coroutine) alloc_pool = QSLIST_HEAD_INITIALIZER(pool);
|
||||||
static unsigned int pool_max_size = POOL_DEFAULT_SIZE;
|
static __thread unsigned int alloc_pool_size;
|
||||||
|
static __thread Notifier coroutine_pool_cleanup_notifier;
|
||||||
|
|
||||||
|
static void coroutine_pool_cleanup(Notifier *n, void *value)
|
||||||
|
{
|
||||||
|
Coroutine *co;
|
||||||
|
Coroutine *tmp;
|
||||||
|
|
||||||
|
QSLIST_FOREACH_SAFE(co, &alloc_pool, pool_next, tmp) {
|
||||||
|
QSLIST_REMOVE_HEAD(&alloc_pool, pool_next);
|
||||||
|
qemu_coroutine_delete(co);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Coroutine *qemu_coroutine_create(CoroutineEntry *entry)
|
Coroutine *qemu_coroutine_create(CoroutineEntry *entry)
|
||||||
{
|
{
|
||||||
Coroutine *co = NULL;
|
Coroutine *co = NULL;
|
||||||
|
|
||||||
if (CONFIG_COROUTINE_POOL) {
|
if (CONFIG_COROUTINE_POOL) {
|
||||||
qemu_mutex_lock(&pool_lock);
|
co = QSLIST_FIRST(&alloc_pool);
|
||||||
co = QSLIST_FIRST(&pool);
|
if (!co) {
|
||||||
if (co) {
|
if (release_pool_size > POOL_BATCH_SIZE) {
|
||||||
QSLIST_REMOVE_HEAD(&pool, pool_next);
|
/* Slow path; a good place to register the destructor, too. */
|
||||||
pool_size--;
|
if (!coroutine_pool_cleanup_notifier.notify) {
|
||||||
|
coroutine_pool_cleanup_notifier.notify = coroutine_pool_cleanup;
|
||||||
|
qemu_thread_atexit_add(&coroutine_pool_cleanup_notifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is not exact; there could be a little skew between
|
||||||
|
* release_pool_size and the actual size of release_pool. But
|
||||||
|
* it is just a heuristic, it does not need to be perfect.
|
||||||
|
*/
|
||||||
|
alloc_pool_size = atomic_xchg(&release_pool_size, 0);
|
||||||
|
QSLIST_MOVE_ATOMIC(&alloc_pool, &release_pool);
|
||||||
|
co = QSLIST_FIRST(&alloc_pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (co) {
|
||||||
|
QSLIST_REMOVE_HEAD(&alloc_pool, pool_next);
|
||||||
|
alloc_pool_size--;
|
||||||
}
|
}
|
||||||
qemu_mutex_unlock(&pool_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!co) {
|
if (!co) {
|
||||||
|
@ -53,39 +81,24 @@ Coroutine *qemu_coroutine_create(CoroutineEntry *entry)
|
||||||
|
|
||||||
static void coroutine_delete(Coroutine *co)
|
static void coroutine_delete(Coroutine *co)
|
||||||
{
|
{
|
||||||
|
co->caller = NULL;
|
||||||
|
|
||||||
if (CONFIG_COROUTINE_POOL) {
|
if (CONFIG_COROUTINE_POOL) {
|
||||||
qemu_mutex_lock(&pool_lock);
|
if (release_pool_size < POOL_BATCH_SIZE * 2) {
|
||||||
if (pool_size < pool_max_size) {
|
QSLIST_INSERT_HEAD_ATOMIC(&release_pool, co, pool_next);
|
||||||
QSLIST_INSERT_HEAD(&pool, co, pool_next);
|
atomic_inc(&release_pool_size);
|
||||||
co->caller = NULL;
|
return;
|
||||||
pool_size++;
|
}
|
||||||
qemu_mutex_unlock(&pool_lock);
|
if (alloc_pool_size < POOL_BATCH_SIZE) {
|
||||||
|
QSLIST_INSERT_HEAD(&alloc_pool, co, pool_next);
|
||||||
|
alloc_pool_size++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
qemu_mutex_unlock(&pool_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_coroutine_delete(co);
|
qemu_coroutine_delete(co);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __attribute__((constructor)) coroutine_pool_init(void)
|
|
||||||
{
|
|
||||||
qemu_mutex_init(&pool_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __attribute__((destructor)) coroutine_pool_cleanup(void)
|
|
||||||
{
|
|
||||||
Coroutine *co;
|
|
||||||
Coroutine *tmp;
|
|
||||||
|
|
||||||
QSLIST_FOREACH_SAFE(co, &pool, pool_next, tmp) {
|
|
||||||
QSLIST_REMOVE_HEAD(&pool, pool_next);
|
|
||||||
qemu_coroutine_delete(co);
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_mutex_destroy(&pool_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void coroutine_swap(Coroutine *from, Coroutine *to)
|
static void coroutine_swap(Coroutine *from, Coroutine *to)
|
||||||
{
|
{
|
||||||
CoroutineAction ret;
|
CoroutineAction ret;
|
||||||
|
@ -137,23 +150,3 @@ void coroutine_fn qemu_coroutine_yield(void)
|
||||||
self->caller = NULL;
|
self->caller = NULL;
|
||||||
coroutine_swap(self, to);
|
coroutine_swap(self, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qemu_coroutine_adjust_pool_size(int n)
|
|
||||||
{
|
|
||||||
qemu_mutex_lock(&pool_lock);
|
|
||||||
|
|
||||||
pool_max_size += n;
|
|
||||||
|
|
||||||
/* Callers should never take away more than they added */
|
|
||||||
assert(pool_max_size >= POOL_DEFAULT_SIZE);
|
|
||||||
|
|
||||||
/* Trim oversized pool down to new max */
|
|
||||||
while (pool_size > pool_max_size) {
|
|
||||||
Coroutine *co = QSLIST_FIRST(&pool);
|
|
||||||
QSLIST_REMOVE_HEAD(&pool, pool_next);
|
|
||||||
pool_size--;
|
|
||||||
qemu_coroutine_delete(co);
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_mutex_unlock(&pool_lock);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1094,6 +1094,48 @@ Example:
|
||||||
"sync": "full",
|
"sync": "full",
|
||||||
"target": "backup.img" } }
|
"target": "backup.img" } }
|
||||||
<- { "return": {} }
|
<- { "return": {} }
|
||||||
|
|
||||||
|
EQMP
|
||||||
|
|
||||||
|
{
|
||||||
|
.name = "blockdev-backup",
|
||||||
|
.args_type = "sync:s,device:B,target:B,speed:i?,"
|
||||||
|
"on-source-error:s?,on-target-error:s?",
|
||||||
|
.mhandler.cmd_new = qmp_marshal_input_blockdev_backup,
|
||||||
|
},
|
||||||
|
|
||||||
|
SQMP
|
||||||
|
blockdev-backup
|
||||||
|
---------------
|
||||||
|
|
||||||
|
The device version of drive-backup: this command takes an existing named device
|
||||||
|
as backup target.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
- "device": the name of the device which should be copied.
|
||||||
|
(json-string)
|
||||||
|
- "target": the name of the backup target device. (json-string)
|
||||||
|
- "sync": what parts of the disk image should be copied to the destination;
|
||||||
|
possibilities include "full" for all the disk, "top" for only the
|
||||||
|
sectors allocated in the topmost image, or "none" to only replicate
|
||||||
|
new I/O (MirrorSyncMode).
|
||||||
|
- "speed": the maximum speed, in bytes per second (json-int, optional)
|
||||||
|
- "on-source-error": the action to take on an error on the source, default
|
||||||
|
'report'. 'stop' and 'enospc' can only be used
|
||||||
|
if the block device supports io-status.
|
||||||
|
(BlockdevOnError, optional)
|
||||||
|
- "on-target-error": the action to take on an error on the target, default
|
||||||
|
'report' (no limitations, since this applies to
|
||||||
|
a different block device than device).
|
||||||
|
(BlockdevOnError, optional)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
-> { "execute": "blockdev-backup", "arguments": { "device": "src-id",
|
||||||
|
"sync": "full",
|
||||||
|
"target": "tgt-id" } }
|
||||||
|
<- { "return": {} }
|
||||||
|
|
||||||
EQMP
|
EQMP
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -1639,7 +1639,13 @@ sub process {
|
||||||
#print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n";
|
#print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n";
|
||||||
#print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n";
|
#print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n";
|
||||||
|
|
||||||
if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln -1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) {
|
# The length of the "previous line" is checked against 80 because it
|
||||||
|
# includes the + at the beginning of the line (if the actual line has
|
||||||
|
# 79 or 80 characters, it is no longer possible to add a space and an
|
||||||
|
# opening brace there)
|
||||||
|
if ($#ctx == 0 && $ctx !~ /{\s*/ &&
|
||||||
|
defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/ &&
|
||||||
|
defined($lines[$ctx_ln - 2]) && length($lines[$ctx_ln - 2]) < 80) {
|
||||||
ERROR("that open brace { should be on the previous line\n" .
|
ERROR("that open brace { should be on the previous line\n" .
|
||||||
"$here\n$ctx\n$rawlines[$ctx_ln - 1]\n");
|
"$here\n$ctx\n$rawlines[$ctx_ln - 1]\n");
|
||||||
}
|
}
|
||||||
|
@ -2542,7 +2548,10 @@ sub process {
|
||||||
|
|
||||||
substr($block, 0, length($cond), '');
|
substr($block, 0, length($cond), '');
|
||||||
|
|
||||||
$seen++ if ($block =~ /^\s*{/);
|
my $spaced_block = $block;
|
||||||
|
$spaced_block =~ s/\n\+/ /g;
|
||||||
|
|
||||||
|
$seen++ if ($spaced_block =~ /^\s*{/);
|
||||||
|
|
||||||
print "APW: cond<$cond> block<$block> allowed<$allowed>\n"
|
print "APW: cond<$cond> block<$block> allowed<$allowed>\n"
|
||||||
if $dbg_adv_apw;
|
if $dbg_adv_apw;
|
||||||
|
|
|
@ -298,7 +298,7 @@ tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y) l
|
||||||
tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a
|
tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a
|
||||||
tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a
|
tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a
|
||||||
|
|
||||||
libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o
|
libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
|
||||||
libqos-obj-y += tests/libqos/i2c.o
|
libqos-obj-y += tests/libqos/i2c.o
|
||||||
libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o
|
libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o
|
||||||
libqos-pc-obj-y += tests/libqos/malloc-pc.o
|
libqos-pc-obj-y += tests/libqos/malloc-pc.o
|
||||||
|
|
|
@ -17,296 +17,28 @@
|
||||||
#include "hw/nvram/fw_cfg.h"
|
#include "hw/nvram/fw_cfg.h"
|
||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/queue.h"
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#define PAGE_SIZE (4096)
|
#define PAGE_SIZE (4096)
|
||||||
|
|
||||||
#define MLIST_ENTNAME entries
|
|
||||||
typedef QTAILQ_HEAD(MemList, MemBlock) MemList;
|
|
||||||
typedef struct MemBlock {
|
|
||||||
QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME;
|
|
||||||
uint64_t size;
|
|
||||||
uint64_t addr;
|
|
||||||
} MemBlock;
|
|
||||||
|
|
||||||
typedef struct PCAlloc
|
|
||||||
{
|
|
||||||
QGuestAllocator alloc;
|
|
||||||
PCAllocOpts opts;
|
|
||||||
uint64_t start;
|
|
||||||
uint64_t end;
|
|
||||||
|
|
||||||
MemList used;
|
|
||||||
MemList free;
|
|
||||||
} PCAlloc;
|
|
||||||
|
|
||||||
static MemBlock *mlist_new(uint64_t addr, uint64_t size)
|
|
||||||
{
|
|
||||||
MemBlock *block;
|
|
||||||
|
|
||||||
if (!size) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
block = g_malloc0(sizeof(MemBlock));
|
|
||||||
|
|
||||||
block->addr = addr;
|
|
||||||
block->size = size;
|
|
||||||
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mlist_delete(MemList *list, MemBlock *node)
|
|
||||||
{
|
|
||||||
g_assert(list && node);
|
|
||||||
QTAILQ_REMOVE(list, node, MLIST_ENTNAME);
|
|
||||||
g_free(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
static MemBlock *mlist_find_key(MemList *head, uint64_t addr)
|
|
||||||
{
|
|
||||||
MemBlock *node;
|
|
||||||
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
|
||||||
if (node->addr == addr) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MemBlock *mlist_find_space(MemList *head, uint64_t size)
|
|
||||||
{
|
|
||||||
MemBlock *node;
|
|
||||||
|
|
||||||
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
|
||||||
if (node->size >= size) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr)
|
|
||||||
{
|
|
||||||
MemBlock *node;
|
|
||||||
g_assert(head && insr);
|
|
||||||
|
|
||||||
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
|
||||||
if (insr->addr < node->addr) {
|
|
||||||
QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME);
|
|
||||||
return insr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME);
|
|
||||||
return insr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint64_t mlist_boundary(MemBlock *node)
|
|
||||||
{
|
|
||||||
return node->size + node->addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right)
|
|
||||||
{
|
|
||||||
g_assert(head && left && right);
|
|
||||||
|
|
||||||
left->size += right->size;
|
|
||||||
mlist_delete(head, right);
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mlist_coalesce(MemList *head, MemBlock *node)
|
|
||||||
{
|
|
||||||
g_assert(node);
|
|
||||||
MemBlock *left;
|
|
||||||
MemBlock *right;
|
|
||||||
char merge;
|
|
||||||
|
|
||||||
do {
|
|
||||||
merge = 0;
|
|
||||||
left = QTAILQ_PREV(node, MemList, MLIST_ENTNAME);
|
|
||||||
right = QTAILQ_NEXT(node, MLIST_ENTNAME);
|
|
||||||
|
|
||||||
/* clowns to the left of me */
|
|
||||||
if (left && mlist_boundary(left) == node->addr) {
|
|
||||||
node = mlist_join(head, left, node);
|
|
||||||
merge = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* jokers to the right */
|
|
||||||
if (right && mlist_boundary(node) == right->addr) {
|
|
||||||
node = mlist_join(head, node, right);
|
|
||||||
merge = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
} while (merge);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t pc_mlist_fulfill(PCAlloc *s, MemBlock *freenode, uint64_t size)
|
|
||||||
{
|
|
||||||
uint64_t addr;
|
|
||||||
MemBlock *usednode;
|
|
||||||
|
|
||||||
g_assert(freenode);
|
|
||||||
g_assert_cmpint(freenode->size, >=, size);
|
|
||||||
|
|
||||||
addr = freenode->addr;
|
|
||||||
if (freenode->size == size) {
|
|
||||||
/* re-use this freenode as our used node */
|
|
||||||
QTAILQ_REMOVE(&s->free, freenode, MLIST_ENTNAME);
|
|
||||||
usednode = freenode;
|
|
||||||
} else {
|
|
||||||
/* adjust the free node and create a new used node */
|
|
||||||
freenode->addr += size;
|
|
||||||
freenode->size -= size;
|
|
||||||
usednode = mlist_new(addr, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
mlist_sort_insert(&s->used, usednode);
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To assert the correctness of the list.
|
|
||||||
* Used only if PC_ALLOC_PARANOID is set. */
|
|
||||||
static void pc_mlist_check(PCAlloc *s)
|
|
||||||
{
|
|
||||||
MemBlock *node;
|
|
||||||
uint64_t addr = s->start > 0 ? s->start - 1 : 0;
|
|
||||||
uint64_t next = s->start;
|
|
||||||
|
|
||||||
QTAILQ_FOREACH(node, &s->free, MLIST_ENTNAME) {
|
|
||||||
g_assert_cmpint(node->addr, >, addr);
|
|
||||||
g_assert_cmpint(node->addr, >=, next);
|
|
||||||
addr = node->addr;
|
|
||||||
next = node->addr + node->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
addr = s->start > 0 ? s->start - 1 : 0;
|
|
||||||
next = s->start;
|
|
||||||
QTAILQ_FOREACH(node, &s->used, MLIST_ENTNAME) {
|
|
||||||
g_assert_cmpint(node->addr, >, addr);
|
|
||||||
g_assert_cmpint(node->addr, >=, next);
|
|
||||||
addr = node->addr;
|
|
||||||
next = node->addr + node->size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t pc_mlist_alloc(PCAlloc *s, uint64_t size)
|
|
||||||
{
|
|
||||||
MemBlock *node;
|
|
||||||
|
|
||||||
node = mlist_find_space(&s->free, size);
|
|
||||||
if (!node) {
|
|
||||||
fprintf(stderr, "Out of guest memory.\n");
|
|
||||||
g_assert_not_reached();
|
|
||||||
}
|
|
||||||
return pc_mlist_fulfill(s, node, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pc_mlist_free(PCAlloc *s, uint64_t addr)
|
|
||||||
{
|
|
||||||
MemBlock *node;
|
|
||||||
|
|
||||||
if (addr == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
node = mlist_find_key(&s->used, addr);
|
|
||||||
if (!node) {
|
|
||||||
fprintf(stderr, "Error: no record found for an allocation at "
|
|
||||||
"0x%016" PRIx64 ".\n",
|
|
||||||
addr);
|
|
||||||
g_assert_not_reached();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rip it out of the used list and re-insert back into the free list. */
|
|
||||||
QTAILQ_REMOVE(&s->used, node, MLIST_ENTNAME);
|
|
||||||
mlist_sort_insert(&s->free, node);
|
|
||||||
mlist_coalesce(&s->free, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t pc_alloc(QGuestAllocator *allocator, size_t size)
|
|
||||||
{
|
|
||||||
PCAlloc *s = container_of(allocator, PCAlloc, alloc);
|
|
||||||
uint64_t rsize = size;
|
|
||||||
uint64_t naddr;
|
|
||||||
|
|
||||||
rsize += (PAGE_SIZE - 1);
|
|
||||||
rsize &= -PAGE_SIZE;
|
|
||||||
g_assert_cmpint((s->start + rsize), <=, s->end);
|
|
||||||
g_assert_cmpint(rsize, >=, size);
|
|
||||||
|
|
||||||
naddr = pc_mlist_alloc(s, rsize);
|
|
||||||
if (s->opts & PC_ALLOC_PARANOID) {
|
|
||||||
pc_mlist_check(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
return naddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pc_free(QGuestAllocator *allocator, uint64_t addr)
|
|
||||||
{
|
|
||||||
PCAlloc *s = container_of(allocator, PCAlloc, alloc);
|
|
||||||
|
|
||||||
pc_mlist_free(s, addr);
|
|
||||||
if (s->opts & PC_ALLOC_PARANOID) {
|
|
||||||
pc_mlist_check(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mostly for valgrind happiness, but it does offer
|
* Mostly for valgrind happiness, but it does offer
|
||||||
* a chokepoint for debugging guest memory leaks, too.
|
* a chokepoint for debugging guest memory leaks, too.
|
||||||
*/
|
*/
|
||||||
void pc_alloc_uninit(QGuestAllocator *allocator)
|
void pc_alloc_uninit(QGuestAllocator *allocator)
|
||||||
{
|
{
|
||||||
PCAlloc *s = container_of(allocator, PCAlloc, alloc);
|
alloc_uninit(allocator);
|
||||||
MemBlock *node;
|
|
||||||
MemBlock *tmp;
|
|
||||||
PCAllocOpts mask;
|
|
||||||
|
|
||||||
/* Check for guest leaks, and destroy the list. */
|
|
||||||
QTAILQ_FOREACH_SAFE(node, &s->used, MLIST_ENTNAME, tmp) {
|
|
||||||
if (s->opts & (PC_ALLOC_LEAK_WARN | PC_ALLOC_LEAK_ASSERT)) {
|
|
||||||
fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; "
|
|
||||||
"size 0x%016" PRIx64 ".\n",
|
|
||||||
node->addr, node->size);
|
|
||||||
}
|
|
||||||
if (s->opts & (PC_ALLOC_LEAK_ASSERT)) {
|
|
||||||
g_assert_not_reached();
|
|
||||||
}
|
|
||||||
g_free(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we have previously asserted that there are no leaks, then there
|
|
||||||
* should be only one node here with a specific address and size. */
|
|
||||||
mask = PC_ALLOC_LEAK_ASSERT | PC_ALLOC_PARANOID;
|
|
||||||
QTAILQ_FOREACH_SAFE(node, &s->free, MLIST_ENTNAME, tmp) {
|
|
||||||
if ((s->opts & mask) == mask) {
|
|
||||||
if ((node->addr != s->start) ||
|
|
||||||
(node->size != s->end - s->start)) {
|
|
||||||
fprintf(stderr, "Free list is corrupted.\n");
|
|
||||||
g_assert_not_reached();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags)
|
QGuestAllocator *pc_alloc_init_flags(QAllocOpts flags)
|
||||||
{
|
{
|
||||||
PCAlloc *s = g_malloc0(sizeof(*s));
|
QGuestAllocator *s = g_malloc0(sizeof(*s));
|
||||||
uint64_t ram_size;
|
uint64_t ram_size;
|
||||||
QFWCFG *fw_cfg = pc_fw_cfg_init();
|
QFWCFG *fw_cfg = pc_fw_cfg_init();
|
||||||
MemBlock *node;
|
MemBlock *node;
|
||||||
|
|
||||||
s->opts = flags;
|
s->opts = flags;
|
||||||
s->alloc.alloc = pc_alloc;
|
s->page_size = PAGE_SIZE;
|
||||||
s->alloc.free = pc_free;
|
|
||||||
|
|
||||||
ram_size = qfw_cfg_get_u64(fw_cfg, FW_CFG_RAM_SIZE);
|
ram_size = qfw_cfg_get_u64(fw_cfg, FW_CFG_RAM_SIZE);
|
||||||
|
|
||||||
|
@ -325,10 +57,10 @@ QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags)
|
||||||
node = mlist_new(s->start, s->end - s->start);
|
node = mlist_new(s->start, s->end - s->start);
|
||||||
QTAILQ_INSERT_HEAD(&s->free, node, MLIST_ENTNAME);
|
QTAILQ_INSERT_HEAD(&s->free, node, MLIST_ENTNAME);
|
||||||
|
|
||||||
return &s->alloc;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QGuestAllocator *pc_alloc_init(void)
|
inline QGuestAllocator *pc_alloc_init(void)
|
||||||
{
|
{
|
||||||
return pc_alloc_init_flags(PC_ALLOC_NO_FLAGS);
|
return pc_alloc_init_flags(ALLOC_NO_FLAGS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,8 @@
|
||||||
|
|
||||||
#include "libqos/malloc.h"
|
#include "libqos/malloc.h"
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
PC_ALLOC_NO_FLAGS = 0x00,
|
|
||||||
PC_ALLOC_LEAK_WARN = 0x01,
|
|
||||||
PC_ALLOC_LEAK_ASSERT = 0x02,
|
|
||||||
PC_ALLOC_PARANOID = 0x04
|
|
||||||
} PCAllocOpts;
|
|
||||||
|
|
||||||
QGuestAllocator *pc_alloc_init(void);
|
QGuestAllocator *pc_alloc_init(void);
|
||||||
QGuestAllocator *pc_alloc_init_flags(PCAllocOpts flags);
|
QGuestAllocator *pc_alloc_init_flags(QAllocOpts flags);
|
||||||
void pc_alloc_uninit(QGuestAllocator *allocator);
|
void pc_alloc_uninit(QGuestAllocator *allocator);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
* libqos malloc support
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014
|
||||||
|
*
|
||||||
|
* Author:
|
||||||
|
* John Snow <jsnow@redhat.com>
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libqos/malloc.h"
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
static void mlist_delete(MemList *list, MemBlock *node)
|
||||||
|
{
|
||||||
|
g_assert(list && node);
|
||||||
|
QTAILQ_REMOVE(list, node, MLIST_ENTNAME);
|
||||||
|
g_free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemBlock *mlist_find_key(MemList *head, uint64_t addr)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
||||||
|
if (node->addr == addr) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemBlock *mlist_find_space(MemList *head, uint64_t size)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
||||||
|
if (node->size >= size) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
g_assert(head && insr);
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
|
||||||
|
if (insr->addr < node->addr) {
|
||||||
|
QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME);
|
||||||
|
return insr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME);
|
||||||
|
return insr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t mlist_boundary(MemBlock *node)
|
||||||
|
{
|
||||||
|
return node->size + node->addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right)
|
||||||
|
{
|
||||||
|
g_assert(head && left && right);
|
||||||
|
|
||||||
|
left->size += right->size;
|
||||||
|
mlist_delete(head, right);
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mlist_coalesce(MemList *head, MemBlock *node)
|
||||||
|
{
|
||||||
|
g_assert(node);
|
||||||
|
MemBlock *left;
|
||||||
|
MemBlock *right;
|
||||||
|
char merge;
|
||||||
|
|
||||||
|
do {
|
||||||
|
merge = 0;
|
||||||
|
left = QTAILQ_PREV(node, MemList, MLIST_ENTNAME);
|
||||||
|
right = QTAILQ_NEXT(node, MLIST_ENTNAME);
|
||||||
|
|
||||||
|
/* clowns to the left of me */
|
||||||
|
if (left && mlist_boundary(left) == node->addr) {
|
||||||
|
node = mlist_join(head, left, node);
|
||||||
|
merge = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* jokers to the right */
|
||||||
|
if (right && mlist_boundary(node) == right->addr) {
|
||||||
|
node = mlist_join(head, node, right);
|
||||||
|
merge = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (merge);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode,
|
||||||
|
uint64_t size)
|
||||||
|
{
|
||||||
|
uint64_t addr;
|
||||||
|
MemBlock *usednode;
|
||||||
|
|
||||||
|
g_assert(freenode);
|
||||||
|
g_assert_cmpint(freenode->size, >=, size);
|
||||||
|
|
||||||
|
addr = freenode->addr;
|
||||||
|
if (freenode->size == size) {
|
||||||
|
/* re-use this freenode as our used node */
|
||||||
|
QTAILQ_REMOVE(&s->free, freenode, MLIST_ENTNAME);
|
||||||
|
usednode = freenode;
|
||||||
|
} else {
|
||||||
|
/* adjust the free node and create a new used node */
|
||||||
|
freenode->addr += size;
|
||||||
|
freenode->size -= size;
|
||||||
|
usednode = mlist_new(addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
mlist_sort_insert(&s->used, usednode);
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To assert the correctness of the list.
|
||||||
|
* Used only if ALLOC_PARANOID is set. */
|
||||||
|
static void mlist_check(QGuestAllocator *s)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
uint64_t addr = s->start > 0 ? s->start - 1 : 0;
|
||||||
|
uint64_t next = s->start;
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(node, &s->free, MLIST_ENTNAME) {
|
||||||
|
g_assert_cmpint(node->addr, >, addr);
|
||||||
|
g_assert_cmpint(node->addr, >=, next);
|
||||||
|
addr = node->addr;
|
||||||
|
next = node->addr + node->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = s->start > 0 ? s->start - 1 : 0;
|
||||||
|
next = s->start;
|
||||||
|
QTAILQ_FOREACH(node, &s->used, MLIST_ENTNAME) {
|
||||||
|
g_assert_cmpint(node->addr, >, addr);
|
||||||
|
g_assert_cmpint(node->addr, >=, next);
|
||||||
|
addr = node->addr;
|
||||||
|
next = node->addr + node->size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
|
||||||
|
node = mlist_find_space(&s->free, size);
|
||||||
|
if (!node) {
|
||||||
|
fprintf(stderr, "Out of guest memory.\n");
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
return mlist_fulfill(s, node, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mlist_free(QGuestAllocator *s, uint64_t addr)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
|
||||||
|
if (addr == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = mlist_find_key(&s->used, addr);
|
||||||
|
if (!node) {
|
||||||
|
fprintf(stderr, "Error: no record found for an allocation at "
|
||||||
|
"0x%016" PRIx64 ".\n",
|
||||||
|
addr);
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rip it out of the used list and re-insert back into the free list. */
|
||||||
|
QTAILQ_REMOVE(&s->used, node, MLIST_ENTNAME);
|
||||||
|
mlist_sort_insert(&s->free, node);
|
||||||
|
mlist_coalesce(&s->free, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemBlock *mlist_new(uint64_t addr, uint64_t size)
|
||||||
|
{
|
||||||
|
MemBlock *block;
|
||||||
|
|
||||||
|
if (!size) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
block = g_malloc0(sizeof(MemBlock));
|
||||||
|
|
||||||
|
block->addr = addr;
|
||||||
|
block->size = size;
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mostly for valgrind happiness, but it does offer
|
||||||
|
* a chokepoint for debugging guest memory leaks, too.
|
||||||
|
*/
|
||||||
|
void alloc_uninit(QGuestAllocator *allocator)
|
||||||
|
{
|
||||||
|
MemBlock *node;
|
||||||
|
MemBlock *tmp;
|
||||||
|
QAllocOpts mask;
|
||||||
|
|
||||||
|
/* Check for guest leaks, and destroy the list. */
|
||||||
|
QTAILQ_FOREACH_SAFE(node, &allocator->used, MLIST_ENTNAME, tmp) {
|
||||||
|
if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) {
|
||||||
|
fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; "
|
||||||
|
"size 0x%016" PRIx64 ".\n",
|
||||||
|
node->addr, node->size);
|
||||||
|
}
|
||||||
|
if (allocator->opts & (ALLOC_LEAK_ASSERT)) {
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
g_free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we have previously asserted that there are no leaks, then there
|
||||||
|
* should be only one node here with a specific address and size. */
|
||||||
|
mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID;
|
||||||
|
QTAILQ_FOREACH_SAFE(node, &allocator->free, MLIST_ENTNAME, tmp) {
|
||||||
|
if ((allocator->opts & mask) == mask) {
|
||||||
|
if ((node->addr != allocator->start) ||
|
||||||
|
(node->size != allocator->end - allocator->start)) {
|
||||||
|
fprintf(stderr, "Free list is corrupted.\n");
|
||||||
|
g_assert_not_reached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t guest_alloc(QGuestAllocator *allocator, size_t size)
|
||||||
|
{
|
||||||
|
uint64_t rsize = size;
|
||||||
|
uint64_t naddr;
|
||||||
|
|
||||||
|
rsize += (allocator->page_size - 1);
|
||||||
|
rsize &= -allocator->page_size;
|
||||||
|
g_assert_cmpint((allocator->start + rsize), <=, allocator->end);
|
||||||
|
g_assert_cmpint(rsize, >=, size);
|
||||||
|
|
||||||
|
naddr = mlist_alloc(allocator, rsize);
|
||||||
|
if (allocator->opts & ALLOC_PARANOID) {
|
||||||
|
mlist_check(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return naddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void guest_free(QGuestAllocator *allocator, uint64_t addr)
|
||||||
|
{
|
||||||
|
mlist_free(allocator, addr);
|
||||||
|
if (allocator->opts & ALLOC_PARANOID) {
|
||||||
|
mlist_check(allocator);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,24 +15,39 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include "qemu/queue.h"
|
||||||
|
|
||||||
typedef struct QGuestAllocator QGuestAllocator;
|
#define MLIST_ENTNAME entries
|
||||||
|
|
||||||
struct QGuestAllocator
|
typedef enum {
|
||||||
{
|
ALLOC_NO_FLAGS = 0x00,
|
||||||
uint64_t (*alloc)(QGuestAllocator *allocator, size_t size);
|
ALLOC_LEAK_WARN = 0x01,
|
||||||
void (*free)(QGuestAllocator *allocator, uint64_t addr);
|
ALLOC_LEAK_ASSERT = 0x02,
|
||||||
};
|
ALLOC_PARANOID = 0x04
|
||||||
|
} QAllocOpts;
|
||||||
|
|
||||||
|
typedef QTAILQ_HEAD(MemList, MemBlock) MemList;
|
||||||
|
typedef struct MemBlock {
|
||||||
|
QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t addr;
|
||||||
|
} MemBlock;
|
||||||
|
|
||||||
|
typedef struct QGuestAllocator {
|
||||||
|
QAllocOpts opts;
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
uint32_t page_size;
|
||||||
|
|
||||||
|
MemList used;
|
||||||
|
MemList free;
|
||||||
|
} QGuestAllocator;
|
||||||
|
|
||||||
|
MemBlock *mlist_new(uint64_t addr, uint64_t size);
|
||||||
|
void alloc_uninit(QGuestAllocator *allocator);
|
||||||
|
|
||||||
/* Always returns page aligned values */
|
/* Always returns page aligned values */
|
||||||
static inline uint64_t guest_alloc(QGuestAllocator *allocator, size_t size)
|
uint64_t guest_alloc(QGuestAllocator *allocator, size_t size);
|
||||||
{
|
void guest_free(QGuestAllocator *allocator, uint64_t addr);
|
||||||
return allocator->alloc(allocator, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void guest_free(QGuestAllocator *allocator, uint64_t addr)
|
|
||||||
{
|
|
||||||
allocator->free(allocator, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
cd tests/qemu-iotests
|
cd tests/qemu-iotests
|
||||||
|
|
||||||
ret=0
|
ret=0
|
||||||
./check -T -qcow2 -g quick || ret=1
|
TEST_DIR=${TEST_DIR:-/tmp/qemu-iotests-quick-$$} ./check -T -qcow2 -g quick || ret=1
|
||||||
|
|
||||||
exit $ret
|
exit $ret
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
#
|
#
|
||||||
# Tests for drive-backup
|
# Tests for drive-backup and blockdev-backup
|
||||||
#
|
#
|
||||||
# Copyright (C) 2013 Red Hat, Inc.
|
# Copyright (C) 2013, 2014 Red Hat, Inc.
|
||||||
#
|
#
|
||||||
# Based on 041.
|
# Based on 041.
|
||||||
#
|
#
|
||||||
|
@ -27,6 +27,7 @@ from iotests import qemu_img, qemu_io
|
||||||
|
|
||||||
test_img = os.path.join(iotests.test_dir, 'test.img')
|
test_img = os.path.join(iotests.test_dir, 'test.img')
|
||||||
target_img = os.path.join(iotests.test_dir, 'target.img')
|
target_img = os.path.join(iotests.test_dir, 'target.img')
|
||||||
|
blockdev_target_img = os.path.join(iotests.test_dir, 'blockdev-target.img')
|
||||||
|
|
||||||
class TestSingleDrive(iotests.QMPTestCase):
|
class TestSingleDrive(iotests.QMPTestCase):
|
||||||
image_len = 64 * 1024 * 1024 # MB
|
image_len = 64 * 1024 * 1024 # MB
|
||||||
|
@ -38,34 +39,41 @@ class TestSingleDrive(iotests.QMPTestCase):
|
||||||
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xd5 1M 32k', test_img)
|
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xd5 1M 32k', test_img)
|
||||||
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 32M 124k', test_img)
|
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 32M 124k', test_img)
|
||||||
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 67043328 64k', test_img)
|
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 67043328 64k', test_img)
|
||||||
|
qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(TestSingleDrive.image_len))
|
||||||
|
|
||||||
self.vm = iotests.VM().add_drive(test_img)
|
self.vm = iotests.VM().add_drive(test_img).add_drive(blockdev_target_img)
|
||||||
self.vm.launch()
|
self.vm.launch()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.vm.shutdown()
|
self.vm.shutdown()
|
||||||
os.remove(test_img)
|
os.remove(test_img)
|
||||||
|
os.remove(blockdev_target_img)
|
||||||
try:
|
try:
|
||||||
os.remove(target_img)
|
os.remove(target_img)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_cancel(self):
|
def do_test_cancel(self, cmd, target):
|
||||||
self.assert_no_active_block_jobs()
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
result = self.vm.qmp('drive-backup', device='drive0',
|
result = self.vm.qmp(cmd, device='drive0', target=target, sync='full')
|
||||||
target=target_img, sync='full')
|
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
event = self.cancel_and_wait()
|
event = self.cancel_and_wait()
|
||||||
self.assert_qmp(event, 'data/type', 'backup')
|
self.assert_qmp(event, 'data/type', 'backup')
|
||||||
|
|
||||||
def test_pause(self):
|
def test_cancel_drive_backup(self):
|
||||||
|
self.do_test_cancel('drive-backup', target_img)
|
||||||
|
|
||||||
|
def test_cancel_blockdev_backup(self):
|
||||||
|
self.do_test_cancel('blockdev-backup', 'drive1')
|
||||||
|
|
||||||
|
def do_test_pause(self, cmd, target, image):
|
||||||
self.assert_no_active_block_jobs()
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
self.vm.pause_drive('drive0')
|
self.vm.pause_drive('drive0')
|
||||||
result = self.vm.qmp('drive-backup', device='drive0',
|
result = self.vm.qmp(cmd, device='drive0',
|
||||||
target=target_img, sync='full')
|
target=target, sync='full')
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
result = self.vm.qmp('block-job-pause', device='drive0')
|
result = self.vm.qmp('block-job-pause', device='drive0')
|
||||||
|
@ -86,14 +94,25 @@ class TestSingleDrive(iotests.QMPTestCase):
|
||||||
self.wait_until_completed()
|
self.wait_until_completed()
|
||||||
|
|
||||||
self.vm.shutdown()
|
self.vm.shutdown()
|
||||||
self.assertTrue(iotests.compare_images(test_img, target_img),
|
self.assertTrue(iotests.compare_images(test_img, image),
|
||||||
'target image does not match source after backup')
|
'target image does not match source after backup')
|
||||||
|
|
||||||
|
def test_pause_drive_backup(self):
|
||||||
|
self.do_test_pause('drive-backup', target_img, target_img)
|
||||||
|
|
||||||
|
def test_pause_blockdev_backup(self):
|
||||||
|
self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img)
|
||||||
|
|
||||||
def test_medium_not_found(self):
|
def test_medium_not_found(self):
|
||||||
result = self.vm.qmp('drive-backup', device='ide1-cd0',
|
result = self.vm.qmp('drive-backup', device='ide1-cd0',
|
||||||
target=target_img, sync='full')
|
target=target_img, sync='full')
|
||||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
|
def test_medium_not_found_blockdev_backup(self):
|
||||||
|
result = self.vm.qmp('blockdev-backup', device='ide1-cd0',
|
||||||
|
target='drive1', sync='full')
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
def test_image_not_found(self):
|
def test_image_not_found(self):
|
||||||
result = self.vm.qmp('drive-backup', device='drive0',
|
result = self.vm.qmp('drive-backup', device='drive0',
|
||||||
target=target_img, sync='full', mode='existing')
|
target=target_img, sync='full', mode='existing')
|
||||||
|
@ -105,31 +124,53 @@ class TestSingleDrive(iotests.QMPTestCase):
|
||||||
format='spaghetti-noodles')
|
format='spaghetti-noodles')
|
||||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
def test_device_not_found(self):
|
def do_test_device_not_found(self, cmd, **args):
|
||||||
result = self.vm.qmp('drive-backup', device='nonexistent',
|
result = self.vm.qmp(cmd, **args)
|
||||||
target=target_img, sync='full')
|
|
||||||
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
|
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
|
||||||
|
|
||||||
|
def test_device_not_found(self):
|
||||||
|
self.do_test_device_not_found('drive-backup', device='nonexistent',
|
||||||
|
target=target_img, sync='full')
|
||||||
|
|
||||||
|
self.do_test_device_not_found('blockdev-backup', device='nonexistent',
|
||||||
|
target='drive0', sync='full')
|
||||||
|
|
||||||
|
self.do_test_device_not_found('blockdev-backup', device='drive0',
|
||||||
|
target='nonexistent', sync='full')
|
||||||
|
|
||||||
|
self.do_test_device_not_found('blockdev-backup', device='nonexistent',
|
||||||
|
target='nonexistent', sync='full')
|
||||||
|
|
||||||
|
def test_target_is_source(self):
|
||||||
|
result = self.vm.qmp('blockdev-backup', device='drive0',
|
||||||
|
target='drive0', sync='full')
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
class TestSetSpeed(iotests.QMPTestCase):
|
class TestSetSpeed(iotests.QMPTestCase):
|
||||||
image_len = 80 * 1024 * 1024 # MB
|
image_len = 80 * 1024 * 1024 # MB
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
qemu_img('create', '-f', iotests.imgfmt, test_img, str(TestSetSpeed.image_len))
|
qemu_img('create', '-f', iotests.imgfmt, test_img, str(TestSetSpeed.image_len))
|
||||||
qemu_io('-f', iotests.imgfmt, '-c', 'write -P1 0 512', test_img)
|
qemu_io('-f', iotests.imgfmt, '-c', 'write -P1 0 512', test_img)
|
||||||
self.vm = iotests.VM().add_drive(test_img)
|
qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(TestSingleDrive.image_len))
|
||||||
|
|
||||||
|
self.vm = iotests.VM().add_drive(test_img).add_drive(blockdev_target_img)
|
||||||
self.vm.launch()
|
self.vm.launch()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.vm.shutdown()
|
self.vm.shutdown()
|
||||||
os.remove(test_img)
|
os.remove(test_img)
|
||||||
os.remove(target_img)
|
os.remove(blockdev_target_img)
|
||||||
|
try:
|
||||||
|
os.remove(target_img)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
def test_set_speed(self):
|
def do_test_set_speed(self, cmd, target):
|
||||||
self.assert_no_active_block_jobs()
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
self.vm.pause_drive('drive0')
|
self.vm.pause_drive('drive0')
|
||||||
result = self.vm.qmp('drive-backup', device='drive0',
|
result = self.vm.qmp(cmd, device='drive0', target=target, sync='full')
|
||||||
target=target_img, sync='full')
|
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
# Default speed is 0
|
# Default speed is 0
|
||||||
|
@ -148,10 +189,10 @@ class TestSetSpeed(iotests.QMPTestCase):
|
||||||
event = self.cancel_and_wait(resume=True)
|
event = self.cancel_and_wait(resume=True)
|
||||||
self.assert_qmp(event, 'data/type', 'backup')
|
self.assert_qmp(event, 'data/type', 'backup')
|
||||||
|
|
||||||
# Check setting speed in drive-backup works
|
# Check setting speed option works
|
||||||
self.vm.pause_drive('drive0')
|
self.vm.pause_drive('drive0')
|
||||||
result = self.vm.qmp('drive-backup', device='drive0',
|
result = self.vm.qmp(cmd, device='drive0',
|
||||||
target=target_img, sync='full', speed=4*1024*1024)
|
target=target, sync='full', speed=4*1024*1024)
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
result = self.vm.qmp('query-block-jobs')
|
result = self.vm.qmp('query-block-jobs')
|
||||||
|
@ -161,18 +202,24 @@ class TestSetSpeed(iotests.QMPTestCase):
|
||||||
event = self.cancel_and_wait(resume=True)
|
event = self.cancel_and_wait(resume=True)
|
||||||
self.assert_qmp(event, 'data/type', 'backup')
|
self.assert_qmp(event, 'data/type', 'backup')
|
||||||
|
|
||||||
def test_set_speed_invalid(self):
|
def test_set_speed_drive_backup(self):
|
||||||
|
self.do_test_set_speed('drive-backup', target_img)
|
||||||
|
|
||||||
|
def test_set_speed_blockdev_backup(self):
|
||||||
|
self.do_test_set_speed('blockdev-backup', 'drive1')
|
||||||
|
|
||||||
|
def do_test_set_speed_invalid(self, cmd, target):
|
||||||
self.assert_no_active_block_jobs()
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
result = self.vm.qmp('drive-backup', device='drive0',
|
result = self.vm.qmp(cmd, device='drive0',
|
||||||
target=target_img, sync='full', speed=-1)
|
target=target, sync='full', speed=-1)
|
||||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
self.assert_no_active_block_jobs()
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
self.vm.pause_drive('drive0')
|
self.vm.pause_drive('drive0')
|
||||||
result = self.vm.qmp('drive-backup', device='drive0',
|
result = self.vm.qmp(cmd, device='drive0',
|
||||||
target=target_img, sync='full')
|
target=target, sync='full')
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
|
result = self.vm.qmp('block-job-set-speed', device='drive0', speed=-1)
|
||||||
|
@ -181,6 +228,12 @@ class TestSetSpeed(iotests.QMPTestCase):
|
||||||
event = self.cancel_and_wait(resume=True)
|
event = self.cancel_and_wait(resume=True)
|
||||||
self.assert_qmp(event, 'data/type', 'backup')
|
self.assert_qmp(event, 'data/type', 'backup')
|
||||||
|
|
||||||
|
def test_set_speed_invalid_drive_backup(self):
|
||||||
|
self.do_test_set_speed_invalid('drive-backup', target_img)
|
||||||
|
|
||||||
|
def test_set_speed_invalid_blockdev_backup(self):
|
||||||
|
self.do_test_set_speed_invalid('blockdev-backup', 'drive1')
|
||||||
|
|
||||||
class TestSingleTransaction(iotests.QMPTestCase):
|
class TestSingleTransaction(iotests.QMPTestCase):
|
||||||
image_len = 64 * 1024 * 1024 # MB
|
image_len = 64 * 1024 * 1024 # MB
|
||||||
|
|
||||||
|
@ -190,41 +243,50 @@ class TestSingleTransaction(iotests.QMPTestCase):
|
||||||
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xd5 1M 32k', test_img)
|
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xd5 1M 32k', test_img)
|
||||||
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 32M 124k', test_img)
|
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 32M 124k', test_img)
|
||||||
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 67043328 64k', test_img)
|
qemu_io('-f', iotests.imgfmt, '-c', 'write -P0xdc 67043328 64k', test_img)
|
||||||
|
qemu_img('create', '-f', iotests.imgfmt, blockdev_target_img, str(TestSingleDrive.image_len))
|
||||||
|
|
||||||
self.vm = iotests.VM().add_drive(test_img)
|
self.vm = iotests.VM().add_drive(test_img).add_drive(blockdev_target_img)
|
||||||
self.vm.launch()
|
self.vm.launch()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.vm.shutdown()
|
self.vm.shutdown()
|
||||||
os.remove(test_img)
|
os.remove(test_img)
|
||||||
|
os.remove(blockdev_target_img)
|
||||||
try:
|
try:
|
||||||
os.remove(target_img)
|
os.remove(target_img)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_cancel(self):
|
def do_test_cancel(self, cmd, target):
|
||||||
self.assert_no_active_block_jobs()
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
result = self.vm.qmp('transaction', actions=[{
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
'type': 'drive-backup',
|
'type': cmd,
|
||||||
'data': { 'device': 'drive0',
|
'data': { 'device': 'drive0',
|
||||||
'target': target_img,
|
'target': target,
|
||||||
'sync': 'full' },
|
'sync': 'full' },
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
event = self.cancel_and_wait()
|
event = self.cancel_and_wait()
|
||||||
self.assert_qmp(event, 'data/type', 'backup')
|
self.assert_qmp(event, 'data/type', 'backup')
|
||||||
|
|
||||||
def test_pause(self):
|
def test_cancel_drive_backup(self):
|
||||||
|
self.do_test_cancel('drive-backup', target_img)
|
||||||
|
|
||||||
|
def test_cancel_blockdev_backup(self):
|
||||||
|
self.do_test_cancel('blockdev-backup', 'drive1')
|
||||||
|
|
||||||
|
def do_test_pause(self, cmd, target, image):
|
||||||
self.assert_no_active_block_jobs()
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
self.vm.pause_drive('drive0')
|
self.vm.pause_drive('drive0')
|
||||||
result = self.vm.qmp('transaction', actions=[{
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
'type': 'drive-backup',
|
'type': cmd,
|
||||||
'data': { 'device': 'drive0',
|
'data': { 'device': 'drive0',
|
||||||
'target': target_img,
|
'target': target,
|
||||||
'sync': 'full' },
|
'sync': 'full' },
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@ -248,19 +310,31 @@ class TestSingleTransaction(iotests.QMPTestCase):
|
||||||
self.wait_until_completed()
|
self.wait_until_completed()
|
||||||
|
|
||||||
self.vm.shutdown()
|
self.vm.shutdown()
|
||||||
self.assertTrue(iotests.compare_images(test_img, target_img),
|
self.assertTrue(iotests.compare_images(test_img, image),
|
||||||
'target image does not match source after backup')
|
'target image does not match source after backup')
|
||||||
|
|
||||||
def test_medium_not_found(self):
|
def test_pause_drive_backup(self):
|
||||||
|
self.do_test_pause('drive-backup', target_img, target_img)
|
||||||
|
|
||||||
|
def test_pause_blockdev_backup(self):
|
||||||
|
self.do_test_pause('blockdev-backup', 'drive1', blockdev_target_img)
|
||||||
|
|
||||||
|
def do_test_medium_not_found(self, cmd, target):
|
||||||
result = self.vm.qmp('transaction', actions=[{
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
'type': 'drive-backup',
|
'type': cmd,
|
||||||
'data': { 'device': 'ide1-cd0',
|
'data': { 'device': 'ide1-cd0',
|
||||||
'target': target_img,
|
'target': target,
|
||||||
'sync': 'full' },
|
'sync': 'full' },
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
|
def test_medium_not_found_drive_backup(self):
|
||||||
|
self.do_test_medium_not_found('drive-backup', target_img)
|
||||||
|
|
||||||
|
def test_medium_not_found_blockdev_backup(self):
|
||||||
|
self.do_test_medium_not_found('blockdev-backup', 'drive1')
|
||||||
|
|
||||||
def test_image_not_found(self):
|
def test_image_not_found(self):
|
||||||
result = self.vm.qmp('transaction', actions=[{
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
'type': 'drive-backup',
|
'type': 'drive-backup',
|
||||||
|
@ -283,6 +357,43 @@ class TestSingleTransaction(iotests.QMPTestCase):
|
||||||
])
|
])
|
||||||
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
|
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
|
||||||
|
|
||||||
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
|
'type': 'blockdev-backup',
|
||||||
|
'data': { 'device': 'nonexistent',
|
||||||
|
'target': 'drive1',
|
||||||
|
'sync': 'full' },
|
||||||
|
}
|
||||||
|
])
|
||||||
|
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
|
||||||
|
|
||||||
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
|
'type': 'blockdev-backup',
|
||||||
|
'data': { 'device': 'drive0',
|
||||||
|
'target': 'nonexistent',
|
||||||
|
'sync': 'full' },
|
||||||
|
}
|
||||||
|
])
|
||||||
|
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
|
||||||
|
|
||||||
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
|
'type': 'blockdev-backup',
|
||||||
|
'data': { 'device': 'nonexistent',
|
||||||
|
'target': 'nonexistent',
|
||||||
|
'sync': 'full' },
|
||||||
|
}
|
||||||
|
])
|
||||||
|
self.assert_qmp(result, 'error/class', 'DeviceNotFound')
|
||||||
|
|
||||||
|
def test_target_is_source(self):
|
||||||
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
|
'type': 'blockdev-backup',
|
||||||
|
'data': { 'device': 'drive0',
|
||||||
|
'target': 'drive0',
|
||||||
|
'sync': 'full' },
|
||||||
|
}
|
||||||
|
])
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
|
||||||
def test_abort(self):
|
def test_abort(self):
|
||||||
result = self.vm.qmp('transaction', actions=[{
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
'type': 'drive-backup',
|
'type': 'drive-backup',
|
||||||
|
@ -298,5 +409,31 @@ class TestSingleTransaction(iotests.QMPTestCase):
|
||||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
self.assert_no_active_block_jobs()
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
|
'type': 'blockdev-backup',
|
||||||
|
'data': { 'device': 'nonexistent',
|
||||||
|
'target': 'drive1',
|
||||||
|
'sync': 'full' },
|
||||||
|
}, {
|
||||||
|
'type': 'Abort',
|
||||||
|
'data': {},
|
||||||
|
}
|
||||||
|
])
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
|
result = self.vm.qmp('transaction', actions=[{
|
||||||
|
'type': 'blockdev-backup',
|
||||||
|
'data': { 'device': 'drive0',
|
||||||
|
'target': 'nonexistent',
|
||||||
|
'sync': 'full' },
|
||||||
|
}, {
|
||||||
|
'type': 'Abort',
|
||||||
|
'data': {},
|
||||||
|
}
|
||||||
|
])
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
self.assert_no_active_block_jobs()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
iotests.main(supported_fmts=['raw', 'qcow2'])
|
iotests.main(supported_fmts=['raw', 'qcow2'])
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
..............
|
........................
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
Ran 14 tests
|
Ran 24 tests
|
||||||
|
|
||||||
OK
|
OK
|
||||||
|
|
|
@ -87,6 +87,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15
|
||||||
|
|
||||||
_supported_fmt qcow2
|
_supported_fmt qcow2
|
||||||
_supported_proto file
|
_supported_proto file
|
||||||
|
_supported_os Linux
|
||||||
_require_command QEMU_NBD
|
_require_command QEMU_NBD
|
||||||
|
|
||||||
# Use -f raw instead of -f $IMGFMT for the NBD connection
|
# Use -f raw instead of -f $IMGFMT for the NBD connection
|
||||||
|
|
|
@ -45,7 +45,8 @@ function do_run_qemu()
|
||||||
|
|
||||||
function run_qemu()
|
function run_qemu()
|
||||||
{
|
{
|
||||||
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | sed -e 's/\("actual-size":\s*\)[0-9]\+/\1SIZE/g'
|
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | _filter_qemu \
|
||||||
|
| sed -e 's/\("actual-size":\s*\)[0-9]\+/\1SIZE/g'
|
||||||
}
|
}
|
||||||
|
|
||||||
size=128M
|
size=128M
|
||||||
|
|
|
@ -51,7 +51,7 @@ function do_run_qemu()
|
||||||
|
|
||||||
function run_qemu()
|
function run_qemu()
|
||||||
{
|
{
|
||||||
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | _filter_qemu_io
|
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu | _filter_qmp | _filter_qemu_io
|
||||||
}
|
}
|
||||||
|
|
||||||
IMG_SIZE=64M
|
IMG_SIZE=64M
|
||||||
|
|
|
@ -52,8 +52,8 @@ read failed: Input/output error
|
||||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
|
||||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", "tray-open": true}}
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", "tray-open": true}}
|
||||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "floppy0", "tray-open": true}}
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "floppy0", "tray-open": true}}
|
||||||
qemu-system-x86_64: Failed to flush the L2 table cache: Input/output error
|
QEMU_PROG: Failed to flush the L2 table cache: Input/output error
|
||||||
qemu-system-x86_64: Failed to flush the refcount block cache: Input/output error
|
QEMU_PROG: Failed to flush the refcount block cache: Input/output error
|
||||||
|
|
||||||
|
|
||||||
=== Testing blkverify on existing block device ===
|
=== Testing blkverify on existing block device ===
|
||||||
|
@ -92,7 +92,7 @@ read failed: Input/output error
|
||||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN"}
|
||||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", "tray-open": true}}
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", "tray-open": true}}
|
||||||
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "floppy0", "tray-open": true}}
|
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "floppy0", "tray-open": true}}
|
||||||
qemu-system-x86_64: Failed to flush the L2 table cache: Input/output error
|
QEMU_PROG: Failed to flush the L2 table cache: Input/output error
|
||||||
qemu-system-x86_64: Failed to flush the refcount block cache: Input/output error
|
QEMU_PROG: Failed to flush the refcount block cache: Input/output error
|
||||||
|
|
||||||
*** done
|
*** done
|
||||||
|
|
|
@ -53,7 +53,7 @@ function do_run_qemu()
|
||||||
|
|
||||||
function run_qemu()
|
function run_qemu()
|
||||||
{
|
{
|
||||||
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | _filter_qemu_io
|
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu | _filter_qmp | _filter_qemu_io
|
||||||
}
|
}
|
||||||
|
|
||||||
test_quorum=$($QEMU_IMG --help|grep quorum)
|
test_quorum=$($QEMU_IMG --help|grep quorum)
|
||||||
|
|
|
@ -45,7 +45,8 @@ function do_run_qemu()
|
||||||
|
|
||||||
function run_qemu()
|
function run_qemu()
|
||||||
{
|
{
|
||||||
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | sed -e 's/\("actual-size":\s*\)[0-9]\+/\1SIZE/g'
|
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp | _filter_qemu \
|
||||||
|
| sed -e 's/\("actual-size":\s*\)[0-9]\+/\1SIZE/g'
|
||||||
}
|
}
|
||||||
|
|
||||||
size=128M
|
size=128M
|
||||||
|
|
|
@ -21,7 +21,6 @@ QMP_VERSION
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
{"error": {"class": "GenericError", "desc": "Device with id 'disk' already exists"}}
|
{"error": {"class": "GenericError", "desc": "Device with id 'disk' already exists"}}
|
||||||
{"error": {"class": "GenericError", "desc": "Device name 'test-node' conflicts with an existing node name"}}
|
{"error": {"class": "GenericError", "desc": "Device name 'test-node' conflicts with an existing node name"}}
|
||||||
main-loop: WARNING: I/O thread spun for 1000 iterations
|
|
||||||
{"error": {"class": "GenericError", "desc": "could not open disk image disk2: node-name=disk is conflicting with a device id"}}
|
{"error": {"class": "GenericError", "desc": "could not open disk image disk2: node-name=disk is conflicting with a device id"}}
|
||||||
{"error": {"class": "GenericError", "desc": "could not open disk image disk2: Duplicate node name"}}
|
{"error": {"class": "GenericError", "desc": "could not open disk image disk2: Duplicate node name"}}
|
||||||
{"error": {"class": "GenericError", "desc": "could not open disk image disk3: node-name=disk3 is conflicting with a device id"}}
|
{"error": {"class": "GenericError", "desc": "could not open disk image disk3: node-name=disk3 is conflicting with a device id"}}
|
||||||
|
|
|
@ -57,7 +57,7 @@ function run_qemu()
|
||||||
# Get the "file": "foo" entry ($foo may only contain escaped double quotes,
|
# Get the "file": "foo" entry ($foo may only contain escaped double quotes,
|
||||||
# which is how we can extract it)
|
# which is how we can extract it)
|
||||||
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_imgfmt | _filter_qmp \
|
do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_imgfmt | _filter_qmp \
|
||||||
| grep "drv0" \
|
| _filter_qemu | grep "drv0" \
|
||||||
| sed -e 's/^.*"file": "\(\(\\"\|[^"]\)*\)".*$/\1/' -e 's/\\"/"/g'
|
| sed -e 's/^.*"file": "\(\(\\"\|[^"]\)*\)".*$/\1/' -e 's/\\"/"/g'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Test case for relative backing file names in complex BDS trees
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
# creator
|
||||||
|
owner=mreitz@redhat.com
|
||||||
|
|
||||||
|
seq="$(basename $0)"
|
||||||
|
echo "QA output created by $seq"
|
||||||
|
|
||||||
|
here="$PWD"
|
||||||
|
tmp=/tmp/$$
|
||||||
|
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
|
||||||
|
|
||||||
|
# Any format supporting backing files
|
||||||
|
_supported_fmt qed qcow qcow2 vmdk
|
||||||
|
_supported_proto file
|
||||||
|
_supported_os Linux
|
||||||
|
_unsupported_imgopts "subformat=monolithicFlat" "subformat=twoGbMaxExtentFlat"
|
||||||
|
|
||||||
|
TEST_IMG_REL=$(basename "$TEST_IMG")
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo '=== Reconstructable filename ==='
|
||||||
|
echo
|
||||||
|
|
||||||
|
TEST_IMG="$TEST_IMG.base" _make_test_img 64M
|
||||||
|
_make_test_img -b "$TEST_IMG_REL.base" 64M
|
||||||
|
# qemu should be able to reconstruct the filename, so relative backing names
|
||||||
|
# should work
|
||||||
|
TEST_IMG="json:{'driver':'$IMGFMT','file':{'driver':'file','filename':'$TEST_IMG'}}" \
|
||||||
|
_img_info | _filter_img_info
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo '=== Non-reconstructable filename ==='
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Across blkdebug without a config file, you cannot reconstruct filenames, so
|
||||||
|
# qemu is incapable of knowing the directory of the top image
|
||||||
|
TEST_IMG="json:{
|
||||||
|
'driver': '$IMGFMT',
|
||||||
|
'file': {
|
||||||
|
'driver': 'blkdebug',
|
||||||
|
'image': {
|
||||||
|
'driver': 'file',
|
||||||
|
'filename': '$TEST_IMG'
|
||||||
|
},
|
||||||
|
'set-state': [
|
||||||
|
{
|
||||||
|
'event': 'read_aio',
|
||||||
|
'new_state': 42
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}" _img_info | _filter_img_info
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo '=== Backing name is always relative to the backed image ==='
|
||||||
|
echo
|
||||||
|
|
||||||
|
# omit the image size; it should work anyway
|
||||||
|
_make_test_img -b "$TEST_IMG_REL.base"
|
||||||
|
|
||||||
|
|
||||||
|
# success, all done
|
||||||
|
echo '*** done'
|
||||||
|
rm -f $seq.full
|
||||||
|
status=0
|
|
@ -0,0 +1,19 @@
|
||||||
|
QA output created by 110
|
||||||
|
|
||||||
|
=== Reconstructable filename ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='t.IMGFMT.base'
|
||||||
|
image: TEST_DIR/t.IMGFMT
|
||||||
|
file format: IMGFMT
|
||||||
|
virtual size: 64M (67108864 bytes)
|
||||||
|
backing file: t.IMGFMT.base (actual path: TEST_DIR/t.IMGFMT.base)
|
||||||
|
|
||||||
|
=== Non-reconstructable filename ===
|
||||||
|
|
||||||
|
qemu-img: Cannot use relative backing file names for 'json:{"driver": "IMGFMT", "file": {"set-state.0.event": "read_aio", "image": {"driver": "file", "filename": "TEST_DIR/t.IMGFMT"}, "driver": "blkdebug", "set-state.0.new_state": 42}}'
|
||||||
|
|
||||||
|
=== Backing name is always relative to the backed image ===
|
||||||
|
|
||||||
|
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file='t.IMGFMT.base'
|
||||||
|
*** done
|
|
@ -238,6 +238,7 @@ QEMU_NBD -- $QEMU_NBD
|
||||||
IMGFMT -- $FULL_IMGFMT_DETAILS
|
IMGFMT -- $FULL_IMGFMT_DETAILS
|
||||||
IMGPROTO -- $FULL_IMGPROTO_DETAILS
|
IMGPROTO -- $FULL_IMGPROTO_DETAILS
|
||||||
PLATFORM -- $FULL_HOST_DETAILS
|
PLATFORM -- $FULL_HOST_DETAILS
|
||||||
|
TEST_DIR -- $TEST_DIR
|
||||||
SOCKET_SCM_HELPER -- $SOCKET_SCM_HELPER
|
SOCKET_SCM_HELPER -- $SOCKET_SCM_HELPER
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
|
@ -155,4 +155,4 @@ _readlink()
|
||||||
}
|
}
|
||||||
|
|
||||||
# make sure this script returns success
|
# make sure this script returns success
|
||||||
/bin/true
|
true
|
||||||
|
|
|
@ -159,6 +159,7 @@ _filter_qemu()
|
||||||
{
|
{
|
||||||
sed -e "s#\\(^\\|(qemu) \\)$(basename $QEMU_PROG):#\1QEMU_PROG:#" \
|
sed -e "s#\\(^\\|(qemu) \\)$(basename $QEMU_PROG):#\1QEMU_PROG:#" \
|
||||||
-e 's#^QEMU [0-9]\+\.[0-9]\+\.[0-9]\+ monitor#QEMU X.Y.Z monitor#' \
|
-e 's#^QEMU [0-9]\+\.[0-9]\+\.[0-9]\+ monitor#QEMU X.Y.Z monitor#' \
|
||||||
|
-e '/main-loop: WARNING: I\/O thread spun for [0-9]\+ iterations/d' \
|
||||||
-e $'s#\r##' # QEMU monitor uses \r\n line endings
|
-e $'s#\r##' # QEMU monitor uses \r\n line endings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,4 +224,4 @@ _filter_qemu_img_map()
|
||||||
}
|
}
|
||||||
|
|
||||||
# make sure this script returns success
|
# make sure this script returns success
|
||||||
/bin/true
|
true
|
||||||
|
|
|
@ -490,4 +490,4 @@ _die()
|
||||||
}
|
}
|
||||||
|
|
||||||
# make sure this script returns success
|
# make sure this script returns success
|
||||||
/bin/true
|
true
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
088 rw auto quick
|
088 rw auto quick
|
||||||
089 rw auto quick
|
089 rw auto quick
|
||||||
090 rw auto quick
|
090 rw auto quick
|
||||||
091 rw auto quick
|
091 rw auto
|
||||||
092 rw auto quick
|
092 rw auto quick
|
||||||
095 rw auto quick
|
095 rw auto quick
|
||||||
097 rw auto backing
|
097 rw auto backing
|
||||||
|
@ -112,6 +112,7 @@
|
||||||
107 rw auto quick
|
107 rw auto quick
|
||||||
108 rw auto quick
|
108 rw auto quick
|
||||||
109 rw auto
|
109 rw auto
|
||||||
|
110 rw auto backing quick
|
||||||
111 rw auto quick
|
111 rw auto quick
|
||||||
113 rw auto quick
|
113 rw auto quick
|
||||||
114 rw auto quick
|
114 rw auto quick
|
||||||
|
|
|
@ -282,12 +282,15 @@ def notrun(reason):
|
||||||
print '%s not run: %s' % (seq, reason)
|
print '%s not run: %s' % (seq, reason)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def main(supported_fmts=[]):
|
def main(supported_fmts=[], supported_oses=['linux']):
|
||||||
'''Run tests'''
|
'''Run tests'''
|
||||||
|
|
||||||
if supported_fmts and (imgfmt not in supported_fmts):
|
if supported_fmts and (imgfmt not in supported_fmts):
|
||||||
notrun('not suitable for this image format: %s' % imgfmt)
|
notrun('not suitable for this image format: %s' % imgfmt)
|
||||||
|
|
||||||
|
if sys.platform not in supported_oses:
|
||||||
|
notrun('not suitable for this OS: %s' % sys.platform)
|
||||||
|
|
||||||
# We need to filter out the time taken from the output so that qemu-iotest
|
# We need to filter out the time taken from the output so that qemu-iotest
|
||||||
# can reliably diff the results against master output.
|
# can reliably diff the results against master output.
|
||||||
import StringIO
|
import StringIO
|
||||||
|
|
|
@ -337,7 +337,7 @@ static void perf_cost(void)
|
||||||
"%luns per coroutine",
|
"%luns per coroutine",
|
||||||
maxcycles,
|
maxcycles,
|
||||||
duration, ops,
|
duration, ops,
|
||||||
(unsigned long)(1000000000 * duration) / maxcycles);
|
(unsigned long)(1000000000.0 * duration / maxcycles));
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#endif
|
#endif
|
||||||
#include "qemu/thread.h"
|
#include "qemu/thread.h"
|
||||||
#include "qemu/atomic.h"
|
#include "qemu/atomic.h"
|
||||||
|
#include "qemu/notify.h"
|
||||||
|
|
||||||
static bool name_threads;
|
static bool name_threads;
|
||||||
|
|
||||||
|
@ -401,6 +402,42 @@ void qemu_event_wait(QemuEvent *ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static pthread_key_t exit_key;
|
||||||
|
|
||||||
|
union NotifierThreadData {
|
||||||
|
void *ptr;
|
||||||
|
NotifierList list;
|
||||||
|
};
|
||||||
|
QEMU_BUILD_BUG_ON(sizeof(union NotifierThreadData) != sizeof(void *));
|
||||||
|
|
||||||
|
void qemu_thread_atexit_add(Notifier *notifier)
|
||||||
|
{
|
||||||
|
union NotifierThreadData ntd;
|
||||||
|
ntd.ptr = pthread_getspecific(exit_key);
|
||||||
|
notifier_list_add(&ntd.list, notifier);
|
||||||
|
pthread_setspecific(exit_key, ntd.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qemu_thread_atexit_remove(Notifier *notifier)
|
||||||
|
{
|
||||||
|
union NotifierThreadData ntd;
|
||||||
|
ntd.ptr = pthread_getspecific(exit_key);
|
||||||
|
notifier_remove(notifier);
|
||||||
|
pthread_setspecific(exit_key, ntd.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qemu_thread_atexit_run(void *arg)
|
||||||
|
{
|
||||||
|
union NotifierThreadData ntd = { .ptr = arg };
|
||||||
|
notifier_list_notify(&ntd.list, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __attribute__((constructor)) qemu_thread_atexit_init(void)
|
||||||
|
{
|
||||||
|
pthread_key_create(&exit_key, qemu_thread_atexit_run);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Attempt to set the threads name; note that this is for debug, so
|
/* Attempt to set the threads name; note that this is for debug, so
|
||||||
* we're not going to fail if we can't set it.
|
* we're not going to fail if we can't set it.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
*/
|
*/
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/thread.h"
|
#include "qemu/thread.h"
|
||||||
|
#include "qemu/notify.h"
|
||||||
#include <process.h>
|
#include <process.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
@ -268,6 +269,7 @@ struct QemuThreadData {
|
||||||
void *(*start_routine)(void *);
|
void *(*start_routine)(void *);
|
||||||
void *arg;
|
void *arg;
|
||||||
short mode;
|
short mode;
|
||||||
|
NotifierList exit;
|
||||||
|
|
||||||
/* Only used for joinable threads. */
|
/* Only used for joinable threads. */
|
||||||
bool exited;
|
bool exited;
|
||||||
|
@ -275,18 +277,40 @@ struct QemuThreadData {
|
||||||
CRITICAL_SECTION cs;
|
CRITICAL_SECTION cs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool atexit_registered;
|
||||||
|
static NotifierList main_thread_exit;
|
||||||
|
|
||||||
static __thread QemuThreadData *qemu_thread_data;
|
static __thread QemuThreadData *qemu_thread_data;
|
||||||
|
|
||||||
|
static void run_main_thread_exit(void)
|
||||||
|
{
|
||||||
|
notifier_list_notify(&main_thread_exit, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void qemu_thread_atexit_add(Notifier *notifier)
|
||||||
|
{
|
||||||
|
if (!qemu_thread_data) {
|
||||||
|
if (!atexit_registered) {
|
||||||
|
atexit_registered = true;
|
||||||
|
atexit(run_main_thread_exit);
|
||||||
|
}
|
||||||
|
notifier_list_add(&main_thread_exit, notifier);
|
||||||
|
} else {
|
||||||
|
notifier_list_add(&qemu_thread_data->exit, notifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void qemu_thread_atexit_remove(Notifier *notifier)
|
||||||
|
{
|
||||||
|
notifier_remove(notifier);
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned __stdcall win32_start_routine(void *arg)
|
static unsigned __stdcall win32_start_routine(void *arg)
|
||||||
{
|
{
|
||||||
QemuThreadData *data = (QemuThreadData *) arg;
|
QemuThreadData *data = (QemuThreadData *) arg;
|
||||||
void *(*start_routine)(void *) = data->start_routine;
|
void *(*start_routine)(void *) = data->start_routine;
|
||||||
void *thread_arg = data->arg;
|
void *thread_arg = data->arg;
|
||||||
|
|
||||||
if (data->mode == QEMU_THREAD_DETACHED) {
|
|
||||||
g_free(data);
|
|
||||||
data = NULL;
|
|
||||||
}
|
|
||||||
qemu_thread_data = data;
|
qemu_thread_data = data;
|
||||||
qemu_thread_exit(start_routine(thread_arg));
|
qemu_thread_exit(start_routine(thread_arg));
|
||||||
abort();
|
abort();
|
||||||
|
@ -296,12 +320,14 @@ void qemu_thread_exit(void *arg)
|
||||||
{
|
{
|
||||||
QemuThreadData *data = qemu_thread_data;
|
QemuThreadData *data = qemu_thread_data;
|
||||||
|
|
||||||
if (data) {
|
notifier_list_notify(&data->exit, NULL);
|
||||||
assert(data->mode != QEMU_THREAD_DETACHED);
|
if (data->mode == QEMU_THREAD_JOINABLE) {
|
||||||
data->ret = arg;
|
data->ret = arg;
|
||||||
EnterCriticalSection(&data->cs);
|
EnterCriticalSection(&data->cs);
|
||||||
data->exited = true;
|
data->exited = true;
|
||||||
LeaveCriticalSection(&data->cs);
|
LeaveCriticalSection(&data->cs);
|
||||||
|
} else {
|
||||||
|
g_free(data);
|
||||||
}
|
}
|
||||||
_endthreadex(0);
|
_endthreadex(0);
|
||||||
}
|
}
|
||||||
|
@ -313,9 +339,10 @@ void *qemu_thread_join(QemuThread *thread)
|
||||||
HANDLE handle;
|
HANDLE handle;
|
||||||
|
|
||||||
data = thread->data;
|
data = thread->data;
|
||||||
if (!data) {
|
if (data->mode == QEMU_THREAD_DETACHED) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Because multiple copies of the QemuThread can exist via
|
* Because multiple copies of the QemuThread can exist via
|
||||||
* qemu_thread_get_self, we need to store a value that cannot
|
* qemu_thread_get_self, we need to store a value that cannot
|
||||||
|
@ -329,7 +356,6 @@ void *qemu_thread_join(QemuThread *thread)
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
}
|
}
|
||||||
ret = data->ret;
|
ret = data->ret;
|
||||||
assert(data->mode != QEMU_THREAD_DETACHED);
|
|
||||||
DeleteCriticalSection(&data->cs);
|
DeleteCriticalSection(&data->cs);
|
||||||
g_free(data);
|
g_free(data);
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -347,6 +373,7 @@ void qemu_thread_create(QemuThread *thread, const char *name,
|
||||||
data->arg = arg;
|
data->arg = arg;
|
||||||
data->mode = mode;
|
data->mode = mode;
|
||||||
data->exited = false;
|
data->exited = false;
|
||||||
|
notifier_list_init(&data->exit);
|
||||||
|
|
||||||
if (data->mode != QEMU_THREAD_DETACHED) {
|
if (data->mode != QEMU_THREAD_DETACHED) {
|
||||||
InitializeCriticalSection(&data->cs);
|
InitializeCriticalSection(&data->cs);
|
||||||
|
@ -358,7 +385,7 @@ void qemu_thread_create(QemuThread *thread, const char *name,
|
||||||
error_exit(GetLastError(), __func__);
|
error_exit(GetLastError(), __func__);
|
||||||
}
|
}
|
||||||
CloseHandle(hThread);
|
CloseHandle(hThread);
|
||||||
thread->data = (mode == QEMU_THREAD_DETACHED) ? NULL : data;
|
thread->data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void qemu_thread_get_self(QemuThread *thread)
|
void qemu_thread_get_self(QemuThread *thread)
|
||||||
|
@ -373,11 +400,10 @@ HANDLE qemu_thread_get_handle(QemuThread *thread)
|
||||||
HANDLE handle;
|
HANDLE handle;
|
||||||
|
|
||||||
data = thread->data;
|
data = thread->data;
|
||||||
if (!data) {
|
if (data->mode == QEMU_THREAD_DETACHED) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(data->mode != QEMU_THREAD_DETACHED);
|
|
||||||
EnterCriticalSection(&data->cs);
|
EnterCriticalSection(&data->cs);
|
||||||
if (!data->exited) {
|
if (!data->exited) {
|
||||||
handle = OpenThread(SYNCHRONIZE | THREAD_SUSPEND_RESUME, FALSE,
|
handle = OpenThread(SYNCHRONIZE | THREAD_SUSPEND_RESUME, FALSE,
|
||||||
|
|
Loading…
Reference in New Issue