vhdx: Support .bdrv_co_create
This adds the .bdrv_co_create driver callback to vhdx, which enables image creation over QMP. Signed-off-by: Kevin Wolf <kwolf@redhat.com> Reviewed-by: Max Reitz <mreitz@redhat.com>
This commit is contained in:
parent
da23248f27
commit
09b68dab5a
220
block/vhdx.c
220
block/vhdx.c
@ -26,6 +26,9 @@
|
|||||||
#include "block/vhdx.h"
|
#include "block/vhdx.h"
|
||||||
#include "migration/blocker.h"
|
#include "migration/blocker.h"
|
||||||
#include "qemu/uuid.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 */
|
/* Options for VHDX creation */
|
||||||
|
|
||||||
@ -39,6 +42,8 @@ typedef enum VHDXImageType {
|
|||||||
VHDX_TYPE_DIFFERENCING, /* Currently unsupported */
|
VHDX_TYPE_DIFFERENCING, /* Currently unsupported */
|
||||||
} VHDXImageType;
|
} VHDXImageType;
|
||||||
|
|
||||||
|
static QemuOptsList vhdx_create_opts;
|
||||||
|
|
||||||
/* Several metadata and region table data entries are identified by
|
/* Several metadata and region table data entries are identified by
|
||||||
* guids in a MS-specific GUID format. */
|
* guids in a MS-specific GUID format. */
|
||||||
|
|
||||||
@ -1792,59 +1797,71 @@ exit:
|
|||||||
* .---- ~ ----------- ~ ------------ ~ ---------------- ~ -----------.
|
* .---- ~ ----------- ~ ------------ ~ ---------------- ~ -----------.
|
||||||
* 1MB
|
* 1MB
|
||||||
*/
|
*/
|
||||||
static int coroutine_fn vhdx_co_create_opts(const char *filename, QemuOpts *opts,
|
static int coroutine_fn vhdx_co_create(BlockdevCreateOptions *opts,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
|
BlockdevCreateOptionsVhdx *vhdx_opts;
|
||||||
|
BlockBackend *blk = NULL;
|
||||||
|
BlockDriverState *bs = NULL;
|
||||||
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
uint64_t image_size = (uint64_t) 2 * GiB;
|
uint64_t image_size;
|
||||||
uint32_t log_size = 1 * MiB;
|
uint32_t log_size;
|
||||||
uint32_t block_size = 0;
|
uint32_t block_size;
|
||||||
uint64_t signature;
|
uint64_t signature;
|
||||||
uint64_t metadata_offset;
|
uint64_t metadata_offset;
|
||||||
bool use_zero_blocks = false;
|
bool use_zero_blocks = false;
|
||||||
|
|
||||||
gunichar2 *creator = NULL;
|
gunichar2 *creator = NULL;
|
||||||
glong creator_items;
|
glong creator_items;
|
||||||
BlockBackend *blk;
|
|
||||||
char *type = NULL;
|
|
||||||
VHDXImageType image_type;
|
VHDXImageType image_type;
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
image_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
assert(opts->driver == BLOCKDEV_DRIVER_VHDX);
|
||||||
BDRV_SECTOR_SIZE);
|
vhdx_opts = &opts->u.vhdx;
|
||||||
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);
|
|
||||||
|
|
||||||
|
/* Validate options and set default values */
|
||||||
|
image_size = vhdx_opts->size;
|
||||||
if (image_size > VHDX_MAX_IMAGE_SIZE) {
|
if (image_size > VHDX_MAX_IMAGE_SIZE) {
|
||||||
error_setg_errno(errp, EINVAL, "Image size too large; max of 64TB");
|
error_setg_errno(errp, EINVAL, "Image size too large; max of 64TB");
|
||||||
ret = -EINVAL;
|
return -EINVAL;
|
||||||
goto exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == NULL) {
|
if (!vhdx_opts->has_log_size) {
|
||||||
type = g_strdup("dynamic");
|
log_size = DEFAULT_LOG_SIZE;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
} else {
|
} else {
|
||||||
error_setg(errp, "Invalid subformat '%s'", type);
|
log_size = vhdx_opts->log_size;
|
||||||
ret = -EINVAL;
|
}
|
||||||
goto exit;
|
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
|
/* These are pretty arbitrary, and mainly designed to keep the BAT
|
||||||
* size reasonable to load into RAM */
|
* 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) {
|
if (image_size > 32 * TiB) {
|
||||||
block_size = 64 * MiB;
|
block_size = 64 * MiB;
|
||||||
} else if (image_size > (uint64_t) 100 * GiB) {
|
} 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
|
/* Create BlockBackend to write to the image */
|
||||||
* min 1MB, and multiple of 1MB */
|
bs = bdrv_open_blockdev_ref(vhdx_opts->file, errp);
|
||||||
log_size = ROUND_UP(log_size, MiB);
|
if (bs == NULL) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
block_size = ROUND_UP(block_size, MiB);
|
blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
|
||||||
block_size = block_size > VHDX_BLOCK_SIZE_MAX ? VHDX_BLOCK_SIZE_MAX :
|
ret = blk_insert_bs(blk, bs, errp);
|
||||||
block_size;
|
|
||||||
|
|
||||||
ret = bdrv_create_file(filename, opts, &local_err);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
goto delete_and_exit;
|
||||||
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;
|
|
||||||
goto exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
blk_set_allow_write_beyond_eof(blk, true);
|
blk_set_allow_write_beyond_eof(blk, true);
|
||||||
|
|
||||||
/* Create (A) */
|
/* Create (A) */
|
||||||
@ -1931,12 +1945,109 @@ static int coroutine_fn vhdx_co_create_opts(const char *filename, QemuOpts *opts
|
|||||||
|
|
||||||
delete_and_exit:
|
delete_and_exit:
|
||||||
blk_unref(blk);
|
blk_unref(blk);
|
||||||
exit:
|
bdrv_unref(bs);
|
||||||
g_free(type);
|
|
||||||
g_free(creator);
|
g_free(creator);
|
||||||
return ret;
|
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 opened r/w, the VHDX driver will automatically replay the log,
|
||||||
* if one is present, inside the vhdx_open() call.
|
* 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_child_perm = bdrv_format_default_perms,
|
||||||
.bdrv_co_readv = vhdx_co_readv,
|
.bdrv_co_readv = vhdx_co_readv,
|
||||||
.bdrv_co_writev = vhdx_co_writev,
|
.bdrv_co_writev = vhdx_co_writev,
|
||||||
|
.bdrv_co_create = vhdx_co_create,
|
||||||
.bdrv_co_create_opts = vhdx_co_create_opts,
|
.bdrv_co_create_opts = vhdx_co_create_opts,
|
||||||
.bdrv_get_info = vhdx_get_info,
|
.bdrv_get_info = vhdx_get_info,
|
||||||
.bdrv_co_check = vhdx_co_check,
|
.bdrv_co_check = vhdx_co_check,
|
||||||
|
@ -3844,6 +3844,44 @@
|
|||||||
'size': 'size',
|
'size': 'size',
|
||||||
'*static': 'bool' } }
|
'*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' } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @BlockdevCreateNotSupported:
|
# @BlockdevCreateNotSupported:
|
||||||
#
|
#
|
||||||
@ -3899,7 +3937,7 @@
|
|||||||
'ssh': 'BlockdevCreateOptionsSsh',
|
'ssh': 'BlockdevCreateOptionsSsh',
|
||||||
'throttle': 'BlockdevCreateNotSupported',
|
'throttle': 'BlockdevCreateNotSupported',
|
||||||
'vdi': 'BlockdevCreateOptionsVdi',
|
'vdi': 'BlockdevCreateOptionsVdi',
|
||||||
'vhdx': 'BlockdevCreateNotSupported',
|
'vhdx': 'BlockdevCreateOptionsVhdx',
|
||||||
'vmdk': 'BlockdevCreateNotSupported',
|
'vmdk': 'BlockdevCreateNotSupported',
|
||||||
'vpc': 'BlockdevCreateNotSupported',
|
'vpc': 'BlockdevCreateNotSupported',
|
||||||
'vvfat': 'BlockdevCreateNotSupported',
|
'vvfat': 'BlockdevCreateNotSupported',
|
||||||
|
Loading…
Reference in New Issue
Block a user