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:
Peter Maydell 2018-03-19 11:44:26 +00:00
commit 2c8cfc0b52
39 changed files with 2661 additions and 533 deletions

10
block.c
View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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, &parallels_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 = &parallels_create_opts,

View file

@ -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,

View file

@ -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,

View file

@ -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 |

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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 = {

View file

@ -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,

View file

@ -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;

View file

@ -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,
},

View file

@ -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:

View file

@ -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);

View file

@ -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:
#

View file

@ -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')

View file

@ -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')

View file

@ -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'])

View file

@ -1,5 +1,5 @@
...
.........
----------------------------------------------------------------------
Ran 3 tests
Ran 9 tests
OK

View file

@ -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"}}

View file

@ -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 ====

View file

@ -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 \

View file

@ -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

View file

@ -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
View 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
View 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

View file

@ -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

View file

@ -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" \

View file

@ -208,3 +208,4 @@
207 rw auto
208 rw auto quick
209 rw auto quick
210 rw auto

View file

@ -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'''

View file

@ -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);

View file

@ -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.

View file

@ -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();
}