Block layer patches
-----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJar5iZAAoJEH8JsnLIjy/W490P/1u8iULa/vgo3B27SYNxrZAe pKFYltBY90uryBWxQaynQpYVE4Hdcm6MLiekThQv6DeTvlv9FnF7bfml/nLWA2ed yx8fSWz45HkhxmtjgSxsnchFEoprCuuj+/+q4BmbVhOdNrnAguQbyQXY+xtQcFJ7 sJ9XruUOPlKplSAs11liN9ZijPawh5xC4ZZOUhA3agWVR/xWw6MgK6KbVi2MoIwa R26Qhy9nhUvGC7IEOPniJ3cIXNX47v+0TALZZLjI85kwDQ5MIJZIA/pABxA7GgFx IHnF2jbxPFga0sFccT/KZPbOGMEh50O7Bm598bgndI82D8AkjnQA9UVMrilqoaQJ fv7USKwnp8j567exKggQYaIMuIKvJHLjjSMTqUmFfxcUtJ/5Qr7Eozm4JyZQKYEZ E3SwYwtFo109zuyz98nMJi+M74yiugib69TPfXN/4ZBGVmCHRvLlU2/nclyXMva3 lrYxVnUYucFHgx5BGrIJ1sJQqby7bdlW5Lq0iiWgUx7II2fVFBntGkub8jNHWQ1z Bs1FN5+qxNrzoMpsCo4a3Cisy/BaoUMB/98qGFeF4/Dd++4u0itWfDZZ6FY6TxzR QLn+BBQ6yS1w+i5vBgdv22RKCqeSu1vLS7XXUh14rtnrXO3PAHx5b7WX9OwGFHxc Kq1Bp7PIaYcAMyLSWEdP =i6Jn -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging Block layer patches # gpg: Signature made Mon 19 Mar 2018 11:01:45 GMT # gpg: using RSA key 7F09B272C88F2FD6 # gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" # Primary key fingerprint: DC3D EB15 9A9A F95D 3D74 56FE 7F09 B272 C88F 2FD6 * remotes/kevin/tags/for-upstream: (46 commits) iotests: Avoid realpath, for CentOS 6 block: fix iotest 146 output expectations iscsi: fix iSER compilation block: Fix leak of ignore_children in error path vvfat: Fix inherit_options flags block/mirror: change the semantic of 'force' of block-job-cancel vpc: Require aligned size in .bdrv_co_create vpc: Support .bdrv_co_create vhdx: Support .bdrv_co_create vdi: Make comments consistent with other drivers qed: Support .bdrv_co_create qcow: Support .bdrv_co_create qemu-iotests: Enable write tests for parallels parallels: Support .bdrv_co_create iotests: Add regression test for commit base locking block: Fix flags in reopen queue vdi: Implement .bdrv_co_create vdi: Move file creation to vdi_co_create_opts vdi: Pull option parsing from vdi_co_create qemu-iotests: Test luks QMP image creation ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
2c8cfc0b52
10
block.c
10
block.c
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
150
block/crypto.c
150
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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
ret = blk_truncate(file, 0, PREALLOC_MODE_OFF, errp);
|
||||
blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
|
||||
ret = blk_insert_bs(blk, bs, errp);
|
||||
if (ret < 0) {
|
||||
goto exit;
|
||||
goto out;
|
||||
}
|
||||
blk_set_allow_write_beyond_eof(blk, true);
|
||||
|
||||
/* Create image format */
|
||||
ret = blk_truncate(blk, 0, PREALLOC_MODE_OFF, errp);
|
||||
if (ret < 0) {
|
||||
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,
|
||||
|
|
198
block/qcow.c
198
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);
|
||||
/* 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(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
|
||||
ret = blk_insert_bs(qcow_blk, bs, errp);
|
||||
if (ret < 0) {
|
||||
error_propagate(errp, local_err);
|
||||
goto cleanup;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
210
block/qed.c
210
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);
|
||||
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 },
|
||||
};
|
||||
|
||||
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);
|
||||
/* 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;
|
||||
}
|
||||
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);
|
||||
ret = -EINVAL;
|
||||
goto finish;
|
||||
}
|
||||
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));
|
||||
ret = -EINVAL;
|
||||
goto finish;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = qed_create(filename, cluster_size, image_size, table_size,
|
||||
backing_file, backing_fmt, opts, errp);
|
||||
/* 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;
|
||||
}
|
||||
|
||||
finish:
|
||||
g_free(backing_file);
|
||||
g_free(backing_fmt);
|
||||
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 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_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);
|
||||
|
||||
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,
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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
|
||||
|
|
147
block/vdi.c
147
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,
|
||||
|
|
222
block/vhdx.c
222
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 (!strcmp(type, "dynamic")) {
|
||||
image_type = VHDX_TYPE_DYNAMIC;
|
||||
} else if (!strcmp(type, "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;
|
||||
if (!vhdx_opts->has_log_size) {
|
||||
log_size = DEFAULT_LOG_SIZE;
|
||||
} else {
|
||||
error_setg(errp, "Invalid subformat '%s'", type);
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
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 (!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;
|
||||
break;
|
||||
case BLOCKDEV_VHDX_SUBFORMAT_FIXED:
|
||||
image_type = VHDX_TYPE_FIXED;
|
||||
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
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
/* Create BlockBackend to write to the image */
|
||||
bs = bdrv_open_blockdev_ref(vhdx_opts->file, errp);
|
||||
if (bs == NULL) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
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);
|
||||
blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
|
||||
ret = blk_insert_bs(blk, bs, errp);
|
||||
if (ret < 0) {
|
||||
error_propagate(errp, local_err);
|
||||
goto exit;
|
||||
goto delete_and_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;
|
||||
goto 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,
|
||||
|
|
241
block/vpc.c
241
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,
|
||||
|
|
|
@ -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 = {
|
||||
|
|
71
blockdev.c
71
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,
|
||||
|
|
360
blockjob.c
360
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,23 +734,43 @@ 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, bool force)
|
||||
{
|
||||
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_cancel(BlockJob *job)
|
||||
void block_job_user_cancel(BlockJob *job, bool force, Error **errp)
|
||||
{
|
||||
if (block_job_started(job)) {
|
||||
block_job_cancel_async(job);
|
||||
block_job_enter(job);
|
||||
} else {
|
||||
block_job_completed(job, -ECANCELED);
|
||||
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
|
||||
|
@ -556,7 +778,7 @@ void block_job_cancel(BlockJob *job)
|
|||
* 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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
||||
|
|
|
@ -62,6 +62,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:
|
||||
|
|
|
@ -53,6 +53,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
|
||||
|
@ -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);
|
||||
|
||||
|
|
|
@ -958,6 +958,77 @@
|
|||
{ 'enum': 'BlockJobType',
|
||||
'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:
|
||||
#
|
||||
|
@ -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
|
||||
|
@ -2186,6 +2293,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:
|
||||
#
|
||||
|
@ -3454,6 +3599,21 @@
|
|||
'size': 'size',
|
||||
'*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:
|
||||
#
|
||||
|
@ -3468,6 +3628,41 @@
|
|||
'data': { 'location': 'BlockdevOptionsNfs',
|
||||
'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:
|
||||
#
|
||||
|
@ -3511,6 +3706,29 @@
|
|||
'*lazy-refcounts': 'bool',
|
||||
'*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:
|
||||
#
|
||||
|
@ -3609,6 +3827,93 @@
|
|||
'data': { 'location': 'BlockdevOptionsSsh',
|
||||
'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:
|
||||
#
|
||||
|
@ -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'
|
||||
} }
|
||||
|
@ -4179,6 +4484,30 @@
|
|||
'offset': 'int',
|
||||
'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:
|
||||
#
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
...
|
||||
.........
|
||||
----------------------------------------------------------------------
|
||||
Ran 3 tests
|
||||
Ran 9 tests
|
||||
|
||||
OK
|
||||
|
|
|
@ -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"}}
|
||||
|
|
|
@ -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 ====
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
210
tests/qemu-iotests/210
Executable file
210
tests/qemu-iotests/210
Executable file
|
@ -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
|
136
tests/qemu-iotests/210.out
Normal file
136
tests/qemu-iotests/210.out
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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" \
|
||||
|
|
|
@ -208,3 +208,4 @@
|
|||
207 rw auto
|
||||
208 rw auto quick
|
||||
209 rw auto quick
|
||||
210 rw auto
|
||||
|
|
|
@ -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'''
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue