diff options
39 files changed, 2652 insertions, 524 deletions
@@ -2883,8 +2883,16 @@ static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, /* Inherit from parent node */ if (parent_options) { + QemuOpts *opts; + QDict *options_copy; assert(!flags); role->inherit_options(&flags, options, parent_flags, parent_options); + options_copy = qdict_clone_shallow(options); + opts = qemu_opts_create(&bdrv_runtime_opts, NULL, 0, &error_abort); + qemu_opts_absorb_qdict(opts, options_copy, NULL); + update_flags_from_options(&flags, opts); + qemu_opts_del(opts); + QDECREF(options_copy); } /* Old values are used for options that aren't set yet */ @@ -3671,12 +3679,12 @@ int bdrv_drop_intermediate(BlockDriverState *top, BlockDriverState *base, GSList *ignore_children = g_slist_prepend(NULL, c); bdrv_check_update_perm(base, NULL, c->perm, c->shared_perm, ignore_children, &local_err); + g_slist_free(ignore_children); if (local_err) { ret = -EPERM; error_report_err(local_err); goto exit; } - g_slist_free(ignore_children); /* If so, update the backing file path in the image file */ if (c->role->update_filename) { diff --git a/block/backup.c b/block/backup.c index 4a16a37229..453cd62c24 100644 --- a/block/backup.c +++ b/block/backup.c @@ -206,7 +206,7 @@ static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret) BdrvDirtyBitmap *bm; BlockDriverState *bs = blk_bs(job->common.blk); - if (ret < 0 || block_job_is_cancelled(&job->common)) { + if (ret < 0) { /* Merge the successor back into the parent, delete nothing. */ bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL); assert(bm); @@ -621,7 +621,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, } /* job->common.len is fixed, so we can't allow resize */ - job = block_job_create(job_id, &backup_job_driver, bs, + job = block_job_create(job_id, &backup_job_driver, txn, bs, BLK_PERM_CONSISTENT_READ, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD, @@ -677,7 +677,6 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL, &error_abort); job->common.len = len; - block_job_txn_add_job(txn, &job->common); return &job->common; diff --git a/block/commit.c b/block/commit.c index 1943c9c3e1..ab4fa3c3cf 100644 --- a/block/commit.c +++ b/block/commit.c @@ -289,7 +289,7 @@ void commit_start(const char *job_id, BlockDriverState *bs, return; } - s = block_job_create(job_id, &commit_job_driver, bs, 0, BLK_PERM_ALL, + s = block_job_create(job_id, &commit_job_driver, NULL, bs, 0, BLK_PERM_ALL, speed, BLOCK_JOB_DEFAULT, NULL, NULL, errp); if (!s) { return; diff --git a/block/crypto.c b/block/crypto.c index e6095e7807..e0b8856f74 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -71,8 +71,6 @@ static ssize_t block_crypto_read_func(QCryptoBlock *block, struct BlockCryptoCreateData { - const char *filename; - QemuOpts *opts; BlockBackend *blk; uint64_t size; }; @@ -103,27 +101,18 @@ static ssize_t block_crypto_init_func(QCryptoBlock *block, Error **errp) { struct BlockCryptoCreateData *data = opaque; - int ret; + + if (data->size > INT64_MAX || headerlen > INT64_MAX - data->size) { + error_setg(errp, "The requested file size is too large"); + return -EFBIG; + } /* User provided size should reflect amount of space made * available to the guest, so we must take account of that * which will be used by the crypto header */ - data->size += headerlen; - - qemu_opt_set_number(data->opts, BLOCK_OPT_SIZE, data->size, &error_abort); - ret = bdrv_create_file(data->filename, data->opts, errp); - if (ret < 0) { - return -1; - } - - data->blk = blk_new_open(data->filename, NULL, NULL, - BDRV_O_RDWR | BDRV_O_PROTOCOL, errp); - if (!data->blk) { - return -1; - } - - return 0; + return blk_truncate(data->blk, data->size + headerlen, PREALLOC_MODE_OFF, + errp); } @@ -322,30 +311,29 @@ static int block_crypto_open_generic(QCryptoBlockFormat format, } -static int block_crypto_create_generic(QCryptoBlockFormat format, - const char *filename, - QemuOpts *opts, - Error **errp) +static int block_crypto_co_create_generic(BlockDriverState *bs, + int64_t size, + QCryptoBlockCreateOptions *opts, + Error **errp) { - int ret = -EINVAL; - QCryptoBlockCreateOptions *create_opts = NULL; + int ret; + BlockBackend *blk; QCryptoBlock *crypto = NULL; - struct BlockCryptoCreateData data = { - .size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), - BDRV_SECTOR_SIZE), - .opts = opts, - .filename = filename, - }; - QDict *cryptoopts; + struct BlockCryptoCreateData data; - cryptoopts = qemu_opts_to_qdict(opts, NULL); + blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL); - create_opts = block_crypto_create_opts_init(format, cryptoopts, errp); - if (!create_opts) { - return -1; + ret = blk_insert_bs(blk, bs, errp); + if (ret < 0) { + goto cleanup; } - crypto = qcrypto_block_create(create_opts, NULL, + data = (struct BlockCryptoCreateData) { + .blk = blk, + .size = size, + }; + + crypto = qcrypto_block_create(opts, NULL, block_crypto_init_func, block_crypto_write_func, &data, @@ -358,10 +346,8 @@ static int block_crypto_create_generic(QCryptoBlockFormat format, ret = 0; cleanup: - QDECREF(cryptoopts); qcrypto_block_free(crypto); - blk_unref(data.blk); - qapi_free_QCryptoBlockCreateOptions(create_opts); + blk_unref(blk); return ret; } @@ -537,7 +523,10 @@ static int64_t block_crypto_getlength(BlockDriverState *bs) uint64_t offset = qcrypto_block_get_payload_offset(crypto->block); assert(offset < INT64_MAX); - assert(offset < len); + + if (offset > len) { + return -EIO; + } len -= offset; @@ -562,12 +551,88 @@ static int block_crypto_open_luks(BlockDriverState *bs, bs, options, flags, errp); } +static int coroutine_fn +block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp) +{ + BlockdevCreateOptionsLUKS *luks_opts; + BlockDriverState *bs = NULL; + QCryptoBlockCreateOptions create_opts; + int ret; + + assert(create_options->driver == BLOCKDEV_DRIVER_LUKS); + luks_opts = &create_options->u.luks; + + bs = bdrv_open_blockdev_ref(luks_opts->file, errp); + if (bs == NULL) { + return -EIO; + } + + create_opts = (QCryptoBlockCreateOptions) { + .format = Q_CRYPTO_BLOCK_FORMAT_LUKS, + .u.luks = *qapi_BlockdevCreateOptionsLUKS_base(luks_opts), + }; + + ret = block_crypto_co_create_generic(bs, luks_opts->size, &create_opts, + errp); + if (ret < 0) { + goto fail; + } + + ret = 0; +fail: + bdrv_unref(bs); + return ret; +} + static int coroutine_fn block_crypto_co_create_opts_luks(const char *filename, QemuOpts *opts, Error **errp) { - return block_crypto_create_generic(Q_CRYPTO_BLOCK_FORMAT_LUKS, - filename, opts, errp); + QCryptoBlockCreateOptions *create_opts = NULL; + BlockDriverState *bs = NULL; + QDict *cryptoopts; + int64_t size; + int ret; + + /* Parse options */ + size = qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0); + + cryptoopts = qemu_opts_to_qdict_filtered(opts, NULL, + &block_crypto_create_opts_luks, + true); + + create_opts = block_crypto_create_opts_init(Q_CRYPTO_BLOCK_FORMAT_LUKS, + cryptoopts, errp); + if (!create_opts) { + ret = -EINVAL; + goto fail; + } + + /* Create protocol layer */ + ret = bdrv_create_file(filename, opts, errp); + if (ret < 0) { + return ret; + } + + bs = bdrv_open(filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp); + if (!bs) { + ret = -EINVAL; + goto fail; + } + + /* Create format layer */ + ret = block_crypto_co_create_generic(bs, size, create_opts, errp); + if (ret < 0) { + goto fail; + } + + ret = 0; +fail: + bdrv_unref(bs); + qapi_free_QCryptoBlockCreateOptions(create_opts); + QDECREF(cryptoopts); + return ret; } static int block_crypto_get_info_luks(BlockDriverState *bs, @@ -623,6 +688,7 @@ BlockDriver bdrv_crypto_luks = { .bdrv_open = block_crypto_open_luks, .bdrv_close = block_crypto_close, .bdrv_child_perm = bdrv_format_default_perms, + .bdrv_co_create = block_crypto_co_create_luks, .bdrv_co_create_opts = block_crypto_co_create_opts_luks, .bdrv_truncate = block_crypto_truncate, .create_opts = &block_crypto_create_opts_luks, diff --git a/block/iscsi.c b/block/iscsi.c index a82170f16e..f5aecfc883 100644 --- a/block/iscsi.c +++ b/block/iscsi.c @@ -2244,7 +2244,7 @@ static BlockDriver bdrv_iser = { .create_opts = &iscsi_create_opts, .bdrv_reopen_prepare = iscsi_reopen_prepare, .bdrv_reopen_commit = iscsi_reopen_commit, - .bdrv_invalidate_cache = iscsi_invalidate_cache, + .bdrv_co_invalidate_cache = iscsi_co_invalidate_cache, .bdrv_getlength = iscsi_getlength, .bdrv_get_info = iscsi_get_info, diff --git a/block/mirror.c b/block/mirror.c index f5bf620942..820f512c7b 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -869,11 +869,8 @@ static void coroutine_fn mirror_run(void *opaque) ret = 0; trace_mirror_before_sleep(s, cnt, s->synced, delay_ns); - if (!s->synced) { - block_job_sleep_ns(&s->common, delay_ns); - if (block_job_is_cancelled(&s->common)) { - break; - } + if (block_job_is_cancelled(&s->common) && s->common.force) { + break; } else if (!should_complete) { delay_ns = (s->in_flight == 0 && cnt == 0 ? SLICE_TIME : 0); block_job_sleep_ns(&s->common, delay_ns); @@ -887,7 +884,8 @@ immediate_exit: * or it was cancelled prematurely so that we do not guarantee that * the target is a copy of the source. */ - assert(ret < 0 || (!s->synced && block_job_is_cancelled(&s->common))); + assert(ret < 0 || ((s->common.force || !s->synced) && + block_job_is_cancelled(&s->common))); assert(need_drain); mirror_wait_for_all_io(s); } @@ -1166,7 +1164,7 @@ static void mirror_start_job(const char *job_id, BlockDriverState *bs, } /* Make sure that the source is not resized while the job is running */ - s = block_job_create(job_id, driver, mirror_top_bs, + s = block_job_create(job_id, driver, NULL, mirror_top_bs, BLK_PERM_CONSISTENT_READ, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | BLK_PERM_GRAPH_MOD, speed, diff --git a/block/parallels.c b/block/parallels.c index c13cb619e6..2da5e56a9d 100644 --- a/block/parallels.c +++ b/block/parallels.c @@ -34,6 +34,9 @@ #include "sysemu/block-backend.h" #include "qemu/module.h" #include "qemu/option.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-block-core.h" #include "qemu/bswap.h" #include "qemu/bitmap.h" #include "migration/blocker.h" @@ -79,6 +82,25 @@ static QemuOptsList parallels_runtime_opts = { }, }; +static QemuOptsList parallels_create_opts = { + .name = "parallels-create-opts", + .head = QTAILQ_HEAD_INITIALIZER(parallels_create_opts.head), + .desc = { + { + .name = BLOCK_OPT_SIZE, + .type = QEMU_OPT_SIZE, + .help = "Virtual disk size", + }, + { + .name = BLOCK_OPT_CLUSTER_SIZE, + .type = QEMU_OPT_SIZE, + .help = "Parallels image cluster size", + .def_value_str = stringify(DEFAULT_CLUSTER_SIZE), + }, + { /* end of list */ } + } +}; + static int64_t bat2sect(BDRVParallelsState *s, uint32_t idx) { @@ -480,46 +502,62 @@ out: } -static int coroutine_fn parallels_co_create_opts(const char *filename, - QemuOpts *opts, - Error **errp) +static int coroutine_fn parallels_co_create(BlockdevCreateOptions* opts, + Error **errp) { + BlockdevCreateOptionsParallels *parallels_opts; + BlockDriverState *bs; + BlockBackend *blk; int64_t total_size, cl_size; - uint8_t tmp[BDRV_SECTOR_SIZE]; - Error *local_err = NULL; - BlockBackend *file; uint32_t bat_entries, bat_sectors; ParallelsHeader header; + uint8_t tmp[BDRV_SECTOR_SIZE]; int ret; - total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), - BDRV_SECTOR_SIZE); - cl_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_CLUSTER_SIZE, - DEFAULT_CLUSTER_SIZE), BDRV_SECTOR_SIZE); + assert(opts->driver == BLOCKDEV_DRIVER_PARALLELS); + parallels_opts = &opts->u.parallels; + + /* Sanity checks */ + total_size = parallels_opts->size; + + if (parallels_opts->has_cluster_size) { + cl_size = parallels_opts->cluster_size; + } else { + cl_size = DEFAULT_CLUSTER_SIZE; + } + if (total_size >= MAX_PARALLELS_IMAGE_FACTOR * cl_size) { - error_propagate(errp, local_err); + error_setg(errp, "Image size is too large for this cluster size"); return -E2BIG; } - ret = bdrv_create_file(filename, opts, &local_err); - if (ret < 0) { - error_propagate(errp, local_err); - return ret; + if (!QEMU_IS_ALIGNED(total_size, BDRV_SECTOR_SIZE)) { + error_setg(errp, "Image size must be a multiple of 512 bytes"); + return -EINVAL; } - file = blk_new_open(filename, NULL, NULL, - BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, - &local_err); - if (file == NULL) { - error_propagate(errp, local_err); + if (!QEMU_IS_ALIGNED(cl_size, BDRV_SECTOR_SIZE)) { + error_setg(errp, "Cluster size must be a multiple of 512 bytes"); + return -EINVAL; + } + + /* Create BlockBackend to write to the image */ + bs = bdrv_open_blockdev_ref(parallels_opts->file, errp); + if (bs == NULL) { return -EIO; } - blk_set_allow_write_beyond_eof(file, true); + blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL); + ret = blk_insert_bs(blk, bs, errp); + if (ret < 0) { + goto out; + } + blk_set_allow_write_beyond_eof(blk, true); - ret = blk_truncate(file, 0, PREALLOC_MODE_OFF, errp); + /* Create image format */ + ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp); if (ret < 0) { - goto exit; + goto out; } bat_entries = DIV_ROUND_UP(total_size, cl_size); @@ -542,24 +580,107 @@ static int coroutine_fn parallels_co_create_opts(const char *filename, memset(tmp, 0, sizeof(tmp)); memcpy(tmp, &header, sizeof(header)); - ret = blk_pwrite(file, 0, tmp, BDRV_SECTOR_SIZE, 0); + ret = blk_pwrite(blk, 0, tmp, BDRV_SECTOR_SIZE, 0); if (ret < 0) { goto exit; } - ret = blk_pwrite_zeroes(file, BDRV_SECTOR_SIZE, + ret = blk_pwrite_zeroes(blk, BDRV_SECTOR_SIZE, (bat_sectors - 1) << BDRV_SECTOR_BITS, 0); if (ret < 0) { goto exit; } - ret = 0; -done: - blk_unref(file); + ret = 0; +out: + blk_unref(blk); + bdrv_unref(bs); return ret; exit: error_setg_errno(errp, -ret, "Failed to create Parallels image"); - goto done; + goto out; +} + +static int coroutine_fn parallels_co_create_opts(const char *filename, + QemuOpts *opts, + Error **errp) +{ + BlockdevCreateOptions *create_options = NULL; + Error *local_err = NULL; + BlockDriverState *bs = NULL; + QDict *qdict = NULL; + QObject *qobj; + Visitor *v; + int ret; + + static const QDictRenames opt_renames[] = { + { BLOCK_OPT_CLUSTER_SIZE, "cluster-size" }, + { NULL, NULL }, + }; + + /* Parse options and convert legacy syntax */ + qdict = qemu_opts_to_qdict_filtered(opts, NULL, ¶llels_create_opts, + true); + + if (!qdict_rename_keys(qdict, opt_renames, errp)) { + ret = -EINVAL; + goto done; + } + + /* Create and open the file (protocol layer) */ + ret = bdrv_create_file(filename, opts, &local_err); + if (ret < 0) { + error_propagate(errp, local_err); + goto done; + } + + bs = bdrv_open(filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp); + if (bs == NULL) { + ret = -EIO; + goto done; + } + + /* Now get the QAPI type BlockdevCreateOptions */ + qdict_put_str(qdict, "driver", "parallels"); + qdict_put_str(qdict, "file", bs->node_name); + + qobj = qdict_crumple(qdict, errp); + QDECREF(qdict); + qdict = qobject_to_qdict(qobj); + if (qdict == NULL) { + ret = -EINVAL; + goto done; + } + + v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); + visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err); + visit_free(v); + + if (local_err) { + error_propagate(errp, local_err); + ret = -EINVAL; + goto done; + } + + /* Silently round up sizes */ + create_options->u.parallels.size = + ROUND_UP(create_options->u.parallels.size, BDRV_SECTOR_SIZE); + create_options->u.parallels.cluster_size = + ROUND_UP(create_options->u.parallels.cluster_size, BDRV_SECTOR_SIZE); + + /* Create the Parallels image (format layer) */ + ret = parallels_co_create(create_options, errp); + if (ret < 0) { + goto done; + } + ret = 0; + +done: + QDECREF(qdict); + bdrv_unref(bs); + qapi_free_BlockdevCreateOptions(create_options); + return ret; } @@ -771,25 +892,6 @@ static void parallels_close(BlockDriverState *bs) error_free(s->migration_blocker); } -static QemuOptsList parallels_create_opts = { - .name = "parallels-create-opts", - .head = QTAILQ_HEAD_INITIALIZER(parallels_create_opts.head), - .desc = { - { - .name = BLOCK_OPT_SIZE, - .type = QEMU_OPT_SIZE, - .help = "Virtual disk size", - }, - { - .name = BLOCK_OPT_CLUSTER_SIZE, - .type = QEMU_OPT_SIZE, - .help = "Parallels image cluster size", - .def_value_str = stringify(DEFAULT_CLUSTER_SIZE), - }, - { /* end of list */ } - } -}; - static BlockDriver bdrv_parallels = { .format_name = "parallels", .instance_size = sizeof(BDRVParallelsState), @@ -803,6 +905,7 @@ static BlockDriver bdrv_parallels = { .bdrv_co_readv = parallels_co_readv, .bdrv_co_writev = parallels_co_writev, .supports_backing = true, + .bdrv_co_create = parallels_co_create, .bdrv_co_create_opts = parallels_co_create_opts, .bdrv_co_check = parallels_co_check, .create_opts = ¶llels_create_opts, diff --git a/block/qcow.c b/block/qcow.c index 47a18d9a3a..2e3770ca63 100644 --- a/block/qcow.c +++ b/block/qcow.c @@ -33,6 +33,8 @@ #include <zlib.h> #include "qapi/qmp/qdict.h" #include "qapi/qmp/qstring.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-block-core.h" #include "crypto/block.h" #include "migration/blocker.h" #include "block/crypto.h" @@ -86,6 +88,8 @@ typedef struct BDRVQcowState { Error *migration_blocker; } BDRVQcowState; +static QemuOptsList qcow_create_opts; + static int decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset); static int qcow_probe(const uint8_t *buf, int buf_size, const char *filename) @@ -810,62 +814,50 @@ static void qcow_close(BlockDriverState *bs) error_free(s->migration_blocker); } -static int coroutine_fn qcow_co_create_opts(const char *filename, QemuOpts *opts, - Error **errp) +static int coroutine_fn qcow_co_create(BlockdevCreateOptions *opts, + Error **errp) { + BlockdevCreateOptionsQcow *qcow_opts; int header_size, backing_filename_len, l1_size, shift, i; QCowHeader header; uint8_t *tmp; int64_t total_size = 0; - char *backing_file = NULL; - Error *local_err = NULL; int ret; + BlockDriverState *bs; BlockBackend *qcow_blk; - char *encryptfmt = NULL; - QDict *options; - QDict *encryptopts = NULL; - QCryptoBlockCreateOptions *crypto_opts = NULL; QCryptoBlock *crypto = NULL; - /* Read out options */ - total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), - BDRV_SECTOR_SIZE); + assert(opts->driver == BLOCKDEV_DRIVER_QCOW); + qcow_opts = &opts->u.qcow; + + /* Sanity checks */ + total_size = qcow_opts->size; if (total_size == 0) { error_setg(errp, "Image size is too small, cannot be zero length"); - ret = -EINVAL; - goto cleanup; + return -EINVAL; } - backing_file = qemu_opt_get_del(opts, BLOCK_OPT_BACKING_FILE); - encryptfmt = qemu_opt_get_del(opts, BLOCK_OPT_ENCRYPT_FORMAT); - if (encryptfmt) { - if (qemu_opt_get(opts, BLOCK_OPT_ENCRYPT)) { - error_setg(errp, "Options " BLOCK_OPT_ENCRYPT " and " - BLOCK_OPT_ENCRYPT_FORMAT " are mutually exclusive"); - ret = -EINVAL; - goto cleanup; - } - } else if (qemu_opt_get_bool_del(opts, BLOCK_OPT_ENCRYPT, false)) { - encryptfmt = g_strdup("aes"); + if (qcow_opts->has_encrypt && + qcow_opts->encrypt->format != Q_CRYPTO_BLOCK_FORMAT_QCOW) + { + error_setg(errp, "Unsupported encryption format"); + return -EINVAL; } - ret = bdrv_create_file(filename, opts, &local_err); - if (ret < 0) { - error_propagate(errp, local_err); - goto cleanup; + /* Create BlockBackend to write to the image */ + bs = bdrv_open_blockdev_ref(qcow_opts->file, errp); + if (bs == NULL) { + return -EIO; } - qcow_blk = blk_new_open(filename, NULL, NULL, - BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, - &local_err); - if (qcow_blk == NULL) { - error_propagate(errp, local_err); - ret = -EIO; - goto cleanup; + qcow_blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL); + ret = blk_insert_bs(qcow_blk, bs, errp); + if (ret < 0) { + goto exit; } - blk_set_allow_write_beyond_eof(qcow_blk, true); + /* Create image format */ ret = blk_truncate(qcow_blk, 0, PREALLOC_MODE_OFF, errp); if (ret < 0) { goto exit; @@ -877,16 +869,15 @@ static int coroutine_fn qcow_co_create_opts(const char *filename, QemuOpts *opts header.size = cpu_to_be64(total_size); header_size = sizeof(header); backing_filename_len = 0; - if (backing_file) { - if (strcmp(backing_file, "fat:")) { + if (qcow_opts->has_backing_file) { + if (strcmp(qcow_opts->backing_file, "fat:")) { header.backing_file_offset = cpu_to_be64(header_size); - backing_filename_len = strlen(backing_file); + backing_filename_len = strlen(qcow_opts->backing_file); header.backing_file_size = cpu_to_be32(backing_filename_len); header_size += backing_filename_len; } else { /* special backing file for vvfat */ - g_free(backing_file); - backing_file = NULL; + qcow_opts->has_backing_file = false; } header.cluster_bits = 9; /* 512 byte cluster to avoid copying unmodified sectors */ @@ -901,26 +892,10 @@ static int coroutine_fn qcow_co_create_opts(const char *filename, QemuOpts *opts header.l1_table_offset = cpu_to_be64(header_size); - options = qemu_opts_to_qdict(opts, NULL); - qdict_extract_subqdict(options, &encryptopts, "encrypt."); - QDECREF(options); - if (encryptfmt) { - if (!g_str_equal(encryptfmt, "aes")) { - error_setg(errp, "Unknown encryption format '%s', expected 'aes'", - encryptfmt); - ret = -EINVAL; - goto exit; - } + if (qcow_opts->has_encrypt) { header.crypt_method = cpu_to_be32(QCOW_CRYPT_AES); - crypto_opts = block_crypto_create_opts_init( - Q_CRYPTO_BLOCK_FORMAT_QCOW, encryptopts, errp); - if (!crypto_opts) { - ret = -EINVAL; - goto exit; - } - - crypto = qcrypto_block_create(crypto_opts, "encrypt.", + crypto = qcrypto_block_create(qcow_opts->encrypt, "encrypt.", NULL, NULL, NULL, errp); if (!crypto) { ret = -EINVAL; @@ -936,9 +911,9 @@ static int coroutine_fn qcow_co_create_opts(const char *filename, QemuOpts *opts goto exit; } - if (backing_file) { + if (qcow_opts->has_backing_file) { ret = blk_pwrite(qcow_blk, sizeof(header), - backing_file, backing_filename_len, 0); + qcow_opts->backing_file, backing_filename_len, 0); if (ret != backing_filename_len) { goto exit; } @@ -959,12 +934,100 @@ static int coroutine_fn qcow_co_create_opts(const char *filename, QemuOpts *opts ret = 0; exit: blk_unref(qcow_blk); -cleanup: - QDECREF(encryptopts); - g_free(encryptfmt); qcrypto_block_free(crypto); - qapi_free_QCryptoBlockCreateOptions(crypto_opts); - g_free(backing_file); + return ret; +} + +static int coroutine_fn qcow_co_create_opts(const char *filename, + QemuOpts *opts, Error **errp) +{ + BlockdevCreateOptions *create_options = NULL; + BlockDriverState *bs = NULL; + QDict *qdict = NULL; + QObject *qobj; + Visitor *v; + const char *val; + Error *local_err = NULL; + int ret; + + static const QDictRenames opt_renames[] = { + { BLOCK_OPT_BACKING_FILE, "backing-file" }, + { BLOCK_OPT_ENCRYPT, BLOCK_OPT_ENCRYPT_FORMAT }, + { NULL, NULL }, + }; + + /* Parse options and convert legacy syntax */ + qdict = qemu_opts_to_qdict_filtered(opts, NULL, &qcow_create_opts, true); + + val = qdict_get_try_str(qdict, BLOCK_OPT_ENCRYPT); + if (val && !strcmp(val, "on")) { + qdict_put_str(qdict, BLOCK_OPT_ENCRYPT, "qcow"); + } else if (val && !strcmp(val, "off")) { + qdict_del(qdict, BLOCK_OPT_ENCRYPT); + } + + val = qdict_get_try_str(qdict, BLOCK_OPT_ENCRYPT_FORMAT); + if (val && !strcmp(val, "aes")) { + qdict_put_str(qdict, BLOCK_OPT_ENCRYPT_FORMAT, "qcow"); + } + + if (!qdict_rename_keys(qdict, opt_renames, errp)) { + ret = -EINVAL; + goto fail; + } + + /* Create and open the file (protocol layer) */ + ret = bdrv_create_file(filename, opts, &local_err); + if (ret < 0) { + error_propagate(errp, local_err); + goto fail; + } + + bs = bdrv_open(filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp); + if (bs == NULL) { + ret = -EIO; + goto fail; + } + + /* Now get the QAPI type BlockdevCreateOptions */ + qdict_put_str(qdict, "driver", "qcow"); + qdict_put_str(qdict, "file", bs->node_name); + + qobj = qdict_crumple(qdict, errp); + QDECREF(qdict); + qdict = qobject_to_qdict(qobj); + if (qdict == NULL) { + ret = -EINVAL; + goto fail; + } + + v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); + visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err); + visit_free(v); + + if (local_err) { + error_propagate(errp, local_err); + ret = -EINVAL; + goto fail; + } + + /* Silently round up size */ + assert(create_options->driver == BLOCKDEV_DRIVER_QCOW); + create_options->u.qcow.size = + ROUND_UP(create_options->u.qcow.size, BDRV_SECTOR_SIZE); + + /* Create the qcow image (format layer) */ + ret = qcow_co_create(create_options, errp); + if (ret < 0) { + goto fail; + } + + ret = 0; +fail: + QDECREF(qdict); + bdrv_unref(bs); + qapi_free_BlockdevCreateOptions(create_options); return ret; } @@ -1128,6 +1191,7 @@ static BlockDriver bdrv_qcow = { .bdrv_close = qcow_close, .bdrv_child_perm = bdrv_format_default_perms, .bdrv_reopen_prepare = qcow_reopen_prepare, + .bdrv_co_create = qcow_co_create, .bdrv_co_create_opts = qcow_co_create_opts, .bdrv_has_zero_init = bdrv_has_zero_init_1, .supports_backing = true, diff --git a/block/qed.c b/block/qed.c index 5e6a6bfaa0..46a84beeed 100644 --- a/block/qed.c +++ b/block/qed.c @@ -20,6 +20,11 @@ #include "trace.h" #include "qed.h" #include "sysemu/block-backend.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-block-core.h" + +static QemuOptsList qed_create_opts; static int bdrv_qed_probe(const uint8_t *buf, int buf_size, const char *filename) @@ -594,57 +599,95 @@ static void bdrv_qed_close(BlockDriverState *bs) qemu_vfree(s->l1_table); } -static int qed_create(const char *filename, uint32_t cluster_size, - uint64_t image_size, uint32_t table_size, - const char *backing_file, const char *backing_fmt, - QemuOpts *opts, Error **errp) +static int coroutine_fn bdrv_qed_co_create(BlockdevCreateOptions *opts, + Error **errp) { - QEDHeader header = { - .magic = QED_MAGIC, - .cluster_size = cluster_size, - .table_size = table_size, - .header_size = 1, - .features = 0, - .compat_features = 0, - .l1_table_offset = cluster_size, - .image_size = image_size, - }; + BlockdevCreateOptionsQed *qed_opts; + BlockBackend *blk = NULL; + BlockDriverState *bs = NULL; + + QEDHeader header; QEDHeader le_header; uint8_t *l1_table = NULL; - size_t l1_size = header.cluster_size * header.table_size; - Error *local_err = NULL; + size_t l1_size; int ret = 0; - BlockBackend *blk; - ret = bdrv_create_file(filename, opts, &local_err); - if (ret < 0) { - error_propagate(errp, local_err); - return ret; + assert(opts->driver == BLOCKDEV_DRIVER_QED); + qed_opts = &opts->u.qed; + + /* Validate options and set default values */ + if (!qed_opts->has_cluster_size) { + qed_opts->cluster_size = QED_DEFAULT_CLUSTER_SIZE; + } + if (!qed_opts->has_table_size) { + qed_opts->table_size = QED_DEFAULT_TABLE_SIZE; } - blk = blk_new_open(filename, NULL, NULL, - BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, - &local_err); - if (blk == NULL) { - error_propagate(errp, local_err); + if (!qed_is_cluster_size_valid(qed_opts->cluster_size)) { + error_setg(errp, "QED cluster size must be within range [%u, %u] " + "and power of 2", + QED_MIN_CLUSTER_SIZE, QED_MAX_CLUSTER_SIZE); + return -EINVAL; + } + if (!qed_is_table_size_valid(qed_opts->table_size)) { + error_setg(errp, "QED table size must be within range [%u, %u] " + "and power of 2", + QED_MIN_TABLE_SIZE, QED_MAX_TABLE_SIZE); + return -EINVAL; + } + if (!qed_is_image_size_valid(qed_opts->size, qed_opts->cluster_size, + qed_opts->table_size)) + { + error_setg(errp, "QED image size must be a non-zero multiple of " + "cluster size and less than %" PRIu64 " bytes", + qed_max_image_size(qed_opts->cluster_size, + qed_opts->table_size)); + return -EINVAL; + } + + /* Create BlockBackend to write to the image */ + bs = bdrv_open_blockdev_ref(qed_opts->file, errp); + if (bs == NULL) { return -EIO; } + blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL); + ret = blk_insert_bs(blk, bs, errp); + if (ret < 0) { + goto out; + } blk_set_allow_write_beyond_eof(blk, true); + /* Prepare image format */ + header = (QEDHeader) { + .magic = QED_MAGIC, + .cluster_size = qed_opts->cluster_size, + .table_size = qed_opts->table_size, + .header_size = 1, + .features = 0, + .compat_features = 0, + .l1_table_offset = qed_opts->cluster_size, + .image_size = qed_opts->size, + }; + + l1_size = header.cluster_size * header.table_size; + /* File must start empty and grow, check truncate is supported */ ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp); if (ret < 0) { goto out; } - if (backing_file) { + if (qed_opts->has_backing_file) { header.features |= QED_F_BACKING_FILE; header.backing_filename_offset = sizeof(le_header); - header.backing_filename_size = strlen(backing_file); + header.backing_filename_size = strlen(qed_opts->backing_file); - if (qed_fmt_is_raw(backing_fmt)) { - header.features |= QED_F_BACKING_FORMAT_NO_PROBE; + if (qed_opts->has_backing_fmt) { + const char *backing_fmt = BlockdevDriver_str(qed_opts->backing_fmt); + if (qed_fmt_is_raw(backing_fmt)) { + header.features |= QED_F_BACKING_FORMAT_NO_PROBE; + } } } @@ -653,7 +696,7 @@ static int qed_create(const char *filename, uint32_t cluster_size, if (ret < 0) { goto out; } - ret = blk_pwrite(blk, sizeof(le_header), backing_file, + ret = blk_pwrite(blk, sizeof(le_header), qed_opts->backing_file, header.backing_filename_size, 0); if (ret < 0) { goto out; @@ -669,6 +712,7 @@ static int qed_create(const char *filename, uint32_t cluster_size, out: g_free(l1_table); blk_unref(blk); + bdrv_unref(bs); return ret; } @@ -676,51 +720,78 @@ static int coroutine_fn bdrv_qed_co_create_opts(const char *filename, QemuOpts *opts, Error **errp) { - uint64_t image_size = 0; - uint32_t cluster_size = QED_DEFAULT_CLUSTER_SIZE; - uint32_t table_size = QED_DEFAULT_TABLE_SIZE; - char *backing_file = NULL; - char *backing_fmt = NULL; + BlockdevCreateOptions *create_options = NULL; + QDict *qdict = NULL; + QObject *qobj; + Visitor *v; + BlockDriverState *bs = NULL; + Error *local_err = NULL; int ret; - image_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), - BDRV_SECTOR_SIZE); - backing_file = qemu_opt_get_del(opts, BLOCK_OPT_BACKING_FILE); - backing_fmt = qemu_opt_get_del(opts, BLOCK_OPT_BACKING_FMT); - cluster_size = qemu_opt_get_size_del(opts, - BLOCK_OPT_CLUSTER_SIZE, - QED_DEFAULT_CLUSTER_SIZE); - table_size = qemu_opt_get_size_del(opts, BLOCK_OPT_TABLE_SIZE, - QED_DEFAULT_TABLE_SIZE); - - if (!qed_is_cluster_size_valid(cluster_size)) { - error_setg(errp, "QED cluster size must be within range [%u, %u] " - "and power of 2", - QED_MIN_CLUSTER_SIZE, QED_MAX_CLUSTER_SIZE); + static const QDictRenames opt_renames[] = { + { BLOCK_OPT_BACKING_FILE, "backing-file" }, + { BLOCK_OPT_BACKING_FMT, "backing-fmt" }, + { BLOCK_OPT_CLUSTER_SIZE, "cluster-size" }, + { BLOCK_OPT_TABLE_SIZE, "table-size" }, + { NULL, NULL }, + }; + + /* Parse options and convert legacy syntax */ + qdict = qemu_opts_to_qdict_filtered(opts, NULL, &qed_create_opts, true); + + if (!qdict_rename_keys(qdict, opt_renames, errp)) { ret = -EINVAL; - goto finish; + goto fail; } - if (!qed_is_table_size_valid(table_size)) { - error_setg(errp, "QED table size must be within range [%u, %u] " - "and power of 2", - QED_MIN_TABLE_SIZE, QED_MAX_TABLE_SIZE); + + /* Create and open the file (protocol layer) */ + ret = bdrv_create_file(filename, opts, &local_err); + if (ret < 0) { + error_propagate(errp, local_err); + goto fail; + } + + bs = bdrv_open(filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp); + if (bs == NULL) { + ret = -EIO; + goto fail; + } + + /* Now get the QAPI type BlockdevCreateOptions */ + qdict_put_str(qdict, "driver", "qed"); + qdict_put_str(qdict, "file", bs->node_name); + + qobj = qdict_crumple(qdict, errp); + QDECREF(qdict); + qdict = qobject_to_qdict(qobj); + if (qdict == NULL) { ret = -EINVAL; - goto finish; + goto fail; } - if (!qed_is_image_size_valid(image_size, cluster_size, table_size)) { - error_setg(errp, "QED image size must be a non-zero multiple of " - "cluster size and less than %" PRIu64 " bytes", - qed_max_image_size(cluster_size, table_size)); + + v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); + visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err); + visit_free(v); + + if (local_err) { + error_propagate(errp, local_err); ret = -EINVAL; - goto finish; + goto fail; } - ret = qed_create(filename, cluster_size, image_size, table_size, - backing_file, backing_fmt, opts, errp); + /* Silently round up size */ + assert(create_options->driver == BLOCKDEV_DRIVER_QED); + create_options->u.qed.size = + ROUND_UP(create_options->u.qed.size, BDRV_SECTOR_SIZE); + + /* Create the qed image (format layer) */ + ret = bdrv_qed_co_create(create_options, errp); -finish: - g_free(backing_file); - g_free(backing_fmt); +fail: + QDECREF(qdict); + bdrv_unref(bs); + qapi_free_BlockdevCreateOptions(create_options); return ret; } @@ -1602,6 +1673,7 @@ static BlockDriver bdrv_qed = { .bdrv_close = bdrv_qed_close, .bdrv_reopen_prepare = bdrv_qed_reopen_prepare, .bdrv_child_perm = bdrv_format_default_perms, + .bdrv_co_create = bdrv_qed_co_create, .bdrv_co_create_opts = bdrv_qed_co_create_opts, .bdrv_has_zero_init = bdrv_has_zero_init_1, .bdrv_co_block_status = bdrv_qed_co_block_status, diff --git a/block/stream.c b/block/stream.c index 499cdacdb0..f3b53f49e2 100644 --- a/block/stream.c +++ b/block/stream.c @@ -244,7 +244,7 @@ void stream_start(const char *job_id, BlockDriverState *bs, /* Prevent concurrent jobs trying to modify the graph structure here, we * already have our own plans. Also don't allow resize as the image size is * queried only at the job start and then cached. */ - s = block_job_create(job_id, &stream_job_driver, bs, + s = block_job_create(job_id, &stream_job_driver, NULL, bs, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED | diff --git a/block/trace-events b/block/trace-events index 7493d521dc..f8c50b4063 100644 --- a/block/trace-events +++ b/block/trace-events @@ -4,6 +4,11 @@ bdrv_open_common(void *bs, const char *filename, int flags, const char *format_name) "bs %p filename \"%s\" flags 0x%x format_name \"%s\"" bdrv_lock_medium(void *bs, bool locked) "bs %p locked %d" +# blockjob.c +block_job_completed(void *job, int ret, int jret) "job %p ret %d corrected ret %d" +block_job_state_transition(void *job, int ret, const char *legal, const char *s0, const char *s1) "job %p (ret: %d) attempting %s transition (%s-->%s)" +block_job_apply_verb(void *job, const char *state, const char *verb, const char *legal) "job %p in state %s; applying verb %s (%s)" + # block/block-backend.c blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x" blk_co_pwritev(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags 0x%x" @@ -48,6 +53,8 @@ qmp_block_job_cancel(void *job) "job %p" qmp_block_job_pause(void *job) "job %p" qmp_block_job_resume(void *job) "job %p" qmp_block_job_complete(void *job) "job %p" +qmp_block_job_finalize(void *job) "job %p" +qmp_block_job_dismiss(void *job) "job %p" qmp_block_stream(void *bs, void *job) "bs %p job %p" # block/file-win32.c diff --git a/block/vdi.c b/block/vdi.c index 2b5ddd0666..d939b034c4 100644 --- a/block/vdi.c +++ b/block/vdi.c @@ -51,6 +51,9 @@ #include "qemu/osdep.h" #include "qapi/error.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-block-core.h" #include "block/block_int.h" #include "sysemu/block-backend.h" #include "qemu/module.h" @@ -140,6 +143,8 @@ #define VDI_DISK_SIZE_MAX ((uint64_t)VDI_BLOCKS_IN_IMAGE_MAX * \ (uint64_t)DEFAULT_CLUSTER_SIZE) +static QemuOptsList vdi_create_opts; + typedef struct { char text[0x40]; uint32_t signature; @@ -716,37 +721,47 @@ nonallocating_write: return ret; } -static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, - Error **errp) +static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options, + size_t block_size, Error **errp) { + BlockdevCreateOptionsVdi *vdi_opts; int ret = 0; uint64_t bytes = 0; uint32_t blocks; - size_t block_size = DEFAULT_CLUSTER_SIZE; uint32_t image_type = VDI_TYPE_DYNAMIC; VdiHeader header; size_t i; size_t bmap_size; int64_t offset = 0; - Error *local_err = NULL; + BlockDriverState *bs_file = NULL; BlockBackend *blk = NULL; uint32_t *bmap = NULL; + assert(create_options->driver == BLOCKDEV_DRIVER_VDI); + vdi_opts = &create_options->u.vdi; + logout("\n"); - /* Read out options. */ - bytes = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), - BDRV_SECTOR_SIZE); -#if defined(CONFIG_VDI_BLOCK_SIZE) - /* TODO: Additional checks (SECTOR_SIZE * 2^n, ...). */ - block_size = qemu_opt_get_size_del(opts, - BLOCK_OPT_CLUSTER_SIZE, - DEFAULT_CLUSTER_SIZE); -#endif -#if defined(CONFIG_VDI_STATIC_IMAGE) - if (qemu_opt_get_bool_del(opts, BLOCK_OPT_STATIC, false)) { + /* Validate options and set default values */ + bytes = vdi_opts->size; + if (vdi_opts->q_static) { image_type = VDI_TYPE_STATIC; } +#ifndef CONFIG_VDI_STATIC_IMAGE + if (image_type == VDI_TYPE_STATIC) { + ret = -ENOTSUP; + error_setg(errp, "Statically allocated images cannot be created in " + "this build"); + goto exit; + } +#endif +#ifndef CONFIG_VDI_BLOCK_SIZE + if (block_size != DEFAULT_CLUSTER_SIZE) { + ret = -ENOTSUP; + error_setg(errp, + "A non-default cluster size is not supported in this build"); + goto exit; + } #endif if (bytes > VDI_DISK_SIZE_MAX) { @@ -757,18 +772,16 @@ static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, goto exit; } - ret = bdrv_create_file(filename, opts, &local_err); - if (ret < 0) { - error_propagate(errp, local_err); + /* Create BlockBackend to write to the image */ + bs_file = bdrv_open_blockdev_ref(vdi_opts->file, errp); + if (!bs_file) { + ret = -EIO; goto exit; } - blk = blk_new_open(filename, NULL, NULL, - BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, - &local_err); - if (blk == NULL) { - error_propagate(errp, local_err); - ret = -EIO; + blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL); + ret = blk_insert_bs(blk, bs_file, errp); + if (ret < 0) { goto exit; } @@ -805,7 +818,7 @@ static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, vdi_header_to_le(&header); ret = blk_pwrite(blk, offset, &header, sizeof(header), 0); if (ret < 0) { - error_setg(errp, "Error writing header to %s", filename); + error_setg(errp, "Error writing header"); goto exit; } offset += sizeof(header); @@ -826,7 +839,7 @@ static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, } ret = blk_pwrite(blk, offset, bmap, bmap_size, 0); if (ret < 0) { - error_setg(errp, "Error writing bmap to %s", filename); + error_setg(errp, "Error writing bmap"); goto exit; } offset += bmap_size; @@ -836,17 +849,96 @@ static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, ret = blk_truncate(blk, offset + blocks * block_size, PREALLOC_MODE_OFF, errp); if (ret < 0) { - error_prepend(errp, "Failed to statically allocate %s", filename); + error_prepend(errp, "Failed to statically allocate file"); goto exit; } } exit: blk_unref(blk); + bdrv_unref(bs_file); g_free(bmap); return ret; } +static int coroutine_fn vdi_co_create(BlockdevCreateOptions *create_options, + Error **errp) +{ + return vdi_co_do_create(create_options, DEFAULT_CLUSTER_SIZE, errp); +} + +static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, + Error **errp) +{ + QDict *qdict = NULL; + BlockdevCreateOptions *create_options = NULL; + BlockDriverState *bs_file = NULL; + uint64_t block_size = DEFAULT_CLUSTER_SIZE; + Visitor *v; + Error *local_err = NULL; + int ret; + + /* Parse options and convert legacy syntax. + * + * Since CONFIG_VDI_BLOCK_SIZE is disabled by default, + * cluster-size is not part of the QAPI schema; therefore we have + * to parse it before creating the QAPI object. */ +#if defined(CONFIG_VDI_BLOCK_SIZE) + block_size = qemu_opt_get_size_del(opts, + BLOCK_OPT_CLUSTER_SIZE, + DEFAULT_CLUSTER_SIZE); + if (block_size < BDRV_SECTOR_SIZE || block_size > UINT32_MAX || + !is_power_of_2(block_size)) + { + error_setg(errp, "Invalid cluster size"); + ret = -EINVAL; + goto done; + } +#endif + + qdict = qemu_opts_to_qdict_filtered(opts, NULL, &vdi_create_opts, true); + + /* Create and open the file (protocol layer) */ + ret = bdrv_create_file(filename, opts, errp); + if (ret < 0) { + goto done; + } + + bs_file = bdrv_open(filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp); + if (!bs_file) { + ret = -EIO; + goto done; + } + + qdict_put_str(qdict, "driver", "vdi"); + qdict_put_str(qdict, "file", bs_file->node_name); + + /* Get the QAPI object */ + v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); + visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err); + visit_free(v); + + if (local_err) { + error_propagate(errp, local_err); + ret = -EINVAL; + goto done; + } + + /* Silently round up size */ + assert(create_options->driver == BLOCKDEV_DRIVER_VDI); + create_options->u.vdi.size = ROUND_UP(create_options->u.vdi.size, + BDRV_SECTOR_SIZE); + + /* Create the vdi image (format layer) */ + ret = vdi_co_do_create(create_options, block_size, errp); +done: + QDECREF(qdict); + qapi_free_BlockdevCreateOptions(create_options); + bdrv_unref(bs_file); + return ret; +} + static void vdi_close(BlockDriverState *bs) { BDRVVdiState *s = bs->opaque; @@ -895,6 +987,7 @@ static BlockDriver bdrv_vdi = { .bdrv_close = vdi_close, .bdrv_reopen_prepare = vdi_reopen_prepare, .bdrv_child_perm = bdrv_format_default_perms, + .bdrv_co_create = vdi_co_create, .bdrv_co_create_opts = vdi_co_create_opts, .bdrv_has_zero_init = bdrv_has_zero_init_1, .bdrv_co_block_status = vdi_co_block_status, diff --git a/block/vhdx.c b/block/vhdx.c index d82350d07c..f1b97f4b49 100644 --- a/block/vhdx.c +++ b/block/vhdx.c @@ -26,6 +26,9 @@ #include "block/vhdx.h" #include "migration/blocker.h" #include "qemu/uuid.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-block-core.h" /* Options for VHDX creation */ @@ -39,6 +42,8 @@ typedef enum VHDXImageType { VHDX_TYPE_DIFFERENCING, /* Currently unsupported */ } VHDXImageType; +static QemuOptsList vhdx_create_opts; + /* Several metadata and region table data entries are identified by * guids in a MS-specific GUID format. */ @@ -1792,59 +1797,71 @@ exit: * .---- ~ ----------- ~ ------------ ~ ---------------- ~ -----------. * 1MB */ -static int coroutine_fn vhdx_co_create_opts(const char *filename, QemuOpts *opts, - Error **errp) +static int coroutine_fn vhdx_co_create(BlockdevCreateOptions *opts, + Error **errp) { + BlockdevCreateOptionsVhdx *vhdx_opts; + BlockBackend *blk = NULL; + BlockDriverState *bs = NULL; + int ret = 0; - uint64_t image_size = (uint64_t) 2 * GiB; - uint32_t log_size = 1 * MiB; - uint32_t block_size = 0; + uint64_t image_size; + uint32_t log_size; + uint32_t block_size; uint64_t signature; uint64_t metadata_offset; bool use_zero_blocks = false; gunichar2 *creator = NULL; glong creator_items; - BlockBackend *blk; - char *type = NULL; VHDXImageType image_type; - Error *local_err = NULL; - image_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), - BDRV_SECTOR_SIZE); - log_size = qemu_opt_get_size_del(opts, VHDX_BLOCK_OPT_LOG_SIZE, 0); - block_size = qemu_opt_get_size_del(opts, VHDX_BLOCK_OPT_BLOCK_SIZE, 0); - type = qemu_opt_get_del(opts, BLOCK_OPT_SUBFMT); - use_zero_blocks = qemu_opt_get_bool_del(opts, VHDX_BLOCK_OPT_ZERO, true); + assert(opts->driver == BLOCKDEV_DRIVER_VHDX); + vhdx_opts = &opts->u.vhdx; + /* Validate options and set default values */ + image_size = vhdx_opts->size; if (image_size > VHDX_MAX_IMAGE_SIZE) { error_setg_errno(errp, EINVAL, "Image size too large; max of 64TB"); - ret = -EINVAL; - goto exit; + return -EINVAL; } - if (type == NULL) { - type = g_strdup("dynamic"); + if (!vhdx_opts->has_log_size) { + log_size = DEFAULT_LOG_SIZE; + } else { + log_size = vhdx_opts->log_size; + } + if (log_size < MiB || (log_size % MiB) != 0) { + error_setg_errno(errp, EINVAL, "Log size must be a multiple of 1 MB"); + return -EINVAL; } - if (!strcmp(type, "dynamic")) { + if (!vhdx_opts->has_block_state_zero) { + use_zero_blocks = true; + } else { + use_zero_blocks = vhdx_opts->block_state_zero; + } + + if (!vhdx_opts->has_subformat) { + vhdx_opts->subformat = BLOCKDEV_VHDX_SUBFORMAT_DYNAMIC; + } + + switch (vhdx_opts->subformat) { + case BLOCKDEV_VHDX_SUBFORMAT_DYNAMIC: image_type = VHDX_TYPE_DYNAMIC; - } else if (!strcmp(type, "fixed")) { + break; + case BLOCKDEV_VHDX_SUBFORMAT_FIXED: image_type = VHDX_TYPE_FIXED; - } else if (!strcmp(type, "differencing")) { - error_setg_errno(errp, ENOTSUP, - "Differencing files not yet supported"); - ret = -ENOTSUP; - goto exit; - } else { - error_setg(errp, "Invalid subformat '%s'", type); - ret = -EINVAL; - goto exit; + break; + default: + g_assert_not_reached(); } /* These are pretty arbitrary, and mainly designed to keep the BAT * size reasonable to load into RAM */ - if (block_size == 0) { + if (vhdx_opts->has_block_size) { + block_size = vhdx_opts->block_size; + } else { if (image_size > 32 * TiB) { block_size = 64 * MiB; } else if (image_size > (uint64_t) 100 * GiB) { @@ -1856,30 +1873,27 @@ static int coroutine_fn vhdx_co_create_opts(const char *filename, QemuOpts *opts } } - - /* make the log size close to what was specified, but must be - * min 1MB, and multiple of 1MB */ - log_size = ROUND_UP(log_size, MiB); - - block_size = ROUND_UP(block_size, MiB); - block_size = block_size > VHDX_BLOCK_SIZE_MAX ? VHDX_BLOCK_SIZE_MAX : - block_size; - - ret = bdrv_create_file(filename, opts, &local_err); - if (ret < 0) { - error_propagate(errp, local_err); - goto exit; + if (block_size < MiB || (block_size % MiB) != 0) { + error_setg_errno(errp, EINVAL, "Block size must be a multiple of 1 MB"); + return -EINVAL; + } + if (block_size > VHDX_BLOCK_SIZE_MAX) { + error_setg_errno(errp, EINVAL, "Block size must not exceed %d", + VHDX_BLOCK_SIZE_MAX); + return -EINVAL; } - blk = blk_new_open(filename, NULL, NULL, - BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, - &local_err); - if (blk == NULL) { - error_propagate(errp, local_err); - ret = -EIO; - goto exit; + /* Create BlockBackend to write to the image */ + bs = bdrv_open_blockdev_ref(vhdx_opts->file, errp); + if (bs == NULL) { + return -EIO; } + blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL); + ret = blk_insert_bs(blk, bs, errp); + if (ret < 0) { + goto delete_and_exit; + } blk_set_allow_write_beyond_eof(blk, true); /* Create (A) */ @@ -1931,12 +1945,109 @@ static int coroutine_fn vhdx_co_create_opts(const char *filename, QemuOpts *opts delete_and_exit: blk_unref(blk); -exit: - g_free(type); + bdrv_unref(bs); g_free(creator); return ret; } +static int coroutine_fn vhdx_co_create_opts(const char *filename, + QemuOpts *opts, + Error **errp) +{ + BlockdevCreateOptions *create_options = NULL; + QDict *qdict = NULL; + QObject *qobj; + Visitor *v; + BlockDriverState *bs = NULL; + Error *local_err = NULL; + int ret; + + static const QDictRenames opt_renames[] = { + { VHDX_BLOCK_OPT_LOG_SIZE, "log-size" }, + { VHDX_BLOCK_OPT_BLOCK_SIZE, "block-size" }, + { VHDX_BLOCK_OPT_ZERO, "block-state-zero" }, + { NULL, NULL }, + }; + + /* Parse options and convert legacy syntax */ + qdict = qemu_opts_to_qdict_filtered(opts, NULL, &vhdx_create_opts, true); + + if (!qdict_rename_keys(qdict, opt_renames, errp)) { + ret = -EINVAL; + goto fail; + } + + /* Create and open the file (protocol layer) */ + ret = bdrv_create_file(filename, opts, &local_err); + if (ret < 0) { + error_propagate(errp, local_err); + goto fail; + } + + bs = bdrv_open(filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp); + if (bs == NULL) { + ret = -EIO; + goto fail; + } + + /* Now get the QAPI type BlockdevCreateOptions */ + qdict_put_str(qdict, "driver", "vhdx"); + qdict_put_str(qdict, "file", bs->node_name); + + qobj = qdict_crumple(qdict, errp); + QDECREF(qdict); + qdict = qobject_to_qdict(qobj); + if (qdict == NULL) { + ret = -EINVAL; + goto fail; + } + + v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); + visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err); + visit_free(v); + + if (local_err) { + error_propagate(errp, local_err); + ret = -EINVAL; + goto fail; + } + + /* Silently round up sizes: + * The image size is rounded to 512 bytes. Make the block and log size + * close to what was specified, but must be at least 1MB, and a multiple of + * 1 MB. Also respect VHDX_BLOCK_SIZE_MAX for block sizes. block_size = 0 + * means auto, which is represented by a missing key in QAPI. */ + assert(create_options->driver == BLOCKDEV_DRIVER_VHDX); + create_options->u.vhdx.size = + ROUND_UP(create_options->u.vhdx.size, BDRV_SECTOR_SIZE); + + if (create_options->u.vhdx.has_log_size) { + create_options->u.vhdx.log_size = + ROUND_UP(create_options->u.vhdx.log_size, MiB); + } + if (create_options->u.vhdx.has_block_size) { + create_options->u.vhdx.block_size = + ROUND_UP(create_options->u.vhdx.block_size, MiB); + + if (create_options->u.vhdx.block_size == 0) { + create_options->u.vhdx.has_block_size = false; + } + if (create_options->u.vhdx.block_size > VHDX_BLOCK_SIZE_MAX) { + create_options->u.vhdx.block_size = VHDX_BLOCK_SIZE_MAX; + } + } + + /* Create the vhdx image (format layer) */ + ret = vhdx_co_create(create_options, errp); + +fail: + QDECREF(qdict); + bdrv_unref(bs); + qapi_free_BlockdevCreateOptions(create_options); + return ret; +} + /* If opened r/w, the VHDX driver will automatically replay the log, * if one is present, inside the vhdx_open() call. * @@ -2005,6 +2116,7 @@ static BlockDriver bdrv_vhdx = { .bdrv_child_perm = bdrv_format_default_perms, .bdrv_co_readv = vhdx_co_readv, .bdrv_co_writev = vhdx_co_writev, + .bdrv_co_create = vhdx_co_create, .bdrv_co_create_opts = vhdx_co_create_opts, .bdrv_get_info = vhdx_get_info, .bdrv_co_check = vhdx_co_check, diff --git a/block/vpc.c b/block/vpc.c index b2e2b9ebd4..28ffa0d2f8 100644 --- a/block/vpc.c +++ b/block/vpc.c @@ -32,6 +32,9 @@ #include "migration/blocker.h" #include "qemu/bswap.h" #include "qemu/uuid.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-block-core.h" /**************************************************************/ @@ -166,6 +169,8 @@ static QemuOptsList vpc_runtime_opts = { } }; +static QemuOptsList vpc_create_opts; + static uint32_t vpc_checksum(uint8_t* buf, size_t size) { uint32_t res = 0; @@ -897,60 +902,19 @@ static int create_fixed_disk(BlockBackend *blk, uint8_t *buf, return ret; } -static int coroutine_fn vpc_co_create_opts(const char *filename, QemuOpts *opts, - Error **errp) +static int calculate_rounded_image_size(BlockdevCreateOptionsVpc *vpc_opts, + uint16_t *out_cyls, + uint8_t *out_heads, + uint8_t *out_secs_per_cyl, + int64_t *out_total_sectors, + Error **errp) { - uint8_t buf[1024]; - VHDFooter *footer = (VHDFooter *) buf; - char *disk_type_param; - int i; + int64_t total_size = vpc_opts->size; uint16_t cyls = 0; uint8_t heads = 0; uint8_t secs_per_cyl = 0; int64_t total_sectors; - int64_t total_size; - int disk_type; - int ret = -EIO; - bool force_size; - Error *local_err = NULL; - BlockBackend *blk = NULL; - - /* Read out options */ - total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0), - BDRV_SECTOR_SIZE); - disk_type_param = qemu_opt_get_del(opts, BLOCK_OPT_SUBFMT); - if (disk_type_param) { - if (!strcmp(disk_type_param, "dynamic")) { - disk_type = VHD_DYNAMIC; - } else if (!strcmp(disk_type_param, "fixed")) { - disk_type = VHD_FIXED; - } else { - error_setg(errp, "Invalid disk type, %s", disk_type_param); - ret = -EINVAL; - goto out; - } - } else { - disk_type = VHD_DYNAMIC; - } - - force_size = qemu_opt_get_bool_del(opts, VPC_OPT_FORCE_SIZE, false); - - ret = bdrv_create_file(filename, opts, &local_err); - if (ret < 0) { - error_propagate(errp, local_err); - goto out; - } - - blk = blk_new_open(filename, NULL, NULL, - BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, - &local_err); - if (blk == NULL) { - error_propagate(errp, local_err); - ret = -EIO; - goto out; - } - - blk_set_allow_write_beyond_eof(blk, true); + int i; /* * Calculate matching total_size and geometry. Increase the number of @@ -961,7 +925,7 @@ static int coroutine_fn vpc_co_create_opts(const char *filename, QemuOpts *opts, * we set the geometry to 65535 x 16 x 255 (CxHxS) sectors and use * the image size from the VHD footer to calculate total_sectors. */ - if (force_size) { + if (vpc_opts->force_size) { /* This will force the use of total_size for sector count, below */ cyls = VHD_CHS_MAX_C; heads = VHD_CHS_MAX_H; @@ -978,19 +942,95 @@ static int coroutine_fn vpc_co_create_opts(const char *filename, QemuOpts *opts, /* Allow a maximum disk size of 2040 GiB */ if (total_sectors > VHD_MAX_SECTORS) { error_setg(errp, "Disk size is too large, max size is 2040 GiB"); - ret = -EFBIG; - goto out; + return -EFBIG; } } else { - total_sectors = (int64_t)cyls * heads * secs_per_cyl; - total_size = total_sectors * BDRV_SECTOR_SIZE; + total_sectors = (int64_t) cyls * heads * secs_per_cyl; + } + + *out_total_sectors = total_sectors; + if (out_cyls) { + *out_cyls = cyls; + *out_heads = heads; + *out_secs_per_cyl = secs_per_cyl; + } + + return 0; +} + +static int coroutine_fn vpc_co_create(BlockdevCreateOptions *opts, + Error **errp) +{ + BlockdevCreateOptionsVpc *vpc_opts; + BlockBackend *blk = NULL; + BlockDriverState *bs = NULL; + + uint8_t buf[1024]; + VHDFooter *footer = (VHDFooter *) buf; + uint16_t cyls = 0; + uint8_t heads = 0; + uint8_t secs_per_cyl = 0; + int64_t total_sectors; + int64_t total_size; + int disk_type; + int ret = -EIO; + + assert(opts->driver == BLOCKDEV_DRIVER_VPC); + vpc_opts = &opts->u.vpc; + + /* Validate options and set default values */ + total_size = vpc_opts->size; + + if (!vpc_opts->has_subformat) { + vpc_opts->subformat = BLOCKDEV_VPC_SUBFORMAT_DYNAMIC; + } + switch (vpc_opts->subformat) { + case BLOCKDEV_VPC_SUBFORMAT_DYNAMIC: + disk_type = VHD_DYNAMIC; + break; + case BLOCKDEV_VPC_SUBFORMAT_FIXED: + disk_type = VHD_FIXED; + break; + default: + g_assert_not_reached(); + } + + /* Create BlockBackend to write to the image */ + bs = bdrv_open_blockdev_ref(vpc_opts->file, errp); + if (bs == NULL) { + return -EIO; + } + + blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL); + ret = blk_insert_bs(blk, bs, errp); + if (ret < 0) { + goto out; + } + blk_set_allow_write_beyond_eof(blk, true); + + /* Get geometry and check that it matches the image size*/ + ret = calculate_rounded_image_size(vpc_opts, &cyls, &heads, &secs_per_cyl, + &total_sectors, errp); + if (ret < 0) { + goto out; + } + + if (total_size != total_sectors * BDRV_SECTOR_SIZE) { + error_setg(errp, "The requested image size cannot be represented in " + "CHS geometry"); + error_append_hint(errp, "Try size=%llu or force-size=on (the " + "latter makes the image incompatible with " + "Virtual PC)", + total_sectors * BDRV_SECTOR_SIZE); + ret = -EINVAL; + goto out; } /* Prepare the Hard Disk Footer */ memset(buf, 0, 1024); memcpy(footer->creator, "conectix", 8); - if (force_size) { + if (vpc_opts->force_size) { memcpy(footer->creator_app, "qem2", 4); } else { memcpy(footer->creator_app, "qemu", 4); @@ -1032,10 +1072,98 @@ static int coroutine_fn vpc_co_create_opts(const char *filename, QemuOpts *opts, out: blk_unref(blk); - g_free(disk_type_param); + bdrv_unref(bs); + return ret; +} + +static int coroutine_fn vpc_co_create_opts(const char *filename, + QemuOpts *opts, Error **errp) +{ + BlockdevCreateOptions *create_options = NULL; + QDict *qdict = NULL; + QObject *qobj; + Visitor *v; + BlockDriverState *bs = NULL; + Error *local_err = NULL; + int ret; + + static const QDictRenames opt_renames[] = { + { VPC_OPT_FORCE_SIZE, "force-size" }, + { NULL, NULL }, + }; + + /* Parse options and convert legacy syntax */ + qdict = qemu_opts_to_qdict_filtered(opts, NULL, &vpc_create_opts, true); + + if (!qdict_rename_keys(qdict, opt_renames, errp)) { + ret = -EINVAL; + goto fail; + } + + /* Create and open the file (protocol layer) */ + ret = bdrv_create_file(filename, opts, &local_err); + if (ret < 0) { + error_propagate(errp, local_err); + goto fail; + } + + bs = bdrv_open(filename, NULL, NULL, + BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp); + if (bs == NULL) { + ret = -EIO; + goto fail; + } + + /* Now get the QAPI type BlockdevCreateOptions */ + qdict_put_str(qdict, "driver", "vpc"); + qdict_put_str(qdict, "file", bs->node_name); + + qobj = qdict_crumple(qdict, errp); + QDECREF(qdict); + qdict = qobject_to_qdict(qobj); + if (qdict == NULL) { + ret = -EINVAL; + goto fail; + } + + v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); + visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err); + visit_free(v); + + if (local_err) { + error_propagate(errp, local_err); + ret = -EINVAL; + goto fail; + } + + /* Silently round up size */ + assert(create_options->driver == BLOCKDEV_DRIVER_VPC); + create_options->u.vpc.size = + ROUND_UP(create_options->u.vpc.size, BDRV_SECTOR_SIZE); + + if (!create_options->u.vpc.force_size) { + int64_t total_sectors; + ret = calculate_rounded_image_size(&create_options->u.vpc, NULL, NULL, + NULL, &total_sectors, errp); + if (ret < 0) { + goto fail; + } + + create_options->u.vpc.size = total_sectors * BDRV_SECTOR_SIZE; + } + + + /* Create the vpc image (format layer) */ + ret = vpc_co_create(create_options, errp); + +fail: + QDECREF(qdict); + bdrv_unref(bs); + qapi_free_BlockdevCreateOptions(create_options); return ret; } + static int vpc_has_zero_init(BlockDriverState *bs) { BDRVVPCState *s = bs->opaque; @@ -1096,6 +1224,7 @@ static BlockDriver bdrv_vpc = { .bdrv_close = vpc_close, .bdrv_reopen_prepare = vpc_reopen_prepare, .bdrv_child_perm = bdrv_format_default_perms, + .bdrv_co_create = vpc_co_create, .bdrv_co_create_opts = vpc_co_create_opts, .bdrv_co_preadv = vpc_co_preadv, diff --git a/block/vvfat.c b/block/vvfat.c index 4a17a49e12..1569783b0f 100644 --- a/block/vvfat.c +++ b/block/vvfat.c @@ -3129,7 +3129,7 @@ static void vvfat_qcow_options(int *child_flags, QDict *child_options, int parent_flags, QDict *parent_options) { qdict_set_default_str(child_options, BDRV_OPT_READ_ONLY, "off"); - *child_flags = BDRV_O_NO_FLUSH; + qdict_set_default_str(child_options, BDRV_OPT_CACHE_NO_FLUSH, "on"); } static const BdrvChildRole child_vvfat_qcow = { diff --git a/blockdev.c b/blockdev.c index b9de18f3b2..a6758c1220 100644 --- a/blockdev.c +++ b/blockdev.c @@ -150,7 +150,7 @@ void blockdev_mark_auto_del(BlockBackend *blk) aio_context_acquire(aio_context); if (bs->job) { - block_job_cancel(bs->job); + block_job_cancel(bs->job, false); } aio_context_release(aio_context); @@ -3274,7 +3274,7 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn, AioContext *aio_context; QDict *options = NULL; Error *local_err = NULL; - int flags; + int flags, job_flags = BLOCK_JOB_DEFAULT; int64_t size; bool set_backing_hd = false; @@ -3293,6 +3293,12 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn, if (!backup->has_job_id) { backup->job_id = NULL; } + if (!backup->has_auto_finalize) { + backup->auto_finalize = true; + } + if (!backup->has_auto_dismiss) { + backup->auto_dismiss = true; + } if (!backup->has_compress) { backup->compress = false; } @@ -3390,11 +3396,17 @@ static BlockJob *do_drive_backup(DriveBackup *backup, BlockJobTxn *txn, goto out; } } + if (!backup->auto_finalize) { + job_flags |= BLOCK_JOB_MANUAL_FINALIZE; + } + if (!backup->auto_dismiss) { + job_flags |= BLOCK_JOB_MANUAL_DISMISS; + } job = backup_job_create(backup->job_id, bs, target_bs, backup->speed, backup->sync, bmap, backup->compress, backup->on_source_error, backup->on_target_error, - BLOCK_JOB_DEFAULT, NULL, NULL, txn, &local_err); + job_flags, NULL, NULL, txn, &local_err); bdrv_unref(target_bs); if (local_err != NULL) { error_propagate(errp, local_err); @@ -3429,6 +3441,7 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn, Error *local_err = NULL; AioContext *aio_context; BlockJob *job = NULL; + int job_flags = BLOCK_JOB_DEFAULT; if (!backup->has_speed) { backup->speed = 0; @@ -3442,6 +3455,12 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn, if (!backup->has_job_id) { backup->job_id = NULL; } + if (!backup->has_auto_finalize) { + backup->auto_finalize = true; + } + if (!backup->has_auto_dismiss) { + backup->auto_dismiss = true; + } if (!backup->has_compress) { backup->compress = false; } @@ -3470,10 +3489,16 @@ BlockJob *do_blockdev_backup(BlockdevBackup *backup, BlockJobTxn *txn, goto out; } } + if (!backup->auto_finalize) { + job_flags |= BLOCK_JOB_MANUAL_FINALIZE; + } + if (!backup->auto_dismiss) { + job_flags |= BLOCK_JOB_MANUAL_DISMISS; + } job = backup_job_create(backup->job_id, bs, target_bs, backup->speed, backup->sync, NULL, backup->compress, backup->on_source_error, backup->on_target_error, - BLOCK_JOB_DEFAULT, NULL, NULL, txn, &local_err); + job_flags, NULL, NULL, txn, &local_err); if (local_err != NULL) { error_propagate(errp, local_err); } @@ -3825,7 +3850,7 @@ void qmp_block_job_cancel(const char *device, } trace_qmp_block_job_cancel(job); - block_job_cancel(job); + block_job_user_cancel(job, force, errp); out: aio_context_release(aio_context); } @@ -3835,12 +3860,12 @@ void qmp_block_job_pause(const char *device, Error **errp) AioContext *aio_context; BlockJob *job = find_block_job(device, &aio_context, errp); - if (!job || block_job_user_paused(job)) { + if (!job) { return; } trace_qmp_block_job_pause(job); - block_job_user_pause(job); + block_job_user_pause(job, errp); aio_context_release(aio_context); } @@ -3849,12 +3874,12 @@ void qmp_block_job_resume(const char *device, Error **errp) AioContext *aio_context; BlockJob *job = find_block_job(device, &aio_context, errp); - if (!job || !block_job_user_paused(job)) { + if (!job) { return; } trace_qmp_block_job_resume(job); - block_job_user_resume(job); + block_job_user_resume(job, errp); aio_context_release(aio_context); } @@ -3872,6 +3897,34 @@ void qmp_block_job_complete(const char *device, Error **errp) aio_context_release(aio_context); } +void qmp_block_job_finalize(const char *id, Error **errp) +{ + AioContext *aio_context; + BlockJob *job = find_block_job(id, &aio_context, errp); + + if (!job) { + return; + } + + trace_qmp_block_job_finalize(job); + block_job_finalize(job, errp); + aio_context_release(aio_context); +} + +void qmp_block_job_dismiss(const char *id, Error **errp) +{ + AioContext *aio_context; + BlockJob *job = find_block_job(id, &aio_context, errp); + + if (!job) { + return; + } + + trace_qmp_block_job_dismiss(job); + block_job_dismiss(&job, errp); + aio_context_release(aio_context); +} + void qmp_change_backing_file(const char *device, const char *image_node_name, const char *backing_file, diff --git a/blockjob.c b/blockjob.c index 801d29d849..ef3ed69ff1 100644 --- a/blockjob.c +++ b/blockjob.c @@ -28,6 +28,7 @@ #include "block/block.h" #include "block/blockjob_int.h" #include "block/block_int.h" +#include "block/trace.h" #include "sysemu/block-backend.h" #include "qapi/error.h" #include "qapi/qapi-events-block-core.h" @@ -41,6 +42,64 @@ * block_job_enter. */ static QemuMutex block_job_mutex; +/* BlockJob State Transition Table */ +bool BlockJobSTT[BLOCK_JOB_STATUS__MAX][BLOCK_JOB_STATUS__MAX] = { + /* U, C, R, P, Y, S, W, D, X, E, N */ + /* U: */ [BLOCK_JOB_STATUS_UNDEFINED] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + /* C: */ [BLOCK_JOB_STATUS_CREATED] = {0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1}, + /* R: */ [BLOCK_JOB_STATUS_RUNNING] = {0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0}, + /* P: */ [BLOCK_JOB_STATUS_PAUSED] = {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}, + /* Y: */ [BLOCK_JOB_STATUS_READY] = {0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0}, + /* S: */ [BLOCK_JOB_STATUS_STANDBY] = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, + /* W: */ [BLOCK_JOB_STATUS_WAITING] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0}, + /* D: */ [BLOCK_JOB_STATUS_PENDING] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0}, + /* X: */ [BLOCK_JOB_STATUS_ABORTING] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0}, + /* E: */ [BLOCK_JOB_STATUS_CONCLUDED] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + /* N: */ [BLOCK_JOB_STATUS_NULL] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, +}; + +bool BlockJobVerbTable[BLOCK_JOB_VERB__MAX][BLOCK_JOB_STATUS__MAX] = { + /* U, C, R, P, Y, S, W, D, X, E, N */ + [BLOCK_JOB_VERB_CANCEL] = {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, + [BLOCK_JOB_VERB_PAUSE] = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, + [BLOCK_JOB_VERB_RESUME] = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, + [BLOCK_JOB_VERB_SET_SPEED] = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, + [BLOCK_JOB_VERB_COMPLETE] = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, + [BLOCK_JOB_VERB_FINALIZE] = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}, + [BLOCK_JOB_VERB_DISMISS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, +}; + +static void block_job_state_transition(BlockJob *job, BlockJobStatus s1) +{ + BlockJobStatus s0 = job->status; + assert(s1 >= 0 && s1 <= BLOCK_JOB_STATUS__MAX); + trace_block_job_state_transition(job, job->ret, BlockJobSTT[s0][s1] ? + "allowed" : "disallowed", + qapi_enum_lookup(&BlockJobStatus_lookup, + s0), + qapi_enum_lookup(&BlockJobStatus_lookup, + s1)); + assert(BlockJobSTT[s0][s1]); + job->status = s1; +} + +static int block_job_apply_verb(BlockJob *job, BlockJobVerb bv, Error **errp) +{ + assert(bv >= 0 && bv <= BLOCK_JOB_VERB__MAX); + trace_block_job_apply_verb(job, qapi_enum_lookup(&BlockJobStatus_lookup, + job->status), + qapi_enum_lookup(&BlockJobVerb_lookup, bv), + BlockJobVerbTable[bv][job->status] ? + "allowed" : "prohibited"); + if (BlockJobVerbTable[bv][job->status]) { + return 0; + } + error_setg(errp, "Job '%s' in state '%s' cannot accept command verb '%s'", + job->id, qapi_enum_lookup(&BlockJobStatus_lookup, job->status), + qapi_enum_lookup(&BlockJobVerb_lookup, bv)); + return -EPERM; +} + static void block_job_lock(void) { qemu_mutex_lock(&block_job_mutex); @@ -58,6 +117,7 @@ static void __attribute__((__constructor__)) block_job_init(void) static void block_job_event_cancelled(BlockJob *job); static void block_job_event_completed(BlockJob *job, const char *msg); +static int block_job_event_pending(BlockJob *job); static void block_job_enter_cond(BlockJob *job, bool(*fn)(BlockJob *job)); /* Transactional group of block jobs */ @@ -171,6 +231,7 @@ static void block_job_detach_aio_context(void *opaque); void block_job_unref(BlockJob *job) { if (--job->refcnt == 0) { + assert(job->status == BLOCK_JOB_STATUS_NULL); BlockDriverState *bs = blk_bs(job->blk); QLIST_REMOVE(job, job_list); bs->job = NULL; @@ -320,25 +381,88 @@ void block_job_start(BlockJob *job) job->pause_count--; job->busy = true; job->paused = false; + block_job_state_transition(job, BLOCK_JOB_STATUS_RUNNING); bdrv_coroutine_enter(blk_bs(job->blk), job->co); } -static void block_job_completed_single(BlockJob *job) +static void block_job_decommission(BlockJob *job) { - assert(job->completed); + assert(job); + job->completed = true; + job->busy = false; + job->paused = false; + job->deferred_to_main_loop = true; + block_job_state_transition(job, BLOCK_JOB_STATUS_NULL); + block_job_unref(job); +} - if (!job->ret) { - if (job->driver->commit) { - job->driver->commit(job); - } - } else { - if (job->driver->abort) { - job->driver->abort(job); - } +static void block_job_do_dismiss(BlockJob *job) +{ + block_job_decommission(job); +} + +static void block_job_conclude(BlockJob *job) +{ + block_job_state_transition(job, BLOCK_JOB_STATUS_CONCLUDED); + if (job->auto_dismiss || !block_job_started(job)) { + block_job_do_dismiss(job); + } +} + +static void block_job_update_rc(BlockJob *job) +{ + if (!job->ret && block_job_is_cancelled(job)) { + job->ret = -ECANCELED; + } + if (job->ret) { + block_job_state_transition(job, BLOCK_JOB_STATUS_ABORTING); + } +} + +static int block_job_prepare(BlockJob *job) +{ + if (job->ret == 0 && job->driver->prepare) { + job->ret = job->driver->prepare(job); + } + return job->ret; +} + +static void block_job_commit(BlockJob *job) +{ + assert(!job->ret); + if (job->driver->commit) { + job->driver->commit(job); } +} + +static void block_job_abort(BlockJob *job) +{ + assert(job->ret); + if (job->driver->abort) { + job->driver->abort(job); + } +} + +static void block_job_clean(BlockJob *job) +{ if (job->driver->clean) { job->driver->clean(job); } +} + +static int block_job_finalize_single(BlockJob *job) +{ + assert(job->completed); + + /* Ensure abort is called for late-transactional failures */ + block_job_update_rc(job); + + if (!job->ret) { + block_job_commit(job); + } else { + block_job_abort(job); + } + block_job_clean(job); if (job->cb) { job->cb(job->opaque, job->ret); @@ -357,14 +481,13 @@ static void block_job_completed_single(BlockJob *job) } } - if (job->txn) { - QLIST_REMOVE(job, txn_list); - block_job_txn_unref(job->txn); - } - block_job_unref(job); + QLIST_REMOVE(job, txn_list); + block_job_txn_unref(job->txn); + block_job_conclude(job); + return 0; } -static void block_job_cancel_async(BlockJob *job) +static void block_job_cancel_async(BlockJob *job, bool force) { if (job->iostatus != BLOCK_DEVICE_IO_STATUS_OK) { block_job_iostatus_reset(job); @@ -375,6 +498,30 @@ static void block_job_cancel_async(BlockJob *job) job->pause_count--; } job->cancelled = true; + /* To prevent 'force == false' overriding a previous 'force == true' */ + job->force |= force; +} + +static int block_job_txn_apply(BlockJobTxn *txn, int fn(BlockJob *), bool lock) +{ + AioContext *ctx; + BlockJob *job, *next; + int rc = 0; + + QLIST_FOREACH_SAFE(job, &txn->jobs, txn_list, next) { + if (lock) { + ctx = blk_get_aio_context(job->blk); + aio_context_acquire(ctx); + } + rc = fn(job); + if (lock) { + aio_context_release(ctx); + } + if (rc) { + break; + } + } + return rc; } static int block_job_finish_sync(BlockJob *job, @@ -436,7 +583,7 @@ static void block_job_completed_txn_abort(BlockJob *job) * on the caller, so leave it. */ QLIST_FOREACH(other_job, &txn->jobs, txn_list) { if (other_job != job) { - block_job_cancel_async(other_job); + block_job_cancel_async(other_job, false); } } while (!QLIST_EMPTY(&txn->jobs)) { @@ -446,18 +593,39 @@ static void block_job_completed_txn_abort(BlockJob *job) assert(other_job->cancelled); block_job_finish_sync(other_job, NULL, NULL); } - block_job_completed_single(other_job); + block_job_finalize_single(other_job); aio_context_release(ctx); } block_job_txn_unref(txn); } +static int block_job_needs_finalize(BlockJob *job) +{ + return !job->auto_finalize; +} + +static void block_job_do_finalize(BlockJob *job) +{ + int rc; + assert(job && job->txn); + + /* prepare the transaction to complete */ + rc = block_job_txn_apply(job->txn, block_job_prepare, true); + if (rc) { + block_job_completed_txn_abort(job); + } else { + block_job_txn_apply(job->txn, block_job_finalize_single, true); + } +} + static void block_job_completed_txn_success(BlockJob *job) { - AioContext *ctx; BlockJobTxn *txn = job->txn; - BlockJob *other_job, *next; + BlockJob *other_job; + + block_job_state_transition(job, BLOCK_JOB_STATUS_WAITING); + /* * Successful completion, see if there are other running jobs in this * txn. @@ -466,14 +634,14 @@ static void block_job_completed_txn_success(BlockJob *job) if (!other_job->completed) { return; } - } - /* We are the last completed job, commit the transaction. */ - QLIST_FOREACH_SAFE(other_job, &txn->jobs, txn_list, next) { - ctx = blk_get_aio_context(other_job->blk); - aio_context_acquire(ctx); assert(other_job->ret == 0); - block_job_completed_single(other_job); - aio_context_release(ctx); + } + + block_job_txn_apply(txn, block_job_event_pending, false); + + /* If no jobs need manual finalization, automatically do so */ + if (block_job_txn_apply(txn, block_job_needs_finalize, false) == 0) { + block_job_do_finalize(job); } } @@ -492,6 +660,9 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp) error_setg(errp, QERR_UNSUPPORTED); return; } + if (block_job_apply_verb(job, BLOCK_JOB_VERB_SET_SPEED, errp)) { + return; + } job->driver->set_speed(job, speed, &local_err); if (local_err) { error_propagate(errp, local_err); @@ -499,7 +670,7 @@ void block_job_set_speed(BlockJob *job, int64_t speed, Error **errp) } job->speed = speed; - if (speed <= old_speed) { + if (speed && speed <= old_speed) { return; } @@ -511,8 +682,10 @@ void block_job_complete(BlockJob *job, Error **errp) { /* Should not be reachable via external interface for internal jobs */ assert(job->id); - if (job->pause_count || job->cancelled || - !block_job_started(job) || !job->driver->complete) { + if (block_job_apply_verb(job, BLOCK_JOB_VERB_COMPLETE, errp)) { + return; + } + if (job->pause_count || job->cancelled || !job->driver->complete) { error_setg(errp, "The active block job '%s' cannot be completed", job->id); return; @@ -521,8 +694,37 @@ void block_job_complete(BlockJob *job, Error **errp) job->driver->complete(job, errp); } -void block_job_user_pause(BlockJob *job) +void block_job_finalize(BlockJob *job, Error **errp) +{ + assert(job && job->id && job->txn); + if (block_job_apply_verb(job, BLOCK_JOB_VERB_FINALIZE, errp)) { + return; + } + block_job_do_finalize(job); +} + +void block_job_dismiss(BlockJob **jobptr, Error **errp) +{ + BlockJob *job = *jobptr; + /* similarly to _complete, this is QMP-interface only. */ + assert(job->id); + if (block_job_apply_verb(job, BLOCK_JOB_VERB_DISMISS, errp)) { + return; + } + + block_job_do_dismiss(job); + *jobptr = NULL; +} + +void block_job_user_pause(BlockJob *job, Error **errp) { + if (block_job_apply_verb(job, BLOCK_JOB_VERB_PAUSE, errp)) { + return; + } + if (job->user_paused) { + error_setg(errp, "Job is already paused"); + return; + } job->user_paused = true; block_job_pause(job); } @@ -532,31 +734,51 @@ bool block_job_user_paused(BlockJob *job) return job->user_paused; } -void block_job_user_resume(BlockJob *job) +void block_job_user_resume(BlockJob *job, Error **errp) { - if (job && job->user_paused && job->pause_count > 0) { - block_job_iostatus_reset(job); - job->user_paused = false; - block_job_resume(job); + assert(job); + if (!job->user_paused || job->pause_count <= 0) { + error_setg(errp, "Can't resume a job that was not paused"); + return; + } + if (block_job_apply_verb(job, BLOCK_JOB_VERB_RESUME, errp)) { + return; } + block_job_iostatus_reset(job); + job->user_paused = false; + block_job_resume(job); } -void block_job_cancel(BlockJob *job) +void block_job_cancel(BlockJob *job, bool force) { - if (block_job_started(job)) { - block_job_cancel_async(job); - block_job_enter(job); - } else { + if (job->status == BLOCK_JOB_STATUS_CONCLUDED) { + block_job_do_dismiss(job); + return; + } + block_job_cancel_async(job, force); + if (!block_job_started(job)) { block_job_completed(job, -ECANCELED); + } else if (job->deferred_to_main_loop) { + block_job_completed_txn_abort(job); + } else { + block_job_enter(job); } } +void block_job_user_cancel(BlockJob *job, bool force, Error **errp) +{ + if (block_job_apply_verb(job, BLOCK_JOB_VERB_CANCEL, errp)) { + return; + } + block_job_cancel(job, force); +} + /* A wrapper around block_job_cancel() taking an Error ** parameter so it may be * used with block_job_finish_sync() without the need for (rather nasty) * function pointer casts there. */ static void block_job_cancel_err(BlockJob *job, Error **errp) { - block_job_cancel(job); + block_job_cancel(job, false); } int block_job_cancel_sync(BlockJob *job) @@ -600,6 +822,9 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp) info->speed = job->speed; info->io_status = job->iostatus; info->ready = job->ready; + info->status = job->status; + info->auto_finalize = job->auto_finalize; + info->auto_dismiss = job->auto_dismiss; return info; } @@ -641,13 +866,24 @@ static void block_job_event_completed(BlockJob *job, const char *msg) &error_abort); } +static int block_job_event_pending(BlockJob *job) +{ + block_job_state_transition(job, BLOCK_JOB_STATUS_PENDING); + if (!job->auto_finalize && !block_job_is_internal(job)) { + qapi_event_send_block_job_pending(job->driver->job_type, + job->id, + &error_abort); + } + return 0; +} + /* * API for block job drivers and the block layer. These functions are * declared in blockjob_int.h. */ void *block_job_create(const char *job_id, const BlockJobDriver *driver, - BlockDriverState *bs, uint64_t perm, + BlockJobTxn *txn, BlockDriverState *bs, uint64_t perm, uint64_t shared_perm, int64_t speed, int flags, BlockCompletionFunc *cb, void *opaque, Error **errp) { @@ -702,6 +938,9 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, job->paused = true; job->pause_count = 1; job->refcnt = 1; + job->auto_finalize = !(flags & BLOCK_JOB_MANUAL_FINALIZE); + job->auto_dismiss = !(flags & BLOCK_JOB_MANUAL_DISMISS); + block_job_state_transition(job, BLOCK_JOB_STATUS_CREATED); aio_timer_init(qemu_get_aio_context(), &job->sleep_timer, QEMU_CLOCK_REALTIME, SCALE_NS, block_job_sleep_timer_cb, job); @@ -724,11 +963,22 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, block_job_set_speed(job, speed, &local_err); if (local_err) { - block_job_unref(job); + block_job_early_fail(job); error_propagate(errp, local_err); return NULL; } } + + /* Single jobs are modeled as single-job transactions for sake of + * consolidating the job management logic */ + if (!txn) { + txn = block_job_txn_new(); + block_job_txn_add_job(txn, job); + block_job_txn_unref(txn); + } else { + block_job_txn_add_job(txn, job); + } + return job; } @@ -747,18 +997,19 @@ void block_job_pause_all(void) void block_job_early_fail(BlockJob *job) { - block_job_unref(job); + assert(job->status == BLOCK_JOB_STATUS_CREATED); + block_job_decommission(job); } void block_job_completed(BlockJob *job, int ret) { + assert(job && job->txn && !job->completed); assert(blk_bs(job->blk)->job == job); - assert(!job->completed); job->completed = true; job->ret = ret; - if (!job->txn) { - block_job_completed_single(job); - } else if (ret < 0 || block_job_is_cancelled(job)) { + block_job_update_rc(job); + trace_block_job_completed(job, ret, job->ret); + if (job->ret) { block_job_completed_txn_abort(job); } else { block_job_completed_txn_success(job); @@ -806,9 +1057,14 @@ void coroutine_fn block_job_pause_point(BlockJob *job) } if (block_job_should_pause(job) && !block_job_is_cancelled(job)) { + BlockJobStatus status = job->status; + block_job_state_transition(job, status == BLOCK_JOB_STATUS_READY ? \ + BLOCK_JOB_STATUS_STANDBY : \ + BLOCK_JOB_STATUS_PAUSED); job->paused = true; block_job_do_yield(job, -1); job->paused = false; + block_job_state_transition(job, status); } if (job->driver->resume) { @@ -914,6 +1170,7 @@ void block_job_iostatus_reset(BlockJob *job) void block_job_event_ready(BlockJob *job) { + block_job_state_transition(job, BLOCK_JOB_STATUS_READY); job->ready = true; if (block_job_is_internal(job)) { @@ -957,8 +1214,9 @@ BlockErrorAction block_job_error_action(BlockJob *job, BlockdevOnError on_err, action, &error_abort); } if (action == BLOCK_ERROR_ACTION_STOP) { + block_job_pause(job); /* make the pause user visible, which will be resumed from QMP. */ - block_job_user_pause(job); + job->user_paused = true; block_job_iostatus_set_err(job, error); } return action; diff --git a/hmp-commands.hx b/hmp-commands.hx index 1723cbe1df..35d862a5d2 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -106,7 +106,8 @@ ETEXI .args_type = "force:-f,device:B", .params = "[-f] device", .help = "stop an active background block operation (use -f" - "\n\t\t\t if the operation is currently paused)", + "\n\t\t\t if you want to abort the operation immediately" + "\n\t\t\t instead of keep running until data is in sync)", .cmd = hmp_block_job_cancel, }, diff --git a/include/block/blockjob.h b/include/block/blockjob.h index 00403d9482..fc645dac68 100644 --- a/include/block/blockjob.h +++ b/include/block/blockjob.h @@ -63,6 +63,12 @@ typedef struct BlockJob { bool cancelled; /** + * Set to true if the job should abort immediately without waiting + * for data to be in sync. + */ + bool force; + + /** * Counter for pause request. If non-zero, the block job is either paused, * or if busy == true will pause itself as soon as possible. */ @@ -127,12 +133,10 @@ typedef struct BlockJob { /** Reference count of the block job */ int refcnt; - /* True if this job has reported completion by calling block_job_completed. - */ + /** True when job has reported completion by calling block_job_completed. */ bool completed; - /* ret code passed to block_job_completed. - */ + /** ret code passed to block_job_completed. */ int ret; /** @@ -141,14 +145,28 @@ typedef struct BlockJob { */ QEMUTimer sleep_timer; - /** Non-NULL if this job is part of a transaction */ + /** Current state; See @BlockJobStatus for details. */ + BlockJobStatus status; + + /** True if this job should automatically finalize itself */ + bool auto_finalize; + + /** True if this job should automatically dismiss itself */ + bool auto_dismiss; + BlockJobTxn *txn; QLIST_ENTRY(BlockJob) txn_list; } BlockJob; typedef enum BlockJobCreateFlags { + /* Default behavior */ BLOCK_JOB_DEFAULT = 0x00, + /* BlockJob is not QMP-created and should not send QMP events */ BLOCK_JOB_INTERNAL = 0x01, + /* BlockJob requires manual finalize step */ + BLOCK_JOB_MANUAL_FINALIZE = 0x02, + /* BlockJob requires manual dismiss step */ + BLOCK_JOB_MANUAL_DISMISS = 0x04, } BlockJobCreateFlags; /** @@ -218,10 +236,11 @@ void block_job_start(BlockJob *job); /** * block_job_cancel: * @job: The job to be canceled. + * @force: Quit a job without waiting for data to be in sync. * * Asynchronously cancel the specified job. */ -void block_job_cancel(BlockJob *job); +void block_job_cancel(BlockJob *job, bool force); /** * block_job_complete: @@ -232,6 +251,32 @@ void block_job_cancel(BlockJob *job); */ void block_job_complete(BlockJob *job, Error **errp); + +/** + * block_job_finalize: + * @job: The job to fully commit and finish. + * @errp: Error object. + * + * For jobs that have finished their work and are pending + * awaiting explicit acknowledgement to commit their work, + * This will commit that work. + * + * FIXME: Make the below statement universally true: + * For jobs that support the manual workflow mode, all graph + * changes that occur as a result will occur after this command + * and before a successful reply. + */ +void block_job_finalize(BlockJob *job, Error **errp); + +/** + * block_job_dismiss: + * @job: The job to be dismissed. + * @errp: Error object. + * + * Remove a concluded job from the query list. + */ +void block_job_dismiss(BlockJob **job, Error **errp); + /** * block_job_query: * @job: The job to get information about. @@ -247,7 +292,7 @@ BlockJobInfo *block_job_query(BlockJob *job, Error **errp); * Asynchronously pause the specified job. * Do not allow a resume until a matching call to block_job_user_resume. */ -void block_job_user_pause(BlockJob *job); +void block_job_user_pause(BlockJob *job, Error **errp); /** * block_job_paused: @@ -264,7 +309,17 @@ bool block_job_user_paused(BlockJob *job); * Resume the specified job. * Must be paired with a preceding block_job_user_pause. */ -void block_job_user_resume(BlockJob *job); +void block_job_user_resume(BlockJob *job, Error **errp); + +/** + * block_job_user_cancel: + * @job: The job to be cancelled. + * @force: Quit a job without waiting for data to be in sync. + * + * Cancels the specified job, but may refuse to do so if the + * operation isn't currently meaningful. + */ +void block_job_user_cancel(BlockJob *job, bool force, Error **errp); /** * block_job_cancel_sync: diff --git a/include/block/blockjob_int.h b/include/block/blockjob_int.h index c9b23b0cc9..642adce68b 100644 --- a/include/block/blockjob_int.h +++ b/include/block/blockjob_int.h @@ -54,6 +54,16 @@ struct BlockJobDriver { void (*complete)(BlockJob *job, Error **errp); /** + * If the callback is not NULL, prepare will be invoked when all the jobs + * belonging to the same transaction complete; or upon this job's completion + * if it is not in a transaction. + * + * This callback will not be invoked if the job has already failed. + * If it fails, abort and then clean will be called. + */ + int (*prepare)(BlockJob *job); + + /** * If the callback is not NULL, it will be invoked when all the jobs * belonging to the same transaction complete; or upon this job's * completion if it is not in a transaction. Skipped if NULL. @@ -114,10 +124,13 @@ struct BlockJobDriver { * block_job_create: * @job_id: The id of the newly-created job, or %NULL to have one * generated automatically. - * @job_type: The class object for the newly-created job. + * @driver: The class object for the newly-created job. + * @txn: The transaction this job belongs to, if any. %NULL otherwise. * @bs: The block * @perm, @shared_perm: Permissions to request for @bs * @speed: The maximum speed, in bytes per second, or 0 for unlimited. + * @flags: Creation flags for the Block Job. + * See @BlockJobCreateFlags * @cb: Completion function for the job. * @opaque: Opaque pointer value passed to @cb. * @errp: Error object. @@ -132,7 +145,7 @@ struct BlockJobDriver { * called from a wrapper that is specific to the job type. */ void *block_job_create(const char *job_id, const BlockJobDriver *driver, - BlockDriverState *bs, uint64_t perm, + BlockJobTxn *txn, BlockDriverState *bs, uint64_t perm, uint64_t shared_perm, int64_t speed, int flags, BlockCompletionFunc *cb, void *opaque, Error **errp); diff --git a/qapi/block-core.json b/qapi/block-core.json index 2b378f510a..5b0ad1a8b7 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -959,6 +959,77 @@ 'data': ['commit', 'stream', 'mirror', 'backup'] } ## +# @BlockJobVerb: +# +# Represents command verbs that can be applied to a blockjob. +# +# @cancel: see @block-job-cancel +# +# @pause: see @block-job-pause +# +# @resume: see @block-job-resume +# +# @set-speed: see @block-job-set-speed +# +# @complete: see @block-job-complete +# +# @dismiss: see @block-job-dismiss +# +# @finalize: see @block-job-finalize +# +# Since: 2.12 +## +{ 'enum': 'BlockJobVerb', + 'data': ['cancel', 'pause', 'resume', 'set-speed', 'complete', 'dismiss', + 'finalize' ] } + +## +# @BlockJobStatus: +# +# Indicates the present state of a given blockjob in its lifetime. +# +# @undefined: Erroneous, default state. Should not ever be visible. +# +# @created: The job has been created, but not yet started. +# +# @running: The job is currently running. +# +# @paused: The job is running, but paused. The pause may be requested by +# either the QMP user or by internal processes. +# +# @ready: The job is running, but is ready for the user to signal completion. +# This is used for long-running jobs like mirror that are designed to +# run indefinitely. +# +# @standby: The job is ready, but paused. This is nearly identical to @paused. +# The job may return to @ready or otherwise be canceled. +# +# @waiting: The job is waiting for other jobs in the transaction to converge +# to the waiting state. This status will likely not be visible for +# the last job in a transaction. +# +# @pending: The job has finished its work, but has finalization steps that it +# needs to make prior to completing. These changes may require +# manual intervention by the management process if manual was set +# to true. These changes may still fail. +# +# @aborting: The job is in the process of being aborted, and will finish with +# an error. The job will afterwards report that it is @concluded. +# This status may not be visible to the management process. +# +# @concluded: The job has finished all work. If manual was set to true, the job +# will remain in the query list until it is dismissed. +# +# @null: The job is in the process of being dismantled. This state should not +# ever be visible externally. +# +# Since: 2.12 +## +{ 'enum': 'BlockJobStatus', + 'data': ['undefined', 'created', 'running', 'paused', 'ready', 'standby', + 'waiting', 'pending', 'aborting', 'concluded', 'null' ] } + +## # @BlockJobInfo: # # Information about a long-running block device operation. @@ -984,12 +1055,22 @@ # # @ready: true if the job may be completed (since 2.2) # +# @status: Current job state/status (since 2.12) +# +# @auto-finalize: Job will finalize itself when PENDING, moving to +# the CONCLUDED state. (since 2.12) +# +# @auto-dismiss: Job will dismiss itself when CONCLUDED, moving to the NULL +# state and disappearing from the query list. (since 2.12) +# # Since: 1.1 ## { 'struct': 'BlockJobInfo', 'data': {'type': 'str', 'device': 'str', 'len': 'int', 'offset': 'int', 'busy': 'bool', 'paused': 'bool', 'speed': 'int', - 'io-status': 'BlockDeviceIoStatus', 'ready': 'bool'} } + 'io-status': 'BlockDeviceIoStatus', 'ready': 'bool', + 'status': 'BlockJobStatus', + 'auto-finalize': 'bool', 'auto-dismiss': 'bool' } } ## # @query-block-jobs: @@ -1139,6 +1220,18 @@ # default 'report' (no limitations, since this applies to # a different block device than @device). # +# @auto-finalize: When false, this job will wait in a PENDING state after it has +# finished its work, waiting for @block-job-finalize. +# When true, this job will automatically perform its abort or +# commit actions. +# Defaults to true. (Since 2.12) +# +# @auto-dismiss: When false, this job will wait in a CONCLUDED state after it +# has completed ceased all work, and wait for @block-job-dismiss. +# When true, this job will automatically disappear from the query +# list without user intervention. +# Defaults to true. (Since 2.12) +# # Note: @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. @@ -1147,10 +1240,12 @@ ## { 'struct': 'DriveBackup', 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', - '*format': 'str', 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode', - '*speed': 'int', '*bitmap': 'str', '*compress': 'bool', + '*format': 'str', 'sync': 'MirrorSyncMode', + '*mode': 'NewImageMode', '*speed': 'int', + '*bitmap': 'str', '*compress': 'bool', '*on-source-error': 'BlockdevOnError', - '*on-target-error': 'BlockdevOnError' } } + '*on-target-error': 'BlockdevOnError', + '*auto-finalize': 'bool', '*auto-dismiss': 'bool' } } ## # @BlockdevBackup: @@ -1180,6 +1275,18 @@ # default 'report' (no limitations, since this applies to # a different block device than @device). # +# @auto-finalize: When false, this job will wait in a PENDING state after it has +# finished its work, waiting for @block-job-finalize. +# When true, this job will automatically perform its abort or +# commit actions. +# Defaults to true. (Since 2.12) +# +# @auto-dismiss: When false, this job will wait in a CONCLUDED state after it +# has completed ceased all work, and wait for @block-job-dismiss. +# When true, this job will automatically disappear from the query +# list without user intervention. +# Defaults to true. (Since 2.12) +# # Note: @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. @@ -1188,11 +1295,10 @@ ## { 'struct': 'BlockdevBackup', 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', - 'sync': 'MirrorSyncMode', - '*speed': 'int', - '*compress': 'bool', + 'sync': 'MirrorSyncMode', '*speed': 'int', '*compress': 'bool', '*on-source-error': 'BlockdevOnError', - '*on-target-error': 'BlockdevOnError' } } + '*on-target-error': 'BlockdevOnError', + '*auto-finalize': 'bool', '*auto-dismiss': 'bool' } } ## # @blockdev-snapshot-sync: @@ -2101,8 +2207,9 @@ # the name of the parameter), but since QEMU 2.7 it can have # other values. # -# @force: whether to allow cancellation of a paused job (default -# false). Since 1.3. +# @force: If true, and the job has already emitted the event BLOCK_JOB_READY, +# abandon the job immediately (even if it is paused) instead of waiting +# for the destination to complete its final synchronization (since 1.3) # # Returns: Nothing on success # If no background operation is active on this device, DeviceNotActive @@ -2187,6 +2294,44 @@ { 'command': 'block-job-complete', 'data': { 'device': 'str' } } ## +# @block-job-dismiss: +# +# For jobs that have already concluded, remove them from the block-job-query +# list. This command only needs to be run for jobs which were started with +# QEMU 2.12+ job lifetime management semantics. +# +# This command will refuse to operate on any job that has not yet reached +# its terminal state, BLOCK_JOB_STATUS_CONCLUDED. For jobs that make use of +# BLOCK_JOB_READY event, block-job-cancel or block-job-complete will still need +# to be used as appropriate. +# +# @id: The job identifier. +# +# Returns: Nothing on success +# +# Since: 2.12 +## +{ 'command': 'block-job-dismiss', 'data': { 'id': 'str' } } + +## +# @block-job-finalize: +# +# Once a job that has manual=true reaches the pending state, it can be +# instructed to finalize any graph changes and do any necessary cleanup +# via this command. +# For jobs in a transaction, instructing one job to finalize will force +# ALL jobs in the transaction to finalize, so it is only necessary to instruct +# a single member job to finalize. +# +# @id: The job identifier. +# +# Returns: Nothing on success +# +# Since: 2.12 +## +{ 'command': 'block-job-finalize', 'data': { 'id': 'str' } } + +## # @BlockdevDiscardOptions: # # Determines how to handle discard requests. @@ -3455,6 +3600,21 @@ '*preallocation': 'PreallocMode' } } ## +# @BlockdevCreateOptionsLUKS: +# +# Driver specific image creation options for LUKS. +# +# @file Node to create the image format on +# @size Size of the virtual disk in bytes +# +# Since: 2.12 +## +{ 'struct': 'BlockdevCreateOptionsLUKS', + 'base': 'QCryptoBlockCreateOptionsLUKS', + 'data': { 'file': 'BlockdevRef', + 'size': 'size' } } + +## # @BlockdevCreateOptionsNfs: # # Driver specific image creation options for NFS. @@ -3469,6 +3629,41 @@ 'size': 'size' } } ## +# @BlockdevCreateOptionsParallels: +# +# Driver specific image creation options for parallels. +# +# @file Node to create the image format on +# @size Size of the virtual disk in bytes +# @cluster-size Cluster size in bytes (default: 1 MB) +# +# Since: 2.12 +## +{ 'struct': 'BlockdevCreateOptionsParallels', + 'data': { 'file': 'BlockdevRef', + 'size': 'size', + '*cluster-size': 'size' } } + +## +# @BlockdevCreateOptionsQcow: +# +# Driver specific image creation options for qcow. +# +# @file Node to create the image format on +# @size Size of the virtual disk in bytes +# @backing-file File name of the backing file if a backing file +# should be used +# @encrypt Encryption options if the image should be encrypted +# +# Since: 2.12 +## +{ 'struct': 'BlockdevCreateOptionsQcow', + 'data': { 'file': 'BlockdevRef', + 'size': 'size', + '*backing-file': 'str', + '*encrypt': 'QCryptoBlockCreateOptions' } } + +## # @BlockdevQcow2Version: # # @v2: The original QCOW2 format as introduced in qemu 0.10 (version 2) @@ -3512,6 +3707,29 @@ '*refcount-bits': 'int' } } ## +# @BlockdevCreateOptionsQed: +# +# Driver specific image creation options for qed. +# +# @file Node to create the image format on +# @size Size of the virtual disk in bytes +# @backing-file File name of the backing file if a backing file +# should be used +# @backing-fmt Name of the block driver to use for the backing file +# @cluster-size Cluster size in bytes (default: 65536) +# @table-size L1/L2 table size (in clusters) +# +# Since: 2.12 +## +{ 'struct': 'BlockdevCreateOptionsQed', + 'data': { 'file': 'BlockdevRef', + 'size': 'size', + '*backing-file': 'str', + '*backing-fmt': 'BlockdevDriver', + '*cluster-size': 'size', + '*table-size': 'int' } } + +## # @BlockdevCreateOptionsRbd: # # Driver specific image creation options for rbd/Ceph. @@ -3610,6 +3828,93 @@ 'size': 'size' } } ## +# @BlockdevCreateOptionsVdi: +# +# Driver specific image creation options for VDI. +# +# @file Node to create the image format on +# @size Size of the virtual disk in bytes +# @static Whether to create a statically (true) or +# dynamically (false) allocated image +# (default: false, i.e. dynamic) +# +# Since: 2.12 +## +{ 'struct': 'BlockdevCreateOptionsVdi', + 'data': { 'file': 'BlockdevRef', + 'size': 'size', + '*static': 'bool' } } + +## +# @BlockdevVhdxSubformat: +# +# @dynamic: Growing image file +# @fixed: Preallocated fixed-size image file +# +# Since: 2.12 +## +{ 'enum': 'BlockdevVhdxSubformat', + 'data': [ 'dynamic', 'fixed' ] } + +## +# @BlockdevCreateOptionsVhdx: +# +# Driver specific image creation options for vhdx. +# +# @file Node to create the image format on +# @size Size of the virtual disk in bytes +# @log-size Log size in bytes, must be a multiple of 1 MB +# (default: 1 MB) +# @block-size Block size in bytes, must be a multiple of 1 MB and not +# larger than 256 MB (default: automatically choose a block +# size depending on the image size) +# @subformat vhdx subformat (default: dynamic) +# @block-state-zero Force use of payload blocks of type 'ZERO'. Non-standard, +# but default. Do not set to 'off' when using 'qemu-img +# convert' with subformat=dynamic. +# +# Since: 2.12 +## +{ 'struct': 'BlockdevCreateOptionsVhdx', + 'data': { 'file': 'BlockdevRef', + 'size': 'size', + '*log-size': 'size', + '*block-size': 'size', + '*subformat': 'BlockdevVhdxSubformat', + '*block-state-zero': 'bool' } } + +## +# @BlockdevVpcSubformat: +# +# @dynamic: Growing image file +# @fixed: Preallocated fixed-size image file +# +# Since: 2.12 +## +{ 'enum': 'BlockdevVpcSubformat', + 'data': [ 'dynamic', 'fixed' ] } + +## +# @BlockdevCreateOptionsVpc: +# +# Driver specific image creation options for vpc (VHD). +# +# @file Node to create the image format on +# @size Size of the virtual disk in bytes +# @subformat vhdx subformat (default: dynamic) +# @force-size Force use of the exact byte size instead of rounding to the +# next size that can be represented in CHS geometry +# (default: false) +# +# Since: 2.12 +## +{ 'struct': 'BlockdevCreateOptionsVpc', + 'data': { 'file': 'BlockdevRef', + 'size': 'size', + '*subformat': 'BlockdevVpcSubformat', + '*force-size': 'bool' } } + +## # @BlockdevCreateNotSupported: # # This is used for all drivers that don't support creating images. @@ -3646,16 +3951,16 @@ 'http': 'BlockdevCreateNotSupported', 'https': 'BlockdevCreateNotSupported', 'iscsi': 'BlockdevCreateNotSupported', - 'luks': 'BlockdevCreateNotSupported', + 'luks': 'BlockdevCreateOptionsLUKS', 'nbd': 'BlockdevCreateNotSupported', 'nfs': 'BlockdevCreateOptionsNfs', 'null-aio': 'BlockdevCreateNotSupported', 'null-co': 'BlockdevCreateNotSupported', 'nvme': 'BlockdevCreateNotSupported', - 'parallels': 'BlockdevCreateNotSupported', + 'parallels': 'BlockdevCreateOptionsParallels', + 'qcow': 'BlockdevCreateOptionsQcow', 'qcow2': 'BlockdevCreateOptionsQcow2', - 'qcow': 'BlockdevCreateNotSupported', - 'qed': 'BlockdevCreateNotSupported', + 'qed': 'BlockdevCreateOptionsQed', 'quorum': 'BlockdevCreateNotSupported', 'raw': 'BlockdevCreateNotSupported', 'rbd': 'BlockdevCreateOptionsRbd', @@ -3663,10 +3968,10 @@ 'sheepdog': 'BlockdevCreateOptionsSheepdog', 'ssh': 'BlockdevCreateOptionsSsh', 'throttle': 'BlockdevCreateNotSupported', - 'vdi': 'BlockdevCreateNotSupported', - 'vhdx': 'BlockdevCreateNotSupported', + 'vdi': 'BlockdevCreateOptionsVdi', + 'vhdx': 'BlockdevCreateOptionsVhdx', 'vmdk': 'BlockdevCreateNotSupported', - 'vpc': 'BlockdevCreateNotSupported', + 'vpc': 'BlockdevCreateOptionsVpc', 'vvfat': 'BlockdevCreateNotSupported', 'vxhs': 'BlockdevCreateNotSupported' } } @@ -4180,6 +4485,30 @@ 'speed' : 'int' } } ## +# @BLOCK_JOB_PENDING: +# +# Emitted when a block job is awaiting explicit authorization to finalize graph +# changes via @block-job-finalize. If this job is part of a transaction, it will +# not emit this event until the transaction has converged first. +# +# @type: job type +# +# @id: The job identifier. +# +# Since: 2.12 +# +# Example: +# +# <- { "event": "BLOCK_JOB_WAITING", +# "data": { "device": "drive0", "type": "mirror" }, +# "timestamp": { "seconds": 1265044230, "microseconds": 450486 } } +# +## +{ 'event': 'BLOCK_JOB_PENDING', + 'data': { 'type' : 'BlockJobType', + 'id' : 'str' } } + +## # @PreallocMode: # # Preallocation mode of QEMU image file diff --git a/tests/qemu-iotests/030 b/tests/qemu-iotests/030 index b5f88959aa..640a6dfd10 100755 --- a/tests/qemu-iotests/030 +++ b/tests/qemu-iotests/030 @@ -86,11 +86,9 @@ class TestSingleDrive(iotests.QMPTestCase): result = self.vm.qmp('block-stream', device='drive0') self.assert_qmp(result, 'return', {}) - result = self.vm.qmp('block-job-pause', device='drive0') - self.assert_qmp(result, 'return', {}) - + self.pause_job('drive0', wait=False) self.vm.resume_drive('drive0') - self.pause_job('drive0') + self.pause_wait('drive0') result = self.vm.qmp('query-block-jobs') offset = self.dictpath(result, 'return[0]/offset') diff --git a/tests/qemu-iotests/055 b/tests/qemu-iotests/055 index 8a5d9fd269..3437c11507 100755 --- a/tests/qemu-iotests/055 +++ b/tests/qemu-iotests/055 @@ -86,11 +86,9 @@ class TestSingleDrive(iotests.QMPTestCase): target=target, sync='full') self.assert_qmp(result, 'return', {}) - result = self.vm.qmp('block-job-pause', device='drive0') - self.assert_qmp(result, 'return', {}) - + self.pause_job('drive0', wait=False) self.vm.resume_drive('drive0') - self.pause_job('drive0') + self.pause_wait('drive0') result = self.vm.qmp('query-block-jobs') offset = self.dictpath(result, 'return[0]/offset') @@ -303,13 +301,12 @@ class TestSingleTransaction(iotests.QMPTestCase): ]) self.assert_qmp(result, 'return', {}) - result = self.vm.qmp('block-job-pause', device='drive0') - self.assert_qmp(result, 'return', {}) + self.pause_job('drive0', wait=False) result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0) self.assert_qmp(result, 'return', {}) - self.pause_job('drive0') + self.pause_wait('drive0') result = self.vm.qmp('query-block-jobs') offset = self.dictpath(result, 'return[0]/offset') @@ -534,11 +531,9 @@ class TestDriveCompression(iotests.QMPTestCase): 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.pause_job('drive0', wait=False) self.vm.resume_drive('drive0') - self.pause_job('drive0') + self.pause_wait('drive0') result = self.vm.qmp('query-block-jobs') offset = self.dictpath(result, 'return[0]/offset') diff --git a/tests/qemu-iotests/056 b/tests/qemu-iotests/056 index 04f2c3c841..223292175a 100755 --- a/tests/qemu-iotests/056 +++ b/tests/qemu-iotests/056 @@ -29,6 +29,26 @@ backing_img = os.path.join(iotests.test_dir, 'backing.img') test_img = os.path.join(iotests.test_dir, 'test.img') target_img = os.path.join(iotests.test_dir, 'target.img') +def img_create(img, fmt=iotests.imgfmt, size='64M', **kwargs): + fullname = os.path.join(iotests.test_dir, '%s.%s' % (img, fmt)) + optargs = [] + for k,v in kwargs.iteritems(): + optargs = optargs + ['-o', '%s=%s' % (k,v)] + args = ['create', '-f', fmt] + optargs + [fullname, size] + iotests.qemu_img(*args) + return fullname + +def try_remove(img): + try: + os.remove(img) + except OSError: + pass + +def io_write_patterns(img, patterns): + for pattern in patterns: + iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) + + class TestSyncModesNoneAndTop(iotests.QMPTestCase): image_len = 64 * 1024 * 1024 # MB @@ -108,5 +128,172 @@ class TestBeforeWriteNotifier(iotests.QMPTestCase): event = self.cancel_and_wait() self.assert_qmp(event, 'data/type', 'backup') +class BackupTest(iotests.QMPTestCase): + def setUp(self): + self.vm = iotests.VM() + self.test_img = img_create('test') + self.dest_img = img_create('dest') + self.vm.add_drive(self.test_img) + self.vm.launch() + + def tearDown(self): + self.vm.shutdown() + try_remove(self.test_img) + try_remove(self.dest_img) + + def hmp_io_writes(self, drive, patterns): + for pattern in patterns: + self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern) + self.vm.hmp_qemu_io(drive, 'flush') + + def qmp_backup_and_wait(self, cmd='drive-backup', serror=None, + aerror=None, **kwargs): + if not self.qmp_backup(cmd, serror, **kwargs): + return False + return self.qmp_backup_wait(kwargs['device'], aerror) + + def qmp_backup(self, cmd='drive-backup', + error=None, **kwargs): + self.assertTrue('device' in kwargs) + res = self.vm.qmp(cmd, **kwargs) + if error: + self.assert_qmp(res, 'error/desc', error) + return False + self.assert_qmp(res, 'return', {}) + return True + + def qmp_backup_wait(self, device, error=None): + event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", + match={'data': {'device': device}}) + self.assertNotEqual(event, None) + try: + failure = self.dictpath(event, 'data/error') + except AssertionError: + # Backup succeeded. + self.assert_qmp(event, 'data/offset', event['data']['len']) + return True + else: + # Failure. + self.assert_qmp(event, 'data/error', qerror) + return False + + def test_dismiss_false(self): + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt, + sync='full', target=self.dest_img, + auto_dismiss=True) + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + + def test_dismiss_true(self): + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt, + sync='full', target=self.dest_img, + auto_dismiss=False) + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return[0]/status', 'concluded') + res = self.vm.qmp('block-job-dismiss', id='drive0') + self.assert_qmp(res, 'return', {}) + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + + def test_dismiss_bad_id(self): + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + res = self.vm.qmp('block-job-dismiss', id='foobar') + self.assert_qmp(res, 'error/class', 'DeviceNotActive') + + def test_dismiss_collision(self): + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt, + sync='full', target=self.dest_img, + auto_dismiss=False) + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return[0]/status', 'concluded') + # Leave zombie job un-dismissed, observe a failure: + res = self.qmp_backup_and_wait(serror='Need a root block node', + device='drive0', format=iotests.imgfmt, + sync='full', target=self.dest_img, + auto_dismiss=False) + self.assertEqual(res, False) + # OK, dismiss the zombie. + res = self.vm.qmp('block-job-dismiss', id='drive0') + self.assert_qmp(res, 'return', {}) + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + # Ensure it's really gone. + self.qmp_backup_and_wait(device='drive0', format=iotests.imgfmt, + sync='full', target=self.dest_img, + auto_dismiss=False) + + def dismissal_failure(self, dismissal_opt): + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + # Give blkdebug something to chew on + self.hmp_io_writes('drive0', + (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) + # Add destination node via blkdebug + res = self.vm.qmp('blockdev-add', + node_name='target0', + driver=iotests.imgfmt, + file={ + 'driver': 'blkdebug', + 'image': { + 'driver': 'file', + 'filename': self.dest_img + }, + 'inject-error': [{ + 'event': 'write_aio', + 'errno': 5, + 'immediately': False, + 'once': True + }], + }) + self.assert_qmp(res, 'return', {}) + + res = self.qmp_backup(cmd='blockdev-backup', + device='drive0', target='target0', + on_target_error='stop', + sync='full', + auto_dismiss=dismissal_opt) + self.assertTrue(res) + event = self.vm.event_wait(name="BLOCK_JOB_ERROR", + match={'data': {'device': 'drive0'}}) + self.assertNotEqual(event, None) + # OK, job should be wedged + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return[0]/status', 'paused') + res = self.vm.qmp('block-job-dismiss', id='drive0') + self.assert_qmp(res, 'error/desc', + "Job 'drive0' in state 'paused' cannot accept" + " command verb 'dismiss'") + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return[0]/status', 'paused') + # OK, unstick job and move forward. + res = self.vm.qmp('block-job-resume', device='drive0') + self.assert_qmp(res, 'return', {}) + # And now we need to wait for it to conclude; + res = self.qmp_backup_wait(device='drive0') + self.assertTrue(res) + if not dismissal_opt: + # Job should now be languishing: + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return[0]/status', 'concluded') + res = self.vm.qmp('block-job-dismiss', id='drive0') + self.assert_qmp(res, 'return', {}) + res = self.vm.qmp('query-block-jobs') + self.assert_qmp(res, 'return', []) + + def test_dismiss_premature(self): + self.dismissal_failure(False) + + def test_dismiss_erroneous(self): + self.dismissal_failure(True) + if __name__ == '__main__': iotests.main(supported_fmts=['qcow2', 'qed']) diff --git a/tests/qemu-iotests/056.out b/tests/qemu-iotests/056.out index 8d7e996700..dae404e278 100644 --- a/tests/qemu-iotests/056.out +++ b/tests/qemu-iotests/056.out @@ -1,5 +1,5 @@ -... +......... ---------------------------------------------------------------------- -Ran 3 tests +Ran 9 tests OK diff --git a/tests/qemu-iotests/109.out b/tests/qemu-iotests/109.out index c189e2833d..8a9b93672b 100644 --- a/tests/qemu-iotests/109.out +++ b/tests/qemu-iotests/109.out @@ -19,7 +19,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 1024, "offset": 1024, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 1024, "offset": 1024, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 1024, "offset": 1024, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 1024, "offset": 1024, "speed": 0, "type": "mirror"}} @@ -45,7 +45,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 197120, "offset": 197120, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 197120, "offset": 197120, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 197120, "offset": 197120, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 197120, "offset": 197120, "speed": 0, "type": "mirror"}} @@ -71,7 +71,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 327680, "offset": 327680, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 327680, "offset": 327680, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 327680, "offset": 327680, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 327680, "offset": 327680, "speed": 0, "type": "mirror"}} @@ -97,7 +97,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 1024, "offset": 1024, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 1024, "offset": 1024, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 1024, "offset": 1024, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 1024, "offset": 1024, "speed": 0, "type": "mirror"}} @@ -123,7 +123,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 65536, "offset": 65536, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 65536, "offset": 65536, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 65536, "offset": 65536, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 65536, "offset": 65536, "speed": 0, "type": "mirror"}} @@ -149,7 +149,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 2560, "offset": 2560, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 2560, "offset": 2560, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 2560, "offset": 2560, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 2560, "offset": 2560, "speed": 0, "type": "mirror"}} @@ -174,7 +174,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 2560, "offset": 2560, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 2560, "offset": 2560, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 2560, "offset": 2560, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 2560, "offset": 2560, "speed": 0, "type": "mirror"}} @@ -199,7 +199,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 31457280, "offset": 31457280, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 31457280, "offset": 31457280, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 31457280, "offset": 31457280, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 31457280, "offset": 31457280, "speed": 0, "type": "mirror"}} @@ -224,7 +224,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 327680, "offset": 327680, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 327680, "offset": 327680, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 327680, "offset": 327680, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 327680, "offset": 327680, "speed": 0, "type": "mirror"}} @@ -249,7 +249,7 @@ read 65536/65536 bytes at offset 0 {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 2048, "offset": 2048, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 2048, "offset": 2048, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 2048, "offset": 2048, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 2048, "offset": 2048, "speed": 0, "type": "mirror"}} @@ -265,7 +265,7 @@ Automatically detecting the format is dangerous for raw images, write operations Specify the 'raw' format explicitly to remove the restrictions. {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 512, "offset": 512, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 512, "offset": 512, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 512, "offset": 512, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 512, "offset": 512, "speed": 0, "type": "mirror"}} @@ -274,7 +274,7 @@ Images are identical. {"return": {}} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "src", "len": 512, "offset": 512, "speed": 0, "type": "mirror"}} -{"return": [{"io-status": "ok", "device": "src", "busy": false, "len": 512, "offset": 512, "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} +{"return": [{"auto-finalize": true, "io-status": "ok", "device": "src", "auto-dismiss": true, "busy": false, "len": 512, "offset": 512, "status": "ready", "paused": false, "speed": 0, "ready": true, "type": "mirror"}]} {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "src", "len": 512, "offset": 512, "speed": 0, "type": "mirror"}} diff --git a/tests/qemu-iotests/146.out b/tests/qemu-iotests/146.out index db6b296b9e..1332189d87 100644 --- a/tests/qemu-iotests/146.out +++ b/tests/qemu-iotests/146.out @@ -54,7 +54,7 @@ Formatting 'TEST_DIR/IMGFMT-create-test.IMGFMT', fmt=IMGFMT size=4294967296 === Testing Image create, force_size === -Formatting 'TEST_DIR/IMGFMT-create-test.IMGFMT', fmt=IMGFMT size=4294967296 force_size=on +Formatting 'TEST_DIR/IMGFMT-create-test.IMGFMT', fmt=IMGFMT size=4294967296 === Read created image, default opts ==== diff --git a/tests/qemu-iotests/153 b/tests/qemu-iotests/153 index adfd02695b..a0fd815483 100755 --- a/tests/qemu-iotests/153 +++ b/tests/qemu-iotests/153 @@ -178,6 +178,18 @@ rm -f "${TEST_IMG}.lnk" &>/dev/null ln -s ${TEST_IMG} "${TEST_IMG}.lnk" || echo "Failed to create link" _run_qemu_with_images "${TEST_IMG}.lnk" "${TEST_IMG}" +echo +echo "== Active commit to intermediate layer should work when base in use ==" +_launch_qemu -drive format=$IMGFMT,file="${TEST_IMG}.a",id=drive0,if=none \ + -device virtio-blk,drive=drive0 + +_send_qemu_cmd $QEMU_HANDLE \ + "{ 'execute': 'qmp_capabilities' }" \ + 'return' +_run_cmd $QEMU_IMG commit -b "${TEST_IMG}.b" "${TEST_IMG}.c" + +_cleanup_qemu + _launch_qemu _send_qemu_cmd $QEMU_HANDLE \ diff --git a/tests/qemu-iotests/153.out b/tests/qemu-iotests/153.out index 34309cfb20..bb721cb747 100644 --- a/tests/qemu-iotests/153.out +++ b/tests/qemu-iotests/153.out @@ -372,6 +372,11 @@ Is another process using the image? == Symbolic link == QEMU_PROG: -drive if=none,file=TEST_DIR/t.qcow2: Failed to get "write" lock Is another process using the image? + +== Active commit to intermediate layer should work when base in use == +{"return": {}} + +_qemu_img_wrapper commit -b TEST_DIR/t.qcow2.b TEST_DIR/t.qcow2.c {"return": {}} Adding drive diff --git a/tests/qemu-iotests/181 b/tests/qemu-iotests/181 index 0c91e8f9de..5e767c6195 100755 --- a/tests/qemu-iotests/181 +++ b/tests/qemu-iotests/181 @@ -44,7 +44,7 @@ trap "_cleanup; exit \$status" 0 1 2 3 15 _supported_fmt generic # Formats that do not support live migration -_unsupported_fmt qcow vdi vhdx vmdk vpc vvfat +_unsupported_fmt qcow vdi vhdx vmdk vpc vvfat parallels _supported_proto generic _supported_os Linux diff --git a/tests/qemu-iotests/210 b/tests/qemu-iotests/210 new file mode 100755 index 0000000000..96a5213e77 --- /dev/null +++ b/tests/qemu-iotests/210 @@ -0,0 +1,210 @@ +#!/bin/bash +# +# Test luks and file image creation +# +# Copyright (C) 2018 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=kwolf@redhat.com + +seq=`basename $0` +echo "QA output created by $seq" + +here=`pwd` +status=1 # failure is the default! + +# get standard environment, filters and checks +. ./common.rc +. ./common.filter + +_supported_fmt luks +_supported_proto file +_supported_os Linux + +function do_run_qemu() +{ + echo Testing: "$@" + $QEMU -nographic -qmp stdio -serial none "$@" + echo +} + +function run_qemu() +{ + do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp \ + | _filter_qemu | _filter_imgfmt \ + | _filter_actual_image_size +} + +echo +echo "=== Successful image creation (defaults) ===" +echo + +size=$((128 * 1024 * 1024)) + +run_qemu -object secret,id=keysec0,data="foo" <<EOF +{ "execute": "qmp_capabilities" } +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "file", + "filename": "$TEST_IMG_FILE", + "size": 0 + } +} +{ "execute": "blockdev-add", + "arguments": { + "driver": "file", + "node-name": "imgfile", + "filename": "$TEST_IMG_FILE" + } +} +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "$IMGFMT", + "file": "imgfile", + "key-secret": "keysec0", + "size": $size, + "iter-time": 10 + } +} +{ "execute": "quit" } +EOF + +_img_info --format-specific | _filter_img_info --format-specific + +echo +echo "=== Successful image creation (with non-default options) ===" +echo + +# Choose a different size to show that we got a new image +size=$((64 * 1024 * 1024)) + +run_qemu -object secret,id=keysec0,data="foo" <<EOF +{ "execute": "qmp_capabilities" } +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "file", + "filename": "$TEST_IMG_FILE", + "size": 0 + } +} +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "$IMGFMT", + "file": { + "driver": "file", + "filename": "$TEST_IMG_FILE" + }, + "size": $size, + "key-secret": "keysec0", + "cipher-alg": "twofish-128", + "cipher-mode": "ctr", + "ivgen-alg": "plain64", + "ivgen-hash-alg": "md5", + "hash-alg": "sha1", + "iter-time": 10 + } +} +{ "execute": "quit" } +EOF + +_img_info --format-specific | _filter_img_info --format-specific + +echo +echo "=== Invalid BlockdevRef ===" +echo + +run_qemu <<EOF +{ "execute": "qmp_capabilities" } +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "$IMGFMT", + "file": "this doesn't exist", + "size": $size + } +} +{ "execute": "quit" } +EOF + +echo +echo "=== Zero size ===" +echo + +run_qemu -blockdev driver=file,filename="$TEST_IMG_FILE",node-name=node0 \ + -object secret,id=keysec0,data="foo" <<EOF +{ "execute": "qmp_capabilities" } +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "$IMGFMT", + "file": "node0", + "key-secret": "keysec0", + "size": 0, + "iter-time": 10 + } +} +{ "execute": "quit" } +EOF + +_img_info | _filter_img_info + + +echo +echo "=== Invalid sizes ===" +echo + +# TODO Negative image sizes aren't handled correctly, but this is a problem +# with QAPI's implementation of the 'size' type and affects other commands as +# well. Once this is fixed, we may want to add a test case here. + +# 1. 2^64 - 512 +# 2. 2^63 = 8 EB (qemu-img enforces image sizes less than this) +# 3. 2^63 - 512 (generally valid, but with the crypto header the file will +# exceed 63 bits) + +run_qemu -blockdev driver=file,filename="$TEST_IMG_FILE",node-name=node0 \ + -object secret,id=keysec0,data="foo" <<EOF +{ "execute": "qmp_capabilities" } +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "$IMGFMT", + "file": "node0", + "key-secret": "keysec0", + "size": 18446744073709551104 + } +} +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "$IMGFMT", + "file": "node0", + "key-secret": "keysec0", + "size": 9223372036854775808 + } +} +{ "execute": "x-blockdev-create", + "arguments": { + "driver": "$IMGFMT", + "file": "node0", + "key-secret": "keysec0", + "size": 9223372036854775296 + } +} +{ "execute": "quit" } +EOF + +# success, all done +echo "*** done" +rm -f $seq.full +status=0 diff --git a/tests/qemu-iotests/210.out b/tests/qemu-iotests/210.out new file mode 100644 index 0000000000..8fcab65909 --- /dev/null +++ b/tests/qemu-iotests/210.out @@ -0,0 +1,136 @@ +QA output created by 210 + +=== Successful image creation (defaults) === + +Testing: -object secret,id=keysec0,data=foo +QMP_VERSION +{"return": {}} +{"return": {}} +{"return": {}} +{"return": {}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} + +image: json:{"driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/t.IMGFMT"}, "key-secret": "keysec0"} +file format: IMGFMT +virtual size: 128M (134217728 bytes) +Format specific information: + ivgen alg: plain64 + hash alg: sha256 + cipher alg: aes-256 + uuid: 00000000-0000-0000-0000-000000000000 + cipher mode: xts + slots: + [0]: + active: true + iters: 1024 + key offset: 4096 + stripes: 4000 + [1]: + active: false + key offset: 262144 + [2]: + active: false + key offset: 520192 + [3]: + active: false + key offset: 778240 + [4]: + active: false + key offset: 1036288 + [5]: + active: false + key offset: 1294336 + [6]: + active: false + key offset: 1552384 + [7]: + active: false + key offset: 1810432 + payload offset: 2068480 + master key iters: 1024 + +=== Successful image creation (with non-default options) === + +Testing: -object secret,id=keysec0,data=foo +QMP_VERSION +{"return": {}} +{"return": {}} +{"return": {}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} + +image: json:{"driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/t.IMGFMT"}, "key-secret": "keysec0"} +file format: IMGFMT +virtual size: 64M (67108864 bytes) +Format specific information: + ivgen alg: plain64 + hash alg: sha1 + cipher alg: twofish-128 + uuid: 00000000-0000-0000-0000-000000000000 + cipher mode: ctr + slots: + [0]: + active: true + iters: 1024 + key offset: 4096 + stripes: 4000 + [1]: + active: false + key offset: 69632 + [2]: + active: false + key offset: 135168 + [3]: + active: false + key offset: 200704 + [4]: + active: false + key offset: 266240 + [5]: + active: false + key offset: 331776 + [6]: + active: false + key offset: 397312 + [7]: + active: false + key offset: 462848 + payload offset: 528384 + master key iters: 1024 + +=== Invalid BlockdevRef === + +Testing: +QMP_VERSION +{"return": {}} +{"error": {"class": "GenericError", "desc": "Cannot find device=this doesn't exist nor node_name=this doesn't exist"}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} + + +=== Zero size === + +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 -object secret,id=keysec0,data=foo +QMP_VERSION +{"return": {}} +{"return": {}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} + +image: json:{"driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/t.IMGFMT"}, "key-secret": "keysec0"} +file format: IMGFMT +virtual size: 0 (0 bytes) + +=== Invalid sizes === + +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 -object secret,id=keysec0,data=foo +QMP_VERSION +{"return": {}} +{"error": {"class": "GenericError", "desc": "The requested file size is too large"}} +{"error": {"class": "GenericError", "desc": "The requested file size is too large"}} +{"error": {"class": "GenericError", "desc": "The requested file size is too large"}} +{"return": {}} +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} + +*** done diff --git a/tests/qemu-iotests/check b/tests/qemu-iotests/check index e6b6ff7a04..ec8033350d 100755 --- a/tests/qemu-iotests/check +++ b/tests/qemu-iotests/check @@ -92,7 +92,7 @@ set_prog_path() { p=`command -v $1 2> /dev/null` if [ -n "$p" -a -x "$p" ]; then - realpath -- "$(type -p "$p")" + type -p "$p" else return 1 fi @@ -284,7 +284,6 @@ testlist options -parallels) IMGFMT=parallels - IMGFMT_GENERIC=false xpand=false ;; @@ -555,7 +554,7 @@ then [ "$QEMU_PROG" = "" ] && _init_error "qemu not found" fi fi -export QEMU_PROG=$(realpath -- "$(type -p "$QEMU_PROG")") +export QEMU_PROG="$(type -p "$QEMU_PROG")" if [ -z "$QEMU_IMG_PROG" ]; then if [ -x "$build_iotests/qemu-img" ]; then @@ -566,7 +565,7 @@ if [ -z "$QEMU_IMG_PROG" ]; then _init_error "qemu-img not found" fi fi -export QEMU_IMG_PROG=$(realpath -- "$(type -p "$QEMU_IMG_PROG")") +export QEMU_IMG_PROG="$(type -p "$QEMU_IMG_PROG")" if [ -z "$QEMU_IO_PROG" ]; then if [ -x "$build_iotests/qemu-io" ]; then @@ -577,7 +576,7 @@ if [ -z "$QEMU_IO_PROG" ]; then _init_error "qemu-io not found" fi fi -export QEMU_IO_PROG=$(realpath -- "$(type -p "$QEMU_IO_PROG")") +export QEMU_IO_PROG="$(type -p "$QEMU_IO_PROG")" if [ -z $QEMU_NBD_PROG ]; then if [ -x "$build_iotests/qemu-nbd" ]; then @@ -588,7 +587,7 @@ if [ -z $QEMU_NBD_PROG ]; then _init_error "qemu-nbd not found" fi fi -export QEMU_NBD_PROG=$(realpath -- "$(type -p "$QEMU_NBD_PROG")") +export QEMU_NBD_PROG="$(type -p "$QEMU_NBD_PROG")" if [ -z "$QEMU_VXHS_PROG" ]; then export QEMU_VXHS_PROG="`set_prog_path qnio_server`" @@ -812,7 +811,7 @@ do else echo " - output mismatch (see $seq.out.bad)" mv $tmp.out $seq.out.bad - $diff -w "$reference" $(realpath $seq.out.bad) + $diff -w "$reference" "$PWD"/$seq.out.bad err=true fi fi diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc index 6fa0495e10..9a65a11026 100644 --- a/tests/qemu-iotests/common.rc +++ b/tests/qemu-iotests/common.rc @@ -332,7 +332,7 @@ _img_info() discard=0 regex_json_spec_start='^ *"format-specific": \{' - $QEMU_IMG info "$@" "$TEST_IMG" 2>&1 | \ + $QEMU_IMG info $QEMU_IMG_EXTRA_ARGS "$@" "$TEST_IMG" 2>&1 | \ sed -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \ -e "s#$TEST_DIR#TEST_DIR#g" \ -e "s#$IMGFMT#IMGFMT#g" \ diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group index 0f912e17b0..efe0e958f2 100644 --- a/tests/qemu-iotests/group +++ b/tests/qemu-iotests/group @@ -208,3 +208,4 @@ 207 rw auto 208 rw auto quick 209 rw auto quick +210 rw auto diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 845be3ad01..b5d7945af8 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -506,10 +506,7 @@ class QMPTestCase(unittest.TestCase): event = self.wait_until_completed(drive=drive) self.assert_qmp(event, 'data/type', 'mirror') - def pause_job(self, job_id='job0'): - result = self.vm.qmp('block-job-pause', device=job_id) - self.assert_qmp(result, 'return', {}) - + def pause_wait(self, job_id='job0'): with Timeout(1, "Timeout waiting for job to pause"): while True: result = self.vm.qmp('query-block-jobs') @@ -517,6 +514,13 @@ class QMPTestCase(unittest.TestCase): if job['device'] == job_id and job['paused'] == True and job['busy'] == False: return job + def pause_job(self, job_id='job0', wait=True): + result = self.vm.qmp('block-job-pause', device=job_id) + self.assert_qmp(result, 'return', {}) + if wait: + return self.pause_wait(job_id) + return result + def notrun(reason): '''Skip this test suite''' diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c index d760e2b243..7673de1062 100644 --- a/tests/test-bdrv-drain.c +++ b/tests/test-bdrv-drain.c @@ -505,6 +505,7 @@ static void coroutine_fn test_job_start(void *opaque) { TestBlockJob *s = opaque; + block_job_event_ready(&s->common); while (!s->should_complete) { block_job_sleep_ns(&s->common, 100000); } @@ -541,8 +542,8 @@ static void test_blockjob_common(enum drain_type drain_type) blk_target = blk_new(BLK_PERM_ALL, BLK_PERM_ALL); blk_insert_bs(blk_target, target, &error_abort); - job = block_job_create("job0", &test_job_driver, src, 0, BLK_PERM_ALL, 0, - 0, NULL, NULL, &error_abort); + job = block_job_create("job0", &test_job_driver, NULL, src, 0, BLK_PERM_ALL, + 0, 0, NULL, NULL, &error_abort); block_job_add_bdrv(job, "target", target, 0, BLK_PERM_ALL, &error_abort); block_job_start(job); diff --git a/tests/test-blockjob-txn.c b/tests/test-blockjob-txn.c index 3591c9617f..5789893dda 100644 --- a/tests/test-blockjob-txn.c +++ b/tests/test-blockjob-txn.c @@ -87,7 +87,7 @@ static const BlockJobDriver test_block_job_driver = { */ static BlockJob *test_block_job_start(unsigned int iterations, bool use_timer, - int rc, int *result) + int rc, int *result, BlockJobTxn *txn) { BlockDriverState *bs; TestBlockJob *s; @@ -101,7 +101,7 @@ static BlockJob *test_block_job_start(unsigned int iterations, g_assert_nonnull(bs); snprintf(job_id, sizeof(job_id), "job%u", counter++); - s = block_job_create(job_id, &test_block_job_driver, bs, + s = block_job_create(job_id, &test_block_job_driver, txn, bs, 0, BLK_PERM_ALL, 0, BLOCK_JOB_DEFAULT, test_block_job_cb, data, &error_abort); s->iterations = iterations; @@ -120,12 +120,11 @@ static void test_single_job(int expected) int result = -EINPROGRESS; txn = block_job_txn_new(); - job = test_block_job_start(1, true, expected, &result); - block_job_txn_add_job(txn, job); + job = test_block_job_start(1, true, expected, &result, txn); block_job_start(job); if (expected == -ECANCELED) { - block_job_cancel(job); + block_job_cancel(job, false); } while (result == -EINPROGRESS) { @@ -160,10 +159,8 @@ static void test_pair_jobs(int expected1, int expected2) int result2 = -EINPROGRESS; txn = block_job_txn_new(); - job1 = test_block_job_start(1, true, expected1, &result1); - block_job_txn_add_job(txn, job1); - job2 = test_block_job_start(2, true, expected2, &result2); - block_job_txn_add_job(txn, job2); + job1 = test_block_job_start(1, true, expected1, &result1, txn); + job2 = test_block_job_start(2, true, expected2, &result2, txn); block_job_start(job1); block_job_start(job2); @@ -173,10 +170,10 @@ static void test_pair_jobs(int expected1, int expected2) block_job_txn_unref(txn); if (expected1 == -ECANCELED) { - block_job_cancel(job1); + block_job_cancel(job1, false); } if (expected2 == -ECANCELED) { - block_job_cancel(job2); + block_job_cancel(job2, false); } while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) { @@ -224,14 +221,12 @@ static void test_pair_jobs_fail_cancel_race(void) int result2 = -EINPROGRESS; txn = block_job_txn_new(); - job1 = test_block_job_start(1, true, -ECANCELED, &result1); - block_job_txn_add_job(txn, job1); - job2 = test_block_job_start(2, false, 0, &result2); - block_job_txn_add_job(txn, job2); + job1 = test_block_job_start(1, true, -ECANCELED, &result1, txn); + job2 = test_block_job_start(2, false, 0, &result2, txn); block_job_start(job1); block_job_start(job2); - block_job_cancel(job1); + block_job_cancel(job1, false); /* Now make job2 finish before the main loop kicks jobs. This simulates * the race between a pending kick and another job completing. diff --git a/tests/test-blockjob.c b/tests/test-blockjob.c index 23bdf1a932..8946bfd37b 100644 --- a/tests/test-blockjob.c +++ b/tests/test-blockjob.c @@ -24,14 +24,15 @@ static void block_job_cb(void *opaque, int ret) { } -static BlockJob *do_test_id(BlockBackend *blk, const char *id, - bool should_succeed) +static BlockJob *mk_job(BlockBackend *blk, const char *id, + const BlockJobDriver *drv, bool should_succeed, + int flags) { BlockJob *job; Error *errp = NULL; - job = block_job_create(id, &test_block_job_driver, blk_bs(blk), - 0, BLK_PERM_ALL, 0, BLOCK_JOB_DEFAULT, block_job_cb, + job = block_job_create(id, drv, NULL, blk_bs(blk), + 0, BLK_PERM_ALL, 0, flags, block_job_cb, NULL, &errp); if (should_succeed) { g_assert_null(errp); @@ -50,6 +51,13 @@ static BlockJob *do_test_id(BlockBackend *blk, const char *id, return job; } +static BlockJob *do_test_id(BlockBackend *blk, const char *id, + bool should_succeed) +{ + return mk_job(blk, id, &test_block_job_driver, + should_succeed, BLOCK_JOB_DEFAULT); +} + /* This creates a BlockBackend (optionally with a name) with a * BlockDriverState inserted. */ static BlockBackend *create_blk(const char *name) @@ -142,6 +150,216 @@ static void test_job_ids(void) destroy_blk(blk[2]); } +typedef struct CancelJob { + BlockJob common; + BlockBackend *blk; + bool should_converge; + bool should_complete; + bool completed; +} CancelJob; + +static void cancel_job_completed(BlockJob *job, void *opaque) +{ + CancelJob *s = opaque; + s->completed = true; + block_job_completed(job, 0); +} + +static void cancel_job_complete(BlockJob *job, Error **errp) +{ + CancelJob *s = container_of(job, CancelJob, common); + s->should_complete = true; +} + +static void coroutine_fn cancel_job_start(void *opaque) +{ + CancelJob *s = opaque; + + while (!s->should_complete) { + if (block_job_is_cancelled(&s->common)) { + goto defer; + } + + if (!s->common.ready && s->should_converge) { + block_job_event_ready(&s->common); + } + + block_job_sleep_ns(&s->common, 100000); + } + + defer: + block_job_defer_to_main_loop(&s->common, cancel_job_completed, s); +} + +static const BlockJobDriver test_cancel_driver = { + .instance_size = sizeof(CancelJob), + .start = cancel_job_start, + .complete = cancel_job_complete, +}; + +static CancelJob *create_common(BlockJob **pjob) +{ + BlockBackend *blk; + BlockJob *job; + CancelJob *s; + + blk = create_blk(NULL); + job = mk_job(blk, "Steve", &test_cancel_driver, true, + BLOCK_JOB_MANUAL_FINALIZE | BLOCK_JOB_MANUAL_DISMISS); + block_job_ref(job); + assert(job->status == BLOCK_JOB_STATUS_CREATED); + s = container_of(job, CancelJob, common); + s->blk = blk; + + *pjob = job; + return s; +} + +static void cancel_common(CancelJob *s) +{ + BlockJob *job = &s->common; + BlockBackend *blk = s->blk; + BlockJobStatus sts = job->status; + + block_job_cancel_sync(job); + if ((sts != BLOCK_JOB_STATUS_CREATED) && + (sts != BLOCK_JOB_STATUS_CONCLUDED)) { + BlockJob *dummy = job; + block_job_dismiss(&dummy, &error_abort); + } + assert(job->status == BLOCK_JOB_STATUS_NULL); + block_job_unref(job); + destroy_blk(blk); +} + +static void test_cancel_created(void) +{ + BlockJob *job; + CancelJob *s; + + s = create_common(&job); + cancel_common(s); +} + +static void test_cancel_running(void) +{ + BlockJob *job; + CancelJob *s; + + s = create_common(&job); + + block_job_start(job); + assert(job->status == BLOCK_JOB_STATUS_RUNNING); + + cancel_common(s); +} + +static void test_cancel_paused(void) +{ + BlockJob *job; + CancelJob *s; + + s = create_common(&job); + + block_job_start(job); + assert(job->status == BLOCK_JOB_STATUS_RUNNING); + + block_job_user_pause(job, &error_abort); + block_job_enter(job); + assert(job->status == BLOCK_JOB_STATUS_PAUSED); + + cancel_common(s); +} + +static void test_cancel_ready(void) +{ + BlockJob *job; + CancelJob *s; + + s = create_common(&job); + + block_job_start(job); + assert(job->status == BLOCK_JOB_STATUS_RUNNING); + + s->should_converge = true; + block_job_enter(job); + assert(job->status == BLOCK_JOB_STATUS_READY); + + cancel_common(s); +} + +static void test_cancel_standby(void) +{ + BlockJob *job; + CancelJob *s; + + s = create_common(&job); + + block_job_start(job); + assert(job->status == BLOCK_JOB_STATUS_RUNNING); + + s->should_converge = true; + block_job_enter(job); + assert(job->status == BLOCK_JOB_STATUS_READY); + + block_job_user_pause(job, &error_abort); + block_job_enter(job); + assert(job->status == BLOCK_JOB_STATUS_STANDBY); + + cancel_common(s); +} + +static void test_cancel_pending(void) +{ + BlockJob *job; + CancelJob *s; + + s = create_common(&job); + + block_job_start(job); + assert(job->status == BLOCK_JOB_STATUS_RUNNING); + + s->should_converge = true; + block_job_enter(job); + assert(job->status == BLOCK_JOB_STATUS_READY); + + block_job_complete(job, &error_abort); + block_job_enter(job); + while (!s->completed) { + aio_poll(qemu_get_aio_context(), true); + } + assert(job->status == BLOCK_JOB_STATUS_PENDING); + + cancel_common(s); +} + +static void test_cancel_concluded(void) +{ + BlockJob *job; + CancelJob *s; + + s = create_common(&job); + + block_job_start(job); + assert(job->status == BLOCK_JOB_STATUS_RUNNING); + + s->should_converge = true; + block_job_enter(job); + assert(job->status == BLOCK_JOB_STATUS_READY); + + block_job_complete(job, &error_abort); + block_job_enter(job); + while (!s->completed) { + aio_poll(qemu_get_aio_context(), true); + } + assert(job->status == BLOCK_JOB_STATUS_PENDING); + + block_job_finalize(job, &error_abort); + assert(job->status == BLOCK_JOB_STATUS_CONCLUDED); + + cancel_common(s); +} + int main(int argc, char **argv) { qemu_init_main_loop(&error_abort); @@ -149,5 +367,12 @@ int main(int argc, char **argv) g_test_init(&argc, &argv, NULL); g_test_add_func("/blockjob/ids", test_job_ids); + g_test_add_func("/blockjob/cancel/created", test_cancel_created); + g_test_add_func("/blockjob/cancel/running", test_cancel_running); + g_test_add_func("/blockjob/cancel/paused", test_cancel_paused); + g_test_add_func("/blockjob/cancel/ready", test_cancel_ready); + g_test_add_func("/blockjob/cancel/standby", test_cancel_standby); + g_test_add_func("/blockjob/cancel/pending", test_cancel_pending); + g_test_add_func("/blockjob/cancel/concluded", test_cancel_concluded); return g_test_run(); } |