Commit 09b68dab authored by Kevin Wolf's avatar Kevin Wolf
Browse files

vhdx: Support .bdrv_co_create



This adds the .bdrv_co_create driver callback to vhdx, which
enables image creation over QMP.

Signed-off-by: default avatarKevin Wolf <kwolf@redhat.com>
Reviewed-by: default avatarMax Reitz <mreitz@redhat.com>
parent da23248f
Loading
Loading
Loading
Loading
+164 −52
Original line number Diff line number Diff line
@@ -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,
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 (!vhdx_opts->has_log_size) {
        log_size = DEFAULT_LOG_SIZE;
    } else {
        log_size = vhdx_opts->log_size;
    }
    if (log_size < MiB || (log_size % MiB) != 0) {
        error_setg_errno(errp, EINVAL, "Log size must be a multiple of 1 MB");
        return -EINVAL;
    }

    if (type == NULL) {
        type = g_strdup("dynamic");
    if (!vhdx_opts->has_block_state_zero) {
        use_zero_blocks = true;
    } else {
        use_zero_blocks = vhdx_opts->block_state_zero;
    }

    if (!vhdx_opts->has_subformat) {
        vhdx_opts->subformat = BLOCKDEV_VHDX_SUBFORMAT_DYNAMIC;
    }

    if (!strcmp(type, "dynamic")) {
    switch (vhdx_opts->subformat) {
    case BLOCKDEV_VHDX_SUBFORMAT_DYNAMIC:
        image_type = VHDX_TYPE_DYNAMIC;
    } else if (!strcmp(type, "fixed")) {
        break;
    case BLOCKDEV_VHDX_SUBFORMAT_FIXED:
        image_type = VHDX_TYPE_FIXED;
    } else if (!strcmp(type, "differencing")) {
        error_setg_errno(errp, ENOTSUP,
                         "Differencing files not yet supported");
        ret = -ENOTSUP;
        goto exit;
    } else {
        error_setg(errp, "Invalid subformat '%s'", type);
        ret = -EINVAL;
        goto exit;
        break;
    default:
        g_assert_not_reached();
    }

    /* These are pretty arbitrary, and mainly designed to keep the BAT
     * size reasonable to load into RAM */
    if (block_size == 0) {
    if (vhdx_opts->has_block_size) {
        block_size = vhdx_opts->block_size;
    } else {
        if (image_size > 32 * TiB) {
            block_size = 64 * MiB;
        } else if (image_size > (uint64_t) 100 * GiB) {
@@ -1856,30 +1873,27 @@ static int coroutine_fn vhdx_co_create_opts(const char *filename, QemuOpts *opts
        }
    }


    /* make the log size close to what was specified, but must be
     * min 1MB, and multiple of 1MB */
    log_size = ROUND_UP(log_size, MiB);

    block_size = ROUND_UP(block_size, MiB);
    block_size = block_size > VHDX_BLOCK_SIZE_MAX ? VHDX_BLOCK_SIZE_MAX :
                                                    block_size;

    ret = bdrv_create_file(filename, opts, &local_err);
    if (ret < 0) {
        error_propagate(errp, local_err);
        goto exit;
    if (block_size < MiB || (block_size % MiB) != 0) {
        error_setg_errno(errp, EINVAL, "Block size must be a multiple of 1 MB");
        return -EINVAL;
    }
    if (block_size > VHDX_BLOCK_SIZE_MAX) {
        error_setg_errno(errp, EINVAL, "Block size must not exceed %d",
                         VHDX_BLOCK_SIZE_MAX);
        return -EINVAL;
    }

    blk = blk_new_open(filename, NULL, NULL,
                       BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
                       &local_err);
    if (blk == NULL) {
        error_propagate(errp, local_err);
        ret = -EIO;
        goto exit;
    /* Create BlockBackend to write to the image */
    bs = bdrv_open_blockdev_ref(vhdx_opts->file, errp);
    if (bs == NULL) {
        return -EIO;
    }

    blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
    ret = blk_insert_bs(blk, bs, errp);
    if (ret < 0) {
        goto delete_and_exit;
    }
    blk_set_allow_write_beyond_eof(blk, true);

    /* Create (A) */
@@ -1931,12 +1945,109 @@ static int coroutine_fn vhdx_co_create_opts(const char *filename, QemuOpts *opts

delete_and_exit:
    blk_unref(blk);
exit:
    g_free(type);
    bdrv_unref(bs);
    g_free(creator);
    return ret;
}

static int coroutine_fn vhdx_co_create_opts(const char *filename,
                                            QemuOpts *opts,
                                            Error **errp)
{
    BlockdevCreateOptions *create_options = NULL;
    QDict *qdict = NULL;
    QObject *qobj;
    Visitor *v;
    BlockDriverState *bs = NULL;
    Error *local_err = NULL;
    int ret;

    static const QDictRenames opt_renames[] = {
        { VHDX_BLOCK_OPT_LOG_SIZE,      "log-size" },
        { VHDX_BLOCK_OPT_BLOCK_SIZE,    "block-size" },
        { VHDX_BLOCK_OPT_ZERO,          "block-state-zero" },
        { NULL, NULL },
    };

    /* Parse options and convert legacy syntax */
    qdict = qemu_opts_to_qdict_filtered(opts, NULL, &vhdx_create_opts, true);

    if (!qdict_rename_keys(qdict, opt_renames, errp)) {
        ret = -EINVAL;
        goto fail;
    }

    /* Create and open the file (protocol layer) */
    ret = bdrv_create_file(filename, opts, &local_err);
    if (ret < 0) {
        error_propagate(errp, local_err);
        goto fail;
    }

    bs = bdrv_open(filename, NULL, NULL,
                   BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL, errp);
    if (bs == NULL) {
        ret = -EIO;
        goto fail;
    }

    /* Now get the QAPI type BlockdevCreateOptions */
    qdict_put_str(qdict, "driver", "vhdx");
    qdict_put_str(qdict, "file", bs->node_name);

    qobj = qdict_crumple(qdict, errp);
    QDECREF(qdict);
    qdict = qobject_to_qdict(qobj);
    if (qdict == NULL) {
        ret = -EINVAL;
        goto fail;
    }

    v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
    visit_type_BlockdevCreateOptions(v, NULL, &create_options, &local_err);
    visit_free(v);

    if (local_err) {
        error_propagate(errp, local_err);
        ret = -EINVAL;
        goto fail;
    }

    /* Silently round up sizes:
     * The image size is rounded to 512 bytes. Make the block and log size
     * close to what was specified, but must be at least 1MB, and a multiple of
     * 1 MB. Also respect VHDX_BLOCK_SIZE_MAX for block sizes. block_size = 0
     * means auto, which is represented by a missing key in QAPI. */
    assert(create_options->driver == BLOCKDEV_DRIVER_VHDX);
    create_options->u.vhdx.size =
        ROUND_UP(create_options->u.vhdx.size, BDRV_SECTOR_SIZE);

    if (create_options->u.vhdx.has_log_size) {
        create_options->u.vhdx.log_size =
            ROUND_UP(create_options->u.vhdx.log_size, MiB);
    }
    if (create_options->u.vhdx.has_block_size) {
        create_options->u.vhdx.block_size =
            ROUND_UP(create_options->u.vhdx.block_size, MiB);

        if (create_options->u.vhdx.block_size == 0) {
            create_options->u.vhdx.has_block_size = false;
        }
        if (create_options->u.vhdx.block_size > VHDX_BLOCK_SIZE_MAX) {
            create_options->u.vhdx.block_size = VHDX_BLOCK_SIZE_MAX;
        }
    }

    /* Create the vhdx image (format layer) */
    ret = vhdx_co_create(create_options, errp);

fail:
    QDECREF(qdict);
    bdrv_unref(bs);
    qapi_free_BlockdevCreateOptions(create_options);
    return ret;
}

/* If opened r/w, the VHDX driver will automatically replay the log,
 * if one is present, inside the vhdx_open() call.
 *
@@ -2005,6 +2116,7 @@ static BlockDriver bdrv_vhdx = {
    .bdrv_child_perm        = bdrv_format_default_perms,
    .bdrv_co_readv          = vhdx_co_readv,
    .bdrv_co_writev         = vhdx_co_writev,
    .bdrv_co_create         = vhdx_co_create,
    .bdrv_co_create_opts    = vhdx_co_create_opts,
    .bdrv_get_info          = vhdx_get_info,
    .bdrv_co_check          = vhdx_co_check,
+39 −1
Original line number Diff line number Diff line
@@ -3844,6 +3844,44 @@
            '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' } }

##
# @BlockdevCreateNotSupported:
#
@@ -3899,7 +3937,7 @@
      'ssh':            'BlockdevCreateOptionsSsh',
      'throttle':       'BlockdevCreateNotSupported',
      'vdi':            'BlockdevCreateOptionsVdi',
      'vhdx':           'BlockdevCreateNotSupported',
      'vhdx':           'BlockdevCreateOptionsVhdx',
      'vmdk':           'BlockdevCreateNotSupported',
      'vpc':            'BlockdevCreateNotSupported',
      'vvfat':          'BlockdevCreateNotSupported',