diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2016-09-06 17:18:17 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2016-09-06 17:18:17 +0100 |
commit | 2926375cffce464fde6b4dabaed1e133d549af39 (patch) | |
tree | b832cd53fd9a2f2a26fe5c70c4182266c780cc76 | |
parent | f9ae6bcf1d4659867cfc5e6d90a5e7fdad02280b (diff) | |
parent | e7f98f2f92827df9944402d1613a4e32fe50215b (diff) |
Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging
Block layer patches
# gpg: Signature made Tue 06 Sep 2016 11:38:01 BST
# gpg: using RSA key 0x7F09B272C88F2FD6
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>"
# Primary key fingerprint: DC3D EB15 9A9A F95D 3D74 56FE 7F09 B272 C88F 2FD6
* remotes/kevin/tags/for-upstream: (36 commits)
block: Allow node name for 'qemu-io' HMP command
qemu-iotests: Log QMP traffic in debug mode
block jobs: Improve error message for missing job ID
coroutine: Assert that no locks are held on termination
coroutine: Let CoMutex remember who holds it
qcow2: fix iovec size at qcow2_co_pwritev_compressed
test-coroutine: Fix coroutine pool corruption
qemu-iotests: add vmdk for test backup compression in 055
qemu-iotests: test backup compression in 055
blockdev-backup: added support for data compression
drive-backup: added support for data compression
block: simplify blockdev-backup
block: simplify drive-backup
block/io: turn on dirty_bitmaps for the compressed writes
block: remove BlockDriver.bdrv_write_compressed
qcow: cleanup qcow_co_pwritev_compressed to avoid the recursion
qcow: add qcow_co_pwritev_compressed
vmdk: add vmdk_co_pwritev_compressed
qcow2: cleanup qcow2_co_pwritev_compressed to avoid the recursion
qcow2: add qcow2_co_pwritev_compressed
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
36 files changed, 588 insertions, 617 deletions
@@ -25,6 +25,7 @@ #include "trace.h" #include "block/block_int.h" #include "block/blockjob.h" +#include "block/nbd.h" #include "qemu/error-report.h" #include "qemu/module.h" #include "qapi/qmp/qerror.h" @@ -2206,6 +2207,7 @@ static void bdrv_close(BlockDriverState *bs) void bdrv_close_all(void) { block_job_cancel_sync_all(); + nbd_export_close_all(); /* Drop references from requests still in flight, such as canceled block * jobs whose AIO context has not been polled yet */ diff --git a/block/backup.c b/block/backup.c index 2c0532314f..bb3bb9a9eb 100644 --- a/block/backup.c +++ b/block/backup.c @@ -47,6 +47,7 @@ typedef struct BackupBlockJob { uint64_t sectors_read; unsigned long *done_bitmap; int64_t cluster_size; + bool compress; NotifierWithReturn before_write; QLIST_HEAD(, CowRequest) inflight_reqs; } BackupBlockJob; @@ -154,7 +155,8 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job, bounce_qiov.size, BDRV_REQ_MAY_UNMAP); } else { ret = blk_co_pwritev(job->target, start * job->cluster_size, - bounce_qiov.size, &bounce_qiov, 0); + bounce_qiov.size, &bounce_qiov, + job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0); } if (ret < 0) { trace_backup_do_cow_write_fail(job, start, ret); @@ -477,6 +479,7 @@ static void coroutine_fn backup_run(void *opaque) void backup_start(const char *job_id, BlockDriverState *bs, BlockDriverState *target, int64_t speed, MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap, + bool compress, BlockdevOnError on_source_error, BlockdevOnError on_target_error, BlockCompletionFunc *cb, void *opaque, @@ -507,6 +510,12 @@ void backup_start(const char *job_id, BlockDriverState *bs, return; } + if (compress && target->drv->bdrv_co_pwritev_compressed == NULL) { + error_setg(errp, "Compression is not supported for this drive %s", + bdrv_get_device_name(target)); + return; + } + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) { return; } @@ -555,6 +564,7 @@ void backup_start(const char *job_id, BlockDriverState *bs, job->sync_mode = sync_mode; job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_INCREMENTAL ? sync_bitmap : NULL; + job->compress = compress; /* If there is no backing file on the target, we cannot rely on COW if our * backup cluster size is smaller than the target cluster size. Even for diff --git a/block/block-backend.c b/block/block-backend.c index effa038924..d1349d90e5 100644 --- a/block/block-backend.c +++ b/block/block-backend.c @@ -410,6 +410,22 @@ bool bdrv_has_blk(BlockDriverState *bs) } /* + * Returns true if @bs has only BlockBackends as parents. + */ +bool bdrv_is_root_node(BlockDriverState *bs) +{ + BdrvChild *c; + + QLIST_FOREACH(c, &bs->parents, next_parent) { + if (c->role != &child_root) { + return false; + } + } + + return true; +} + +/* * Return @blk's DriveInfo if any, else null. */ DriveInfo *blk_legacy_dinfo(BlockBackend *blk) @@ -727,21 +743,6 @@ static int blk_check_byte_request(BlockBackend *blk, int64_t offset, return 0; } -static int blk_check_request(BlockBackend *blk, int64_t sector_num, - int nb_sectors) -{ - if (sector_num < 0 || sector_num > INT64_MAX / BDRV_SECTOR_SIZE) { - return -EIO; - } - - if (nb_sectors < 0 || nb_sectors > INT_MAX / BDRV_SECTOR_SIZE) { - return -EIO; - } - - return blk_check_byte_request(blk, sector_num * BDRV_SECTOR_SIZE, - nb_sectors * BDRV_SECTOR_SIZE); -} - int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset, unsigned int bytes, QEMUIOVector *qiov, BdrvRequestFlags flags) @@ -1484,15 +1485,11 @@ int coroutine_fn blk_co_pwrite_zeroes(BlockBackend *blk, int64_t offset, flags | BDRV_REQ_ZERO_WRITE); } -int blk_write_compressed(BlockBackend *blk, int64_t sector_num, - const uint8_t *buf, int nb_sectors) +int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf, + int count) { - int ret = blk_check_request(blk, sector_num, nb_sectors); - if (ret < 0) { - return ret; - } - - return bdrv_write_compressed(blk_bs(blk), sector_num, buf, nb_sectors); + return blk_prw(blk, offset, (void *) buf, count, blk_write_entry, + BDRV_REQ_WRITE_COMPRESSED); } int blk_truncate(BlockBackend *blk, int64_t offset) diff --git a/block/io.c b/block/io.c index 420944d80d..fdf70807b0 100644 --- a/block/io.c +++ b/block/io.c @@ -540,17 +540,6 @@ static int bdrv_check_byte_request(BlockDriverState *bs, int64_t offset, return 0; } -static int bdrv_check_request(BlockDriverState *bs, int64_t sector_num, - int nb_sectors) -{ - if (nb_sectors < 0 || nb_sectors > BDRV_REQUEST_MAX_SECTORS) { - return -EIO; - } - - return bdrv_check_byte_request(bs, sector_num * BDRV_SECTOR_SIZE, - nb_sectors * BDRV_SECTOR_SIZE); -} - typedef struct RwCo { BdrvChild *child; int64_t offset; @@ -897,6 +886,19 @@ emulate_flags: return ret; } +static int coroutine_fn +bdrv_driver_pwritev_compressed(BlockDriverState *bs, uint64_t offset, + uint64_t bytes, QEMUIOVector *qiov) +{ + BlockDriver *drv = bs->drv; + + if (!drv->bdrv_co_pwritev_compressed) { + return -ENOTSUP; + } + + return drv->bdrv_co_pwritev_compressed(bs, offset, bytes, qiov); +} + static int coroutine_fn bdrv_co_do_copy_on_readv(BlockDriverState *bs, int64_t offset, unsigned int bytes, QEMUIOVector *qiov) { @@ -1315,6 +1317,8 @@ static int coroutine_fn bdrv_aligned_pwritev(BlockDriverState *bs, } else if (flags & BDRV_REQ_ZERO_WRITE) { bdrv_debug_event(bs, BLKDBG_PWRITEV_ZERO); ret = bdrv_co_do_pwrite_zeroes(bs, offset, bytes, flags); + } else if (flags & BDRV_REQ_WRITE_COMPRESSED) { + ret = bdrv_driver_pwritev_compressed(bs, offset, bytes, qiov); } else if (bytes <= max_transfer) { bdrv_debug_event(bs, BLKDBG_PWRITEV); ret = bdrv_driver_pwritev(bs, offset, bytes, qiov, flags); @@ -1879,28 +1883,6 @@ int bdrv_is_allocated_above(BlockDriverState *top, return 0; } -int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, - const uint8_t *buf, int nb_sectors) -{ - BlockDriver *drv = bs->drv; - int ret; - - if (!drv) { - return -ENOMEDIUM; - } - if (!drv->bdrv_write_compressed) { - return -ENOTSUP; - } - ret = bdrv_check_request(bs, sector_num, nb_sectors); - if (ret < 0) { - return ret; - } - - assert(QLIST_EMPTY(&bs->dirty_bitmaps)); - - return drv->bdrv_write_compressed(bs, sector_num, buf, nb_sectors); -} - typedef struct BdrvVmstateCo { BlockDriverState *bs; QEMUIOVector *qiov; diff --git a/block/qcow.c b/block/qcow.c index 6f9b2e2d26..94f01b3d0c 100644 --- a/block/qcow.c +++ b/block/qcow.c @@ -913,75 +913,32 @@ static int qcow_make_empty(BlockDriverState *bs) return 0; } -typedef struct QcowWriteCo { - BlockDriverState *bs; - int64_t sector_num; - const uint8_t *buf; - int nb_sectors; - int ret; -} QcowWriteCo; - -static void qcow_write_co_entry(void *opaque) -{ - QcowWriteCo *co = opaque; - QEMUIOVector qiov; - - struct iovec iov = (struct iovec) { - .iov_base = (uint8_t*) co->buf, - .iov_len = co->nb_sectors * BDRV_SECTOR_SIZE, - }; - qemu_iovec_init_external(&qiov, &iov, 1); - - co->ret = qcow_co_writev(co->bs, co->sector_num, co->nb_sectors, &qiov); -} - -/* Wrapper for non-coroutine contexts */ -static int qcow_write(BlockDriverState *bs, int64_t sector_num, - const uint8_t *buf, int nb_sectors) -{ - Coroutine *co; - AioContext *aio_context = bdrv_get_aio_context(bs); - QcowWriteCo data = { - .bs = bs, - .sector_num = sector_num, - .buf = buf, - .nb_sectors = nb_sectors, - .ret = -EINPROGRESS, - }; - co = qemu_coroutine_create(qcow_write_co_entry, &data); - qemu_coroutine_enter(co); - while (data.ret == -EINPROGRESS) { - aio_poll(aio_context, true); - } - return data.ret; -} - /* XXX: put compressed sectors first, then all the cluster aligned tables to avoid losing bytes in alignment */ -static int qcow_write_compressed(BlockDriverState *bs, int64_t sector_num, - const uint8_t *buf, int nb_sectors) +static coroutine_fn int +qcow_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset, + uint64_t bytes, QEMUIOVector *qiov) { BDRVQcowState *s = bs->opaque; + QEMUIOVector hd_qiov; + struct iovec iov; z_stream strm; int ret, out_len; - uint8_t *out_buf; + uint8_t *buf, *out_buf; uint64_t cluster_offset; - if (nb_sectors != s->cluster_sectors) { - ret = -EINVAL; - - /* Zero-pad last write if image size is not cluster aligned */ - if (sector_num + nb_sectors == bs->total_sectors && - nb_sectors < s->cluster_sectors) { - uint8_t *pad_buf = qemu_blockalign(bs, s->cluster_size); - memset(pad_buf, 0, s->cluster_size); - memcpy(pad_buf, buf, nb_sectors * BDRV_SECTOR_SIZE); - ret = qcow_write_compressed(bs, sector_num, - pad_buf, s->cluster_sectors); - qemu_vfree(pad_buf); + buf = qemu_blockalign(bs, s->cluster_size); + if (bytes != s->cluster_size) { + if (bytes > s->cluster_size || + offset + bytes != bs->total_sectors << BDRV_SECTOR_BITS) + { + qemu_vfree(buf); + return -EINVAL; } - return ret; + /* Zero-pad last write if image size is not cluster aligned */ + memset(buf + bytes, 0, s->cluster_size - bytes); } + qemu_iovec_to_buf(qiov, 0, buf, qiov->size); out_buf = g_malloc(s->cluster_size); @@ -1012,27 +969,35 @@ static int qcow_write_compressed(BlockDriverState *bs, int64_t sector_num, if (ret != Z_STREAM_END || out_len >= s->cluster_size) { /* could not compress: write normal cluster */ - ret = qcow_write(bs, sector_num, buf, s->cluster_sectors); - if (ret < 0) { - goto fail; - } - } else { - cluster_offset = get_cluster_offset(bs, sector_num << 9, 2, - out_len, 0, 0); - if (cluster_offset == 0) { - ret = -EIO; - goto fail; - } - - cluster_offset &= s->cluster_offset_mask; - ret = bdrv_pwrite(bs->file, cluster_offset, out_buf, out_len); + ret = qcow_co_writev(bs, offset >> BDRV_SECTOR_BITS, + bytes >> BDRV_SECTOR_BITS, qiov); if (ret < 0) { goto fail; } + goto success; } + qemu_co_mutex_lock(&s->lock); + cluster_offset = get_cluster_offset(bs, offset, 2, out_len, 0, 0); + qemu_co_mutex_unlock(&s->lock); + if (cluster_offset == 0) { + ret = -EIO; + goto fail; + } + cluster_offset &= s->cluster_offset_mask; + iov = (struct iovec) { + .iov_base = out_buf, + .iov_len = out_len, + }; + qemu_iovec_init_external(&hd_qiov, &iov, 1); + ret = bdrv_co_pwritev(bs->file, cluster_offset, out_len, &hd_qiov, 0); + if (ret < 0) { + goto fail; + } +success: ret = 0; fail: + qemu_vfree(buf); g_free(out_buf); return ret; } @@ -1085,7 +1050,7 @@ static BlockDriver bdrv_qcow = { .bdrv_set_key = qcow_set_key, .bdrv_make_empty = qcow_make_empty, - .bdrv_write_compressed = qcow_write_compressed, + .bdrv_co_pwritev_compressed = qcow_co_pwritev_compressed, .bdrv_get_info = qcow_get_info, .create_opts = &qcow_create_opts, diff --git a/block/qcow2.c b/block/qcow2.c index 91ef4dfefc..c079aa83b6 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -2533,84 +2533,39 @@ static int qcow2_truncate(BlockDriverState *bs, int64_t offset) return 0; } -typedef struct Qcow2WriteCo { - BlockDriverState *bs; - int64_t sector_num; - const uint8_t *buf; - int nb_sectors; - int ret; -} Qcow2WriteCo; - -static void qcow2_write_co_entry(void *opaque) -{ - Qcow2WriteCo *co = opaque; - QEMUIOVector qiov; - uint64_t offset = co->sector_num * BDRV_SECTOR_SIZE; - uint64_t bytes = co->nb_sectors * BDRV_SECTOR_SIZE; - - struct iovec iov = (struct iovec) { - .iov_base = (uint8_t*) co->buf, - .iov_len = bytes, - }; - qemu_iovec_init_external(&qiov, &iov, 1); - - co->ret = qcow2_co_pwritev(co->bs, offset, bytes, &qiov, 0); -} - -/* Wrapper for non-coroutine contexts */ -static int qcow2_write(BlockDriverState *bs, int64_t sector_num, - const uint8_t *buf, int nb_sectors) -{ - Coroutine *co; - AioContext *aio_context = bdrv_get_aio_context(bs); - Qcow2WriteCo data = { - .bs = bs, - .sector_num = sector_num, - .buf = buf, - .nb_sectors = nb_sectors, - .ret = -EINPROGRESS, - }; - co = qemu_coroutine_create(qcow2_write_co_entry, &data); - qemu_coroutine_enter(co); - while (data.ret == -EINPROGRESS) { - aio_poll(aio_context, true); - } - return data.ret; -} - /* XXX: put compressed sectors first, then all the cluster aligned tables to avoid losing bytes in alignment */ -static int qcow2_write_compressed(BlockDriverState *bs, int64_t sector_num, - const uint8_t *buf, int nb_sectors) +static coroutine_fn int +qcow2_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset, + uint64_t bytes, QEMUIOVector *qiov) { BDRVQcow2State *s = bs->opaque; + QEMUIOVector hd_qiov; + struct iovec iov; z_stream strm; int ret, out_len; - uint8_t *out_buf; + uint8_t *buf, *out_buf; uint64_t cluster_offset; - if (nb_sectors == 0) { + if (bytes == 0) { /* align end of file to a sector boundary to ease reading with sector based I/Os */ cluster_offset = bdrv_getlength(bs->file->bs); return bdrv_truncate(bs->file->bs, cluster_offset); } - if (nb_sectors != s->cluster_sectors) { - ret = -EINVAL; - - /* Zero-pad last write if image size is not cluster aligned */ - if (sector_num + nb_sectors == bs->total_sectors && - nb_sectors < s->cluster_sectors) { - uint8_t *pad_buf = qemu_blockalign(bs, s->cluster_size); - memset(pad_buf, 0, s->cluster_size); - memcpy(pad_buf, buf, nb_sectors * BDRV_SECTOR_SIZE); - ret = qcow2_write_compressed(bs, sector_num, - pad_buf, s->cluster_sectors); - qemu_vfree(pad_buf); + buf = qemu_blockalign(bs, s->cluster_size); + if (bytes != s->cluster_size) { + if (bytes > s->cluster_size || + offset + bytes != bs->total_sectors << BDRV_SECTOR_BITS) + { + qemu_vfree(buf); + return -EINVAL; } - return ret; + /* Zero-pad last write if image size is not cluster aligned */ + memset(buf + bytes, 0, s->cluster_size - bytes); } + qemu_iovec_to_buf(qiov, 0, buf, bytes); out_buf = g_malloc(s->cluster_size); @@ -2641,33 +2596,44 @@ static int qcow2_write_compressed(BlockDriverState *bs, int64_t sector_num, if (ret != Z_STREAM_END || out_len >= s->cluster_size) { /* could not compress: write normal cluster */ - ret = qcow2_write(bs, sector_num, buf, s->cluster_sectors); + ret = qcow2_co_pwritev(bs, offset, bytes, qiov, 0); if (ret < 0) { goto fail; } - } else { - cluster_offset = qcow2_alloc_compressed_cluster_offset(bs, - sector_num << 9, out_len); - if (!cluster_offset) { - ret = -EIO; - goto fail; - } - cluster_offset &= s->cluster_offset_mask; + goto success; + } - ret = qcow2_pre_write_overlap_check(bs, 0, cluster_offset, out_len); - if (ret < 0) { - goto fail; - } + qemu_co_mutex_lock(&s->lock); + cluster_offset = + qcow2_alloc_compressed_cluster_offset(bs, offset, out_len); + if (!cluster_offset) { + qemu_co_mutex_unlock(&s->lock); + ret = -EIO; + goto fail; + } + cluster_offset &= s->cluster_offset_mask; - BLKDBG_EVENT(bs->file, BLKDBG_WRITE_COMPRESSED); - ret = bdrv_pwrite(bs->file, cluster_offset, out_buf, out_len); - if (ret < 0) { - goto fail; - } + ret = qcow2_pre_write_overlap_check(bs, 0, cluster_offset, out_len); + qemu_co_mutex_unlock(&s->lock); + if (ret < 0) { + goto fail; } + iov = (struct iovec) { + .iov_base = out_buf, + .iov_len = out_len, + }; + qemu_iovec_init_external(&hd_qiov, &iov, 1); + + BLKDBG_EVENT(bs->file, BLKDBG_WRITE_COMPRESSED); + ret = bdrv_co_pwritev(bs->file, cluster_offset, out_len, &hd_qiov, 0); + if (ret < 0) { + goto fail; + } +success: ret = 0; fail: + qemu_vfree(buf); g_free(out_buf); return ret; } @@ -3412,7 +3378,7 @@ BlockDriver bdrv_qcow2 = { .bdrv_co_pwrite_zeroes = qcow2_co_pwrite_zeroes, .bdrv_co_pdiscard = qcow2_co_pdiscard, .bdrv_truncate = qcow2_truncate, - .bdrv_write_compressed = qcow2_write_compressed, + .bdrv_co_pwritev_compressed = qcow2_co_pwritev_compressed, .bdrv_make_empty = qcow2_make_empty, .bdrv_snapshot_create = qcow2_snapshot_create, diff --git a/block/vmdk.c b/block/vmdk.c index 46d474e442..a11c27a1c4 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -1645,56 +1645,11 @@ vmdk_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes, return ret; } -typedef struct VmdkWriteCompressedCo { - BlockDriverState *bs; - int64_t sector_num; - const uint8_t *buf; - int nb_sectors; - int ret; -} VmdkWriteCompressedCo; - -static void vmdk_co_write_compressed(void *opaque) -{ - VmdkWriteCompressedCo *co = opaque; - QEMUIOVector local_qiov; - uint64_t offset = co->sector_num * BDRV_SECTOR_SIZE; - uint64_t bytes = co->nb_sectors * BDRV_SECTOR_SIZE; - - struct iovec iov = (struct iovec) { - .iov_base = (uint8_t*) co->buf, - .iov_len = bytes, - }; - qemu_iovec_init_external(&local_qiov, &iov, 1); - - co->ret = vmdk_pwritev(co->bs, offset, bytes, &local_qiov, false, false); -} - -static int vmdk_write_compressed(BlockDriverState *bs, - int64_t sector_num, - const uint8_t *buf, - int nb_sectors) +static int coroutine_fn +vmdk_co_pwritev_compressed(BlockDriverState *bs, uint64_t offset, + uint64_t bytes, QEMUIOVector *qiov) { - BDRVVmdkState *s = bs->opaque; - - if (s->num_extents == 1 && s->extents[0].compressed) { - Coroutine *co; - AioContext *aio_context = bdrv_get_aio_context(bs); - VmdkWriteCompressedCo data = { - .bs = bs, - .sector_num = sector_num, - .buf = buf, - .nb_sectors = nb_sectors, - .ret = -EINPROGRESS, - }; - co = qemu_coroutine_create(vmdk_co_write_compressed, &data); - qemu_coroutine_enter(co); - while (data.ret == -EINPROGRESS) { - aio_poll(aio_context, true); - } - return data.ret; - } else { - return -ENOTSUP; - } + return vmdk_co_pwritev(bs, offset, bytes, qiov, 0); } static int coroutine_fn vmdk_co_pwrite_zeroes(BlockDriverState *bs, @@ -2393,7 +2348,7 @@ static BlockDriver bdrv_vmdk = { .bdrv_reopen_prepare = vmdk_reopen_prepare, .bdrv_co_preadv = vmdk_co_preadv, .bdrv_co_pwritev = vmdk_co_pwritev, - .bdrv_write_compressed = vmdk_write_compressed, + .bdrv_co_pwritev_compressed = vmdk_co_pwritev_compressed, .bdrv_co_pwrite_zeroes = vmdk_co_pwrite_zeroes, .bdrv_close = vmdk_close, .bdrv_create = vmdk_create, diff --git a/blockdev-nbd.c b/blockdev-nbd.c index 12cae0ea72..ca41cc6fdd 100644 --- a/blockdev-nbd.c +++ b/blockdev-nbd.c @@ -145,7 +145,8 @@ void qmp_nbd_server_start(SocketAddress *addr, void qmp_nbd_server_add(const char *device, bool has_writable, bool writable, Error **errp) { - BlockBackend *blk; + BlockDriverState *bs = NULL; + BlockBackend *on_eject_blk; NBDExport *exp; if (!nbd_server) { @@ -158,26 +159,22 @@ void qmp_nbd_server_add(const char *device, bool has_writable, bool writable, return; } - blk = blk_by_name(device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", device); - return; - } - if (!blk_is_inserted(blk)) { - error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, device); + on_eject_blk = blk_by_name(device); + + bs = bdrv_lookup_bs(device, device, errp); + if (!bs) { return; } if (!has_writable) { writable = false; } - if (blk_is_read_only(blk)) { + if (bdrv_is_read_only(bs)) { writable = false; } - exp = nbd_export_new(blk, 0, -1, writable ? 0 : NBD_FLAG_READ_ONLY, NULL, - errp); + exp = nbd_export_new(bs, 0, -1, writable ? 0 : NBD_FLAG_READ_ONLY, + NULL, false, on_eject_blk, errp); if (!exp) { return; } diff --git a/blockdev.c b/blockdev.c index 21614004d1..97062e33c3 100644 --- a/blockdev.c +++ b/blockdev.c @@ -1174,6 +1174,28 @@ fail: return dinfo; } +static BlockDriverState *qmp_get_root_bs(const char *name, Error **errp) +{ + BlockDriverState *bs; + + bs = bdrv_lookup_bs(name, name, errp); + if (bs == NULL) { + return NULL; + } + + if (!bdrv_is_root_node(bs)) { + error_setg(errp, "Need a root block node"); + return NULL; + } + + if (!bdrv_is_inserted(bs)) { + error_setg(errp, "Device has no medium"); + return NULL; + } + + return bs; +} + void hmp_commit(Monitor *mon, const QDict *qdict) { const char *device = qdict_get_str(qdict, "device"); @@ -1284,21 +1306,17 @@ SnapshotInfo *qmp_blockdev_snapshot_delete_internal_sync(const char *device, Error **errp) { BlockDriverState *bs; - BlockBackend *blk; AioContext *aio_context; QEMUSnapshotInfo sn; Error *local_err = NULL; SnapshotInfo *info = NULL; int ret; - blk = blk_by_name(device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", device); + bs = qmp_get_root_bs(device, errp); + if (!bs) { return NULL; } - - aio_context = blk_get_aio_context(blk); + aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); if (!has_id) { @@ -1314,12 +1332,6 @@ SnapshotInfo *qmp_blockdev_snapshot_delete_internal_sync(const char *device, goto out_aio_context; } - if (!blk_is_available(blk)) { - error_setg(errp, "Device '%s' has no medium", device); - goto out_aio_context; - } - bs = blk_bs(blk); - if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_INTERNAL_SNAPSHOT_DELETE, errp)) { goto out_aio_context; } @@ -1499,7 +1511,6 @@ static void internal_snapshot_prepare(BlkActionState *common, Error *local_err = NULL; const char *device; const char *name; - BlockBackend *blk; BlockDriverState *bs; QEMUSnapshotInfo old_sn, *sn; bool ret; @@ -1522,23 +1533,15 @@ static void internal_snapshot_prepare(BlkActionState *common, return; } - blk = blk_by_name(device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", device); + bs = qmp_get_root_bs(device, errp); + if (!bs) { return; } /* AioContext is released in .clean() */ - state->aio_context = blk_get_aio_context(blk); + state->aio_context = bdrv_get_aio_context(bs); aio_context_acquire(state->aio_context); - if (!blk_is_available(blk)) { - error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, device); - return; - } - bs = blk_bs(blk); - state->bs = bs; bdrv_drained_begin(bs); @@ -1838,56 +1841,31 @@ typedef struct DriveBackupState { BlockJob *job; } DriveBackupState; -static void do_drive_backup(const char *job_id, const char *device, - const char *target, bool has_format, - const char *format, enum MirrorSyncMode sync, - bool has_mode, enum NewImageMode mode, - bool has_speed, int64_t speed, - bool has_bitmap, const char *bitmap, - bool has_on_source_error, - BlockdevOnError on_source_error, - bool has_on_target_error, - BlockdevOnError on_target_error, - BlockJobTxn *txn, Error **errp); +static void do_drive_backup(DriveBackup *backup, BlockJobTxn *txn, + Error **errp); static void drive_backup_prepare(BlkActionState *common, Error **errp) { DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common); - BlockBackend *blk; + BlockDriverState *bs; DriveBackup *backup; Error *local_err = NULL; assert(common->action->type == TRANSACTION_ACTION_KIND_DRIVE_BACKUP); backup = common->action->u.drive_backup.data; - blk = blk_by_name(backup->device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", backup->device); - return; - } - - if (!blk_is_available(blk)) { - error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, backup->device); + bs = qmp_get_root_bs(backup->device, errp); + if (!bs) { return; } /* AioContext is released in .clean() */ - state->aio_context = blk_get_aio_context(blk); + state->aio_context = bdrv_get_aio_context(bs); aio_context_acquire(state->aio_context); - bdrv_drained_begin(blk_bs(blk)); - state->bs = blk_bs(blk); - - do_drive_backup(backup->has_job_id ? backup->job_id : NULL, - backup->device, backup->target, - backup->has_format, backup->format, - backup->sync, - backup->has_mode, backup->mode, - backup->has_speed, backup->speed, - backup->has_bitmap, backup->bitmap, - backup->has_on_source_error, backup->on_source_error, - backup->has_on_target_error, backup->on_target_error, - common->block_job_txn, &local_err); + bdrv_drained_begin(bs); + state->bs = bs; + + do_drive_backup(backup, common->block_job_txn, &local_err); if (local_err) { error_propagate(errp, local_err); return; @@ -1924,34 +1902,21 @@ typedef struct BlockdevBackupState { AioContext *aio_context; } BlockdevBackupState; -static void do_blockdev_backup(const char *job_id, 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, - BlockJobTxn *txn, Error **errp); +static void do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn, + Error **errp); static void blockdev_backup_prepare(BlkActionState *common, Error **errp) { BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, common); BlockdevBackup *backup; - BlockBackend *blk; - BlockDriverState *target; + BlockDriverState *bs, *target; Error *local_err = NULL; assert(common->action->type == TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP); backup = common->action->u.blockdev_backup.data; - blk = blk_by_name(backup->device); - if (!blk) { - error_setg(errp, "Device '%s' not found", backup->device); - return; - } - - if (!blk_is_available(blk)) { - error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, backup->device); + bs = qmp_get_root_bs(backup->device, errp); + if (!bs) { return; } @@ -1961,22 +1926,17 @@ static void blockdev_backup_prepare(BlkActionState *common, Error **errp) } /* AioContext is released in .clean() */ - state->aio_context = blk_get_aio_context(blk); + 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); - state->bs = blk_bs(blk); + state->bs = bs; bdrv_drained_begin(state->bs); - do_blockdev_backup(backup->has_job_id ? backup->job_id : NULL, - 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, - common->block_job_txn, &local_err); + do_blockdev_backup(backup, common->block_job_txn, &local_err); if (local_err) { error_propagate(errp, local_err); return; @@ -2983,7 +2943,6 @@ void qmp_block_stream(bool has_job_id, const char *job_id, const char *device, bool has_on_error, BlockdevOnError on_error, Error **errp) { - BlockBackend *blk; BlockDriverState *bs; BlockDriverState *base_bs = NULL; AioContext *aio_context; @@ -2994,22 +2953,14 @@ void qmp_block_stream(bool has_job_id, const char *job_id, const char *device, on_error = BLOCKDEV_ON_ERROR_REPORT; } - blk = blk_by_name(device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", device); + bs = qmp_get_root_bs(device, errp); + if (!bs) { return; } - aio_context = blk_get_aio_context(blk); + aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); - if (!blk_is_available(blk)) { - error_setg(errp, "Device '%s' has no medium", device); - goto out; - } - bs = blk_bs(blk); - if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_STREAM, errp)) { goto out; } @@ -3055,7 +3006,6 @@ void qmp_block_commit(bool has_job_id, const char *job_id, const char *device, bool has_speed, int64_t speed, Error **errp) { - BlockBackend *blk; BlockDriverState *bs; BlockDriverState *base_bs, *top_bs; AioContext *aio_context; @@ -3074,22 +3024,22 @@ void qmp_block_commit(bool has_job_id, const char *job_id, const char *device, * live commit feature versions; for this to work, we must make sure to * perform the device lookup before any generic errors that may occur in a * scenario in which all optional arguments are omitted. */ - blk = blk_by_name(device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", device); + bs = qmp_get_root_bs(device, &local_err); + if (!bs) { + bs = bdrv_lookup_bs(device, device, NULL); + if (!bs) { + error_free(local_err); + error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, + "Device '%s' not found", device); + } else { + error_propagate(errp, local_err); + } return; } - aio_context = blk_get_aio_context(blk); + aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); - if (!blk_is_available(blk)) { - error_setg(errp, "Device '%s' has no medium", device); - goto out; - } - bs = blk_bs(blk); - if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT_SOURCE, errp)) { goto out; } @@ -3155,19 +3105,8 @@ out: aio_context_release(aio_context); } -static void do_drive_backup(const char *job_id, const char *device, - const char *target, bool has_format, - const char *format, enum MirrorSyncMode sync, - bool has_mode, enum NewImageMode mode, - bool has_speed, int64_t speed, - bool has_bitmap, const char *bitmap, - bool has_on_source_error, - BlockdevOnError on_source_error, - bool has_on_target_error, - BlockdevOnError on_target_error, - BlockJobTxn *txn, Error **errp) +static void do_drive_backup(DriveBackup *backup, BlockJobTxn *txn, Error **errp) { - BlockBackend *blk; BlockDriverState *bs; BlockDriverState *target_bs; BlockDriverState *source = NULL; @@ -3178,39 +3117,36 @@ static void do_drive_backup(const char *job_id, const char *device, int flags; int64_t size; - if (!has_speed) { - speed = 0; + if (!backup->has_speed) { + backup->speed = 0; } - if (!has_on_source_error) { - on_source_error = BLOCKDEV_ON_ERROR_REPORT; + if (!backup->has_on_source_error) { + backup->on_source_error = BLOCKDEV_ON_ERROR_REPORT; } - if (!has_on_target_error) { - on_target_error = BLOCKDEV_ON_ERROR_REPORT; + if (!backup->has_on_target_error) { + backup->on_target_error = BLOCKDEV_ON_ERROR_REPORT; + } + if (!backup->has_mode) { + backup->mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; } - if (!has_mode) { - mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; + if (!backup->has_job_id) { + backup->job_id = NULL; + } + if (!backup->has_compress) { + backup->compress = false; } - blk = blk_by_name(device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", device); + bs = qmp_get_root_bs(backup->device, errp); + if (!bs) { return; } - aio_context = blk_get_aio_context(blk); + aio_context = bdrv_get_aio_context(bs); 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 (!blk_is_available(blk)) { - error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, device); - goto out; - } - bs = blk_bs(blk); - - if (!has_format) { - format = mode == NEW_IMAGE_MODE_EXISTING ? NULL : bs->drv->format_name; + if (!backup->has_format) { + backup->format = backup->mode == NEW_IMAGE_MODE_EXISTING ? + NULL : (char*) bs->drv->format_name; } /* Early check to avoid creating target */ @@ -3222,13 +3158,13 @@ static void do_drive_backup(const char *job_id, const char *device, /* See if we have a backing HD we can use to create our new image * on top of. */ - if (sync == MIRROR_SYNC_MODE_TOP) { + if (backup->sync == MIRROR_SYNC_MODE_TOP) { source = backing_bs(bs); if (!source) { - sync = MIRROR_SYNC_MODE_FULL; + backup->sync = MIRROR_SYNC_MODE_FULL; } } - if (sync == MIRROR_SYNC_MODE_NONE) { + if (backup->sync == MIRROR_SYNC_MODE_NONE) { source = bs; } @@ -3238,14 +3174,14 @@ static void do_drive_backup(const char *job_id, const char *device, goto out; } - if (mode != NEW_IMAGE_MODE_EXISTING) { - assert(format); + if (backup->mode != NEW_IMAGE_MODE_EXISTING) { + assert(backup->format); if (source) { - bdrv_img_create(target, format, source->filename, + bdrv_img_create(backup->target, backup->format, source->filename, source->drv->format_name, NULL, size, flags, &local_err, false); } else { - bdrv_img_create(target, format, NULL, NULL, NULL, + bdrv_img_create(backup->target, backup->format, NULL, NULL, NULL, size, flags, &local_err, false); } } @@ -3255,30 +3191,30 @@ static void do_drive_backup(const char *job_id, const char *device, goto out; } - if (format) { + if (backup->format) { options = qdict_new(); - qdict_put(options, "driver", qstring_from_str(format)); + qdict_put(options, "driver", qstring_from_str(backup->format)); } - target_bs = bdrv_open(target, NULL, options, flags, errp); + target_bs = bdrv_open(backup->target, NULL, options, flags, errp); if (!target_bs) { goto out; } bdrv_set_aio_context(target_bs, aio_context); - if (has_bitmap) { - bmap = bdrv_find_dirty_bitmap(bs, bitmap); + if (backup->has_bitmap) { + bmap = bdrv_find_dirty_bitmap(bs, backup->bitmap); if (!bmap) { - error_setg(errp, "Bitmap '%s' could not be found", bitmap); + error_setg(errp, "Bitmap '%s' could not be found", backup->bitmap); bdrv_unref(target_bs); goto out; } } - backup_start(job_id, bs, target_bs, speed, sync, bmap, - on_source_error, on_target_error, - block_job_cb, bs, txn, &local_err); + backup_start(backup->job_id, bs, target_bs, backup->speed, backup->sync, + bmap, backup->compress, backup->on_source_error, + backup->on_target_error, block_job_cb, bs, txn, &local_err); bdrv_unref(target_bs); if (local_err != NULL) { error_propagate(errp, local_err); @@ -3289,24 +3225,9 @@ out: aio_context_release(aio_context); } -void qmp_drive_backup(bool has_job_id, const char *job_id, - const char *device, const char *target, - bool has_format, const char *format, - enum MirrorSyncMode sync, - bool has_mode, enum NewImageMode mode, - bool has_speed, int64_t speed, - bool has_bitmap, const char *bitmap, - bool has_on_source_error, BlockdevOnError on_source_error, - bool has_on_target_error, BlockdevOnError on_target_error, - Error **errp) +void qmp_drive_backup(DriveBackup *arg, Error **errp) { - return do_drive_backup(has_job_id ? job_id : NULL, device, target, - has_format, format, sync, - has_mode, mode, has_speed, speed, - has_bitmap, bitmap, - has_on_source_error, on_source_error, - has_on_target_error, on_target_error, - NULL, errp); + return do_drive_backup(arg, NULL, errp); } BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp) @@ -3314,47 +3235,38 @@ BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp) return bdrv_named_nodes_list(errp); } -void do_blockdev_backup(const char *job_id, 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, - BlockJobTxn *txn, Error **errp) +void do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn, Error **errp) { - BlockBackend *blk; BlockDriverState *bs; BlockDriverState *target_bs; Error *local_err = NULL; AioContext *aio_context; - if (!has_speed) { - speed = 0; + if (!backup->has_speed) { + backup->speed = 0; } - if (!has_on_source_error) { - on_source_error = BLOCKDEV_ON_ERROR_REPORT; + if (!backup->has_on_source_error) { + backup->on_source_error = BLOCKDEV_ON_ERROR_REPORT; } - if (!has_on_target_error) { - on_target_error = BLOCKDEV_ON_ERROR_REPORT; + if (!backup->has_on_target_error) { + backup->on_target_error = BLOCKDEV_ON_ERROR_REPORT; + } + if (!backup->has_job_id) { + backup->job_id = NULL; + } + if (!backup->has_compress) { + backup->compress = false; } - blk = blk_by_name(device); - if (!blk) { - error_setg(errp, "Device '%s' not found", device); + bs = qmp_get_root_bs(backup->device, errp); + if (!bs) { return; } - aio_context = blk_get_aio_context(blk); + aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); - if (!blk_is_available(blk)) { - error_setg(errp, "Device '%s' has no medium", device); - goto out; - } - bs = blk_bs(blk); - - target_bs = bdrv_lookup_bs(target, target, errp); + target_bs = bdrv_lookup_bs(backup->target, backup->target, errp); if (!target_bs) { goto out; } @@ -3370,8 +3282,9 @@ void do_blockdev_backup(const char *job_id, const char *device, goto out; } } - backup_start(job_id, bs, target_bs, speed, sync, NULL, on_source_error, - on_target_error, block_job_cb, bs, txn, &local_err); + backup_start(backup->job_id, bs, target_bs, backup->speed, backup->sync, + NULL, backup->compress, backup->on_source_error, + backup->on_target_error, block_job_cb, bs, txn, &local_err); if (local_err != NULL) { error_propagate(errp, local_err); } @@ -3379,21 +3292,9 @@ out: aio_context_release(aio_context); } -void qmp_blockdev_backup(bool has_job_id, const char *job_id, - 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) +void qmp_blockdev_backup(BlockdevBackup *arg, Error **errp) { - do_blockdev_backup(has_job_id ? job_id : NULL, device, target, - sync, has_speed, speed, - has_on_source_error, on_source_error, - has_on_target_error, on_target_error, - NULL, errp); + do_blockdev_backup(arg, NULL, errp); } /* Parameter check and block job starting for drive mirroring. @@ -3469,7 +3370,6 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, void qmp_drive_mirror(DriveMirror *arg, Error **errp) { BlockDriverState *bs; - BlockBackend *blk; BlockDriverState *source, *target_bs; AioContext *aio_context; BlockMirrorBackingMode backing_mode; @@ -3479,21 +3379,14 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp) int64_t size; const char *format = arg->format; - blk = blk_by_name(arg->device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", arg->device); + bs = qmp_get_root_bs(arg->device, errp); + if (!bs) { return; } - aio_context = blk_get_aio_context(blk); + aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); - if (!blk_is_available(blk)) { - error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, arg->device); - goto out; - } - bs = blk_bs(blk); if (!arg->has_mode) { arg->mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; } @@ -3630,21 +3523,13 @@ void qmp_blockdev_mirror(bool has_job_id, const char *job_id, Error **errp) { BlockDriverState *bs; - BlockBackend *blk; BlockDriverState *target_bs; AioContext *aio_context; BlockMirrorBackingMode backing_mode = MIRROR_LEAVE_BACKING_CHAIN; Error *local_err = NULL; - blk = blk_by_name(device); - if (!blk) { - error_setg(errp, "Device '%s' not found", device); - return; - } - bs = blk_bs(blk); - + bs = qmp_get_root_bs(device, errp); if (!bs) { - error_setg(errp, "Device '%s' has no media", device); return; } @@ -3785,7 +3670,6 @@ void qmp_change_backing_file(const char *device, const char *backing_file, Error **errp) { - BlockBackend *blk; BlockDriverState *bs = NULL; AioContext *aio_context; BlockDriverState *image_bs = NULL; @@ -3794,22 +3678,14 @@ void qmp_change_backing_file(const char *device, int open_flags; int ret; - blk = blk_by_name(device); - if (!blk) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", device); + bs = qmp_get_root_bs(device, errp); + if (!bs) { return; } - aio_context = blk_get_aio_context(blk); + aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); - if (!blk_is_available(blk)) { - error_setg(errp, "Device '%s' has no medium", device); - goto out; - } - bs = blk_bs(blk); - image_bs = bdrv_lookup_bs(NULL, image_node_name, &local_err); if (local_err) { error_propagate(errp, local_err); diff --git a/blockjob.c b/blockjob.c index a5ba3bee52..a167f96fd4 100644 --- a/blockjob.c +++ b/blockjob.c @@ -132,6 +132,10 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, if (job_id == NULL) { job_id = bdrv_get_device_name(bs); + if (!*job_id) { + error_setg(errp, "An explicit job ID is required for this node"); + return NULL; + } } if (!id_wellformed(job_id)) { diff --git a/hmp-commands.hx b/hmp-commands.hx index 848efee5d1..74f32e515c 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -1182,8 +1182,8 @@ ETEXI { .name = "drive_backup", - .args_type = "reuse:-n,full:-f,device:B,target:s,format:s?", - .params = "[-n] [-f] device target [format]", + .args_type = "reuse:-n,full:-f,compress:-c,device:B,target:s,format:s?", + .params = "[-n] [-f] [-c] device target [format]", .help = "initiates a point-in-time\n\t\t\t" "copy for a device. The device's contents are\n\t\t\t" "copied to the new image file, excluding data that\n\t\t\t" @@ -1191,7 +1191,9 @@ ETEXI "The -n flag requests QEMU to reuse the image found\n\t\t\t" "in new-image-file, instead of recreating it from scratch.\n\t\t\t" "The -f flag requests QEMU to copy the whole disk,\n\t\t\t" - "so that the result does not need a backing file.\n\t\t\t", + "so that the result does not need a backing file.\n\t\t\t" + "The -c flag requests QEMU to compress backup data\n\t\t\t" + "(if the target format supports it).\n\t\t\t", .mhandler.cmd = hmp_drive_backup, }, STEXI @@ -1109,8 +1109,19 @@ void hmp_drive_backup(Monitor *mon, const QDict *qdict) const char *format = qdict_get_try_str(qdict, "format"); bool reuse = qdict_get_try_bool(qdict, "reuse", false); bool full = qdict_get_try_bool(qdict, "full", false); - enum NewImageMode mode; + bool compress = qdict_get_try_bool(qdict, "compress", false); Error *err = NULL; + DriveBackup backup = { + .device = (char *)device, + .target = (char *)filename, + .has_format = !!format, + .format = (char *)format, + .sync = full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP, + .has_mode = true, + .mode = reuse ? NEW_IMAGE_MODE_EXISTING : NEW_IMAGE_MODE_ABSOLUTE_PATHS, + .has_compress = !!compress, + .compress = compress, + }; if (!filename) { error_setg(&err, QERR_MISSING_PARAMETER, "target"); @@ -1118,16 +1129,7 @@ void hmp_drive_backup(Monitor *mon, const QDict *qdict) return; } - if (reuse) { - mode = NEW_IMAGE_MODE_EXISTING; - } else { - mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; - } - - qmp_drive_backup(false, NULL, device, filename, !!format, format, - full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP, - true, mode, false, 0, false, NULL, - false, 0, false, 0, &err); + qmp_drive_backup(&backup, &err); hmp_handle_error(mon, &err); } @@ -1921,11 +1923,22 @@ void hmp_chardev_remove(Monitor *mon, const QDict *qdict) void hmp_qemu_io(Monitor *mon, const QDict *qdict) { BlockBackend *blk; + BlockBackend *local_blk = NULL; const char* device = qdict_get_str(qdict, "device"); const char* command = qdict_get_str(qdict, "command"); Error *err = NULL; blk = blk_by_name(device); + if (!blk) { + BlockDriverState *bs = bdrv_lookup_bs(NULL, device, &err); + if (bs) { + blk = local_blk = blk_new(); + blk_insert_bs(blk, bs); + } else { + goto fail; + } + } + if (blk) { AioContext *aio_context = blk_get_aio_context(blk); aio_context_acquire(aio_context); @@ -1938,6 +1951,8 @@ void hmp_qemu_io(Monitor *mon, const QDict *qdict) "Device '%s' not found", device); } +fail: + blk_unref(local_blk); hmp_handle_error(mon, &err); } diff --git a/hw/ide/qdev.c b/hw/ide/qdev.c index 67c76bfcd6..2eb055ae70 100644 --- a/hw/ide/qdev.c +++ b/hw/ide/qdev.c @@ -75,10 +75,6 @@ static int ide_qdev_init(DeviceState *qdev) IDEDeviceClass *dc = IDE_DEVICE_GET_CLASS(dev); IDEBus *bus = DO_UPCAST(IDEBus, qbus, qdev->parent_bus); - if (!dev->conf.blk) { - error_report("No drive specified"); - goto err; - } if (dev->unit == -1) { dev->unit = bus->master ? 1 : 0; } @@ -158,6 +154,16 @@ static int ide_dev_initfn(IDEDevice *dev, IDEDriveKind kind) IDEState *s = bus->ifs + dev->unit; Error *err = NULL; + if (!dev->conf.blk) { + if (kind != IDE_CD) { + error_report("No drive specified"); + return -1; + } else { + /* Anonymous BlockBackend for an empty drive */ + dev->conf.blk = blk_new(); + } + } + if (dev->conf.discard_granularity == -1) { dev->conf.discard_granularity = 512; } else if (dev->conf.discard_granularity && @@ -257,7 +263,11 @@ static int ide_cd_initfn(IDEDevice *dev) static int ide_drive_initfn(IDEDevice *dev) { - DriveInfo *dinfo = blk_legacy_dinfo(dev->conf.blk); + DriveInfo *dinfo = NULL; + + if (dev->conf.blk) { + dinfo = blk_legacy_dinfo(dev->conf.blk); + } return ide_dev_initfn(dev, dinfo && dinfo->media_cd ? IDE_CD : IDE_HD); } diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c index 836a1553ed..99c9d618da 100644 --- a/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c @@ -2359,6 +2359,11 @@ static void scsi_hd_realize(SCSIDevice *dev, Error **errp) static void scsi_cd_realize(SCSIDevice *dev, Error **errp) { SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev); + + if (!dev->conf.blk) { + dev->conf.blk = blk_new(); + } + s->qdev.blocksize = 2048; s->qdev.type = TYPE_ROM; s->features |= 1 << SCSI_DISK_F_REMOVABLE; diff --git a/include/block/block.h b/include/block/block.h index 11c162d594..7edce5c35f 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -65,9 +65,10 @@ typedef enum { BDRV_REQ_MAY_UNMAP = 0x4, BDRV_REQ_NO_SERIALISING = 0x8, BDRV_REQ_FUA = 0x10, + BDRV_REQ_WRITE_COMPRESSED = 0x20, /* Mask of valid flags */ - BDRV_REQ_MASK = 0x1f, + BDRV_REQ_MASK = 0x3f, } BdrvRequestFlags; typedef struct BlockSizes { @@ -399,8 +400,6 @@ const char *bdrv_get_node_name(const BlockDriverState *bs); const char *bdrv_get_device_name(const BlockDriverState *bs); const char *bdrv_get_device_or_node_name(const BlockDriverState *bs); int bdrv_get_flags(BlockDriverState *bs); -int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, - const uint8_t *buf, int nb_sectors); int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi); ImageInfoSpecific *bdrv_get_specific_info(BlockDriverState *bs); void bdrv_round_sectors_to_clusters(BlockDriverState *bs, diff --git a/include/block/block_int.h b/include/block/block_int.h index 1e939de4fe..0ca6a78eb3 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -204,8 +204,8 @@ struct BlockDriver { bool has_variable_length; int64_t (*bdrv_get_allocated_file_size)(BlockDriverState *bs); - int (*bdrv_write_compressed)(BlockDriverState *bs, int64_t sector_num, - const uint8_t *buf, int nb_sectors); + int coroutine_fn (*bdrv_co_pwritev_compressed)(BlockDriverState *bs, + uint64_t offset, uint64_t bytes, QEMUIOVector *qiov); int (*bdrv_snapshot_create)(BlockDriverState *bs, QEMUSnapshotInfo *sn_info); @@ -765,6 +765,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs, void backup_start(const char *job_id, BlockDriverState *bs, BlockDriverState *target, int64_t speed, MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap, + bool compress, BlockdevOnError on_source_error, BlockdevOnError on_target_error, BlockCompletionFunc *cb, void *opaque, diff --git a/include/block/nbd.h b/include/block/nbd.h index 1897557a9b..80610ff31b 100644 --- a/include/block/nbd.h +++ b/include/block/nbd.h @@ -103,8 +103,9 @@ int nbd_disconnect(int fd); typedef struct NBDExport NBDExport; typedef struct NBDClient NBDClient; -NBDExport *nbd_export_new(BlockBackend *blk, off_t dev_offset, off_t size, +NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size, uint16_t nbdflags, void (*close)(NBDExport *), + bool writethrough, BlockBackend *on_eject_blk, Error **errp); void nbd_export_close(NBDExport *exp); void nbd_export_get(NBDExport *exp); diff --git a/include/qemu/coroutine.h b/include/qemu/coroutine.h index ac8d4c9cc8..29a20782f0 100644 --- a/include/qemu/coroutine.h +++ b/include/qemu/coroutine.h @@ -143,6 +143,7 @@ bool qemu_co_queue_empty(CoQueue *queue); */ typedef struct CoMutex { bool locked; + Coroutine *holder; CoQueue queue; } CoMutex; diff --git a/include/qemu/coroutine_int.h b/include/qemu/coroutine_int.h index 581a7f5140..6df9d33352 100644 --- a/include/qemu/coroutine_int.h +++ b/include/qemu/coroutine_int.h @@ -39,6 +39,7 @@ struct Coroutine { void *entry_arg; Coroutine *caller; QSLIST_ENTRY(Coroutine) pool_next; + size_t locks_held; /* Coroutines that should be woken up when we yield or terminate */ QSIMPLEQ_HEAD(, Coroutine) co_queue_wakeup; diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h index 2da4905d18..4808a9621a 100644 --- a/include/sysemu/block-backend.h +++ b/include/sysemu/block-backend.h @@ -98,6 +98,7 @@ BlockDriverState *blk_bs(BlockBackend *blk); void blk_remove_bs(BlockBackend *blk); void blk_insert_bs(BlockBackend *blk, BlockDriverState *bs); bool bdrv_has_blk(BlockDriverState *bs); +bool bdrv_is_root_node(BlockDriverState *bs); void blk_set_allow_write_beyond_eof(BlockBackend *blk, bool allow); void blk_iostatus_enable(BlockBackend *blk); @@ -203,8 +204,8 @@ void *blk_aio_get(const AIOCBInfo *aiocb_info, BlockBackend *blk, BlockCompletionFunc *cb, void *opaque); int coroutine_fn blk_co_pwrite_zeroes(BlockBackend *blk, int64_t offset, int count, BdrvRequestFlags flags); -int blk_write_compressed(BlockBackend *blk, int64_t sector_num, - const uint8_t *buf, int nb_sectors); +int blk_pwrite_compressed(BlockBackend *blk, int64_t offset, const void *buf, + int count); int blk_truncate(BlockBackend *blk, int64_t offset); int blk_pdiscard(BlockBackend *blk, int64_t offset, int count); int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf, diff --git a/nbd/server.c b/nbd/server.c index 80fbb4da1d..472f584c32 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -69,6 +69,7 @@ struct NBDExport { AioContext *ctx; + BlockBackend *eject_notifier_blk; Notifier eject_notifier; }; @@ -807,11 +808,18 @@ static void nbd_eject_notifier(Notifier *n, void *data) nbd_export_close(exp); } -NBDExport *nbd_export_new(BlockBackend *blk, off_t dev_offset, off_t size, +NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset, off_t size, uint16_t nbdflags, void (*close)(NBDExport *), + bool writethrough, BlockBackend *on_eject_blk, Error **errp) { + BlockBackend *blk; NBDExport *exp = g_malloc0(sizeof(NBDExport)); + + blk = blk_new(); + blk_insert_bs(blk, bs); + blk_set_enable_write_cache(blk, !writethrough); + exp->refcount = 1; QTAILQ_INIT(&exp->clients); exp->blk = blk; @@ -827,11 +835,14 @@ NBDExport *nbd_export_new(BlockBackend *blk, off_t dev_offset, off_t size, exp->close = close; exp->ctx = blk_get_aio_context(blk); - blk_ref(blk); blk_add_aio_context_notifier(blk, blk_aio_attached, blk_aio_detach, exp); - exp->eject_notifier.notify = nbd_eject_notifier; - blk_add_remove_bs_notifier(blk, &exp->eject_notifier); + if (on_eject_blk) { + blk_ref(on_eject_blk); + exp->eject_notifier_blk = on_eject_blk; + exp->eject_notifier.notify = nbd_eject_notifier; + blk_add_remove_bs_notifier(on_eject_blk, &exp->eject_notifier); + } /* * NBD exports are used for non-shared storage migration. Make sure @@ -844,6 +855,7 @@ NBDExport *nbd_export_new(BlockBackend *blk, off_t dev_offset, off_t size, return exp; fail: + blk_unref(blk); g_free(exp); return NULL; } @@ -914,7 +926,10 @@ void nbd_export_put(NBDExport *exp) } if (exp->blk) { - notifier_remove(&exp->eject_notifier); + if (exp->eject_notifier_blk) { + notifier_remove(&exp->eject_notifier); + blk_unref(exp->eject_notifier_blk); + } blk_remove_aio_context_notifier(exp->blk, blk_aio_attached, blk_aio_detach, exp); blk_unref(exp->blk); diff --git a/qapi/block-core.json b/qapi/block-core.json index 5e2d7d78d2..31f9990754 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -876,7 +876,7 @@ # @job-id: #optional identifier for the newly-created block job. If # omitted, the device name will be used. (Since 2.7) # -# @device: the name of the device which should be copied. +# @device: the device name or node-name of a root node which should be copied. # # @target: the target of the new image. If the file exists, or if it # is a device, the existing file/device will be used as the new @@ -898,6 +898,9 @@ # Must be present if sync is "incremental", must NOT be present # otherwise. (Since 2.4) # +# @compress: #optional true to compress data, if the target format supports it. +# (default: false) (since 2.7) +# # @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). @@ -915,7 +918,7 @@ { 'struct': 'DriveBackup', 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', '*format': 'str', 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode', - '*speed': 'int', '*bitmap': 'str', + '*speed': 'int', '*bitmap': 'str', '*compress': 'bool', '*on-source-error': 'BlockdevOnError', '*on-target-error': 'BlockdevOnError' } } @@ -925,7 +928,7 @@ # @job-id: #optional identifier for the newly-created block job. If # omitted, the device name will be used. (Since 2.7) # -# @device: the name of the device which should be copied. +# @device: the device name or node-name of a root node which should be copied. # # @target: the device name or node-name of the backup target node. # @@ -936,6 +939,9 @@ # @speed: #optional the maximum speed, in bytes per second. The default is 0, # for unlimited. # +# @compress: #optional true to compress data, if the target format supports it. +# (default: false) (since 2.7) +# # @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). @@ -954,6 +960,7 @@ 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', 'sync': 'MirrorSyncMode', '*speed': 'int', + '*compress': 'bool', '*on-source-error': 'BlockdevOnError', '*on-target-error': 'BlockdevOnError' } } @@ -998,7 +1005,8 @@ # @image-node-name: The name of the block driver state node of the # image to modify. # -# @device: The name of the device that owns image-node-name. +# @device: The device name or node-name of the root node that owns +# image-node-name. # # @backing-file: The string to write as the backing file. This # string is not validated, so care should be taken @@ -1020,7 +1028,7 @@ # @job-id: #optional identifier for the newly-created block job. If # omitted, the device name will be used. (Since 2.7) # -# @device: the name of the device +# @device: the device name or node-name of a root node # # @base: #optional The file name of the backing image to write data into. # If not specified, this is the deepest backing image @@ -1086,11 +1094,12 @@ # For the arguments, see the documentation of DriveBackup. # # Returns: nothing on success -# If @device is not a valid block device, DeviceNotFound +# If @device is not a valid block device, GenericError # # Since 1.6 ## -{ 'command': 'drive-backup', 'data': 'DriveBackup' } +{ 'command': 'drive-backup', 'boxed': true, + 'data': 'DriveBackup' } ## # @blockdev-backup @@ -1103,9 +1112,13 @@ # # For the arguments, see the documentation of BlockdevBackup. # +# Returns: nothing on success +# If @device is not a valid block device, DeviceNotFound +# # Since 2.3 ## -{ 'command': 'blockdev-backup', 'data': 'BlockdevBackup' } +{ 'command': 'blockdev-backup', 'boxed': true, + 'data': 'BlockdevBackup' } ## @@ -1127,7 +1140,7 @@ # See DriveMirror for parameter descriptions # # Returns: nothing on success -# If @device is not a valid block device, DeviceNotFound +# If @device is not a valid block device, GenericError # # Since 1.3 ## @@ -1142,7 +1155,8 @@ # @job-id: #optional identifier for the newly-created block job. If # omitted, the device name will be used. (Since 2.7) # -# @device: the name of the device whose writes should be mirrored. +# @device: the device name or node-name of a root node whose writes should be +# mirrored. # # @target: the target of the new image. If the file exists, or if it # is a device, the existing file/device will be used as the new @@ -1277,7 +1291,8 @@ # @job-id: #optional identifier for the newly-created block job. If # omitted, the device name will be used. (Since 2.7) # -# @device: the name of the device whose writes should be mirrored. +# @device: The device name or node-name of a root node whose writes should be +# mirrored. # # @target: the id or node-name of the block device to mirror to. This mustn't be # attached to guest. @@ -1462,7 +1477,7 @@ # @job-id: #optional identifier for the newly-created block job. If # omitted, the device name will be used. (Since 2.7) # -# @device: the device name +# @device: the device name or node-name of a root node # # @base: #optional the common backing file name # @@ -1487,9 +1502,6 @@ # 'stop' and 'enospc' can only be used if the block device # supports io-status (see BlockInfo). Since 1.3. # -# Returns: Nothing on success -# If @device does not exist, DeviceNotFound -# # Since: 1.1 ## { 'command': 'block-stream', diff --git a/qapi/block.json b/qapi/block.json index 937337dce5..8b08bd2fd0 100644 --- a/qapi/block.json +++ b/qapi/block.json @@ -58,7 +58,8 @@ ## # @BlockdevSnapshotInternal # -# @device: the name of the device to generate the snapshot from +# @device: the device name or node-name of a root node to generate the snapshot +# from # # @name: the name of the internal snapshot to be created # @@ -80,7 +81,7 @@ # For the arguments, see the documentation of BlockdevSnapshotInternal. # # Returns: nothing on success -# If @device is not a valid block device, DeviceNotFound +# If @device is not a valid block device, GenericError # If any snapshot matching @name exists, or @name is empty, # GenericError # If the format of the image used does not support it, @@ -99,14 +100,15 @@ # both. One of the name or id is required. Return SnapshotInfo for the # successfully deleted snapshot. # -# @device: the name of the device to delete the snapshot from +# @device: the device name or node-name of a root node to delete the snapshot +# from # # @id: optional the snapshot's ID to be deleted # # @name: optional the snapshot's name to be deleted # # Returns: SnapshotInfo on success -# If @device is not a valid block device, DeviceNotFound +# If @device is not a valid block device, GenericError # If snapshot not found, GenericError # If the format of the image used does not support it, # BlockFormatFeatureNotSupported @@ -159,9 +161,9 @@ ## # @nbd-server-add: # -# Export a device to QEMU's embedded NBD server. +# Export a block node to QEMU's embedded NBD server. # -# @device: Block device to be exported +# @device: The device name or node name of the node to be exported # # @writable: Whether clients should be able to write to the device via the # NBD connection (default false). #optional diff --git a/qemu-img.c b/qemu-img.c index f204d04136..1090286a9f 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -1590,7 +1590,9 @@ static int convert_write(ImgConvertState *s, int64_t sector_num, int nb_sectors, break; } - ret = blk_write_compressed(s->target, sector_num, buf, n); + ret = blk_pwrite_compressed(s->target, + sector_num << BDRV_SECTOR_BITS, + buf, n << BDRV_SECTOR_BITS); if (ret < 0) { return ret; } @@ -1727,7 +1729,7 @@ static int convert_do_copy(ImgConvertState *s) if (s->compressed) { /* signal EOF to align */ - ret = blk_write_compressed(s->target, 0, NULL, 0); + ret = blk_pwrite_compressed(s->target, 0, NULL, 0); if (ret < 0) { goto fail; } @@ -2032,7 +2034,7 @@ static int img_convert(int argc, char **argv) const char *preallocation = qemu_opt_get(opts, BLOCK_OPT_PREALLOC); - if (!drv->bdrv_write_compressed) { + if (!drv->bdrv_co_pwritev_compressed) { error_report("Compression not supported for this file format"); ret = -1; goto out; diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c index 25954f5634..3a3838a079 100644 --- a/qemu-io-cmds.c +++ b/qemu-io-cmds.c @@ -504,7 +504,7 @@ static int do_write_compressed(BlockBackend *blk, char *buf, int64_t offset, return -ERANGE; } - ret = blk_write_compressed(blk, offset >> 9, (uint8_t *)buf, count >> 9); + ret = blk_pwrite_compressed(blk, offset, buf, count); if (ret < 0) { return ret; } diff --git a/qemu-nbd.c b/qemu-nbd.c index e3571c2025..99297a556f 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -910,8 +910,8 @@ int main(int argc, char **argv) } } - exp = nbd_export_new(blk, dev_offset, fd_size, nbdflags, nbd_export_closed, - &local_err); + exp = nbd_export_new(bs, dev_offset, fd_size, nbdflags, nbd_export_closed, + writethrough, NULL, &local_err); if (!exp) { error_report_err(local_err); exit(EXIT_FAILURE); diff --git a/qmp-commands.hx b/qmp-commands.hx index 5c8d1d5e9f..e6c9193600 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -1120,7 +1120,7 @@ Arguments: - "job-id": Identifier for the newly-created block job. If omitted, the device name will be used. (json-string, optional) -- "device": The device's ID, must be unique (json-string) +- "device": The device name or node-name of a root node (json-string) - "base": The file name of the backing image above which copying starts (json-string, optional) - "backing-file": The backing file string to write into the active layer. This @@ -1166,7 +1166,7 @@ Arguments: - "job-id": Identifier for the newly-created block job. If omitted, the device name will be used. (json-string, optional) -- "device": The device's ID, must be unique (json-string) +- "device": The device name or node-name of a root node (json-string) - "base": The file name of the backing image to write data into. If not specified, this is the deepest backing image (json-string, optional) @@ -1217,7 +1217,8 @@ EQMP { .name = "drive-backup", .args_type = "job-id:s?,sync:s,device:B,target:s,speed:i?,mode:s?," - "format:s?,bitmap:s?,on-source-error:s?,on-target-error:s?", + "format:s?,bitmap:s?,compress:b?," + "on-source-error:s?,on-target-error:s?", .mhandler.cmd_new = qmp_marshal_drive_backup, }, @@ -1235,7 +1236,7 @@ Arguments: - "job-id": Identifier for the newly-created block job. If omitted, the device name will be used. (json-string, optional) -- "device": the name of the device which should be copied. +- "device": the device name or node-name of a root node which should be copied. (json-string) - "target": the target of the new image. If the file exists, or if it is a device, the existing file/device will be used as the new @@ -1253,6 +1254,8 @@ Arguments: - "mode": whether and how QEMU should create a new image (NewImageMode, optional, default 'absolute-paths') - "speed": the maximum speed, in bytes per second (json-int, optional) +- "compress": true to compress data, if the target format supports it. + (json-bool, optional, default false) - "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. @@ -1272,7 +1275,7 @@ EQMP { .name = "blockdev-backup", - .args_type = "job-id:s?,sync:s,device:B,target:B,speed:i?," + .args_type = "job-id:s?,sync:s,device:B,target:B,speed:i?,compress:b?," "on-source-error:s?,on-target-error:s?", .mhandler.cmd_new = qmp_marshal_blockdev_backup, }, @@ -1288,7 +1291,7 @@ Arguments: - "job-id": Identifier for the newly-created block job. If omitted, the device name will be used. (json-string, optional) -- "device": the name of the device which should be copied. +- "device": the device name or node-name of a root node 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; @@ -1296,6 +1299,8 @@ Arguments: 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) +- "compress": true to compress data, if the target format supports it. + (json-bool, optional, default false) - "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. @@ -1407,7 +1412,8 @@ actions array: - "mode": whether and how QEMU should create the snapshot file (NewImageMode, optional, default "absolute-paths") When "type" is "blockdev-snapshot-internal-sync": - - "device": device name to snapshot (json-string) + - "device": the device name or node-name of a root node to snapshot + (json-string) - "name": name of the new snapshot (json-string) Example: @@ -1608,7 +1614,8 @@ name already exists, the operation will fail. Arguments: -- "device": device name to snapshot (json-string) +- "device": the device name or node-name of a root node to snapshot + (json-string) - "name": name of the new snapshot (json-string) Example: @@ -1639,7 +1646,7 @@ fail. Arguments: -- "device": device name (json-string) +- "device": the device name or node-name of a root node (json-string) - "id": ID of the snapshot (json-string, optional) - "name": name of the snapshot (json-string, optional) @@ -1687,7 +1694,8 @@ Arguments: - "job-id": Identifier for the newly-created block job. If omitted, the device name will be used. (json-string, optional) -- "device": device name to operate on (json-string) +- "device": the device name or node-name of a root node whose writes should be + mirrored. (json-string) - "target": name of new image file (json-string) - "format": format of new image (json-string, optional) - "node-name": the name of the new block driver state in the node graph @@ -1747,7 +1755,8 @@ Arguments: - "job-id": Identifier for the newly-created block job. If omitted, the device name will be used. (json-string, optional) -- "device": device name to operate on (json-string) +- "device": The device name or node-name of a root node whose writes should be + mirrored (json-string) - "target": device name to mirror to (json-string) - "replaces": the block driver node name to replace when finished (json-string, optional) @@ -1803,7 +1812,8 @@ Arguments: "device". (json-string, optional) -- "device": The name of the device. +- "device": The device name or node-name of the root node that owns + image-node-name. (json-string) - "backing-file": The string to write as the backing file. This string is diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030 index 3ac2443e5b..107049b50f 100755 --- a/tests/qemu-iotests/030 +++ b/tests/qemu-iotests/030 @@ -126,7 +126,7 @@ class TestSingleDrive(iotests.QMPTestCase): def test_device_not_found(self): result = self.vm.qmp('block-stream', device='nonexistent') - self.assert_qmp(result, 'error/class', 'DeviceNotFound') + self.assert_qmp(result, 'error/class', 'GenericError') class TestSmallerBackingFile(iotests.QMPTestCase): diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041 index cbf5e0ba5c..80939c0d0d 100755 --- a/tests/qemu-iotests/041 +++ b/tests/qemu-iotests/041 @@ -38,7 +38,6 @@ class TestSingleDrive(iotests.QMPTestCase): image_len = 1 * 1024 * 1024 # MB qmp_cmd = 'drive-mirror' qmp_target = target_img - not_found_error = 'DeviceNotFound' def setUp(self): iotests.create_image(backing_img, self.image_len) @@ -176,7 +175,7 @@ class TestSingleDrive(iotests.QMPTestCase): result = self.vm.qmp(self.qmp_cmd, device='ide1-cd0', sync='full', target=self.qmp_target) - self.assert_qmp(result, 'error/class', self.not_found_error) + self.assert_qmp(result, 'error/class', 'GenericError') def test_image_not_found(self): result = self.vm.qmp(self.qmp_cmd, device='drive0', sync='full', @@ -186,12 +185,11 @@ class TestSingleDrive(iotests.QMPTestCase): def test_device_not_found(self): result = self.vm.qmp(self.qmp_cmd, device='nonexistent', sync='full', target=self.qmp_target) - self.assert_qmp(result, 'error/class', self.not_found_error) + self.assert_qmp(result, 'error/class', 'GenericError') class TestSingleBlockdev(TestSingleDrive): qmp_cmd = 'blockdev-mirror' qmp_target = 'node1' - not_found_error = 'GenericError' def setUp(self): TestSingleDrive.setUp(self) @@ -922,7 +920,7 @@ class TestRepairQuorum(iotests.QMPTestCase): node_name='repair0', replaces='img1', target=quorum_repair_img, format=iotests.imgfmt) - self.assert_qmp(result, 'error/class', 'DeviceNotFound') + self.assert_qmp(result, 'error/class', 'GenericError') def test_wrong_sync_mode(self): if not self.has_quorum(): diff --git a/tests/qemu-iotests/055 b/tests/qemu-iotests/055 index c8e3578702..ff4535e3ea 100755 --- a/tests/qemu-iotests/055 +++ b/tests/qemu-iotests/055 @@ -134,10 +134,7 @@ class TestSingleDrive(iotests.QMPTestCase): def do_test_device_not_found(self, cmd, **args): result = self.vm.qmp(cmd, **args) - if cmd == 'drive-backup': - self.assert_qmp(result, 'error/class', 'DeviceNotFound') - else: - self.assert_qmp(result, 'error/class', 'GenericError') + self.assert_qmp(result, 'error/class', 'GenericError') def test_device_not_found(self): self.do_test_device_not_found('drive-backup', device='nonexistent', @@ -371,7 +368,7 @@ class TestSingleTransaction(iotests.QMPTestCase): 'sync': 'full' }, } ]) - self.assert_qmp(result, 'error/class', 'DeviceNotFound') + self.assert_qmp(result, 'error/class', 'GenericError') result = self.vm.qmp('transaction', actions=[{ 'type': 'blockdev-backup', @@ -451,5 +448,123 @@ class TestSingleTransaction(iotests.QMPTestCase): self.assert_qmp(result, 'error/class', 'GenericError') self.assert_no_active_block_jobs() + +class TestDriveCompression(iotests.QMPTestCase): + image_len = 64 * 1024 * 1024 # MB + fmt_supports_compression = [{'type': 'qcow2', 'args': ()}, + {'type': 'vmdk', 'args': ('-o', 'subformat=streamOptimized')}] + + def setUp(self): + # Write data to the image so we can compare later + qemu_img('create', '-f', iotests.imgfmt, test_img, str(TestDriveCompression.image_len)) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x11 0 64k', test_img) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x00 64k 128k', test_img) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x22 162k 32k', test_img) + qemu_io('-f', iotests.imgfmt, '-c', 'write -P0x33 67043328 64k', test_img) + + def tearDown(self): + self.vm.shutdown() + os.remove(test_img) + os.remove(blockdev_target_img) + try: + os.remove(target_img) + except OSError: + pass + + def do_prepare_drives(self, fmt, args): + self.vm = iotests.VM().add_drive(test_img) + + qemu_img('create', '-f', fmt, blockdev_target_img, + str(TestDriveCompression.image_len), *args) + self.vm.add_drive(blockdev_target_img, format=fmt) + + self.vm.launch() + + def do_test_compress_complete(self, cmd, format, **args): + self.do_prepare_drives(format['type'], format['args']) + + self.assert_no_active_block_jobs() + + result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) + self.assert_qmp(result, 'return', {}) + + self.wait_until_completed() + + self.vm.shutdown() + self.assertTrue(iotests.compare_images(test_img, blockdev_target_img, + iotests.imgfmt, format['type']), + 'target image does not match source after backup') + + def test_complete_compress_drive_backup(self): + for format in TestDriveCompression.fmt_supports_compression: + self.do_test_compress_complete('drive-backup', format, + target=blockdev_target_img, mode='existing') + + def test_complete_compress_blockdev_backup(self): + for format in TestDriveCompression.fmt_supports_compression: + self.do_test_compress_complete('blockdev-backup', format, target='drive1') + + def do_test_compress_cancel(self, cmd, format, **args): + self.do_prepare_drives(format['type'], format['args']) + + self.assert_no_active_block_jobs() + + result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) + self.assert_qmp(result, 'return', {}) + + event = self.cancel_and_wait() + self.assert_qmp(event, 'data/type', 'backup') + + self.vm.shutdown() + + def test_compress_cancel_drive_backup(self): + for format in TestDriveCompression.fmt_supports_compression: + self.do_test_compress_cancel('drive-backup', format, + target=blockdev_target_img, mode='existing') + + def test_compress_cancel_blockdev_backup(self): + for format in TestDriveCompression.fmt_supports_compression: + self.do_test_compress_cancel('blockdev-backup', format, target='drive1') + + def do_test_compress_pause(self, cmd, format, **args): + self.do_prepare_drives(format['type'], format['args']) + + self.assert_no_active_block_jobs() + + self.vm.pause_drive('drive0') + result = self.vm.qmp(cmd, device='drive0', sync='full', compress=True, **args) + self.assert_qmp(result, 'return', {}) + + result = self.vm.qmp('block-job-pause', device='drive0') + self.assert_qmp(result, 'return', {}) + + self.vm.resume_drive('drive0') + time.sleep(1) + result = self.vm.qmp('query-block-jobs') + offset = self.dictpath(result, 'return[0]/offset') + + time.sleep(1) + result = self.vm.qmp('query-block-jobs') + self.assert_qmp(result, 'return[0]/offset', offset) + + result = self.vm.qmp('block-job-resume', device='drive0') + self.assert_qmp(result, 'return', {}) + + self.wait_until_completed() + + self.vm.shutdown() + self.assertTrue(iotests.compare_images(test_img, blockdev_target_img, + iotests.imgfmt, format['type']), + 'target image does not match source after backup') + + def test_compress_pause_drive_backup(self): + for format in TestDriveCompression.fmt_supports_compression: + self.do_test_compress_pause('drive-backup', format, + target=blockdev_target_img, mode='existing') + + def test_compress_pause_blockdev_backup(self): + for format in TestDriveCompression.fmt_supports_compression: + self.do_test_compress_pause('blockdev-backup', format, target='drive1') + if __name__ == '__main__': iotests.main(supported_fmts=['raw', 'qcow2']) diff --git a/tests/qemu-iotests/055.out b/tests/qemu-iotests/055.out index 42314e9c00..5ce2f9a2ed 100644 --- a/tests/qemu-iotests/055.out +++ b/tests/qemu-iotests/055.out @@ -1,5 +1,5 @@ -........................ +.............................. ---------------------------------------------------------------------- -Ran 24 tests +Ran 30 tests OK diff --git a/tests/qemu-iotests/057 b/tests/qemu-iotests/057 index 9cdd582e39..9f0a5a3057 100755 --- a/tests/qemu-iotests/057 +++ b/tests/qemu-iotests/057 @@ -182,7 +182,7 @@ class TestSingleTransaction(ImageSnapshotTestCase): 'name': 'a' }, }] result = self.vm.qmp('transaction', actions = actions) - self.assert_qmp(result, 'error/class', 'DeviceNotFound') + self.assert_qmp(result, 'error/class', 'GenericError') def test_error_exist(self): self.createSnapshotInTransaction(1) @@ -239,7 +239,7 @@ class TestSnapshotDelete(ImageSnapshotTestCase): result = self.vm.qmp('blockdev-snapshot-delete-internal-sync', device = 'drive_error', id = '0') - self.assert_qmp(result, 'error/class', 'DeviceNotFound') + self.assert_qmp(result, 'error/class', 'GenericError') def test_error_no_id_and_name(self): result = self.vm.qmp('blockdev-snapshot-delete-internal-sync', diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index dbe0ee548a..f1f36d7fc7 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -50,6 +50,7 @@ cachemode = os.environ.get('CACHEMODE') qemu_default_machine = os.environ.get('QEMU_DEFAULT_MACHINE') socket_scm_helper = os.environ.get('SOCKET_SCM_HELPER', 'socket_scm_helper') +debug = False def qemu_img(*args): '''Run qemu-img and return the exit code''' @@ -86,10 +87,10 @@ def qemu_io(*args): sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args))) return subp.communicate()[0] -def compare_images(img1, img2): +def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt): '''Return True if two image files are identical''' - return qemu_img('compare', '-f', imgfmt, - '-F', imgfmt, img1, img2) == 0 + return qemu_img('compare', '-f', fmt1, + '-F', fmt2, img1, img2) == 0 def create_image(name, size): '''Create a fully-allocated raw image with sector markers''' @@ -134,6 +135,8 @@ class VM(qtest.QEMUQtestMachine): def __init__(self): super(VM, self).__init__(qemu_prog, qemu_opts, test_dir=test_dir, socket_scm_helper=socket_scm_helper) + if debug: + self._debug = True self._num_drives = 0 def add_drive_raw(self, opts): @@ -141,14 +144,14 @@ class VM(qtest.QEMUQtestMachine): self._args.append(opts) return self - def add_drive(self, path, opts='', interface='virtio'): + def add_drive(self, path, opts='', interface='virtio', format=imgfmt): '''Add a virtio-blk drive to the VM''' options = ['if=%s' % interface, 'id=drive%d' % self._num_drives] if path is not None: options.append('file=%s' % path) - options.append('format=%s' % imgfmt) + options.append('format=%s' % format) options.append('cache=%s' % cachemode) if opts: @@ -318,6 +321,8 @@ def verify_quorum(): def main(supported_fmts=[], supported_oses=['linux']): '''Run tests''' + global debug + # We are using TEST_DIR and QEMU_DEFAULT_MACHINE as proxies to # indicate that we're not being run via "check". There may be # other things set up by "check" that individual test cases rely diff --git a/tests/test-coroutine.c b/tests/test-coroutine.c index ee5e06d327..6431dd6d7c 100644 --- a/tests/test-coroutine.c +++ b/tests/test-coroutine.c @@ -139,13 +139,20 @@ static void test_co_queue(void) { Coroutine *c1; Coroutine *c2; + Coroutine tmp; c2 = qemu_coroutine_create(c2_fn, NULL); c1 = qemu_coroutine_create(c1_fn, c2); qemu_coroutine_enter(c1); + + /* c1 shouldn't be used any more now; make sure we segfault if it is */ + tmp = *c1; memset(c1, 0xff, sizeof(Coroutine)); qemu_coroutine_enter(c2); + + /* Must restore the coroutine now to avoid corrupted pool */ + *c1 = tmp; } /* diff --git a/util/qemu-coroutine-lock.c b/util/qemu-coroutine-lock.c index 22aa9abb30..14cf9ce458 100644 --- a/util/qemu-coroutine-lock.c +++ b/util/qemu-coroutine-lock.c @@ -129,6 +129,8 @@ void coroutine_fn qemu_co_mutex_lock(CoMutex *mutex) } mutex->locked = true; + mutex->holder = self; + self->locks_held++; trace_qemu_co_mutex_lock_return(mutex, self); } @@ -140,9 +142,12 @@ void coroutine_fn qemu_co_mutex_unlock(CoMutex *mutex) trace_qemu_co_mutex_unlock_entry(mutex, self); assert(mutex->locked == true); + assert(mutex->holder == self); assert(qemu_in_coroutine()); mutex->locked = false; + mutex->holder = NULL; + self->locks_held--; qemu_co_queue_next(&mutex->queue); trace_qemu_co_mutex_unlock_return(mutex, self); @@ -156,14 +161,19 @@ void qemu_co_rwlock_init(CoRwlock *lock) void qemu_co_rwlock_rdlock(CoRwlock *lock) { + Coroutine *self = qemu_coroutine_self(); + while (lock->writer) { qemu_co_queue_wait(&lock->queue); } lock->reader++; + self->locks_held++; } void qemu_co_rwlock_unlock(CoRwlock *lock) { + Coroutine *self = qemu_coroutine_self(); + assert(qemu_in_coroutine()); if (lock->writer) { lock->writer = false; @@ -176,12 +186,16 @@ void qemu_co_rwlock_unlock(CoRwlock *lock) qemu_co_queue_next(&lock->queue); } } + self->locks_held--; } void qemu_co_rwlock_wrlock(CoRwlock *lock) { + Coroutine *self = qemu_coroutine_self(); + while (lock->writer || lock->reader) { qemu_co_queue_wait(&lock->queue); } lock->writer = true; + self->locks_held++; } diff --git a/util/qemu-coroutine.c b/util/qemu-coroutine.c index 89f21a9cec..3cbf225487 100644 --- a/util/qemu-coroutine.c +++ b/util/qemu-coroutine.c @@ -122,6 +122,7 @@ void qemu_coroutine_enter(Coroutine *co) case COROUTINE_YIELD: return; case COROUTINE_TERMINATE: + assert(!co->locks_held); trace_qemu_coroutine_terminate(co); coroutine_delete(co); return; |